Tapioca DAO - glcanvas's results

The first ever Omnichain money market, powered by LayerZero.

General Information

Platform: Code4rena

Start Date: 05/07/2023

Pot Size: $390,000 USDC

Total HM: 136

Participants: 132

Period: about 1 month

Judge: LSDan

Total Solo HM: 56

Id: 261

League: ETH

Tapioca DAO

Findings Distribution

Researcher Performance

Rank: 42/132

Findings: 6

Award: $1,199.83

🌟 Selected for report: 0

🚀 Solo Findings: 1

Findings Information

🌟 Selected for report: KIntern_NA

Also found by: bin2chen, glcanvas

Labels

bug
3 (High Risk)
satisfactory
edited-by-warden
duplicate-1094

Awards

698.0846 USDC - $698.08

External Links

Lines of code

https://github.com/Tapioca-DAO/tap-token-audit/blob/59749be5bc2286f0bdbf59d7ddc258ddafd49a9f/contracts/governance/twTAP.sol#L498-L520 https://github.com/Tapioca-DAO/tap-token-audit/blob/59749be5bc2286f0bdbf59d7ddc258ddafd49a9f/contracts/governance/twTAP.sol#L514 https://github.com/Tapioca-DAO/tap-token-audit/blob/59749be5bc2286f0bdbf59d7ddc258ddafd49a9f/contracts/tokens/BaseTapOFT.sol#L163-L196 https://github.com/Tapioca-DAO/tap-token-audit/blob/59749be5bc2286f0bdbf59d7ddc258ddafd49a9f/contracts/tokens/BaseTapOFT.sol#L198-L245

Vulnerability details

Vulnerability details

Impact

Possible to withdraw more expensive funds from twTAP contract. For, example, we observes that, pool rewards with shitcoin and USDT, then, we can make a request so that we extract all USDT from the contract. Due to incorrect validation

Proof of Concept

To process this exploit need to use TapOFT contract (which is implementation of BaseTapOFT).

  • We lock a bit of tapOFT tokens on twTAP contract with function participate. -- We receive a tokenId
  • We call claimRewards on TapOFT contract. This function implements cross-chain call with passed parameters. Please, note -- here we pass address[] memory rewardTokens, which just encode as it is, No any additional checks.
  • Source contract calls _nonblockingLzReceive on destination chain. In turn, this decodes passed data and call _claimRewards.
  • _claimRewards decodes passed payload --
( .... IERC20[] memory rewardTokens, ... ) = abi.decode( _payload, ( .... IERC20[],... ) );

Note, we decodes IERC20[] memory rewardTokens,, then this array of tokens passed to twTap.claimAndSendRewards(tokenID, rewardTokens).

  • Next, deeper in execution flow -- it calls _claimRewardsOn, this function accepts IERC20[] memory _rewardTokens as it was created on Source chain, without any checks.
  • In _claimRewardsOn we at the first calls claimable -- uint256[] memory amounts = claimable(_tokenId); -- this returns array of tokens amount which might be claimed. (i.e. [100, 200, 300] -- means, you may receive 100 tokens for token which has index 0 in map rewardTokenIndex)
  • Then we iterates over passed array of ERC20 tokens
