Tapioca DAO - SaeedAlipoor01988'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: 20/132

Findings: 13

Award: $3,585.64

🌟 Selected for report: 2

πŸš€ Solo Findings: 1

Findings Information

🌟 Selected for report: ItsNio

Also found by: ItsNio, SaeedAlipoor01988

Labels

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

Awards

1163.4744 USDC - $1,163.47

External Links

Lines of code

https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/singularity/SGLCommon.sol#L61

Vulnerability details

Impact

The value of utilization is calculated before Accrue interest and using the invalid (out of date) value of _totalBorrow.elastic.

Proof of Concept

The Utilization value is calculated by the formula below, at line SGLCommon.sol#L61.

utilization = fullAssetAmount == 0 ? 0 : (uint256(_totalBorrow.elastic) * UTILIZATION_PRECISION) / fullAssetAmount;

The problem is that, in the Accrue interest block, value of _totalBorrow.elastic, is updated at line SGLCommon.sol#L104 :

// Accrue interest extraAmount = (uint256(_totalBorrow.elastic) * _accrueInfo.interestPerSecond * elapsedTime) / 1e18; _totalBorrow.elastic += uint128(extraAmount);

And then, at line SGLCommon.sol#L112, interest rate get updated using the Utilization value :

if (utilization < minimumTargetUtilization) { uint256 underFactor = ((minimumTargetUtilization - utilization) * FACTOR_PRECISION) / minimumTargetUtilization; uint256 scale = interestElasticity + (underFactor * underFactor * elapsedTime); _accrueInfo.interestPerSecond = uint64( (uint256(_accrueInfo.interestPerSecond) * interestElasticity) / scale ); if (_accrueInfo.interestPerSecond < minimumInterestPerSecond) { _accrueInfo.interestPerSecond = minimumInterestPerSecond; // 0.25% APR minimum } } else if (utilization > maximumTargetUtilization) { uint256 overFactor = ((utilization - maximumTargetUtilization) * FACTOR_PRECISION) / fullUtilizationMinusMax; uint256 scale = interestElasticity + (overFactor * overFactor * elapsedTime); uint256 newInterestPerSecond = (uint256( _accrueInfo.interestPerSecond ) * scale) / interestElasticity; if (newInterestPerSecond > maximumInterestPerSecond) { newInterestPerSecond = maximumInterestPerSecond; // 1000% APR maximum } _accrueInfo.interestPerSecond = uint64(newInterestPerSecond); }

Tools Used

Manually

The value of utilization, should get updated before line SGLCommon.sol#L112.

Assessed type

Other

#0 - c4-pre-sort

2023-08-05T00:08:11Z

minhquanym marked the issue as duplicate of #1057

#1 - c4-judge

2023-09-12T16:44:43Z

dmvt marked the issue as satisfactory

Findings Information

🌟 Selected for report: GalloDaSballo

Also found by: 0xfuje, KIntern_NA, SaeedAlipoor01988, gizzy

Labels

bug
2 (Med Risk)
low quality report
satisfactory
edited-by-warden
duplicate-1553

Awards

101.7807 USDC - $101.78

External Links

Lines of code

https://github.com/Tapioca-DAO/tapioca-periph-audit/blob/023751a4e987cf7c203ab25d3abba58f7344f213/contracts/Swapper/UniswapV3Swapper.sol#L38 https://github.com/Tapioca-DAO/tapioca-periph-audit/blob/023751a4e987cf7c203ab25d3abba58f7344f213/contracts/Swapper/UniswapV3Swapper.sol#L184 https://github.com/Tapioca-DAO/tapioca-yieldbox-strategies-audit/blob/05ba7108a83c66dada98bc5bc75cf18004f2a49b/contracts/stargate/StargateSwapperV3.sol#L29

Vulnerability details

Impact

Hardcoded UNISWAP_FEE can reduce significantly the possibilities and will lead to non optimal routes.

Proof of Concept

Uniswap v3 introduces multiple pools for each token pair, each with a different swapping fee. Liquidity providers may initially create pools at three fee levels: 0.05%, 0.30%, and 1%. https://info.uniswap.org/pairs#/pools

For example, for USDT / WETH, the optimal route is currently USDT -> USDC and USDC -> ETH with USDT -> ETH. The pool USDT / ETH with 0.3% fees is tiny compared to the ones with 0.05 %, and the pool USDC / ETH with 0.3% fees is tiny compared to the ones with 0.05 %. Therefore, using the current implementation would create a significant loss of revenue.

Tools Used

Manually

You may want to add the possibility to do the swaps through an efficient aggregator like 1Inch or Paraswap, it will be way more optimal.

Assessed type

Uniswap

#0 - c4-pre-sort

2023-08-06T08:19:41Z

minhquanym marked the issue as low quality report

#1 - minhquanym

2023-08-06T08:19:43Z

Consider QA

#2 - c4-pre-sort

2023-08-06T16:25:44Z

minhquanym marked the issue as primary issue

#3 - c4-judge

2023-09-19T18:13:57Z

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

#4 - c4-judge

2023-09-19T18:14:06Z

dmvt marked the issue as satisfactory

Findings Information

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sponsor confirmed
edited-by-warden
duplicate-1456

Awards

76.3356 USDC - $76.34

External Links

Lines of code

https://github.com/Tapioca-DAO/tapioca-yieldbox-strategies-audit/blob/05ba7108a83c66dada98bc5bc75cf18004f2a49b/contracts/yearn/YearnStrategy.sol#L145

Vulnerability details

Impact

The value of maxLoss to withdraw from yearn vault is hard-coded to 0. because of below assert, there is a possibility that withdrawal from yearn vaults is not possible.

https://github.com/yearn/yearn-vaults/blob/97ca1b2e4fcf20f4be0ff456dabd020bfeb6697b/contracts/Vault.vy#L1155

Proof of Concept

The maxLoss is The maximum acceptable loss to sustain on withdrawal. defaults value for yearn vaults is 0.01%. If a loss is specified, up to that amount of shares may be burnt to cover losses on withdrawal. there is a assert check in the Vault.vy contract as loss protection. this loss protection is put in place to revert if losses from withdrawing are more than what is considered acceptable.

https://github.com/yearn/yearn-vaults/blob/97ca1b2e4fcf20f4be0ff456dabd020bfeb6697b/contracts/Vault.vy#L1155

# NOTE: This loss protection is put in place to revert if losses from # withdrawing are more than what is considered acceptable. assert totalLoss <= maxLoss * (value + totalLoss) / MAX_BPS

Tapioca is hard-coded the maxLoss to 0 in the YearnStrategy.sol contract. that means In the process of withdrawing from yearn vault, totalLoss should be 0 and this can be almost impossible and lead to the locking of fund in yearn vault. https://github.com/Tapioca-DAO/tapioca-yieldbox-strategies-audit/blob/05ba7108a83c66dada98bc5bc75cf18004f2a49b/contracts/yearn/YearnStrategy.sol#L145

vault.withdraw(toWithdraw, address(this), 0);

But if 0 mean Yearn default maxLoss, you should know, it is 0.01%. In certain cases where the amount to be withdrawn is large enough, the Yearn withdrawal will revert. in a situation where the market is fluctuating, users' funds can be locked in the yearn vault.

Tools Used

Manually

Allow the user to enter the value of maxLoss.

Assessed type

DoS

#0 - c4-pre-sort

2023-08-05T08:53:53Z

minhquanym marked the issue as primary issue

#1 - minhquanym

2023-08-05T08:58:24Z

Consider Medium

#2 - c4-sponsor

2023-08-14T15:24:16Z

0xRektora marked the issue as sponsor confirmed

#3 - c4-judge

2023-09-13T09:07:52Z

dmvt changed the severity to 2 (Med Risk)

#4 - c4-judge

2023-09-13T09:08:31Z

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

#5 - c4-judge

2023-09-13T09:08:42Z

dmvt marked the issue as satisfactory

Findings Information

🌟 Selected for report: windhustler

Also found by: SaeedAlipoor01988, bin2chen, peakbolt, rvierdiiev

Labels

bug
2 (Med Risk)
downgraded by judge
partial-50
edited-by-warden
duplicate-1248

Awards

50.8904 USDC - $50.89

External Links

Lines of code

https://github.com/Tapioca-DAO/tapiocaz-audit/blob/bcf61f79464cfdc0484aa272f9f6e28d5de36a8f/contracts/tOFT/modules/BaseTOFTOptionsModule.sol#L221 https://github.com/LayerZero-Labs/solidity-examples/blob/34cf25c06f5cf26fdbef80e0d693c77552aaa2db/contracts/token/oft/v2/BaseOFTV2.sol#L17

Vulnerability details

Impact

Transactions sent to LZ will get revert.

Proof of Concept

When sending messages using the LayerZero architecture, native tokens must be supplied to cover the cost of delivering the message at the receiving chain. the OFTV2.sendFrom function is payable function and get native token as gas fee.

function sendFrom(address _from, uint16 _dstChainId, bytes32 _toAddress, uint _amount, LzCallParams calldata _callParams) public payable virtual override { _send(_from, _dstChainId, _toAddress, _amount, _callParams.refundAddress, _callParams.zroPaymentAddress, _callParams.adapterParams); }

In the BaseTOFTOptionsModule.sol.exerciseInternal function, sendFrom is called without passing value.

if (tapSendData.withdrawOnAnotherChain) { ISendFrom(tapSendData.tapOftAddress).sendFrom( address(this), tapSendData.lzDstChainId, LzLib.addressToBytes32(from), tapAmount, ISendFrom.LzCallParams({ refundAddress: payable(from), zroPaymentAddress: tapSendData.zroPaymentAddress, adapterParams: LzLib.buildDefaultAdapterParams( tapSendData.extraGas ) }) );

Same issue is when Balancer.sol calls the StargateRouter's swap() which also requires value. https://stargateprotocol.gitbook.io/stargate/developers/how-to-swap

https://github.com/Tapioca-DAO/tapiocaz-audit/blob/bcf61f79464cfdc0484aa272f9f6e28d5de36a8f/contracts/Balancer.sol#L322 https://github.com/Tapioca-DAO/tapiocaz-audit/blob/bcf61f79464cfdc0484aa272f9f6e28d5de36a8f/contracts/Balancer.sol#L288

Tools Used

Manually

Pass value for ISendFrom(tapSendData.tapOftAddress).sendFrom in BaseTOFTOptionsModule.sol.exerciseInternal.

Assessed type

Error

#0 - c4-pre-sort

2023-08-06T09:31:45Z

minhquanym marked the issue as duplicate of #1199

#1 - c4-judge

2023-09-21T11:33:16Z

dmvt changed the severity to 2 (Med Risk)

#2 - c4-judge

2023-09-21T11:38:00Z

dmvt marked the issue as partial-50

Findings Information

🌟 Selected for report: peakbolt

Also found by: Breeje, SaeedAlipoor01988, ayeslick, ck, ladboy233, ltyu, vagrant

Labels

bug
2 (Med Risk)
satisfactory
edited-by-warden
duplicate-1169

Awards

46.3738 USDC - $46.37

External Links

Lines of code

https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/singularity/SGLBorrow.sol#L50

Vulnerability details

Impact

Borrowers can't repay when the system is paused, so they must pay more interest for the paused period.

Proof of Concept

Borrower can repay the loan using Singularity.sol#L296.repay function. But this function doesn't work when the system is paused and Borrowers have no way to avoid it.

function repay( address from, address to, bool skim, uint256 part ) public notPaused allowedBorrow(from, part) returns (uint256 amount) { updateExchangeRate(); _accrue(); amount = _repay(from, to, skim, part); }

The Market.sol#L245.updatePause function, is using for change pause mode and only conservator role can use it.as i see, conservator account is EAO.

function updatePause(bool val) external { require(msg.sender == conservator, "Market: unauthorized"); require(val != paused, "Market: same state"); emit PausedUpdated(paused, val); paused = val; } /// @dev conservator can pause/unpause the contract address public conservator;

The role of conservator is determined by the owner. So conservator can be the owner himself. https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/Market.sol#L187

There is no assurance that the conservator would pause the community for a while to get more interest.

Tools Used

Manually

Change approach to make repay function work for pause period or modify the interest calculation formula not to add interest for the pause period.

Assessed type

Other

#0 - c4-pre-sort

2023-08-04T23:39:26Z

minhquanym marked the issue as primary issue

#1 - c4-pre-sort

2023-08-04T23:42:38Z

minhquanym marked the issue as duplicate of #1169

#2 - c4-judge

2023-09-29T19:13:18Z

dmvt marked the issue as satisfactory

Findings Information

🌟 Selected for report: zzzitron

Also found by: 0xG0P1, 0xRobocop, RedOneN, SaeedAlipoor01988, bin2chen, cergyk, rvierdiiev, unsafesol

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
edited-by-warden
duplicate-1040

Awards

37.0991 USDC - $37.10

External Links

Lines of code

https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/singularity/SGLLiquidation.sol#L294 https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/Market.sol#L53

Vulnerability details

Impact

In the long run, the value of Market.sol#L53.totalCollateralShare ( total collateral supplied ) increases infinitely.

Proof of Concept

In the liquidation process, two methods play an essential role. SGLLiquidation.sol#L97._orderBookLiquidation() and SGLLiquidation.sol#L294._liquidateUser().

The contract will call closedLiquidation() function if not liquidationQueue exists or no liquidationQueue bid avail exists. function _liquidateUser() is called inside function closedLiquidation().

function _closedLiquidation( address[] calldata users, uint256[] calldata maxBorrowParts, ISwapper swapper, uint256 _exchangeRate, bytes calldata swapData ) private { uint256 liquidatedCount = 0; for (uint256 i = 0; i < users.length; i++) { address user = users[i]; if (!_isSolvent(user, _exchangeRate)) { liquidatedCount++; _liquidateUser( user, maxBorrowParts[i], swapper, _exchangeRate, swapData ); } } require(liquidatedCount > 0, "SGL: no users found"); }

When the user fund is liquidated, three general variables in the project must be updated. In the _orderBookLiquidation(), these three variables are updated as follows, totalCollateralShare and totalBorrow :

https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/singularity/SGLLiquidation.sol#L156C1-L157C52

_totalBorrow.elastic -= uint128(allBorrowAmount); _totalBorrow.base -= uint128(allBorrowPart); totalBorrow = _totalBorrow; totalCollateralShare -= allCollateralShare;

Briefly, the variable totalCollateralShare is defined as the value of total collateral supplied in the protocol. In the liquidation process, the user's collateral is sold for borrowed asset. so the amount of totalCollateralShare should get reduced to the amount of collateral sold in the form of shares. you can see the conversion example below :

https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/singularity/SGLLiquidation.sol#L125

uint256 amountWithBonus = borrowAmount + @ audit : how much asset amount (borrowAmount * liquidationMultiplier) / FEE_PRECISION; uint256 collateralShare = yieldBox.toShare( @audit : convert asset to collateral share collateralId, (amountWithBonus * _exchangeRate) / EXCHANGE_RATE_PRECISION, false ); userCollateralShare[user] -= collateralShare; @audit : subtract collateral share from user collateral share // @audit : Keep local totals allCollateralShare += collateralShare; allBorrowAmount += borrowAmount; allBorrowPart += borrowPart; } } @audit : update local totals and sync storage total _totalBorrow.elastic -= uint128(allBorrowAmount); _totalBorrow.base -= uint128(allBorrowPart); totalBorrow = _totalBorrow; totalCollateralShare -= allCollateralShare;

The problem is that, in the SGLLiquidation.sol#L294._liquidateUser()and SGLLiquidation.sol#L204._updateBorrowAndCollateralShare() function, the value of totalBorrow is updated but value of totalCollateralShare doesn't get update. function _updateBorrowAndCollateralShare() is called inside function _liquidateUser().

https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/singularity/SGLLiquidation.sol#L252

uint256 amountWithBonus = borrowAmount + (borrowAmount * liquidationMultiplier) / FEE_PRECISION; collateralShare = yieldBox.toShare( collateralId, (amountWithBonus * _exchangeRate) / EXCHANGE_RATE_PRECISION, false ); if (collateralShare > userCollateralShare[user]) { collateralShare = userCollateralShare[user]; } userCollateralShare[user] -= collateralShare; require(borrowAmount != 0, "SGL: solvent"); totalBorrow.elastic -= uint128(borrowAmount); totalBorrow.base -= uint128(borrowPart);

Because of this, in the long run, the value of Market.sol#L53.totalCollateralShare ( total collateral supplied ) increases infinitely.

Tools Used

Manually

Update totalCollateralShare value in the _liquidateUser function same as in the _orderBookLiquidation function.

Assessed type

Other

#0 - c4-pre-sort

2023-08-05T01:11:51Z

minhquanym marked the issue as primary issue

#1 - c4-pre-sort

2023-08-05T01:14:03Z

minhquanym marked the issue as duplicate of #1040

#2 - c4-judge

2023-09-12T16:59:20Z

dmvt changed the severity to 2 (Med Risk)

#3 - c4-judge

2023-09-12T17:06:39Z

dmvt marked the issue as satisfactory

Findings Information

🌟 Selected for report: SaeedAlipoor01988

Also found by: kaden

Labels

bug
2 (Med Risk)
primary issue
selected for report
sponsor confirmed
M-65

Awards

453.755 USDC - $453.76

External Links

Lines of code

https://github.com/Tapioca-DAO/tapioca-yieldbox-strategies-audit/blob/05ba7108a83c66dada98bc5bc75cf18004f2a49b/contracts/curve/TricryptoNativeStrategy.sol#L30

Vulnerability details

Impact

Please check PoC.

Proof of Concept

Curve allows Curve LP token holders to stake their position to receive CRV and potentially other bonus reward tokens. but in TricryptoNativeStrategy contract, it is assumed that only CRV token is available as reward. used lpGauge.claimable_tokens to get the amount of currently mintable CRV from gauge. accounting for rewards, from lpGauge contract, has been done for CRV.

uint256 claimable = lpGauge.claimable_tokens(address(this)); if (claimable > 0) { uint256 crvBalanceBefore = rewardToken.balanceOf(address(this)); minter.mint(address(lpGauge)); uint256 crvBalanceAfter = rewardToken.balanceOf(address(this)); if (crvBalanceAfter > crvBalanceBefore) { uint256 crvAmount = crvBalanceAfter - crvBalanceBefore; ISwapper.SwapData memory swapData = swapper.buildSwapData( address(rewardToken), address(wrappedNative), crvAmount, 0, false, false ); uint256 calcAmount = swapper.getOutputAmount(swapData, ""); uint256 minAmount = calcAmount - (calcAmount * 50) / 10_000; //0.5% swapper.swap(swapData, minAmount, address(this), ""); uint256 queued = wrappedNative.balanceOf(address(this)); _addLiquidityAndStake(queued); emit AmountDeposited(queued); } }

Based on the Curve docs and LiquidityGaugeV3.vy smart contract, it's possible that more reward tokens (contracts) get added to the lpGauge contract:

https://github.com/curvefi/curve-dao-contracts/blob/567927551903f71ce5a73049e077be87111963cc/contracts/gauges/LiquidityGaugeV3.vy#L689C22-L705C8

In function _withdraw, withdrawal from lpGauge contract is done as follows. this function have an optional argument _claim_rewards, which when set to True will claim any pending rewards.

lpGauge.withdraw(lpBalance, true);

The value of _claim_rewards is true and causes rewards to be collected to the TricryptoNativeStrategy contract, but no management has been done on these pending rewards and causes these rewards to be lost.

Tools Used

Manually

And you can querying Reward Information by below function's : https://curve.readthedocs.io/dao-gauges.html#querying-reward-information

Assessed type

Invalid Validation

#0 - c4-pre-sort

2023-08-07T02:06:40Z

minhquanym marked the issue as primary issue

#1 - minhquanym

2023-08-07T02:06:45Z

Similar #162

#2 - c4-sponsor

2023-09-06T07:58:59Z

cryptotechmaker (sponsor) confirmed

#3 - c4-judge

2023-09-19T14:09:57Z

dmvt marked the issue as selected for report

Findings Information

🌟 Selected for report: dirk_y

Also found by: KIntern_NA, SaeedAlipoor01988, ltyu

Labels

bug
2 (Med Risk)
satisfactory
duplicate-188

Awards

141.3621 USDC - $141.36

External Links

Lines of code

https://github.com/Tapioca-DAO/tapioca-periph-audit/blob/023751a4e987cf7c203ab25d3abba58f7344f213/contracts/oracle/implementations/SGOracle.sol#L51

Vulnerability details

Impact

Using a wrong decimal number on the exchange rate could cause incorrect pricing.

Proof of Concept

The BigBang contract assumes the exchange rates returned from SGOracle.sol have 18 decimals, while, however, which is not true. below is code for get price based on the USDT pool and USDT price from chainlink :

contract BaseContract {

//100181073 function GetP() public pure returns(uint) { return (23090372827983 * uint256(99977000)) / 23043336774138; }

}

Reurned price is with 9 decimals. Using a wrong decimal number on the exchange rate could cause incorrect pricing.

Tools Used

Manually

Based on the pool decimals and UNDERLYING oracle decimals, first, scale decimals of exchange rate to 18.

Assessed type

Decimal

#0 - c4-pre-sort

2023-08-06T01:06:14Z

minhquanym marked the issue as duplicate of #188

#1 - c4-judge

2023-09-29T18:15:05Z

dmvt marked the issue as satisfactory

Findings Information

🌟 Selected for report: SaeedAlipoor01988

Labels

bug
2 (Med Risk)
disagree with severity
primary issue
satisfactory
selected for report
sponsor confirmed
edited-by-warden
M-88

Awards

1008.3444 USDC - $1,008.34

External Links

Lines of code

https://github.com/Tapioca-DAO/tapioca-periph-audit/blob/023751a4e987cf7c203ab25d3abba58f7344f213/contracts/Swapper/UniswapV3Swapper.sol#L38 https://github.com/Tapioca-DAO/tapioca-periph-audit/blob/023751a4e987cf7c203ab25d3abba58f7344f213/contracts/Swapper/UniswapV3Swapper.sol#L61 https://github.com/Tapioca-DAO/tapioca-periph-audit/blob/023751a4e987cf7c203ab25d3abba58f7344f213/contracts/Swapper/UniswapV3Swapper.sol#L96 https://github.com/Tapioca-DAO/tapioca-yieldbox-strategies-audit/blob/05ba7108a83c66dada98bc5bc75cf18004f2a49b/contracts/stargate/StargateSwapperV3.sol#L29

Vulnerability details

Impact

If the UniswapV3Swapper.sol.poolFee is set to the value of the pool that is not yet deployed, an attacker can deploy & initialize the pool with his desired price, then the oracle will get the rate from that manipulated pool.

Proof of Concept

In the UniswapV3Swapper.sol, value of poolFee is coded to 3000 and is changeable by UniswapV3Swapper.sol.setPoolFee method. this value is used in the UniswapV3Factory to find pool and get best rate for swap. because uniswapv3 is using the fee value to find the address of a pool.

address pool = factory.getPool(tokenIn, tokenOut, poolFee); (int24 tick, ) = OracleLibrary.consult(pool, 60); amountIn = OracleLibrary.getQuoteAtTick( tick, uint128(amountOut), tokenOut, tokenIn );

Uniswap v3 introduces multiple pools for each token pair, each with a different swapping fee. Liquidity providers may initially create pools at three fee levels: 0.05%, 0.30%, and 1%. More fee levels may be added by UNI governance, e.g. the 0.01% fee level added by this governance proposal in November 2021, as executed here. According to https://docs.uniswap.org/concepts/protocol/fees#pool-fees-tiers

an attacker could check the defaultFee of the StargateSwapperV3.sol and then check what tokens tapioca markets have in it's balance, if there is a right match where the defaultFee for a token pair does not exist, attacker could deploy and initialized it with his desired price.

https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/UniswapV3Factory.sol#L35 https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/UniswapV3Factory.sol#L20

Tools Used

Manually

recommend the protocol valid the pool exists with token0 and token1 and fee setting before using the oracle when deploying the price oracle contract.

Assessed type

Invalid Validation

#0 - c4-pre-sort

2023-08-05T15:55:37Z

minhquanym marked the issue as primary issue

#1 - c4-sponsor

2023-08-18T20:01:36Z

0xRektora marked the issue as disagree with severity

#2 - 0xRektora

2023-08-18T20:06:48Z

Severity is right, plugin won't undo, should be medium.

#3 - c4-sponsor

2023-08-18T20:06:56Z

0xRektora marked the issue as sponsor confirmed

#4 - c4-judge

2023-09-20T19:59:30Z

dmvt marked the issue as satisfactory

#5 - c4-judge

2023-10-02T14:25:18Z

dmvt marked the issue as selected for report

Awards

2.1417 USDC - $2.14

Labels

bug
2 (Med Risk)
disagree with severity
downgraded by judge
satisfactory
sponsor confirmed
edited-by-warden
duplicate-163

External Links

Lines of code

https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/bigBang/BigBang.sol#L603 https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/singularity/SGLLiquidation.sol#L337

Vulnerability details

Impact

In the BigBang.sol._liquidateUser, the DEX transaction to swap the collateralShare into Asset can be done without slippage protection and at a loss.

Proof of Concept

The BigBang.sol.liquidate function, get collateralToAssetSwapData as input from users. In the _liquidateUser function, minAssetMount value is extracted from collateralToAssetSwapData. as you can see, the value of minAssetMount by default is 0, if the user has also entered the value of minAssetMount as zero, the same default value (*** 0 value for slippage ***) will be used in the swap transaction.

uint256 minAssetMount = 0; if (_dexData.length > 0) { minAssetMount = abi.decode(_dexData, (uint256)); } swapper.swap(swapData, minAssetMount, address(this), "");

As a result, the swap transaction will be done with zero value for slippage, which can cause the contract to receive asset with a lower value than the collateral given to the user.

Same is happening in the SGLLiquidation.sol._liquidateUser function. SGLLiquidation.sol._liquidateUser function, get dexData as input from users. In the _liquidateUser, minAssetAmount value is extracted from dexData. as you can see, the value of minAssetAmount by default is 0, if the user has also entered the value of minAssetAmount as zero, the same default value (0) will be used in the swap transaction.

uint256 minAssetAmount = 0; if (dexData.length > 0) { minAssetAmount = abi.decode(dexData, (uint256)); } ISwapper.SwapData memory swapData = swapper.buildSwapData( collateralId, assetId, 0, collateralShare, true, true ); swapper.swap(swapData, minAssetAmount, address(this), "");

As a result, DEX transaction to swap the collateralShare into Asset can be done without slippage protection and at a loss.

Tools Used

Manually

Add slippage protection.

Assessed type

Uniswap

#0 - c4-pre-sort

2023-08-05T12:55:45Z

minhquanym marked the issue as low quality report

#1 - c4-pre-sort

2023-08-05T14:40:38Z

minhquanym marked the issue as primary issue

#2 - c4-pre-sort

2023-08-08T18:53:05Z

minhquanym marked the issue as remove high or low quality report

#3 - c4-sponsor

2023-08-14T22:41:09Z

0xRektora marked the issue as disagree with severity

#4 - 0xRektora

2023-08-14T22:45:56Z

Valid, should be marked as medium. Liquidator has more incentives to cooperate to get his reward rather than using wrong values.

#5 - c4-sponsor

2023-08-15T05:29:17Z

cryptolyndon marked the issue as sponsor disputed

#6 - c4-sponsor

2023-08-15T05:33:24Z

cryptolyndon marked the issue as sponsor confirmed

#7 - c4-judge

2023-09-13T09:29:36Z

dmvt changed the severity to 2 (Med Risk)

#8 - dmvt

2023-09-13T09:32:03Z

I agree with the sponsor on severity. Several external factors are involved.

#9 - c4-judge

2023-09-13T09:32:47Z

dmvt marked the issue as selected for report

#10 - c4-judge

2023-09-19T11:53:45Z

dmvt marked the issue as duplicate of #245

#11 - c4-judge

2023-10-02T11:45:23Z

dmvt marked the issue as not selected for report

#12 - c4-judge

2023-10-02T14:01:49Z

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