uint256 len = _rewardTokens.length; for (uint256 i = 0; i < len; ) { uint256 claimableIndex = rewardTokenIndex[_rewardTokens[i]]; uint256 amount = amounts[i]; if (amount > 0) { // Math is safe: `amount` calculated safely in `claimable()` claimed[_tokenId][claimableIndex] += amount; rewardTokens[claimableIndex].safeTransfer(_to, amount); } ++i; }

And here:

uint256 claimableIndex = rewardTokenIndex[_rewardTokens[i]]; uint256 amount = amounts[i];

We may chose which token use, but amount calculated by contract. Therefore, if for example, in contract registered:

  • token 0 -- shitcoin
  • token 1 -- USDT
  • token 2 -- shitcoin

we can pass to TapOFT#claimRewards tokens like: [1, 1, 1] and in _claimRewardsOn token 1 will receive payments thee times a row, thats wrong and incorrect.

Please, note:

A similar attack can be made if we pass an array of non-existent tokens, then this: uint256 claimableIndex = rewardTokenIndex[_rewardTokens[i]]; will return us the index 0 because this is how the storage in EVM works and we can call the token transfer with the index 0 several times.

Tools Used

Manual review

  • Add uniqueness validation -- revert is any token passed more than once
  • Add existing validation -- revert if token absent on twTAP contract

Assessed type

Access Control

#0 - c4-pre-sort

2023-08-07T03:42:44Z

minhquanym marked the issue as primary issue

#1 - c4-pre-sort

2023-08-07T03:43:57Z

minhquanym marked the issue as duplicate of #1304

#2 - c4-judge

2023-09-22T12:44:28Z

dmvt marked the issue as satisfactory

Findings Information

🌟 Selected for report: zzzitron

Also found by: glcanvas

Labels

bug
2 (Med Risk)
grade-c
low quality report
partial-50
duplicate-1350

Awards

174.5212 USDC - $174.52

External Links

Lines of code

https://github.com/Tapioca-DAO/tap-token-audit/blob/59749be5bc2286f0bdbf59d7ddc258ddafd49a9f/contracts/options/TapiocaOptionLiquidityProvision.sol#L276-L293 https://github.com/Tapioca-DAO/tap-token-audit/blob/59749be5bc2286f0bdbf59d7ddc258ddafd49a9f/contracts/options/TapiocaOptionLiquidityProvision.sol#L297-L329 https://github.com/Tapioca-DAO/tap-token-audit/blob/59749be5bc2286f0bdbf59d7ddc258ddafd49a9f/contracts/options/TapiocaOptionLiquidityProvision.sol#L126-L143

Vulnerability details

Vulnerability details

Impact

Possible to register assetID twice with different singularity.

Proof of Concept

In TapiocaOptionLiquidityProvision let's consider registerSingularity This function only validates singularity uniqueness. But do not validate assetID uniqueness.

So, we can call registerSingularity twice with same assetID. And singularities array will contains the same assetID twice.

When we decide to remove singularity we call unregisterSingularity and iterates over _singularities, And, therefore, we delete only first assetID occurrence, but the last one will be in this array. Moreover we can't call unregisterSingularity because singularity will be removed, and assetID will point out to zero element.

This lead to case, when someone calls getSingularityPools he may get pool information which contains zeros. Because this construction

pools[i] = activeSingularities[sglAssetIDToAddress[_singularities[i]]];

will be point out to zero memory.

Therefore, user may observe incorrect data, which will be impossible to fix.

Tools Used

Manual review

Add uniqueness constraint to assetID

Assessed type

Invalid Validation

#0 - c4-pre-sort

2023-08-07T04:21:46Z

minhquanym marked the issue as low quality report

#1 - minhquanym

2023-08-07T04:22:02Z

onlyOwner func. Consider QA

#2 - c4-pre-sort

2023-08-09T07:39:47Z

minhquanym marked the issue as primary issue

#3 - c4-judge

2023-09-20T15:24:26Z

dmvt changed the severity to QA (Quality Assurance)

#4 - c4-judge

2023-09-20T15:38:12Z

dmvt marked the issue as grade-c

#5 - c4-judge

2023-09-20T15:40:49Z

This previously downgraded issue has been upgraded by dmvt

#6 - c4-judge

2023-09-20T15:43:59Z

dmvt marked the issue as partial-50

#7 - dmvt

2023-09-20T15:44:19Z

really hard to read, but correct

#8 - c4-judge

2023-09-20T15:45:02Z

dmvt marked issue #1350 as primary and marked this issue as a duplicate of 1350

Findings Information

🌟 Selected for report: peakbolt

Also found by: ak1, bin2chen, glcanvas, rvierdiiev, unsafesol

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
duplicate-1175

Awards

76.3356 USDC - $76.34

External Links

Lines of code

https://github.com/Tapioca-DAO/tap-token-audit/blob/59749be5bc2286f0bdbf59d7ddc258ddafd49a9f/contracts/option-airdrop/AirdropBroker.sol#L84 https://github.com/Tapioca-DAO/tap-token-audit/blob/59749be5bc2286f0bdbf59d7ddc258ddafd49a9f/contracts/option-airdrop/AirdropBroker.sol#L459-L461

Vulnerability details

Impact

Users participated in phase 3 of AirdropBroker will receive only 714 tokens. But at the same time Token has 18 decimals. So this phase is wrong.

Proof of Concept

This is typo, But it's critical typo, because on phase3 users will receive 714 tokens it's 1e16 times less than single token.

uint256 public constant PHASE_3_AMOUNT_PER_USER = 714;
uint128 expiry = uint128(lastEpochUpdate + EPOCH_DURATION); // Set expiry to the end of the epoch uint256 eligibleAmount = PHASE_3_AMOUNT_PER_USER; uint128 discount = uint128(PHASE_3_DISCOUNT); oTAPTokenID = aoTAP.mint(msg.sender, expiry, discount, eligibleAmount);

Tools Used

Manual review

Fix with: uint256 public constant PHASE_3_AMOUNT_PER_USER = 714 * 1e18;

Assessed type

Error

#0 - c4-pre-sort

2023-08-05T15:10:10Z

minhquanym marked the issue as duplicate of #173

#1 - c4-judge

2023-09-18T13:28:00Z

dmvt changed the severity to 2 (Med Risk)

#2 - c4-judge

2023-09-18T13:29:30Z

dmvt marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter