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
Rank: 55/132
Findings: 7
Award: $791.16
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: Sathish9098
Also found by: 0xSmartContract, 0xnev, Udsen, jasonxiale, rvierdiiev, tsvetanovv
58.8874 USDC - $58.89
AMMs should provide their users with an option to limit the execution of their pending actions, such as swaps or adding and removing liquidity. The most common solution is to include a deadline timestamp as a parameter (for example see Uniswap V2). If such an option is not present, users can unknowingly perform bad trades
The function CurveSwapper.swap doesn't set any deadline related parameter
function swap( SwapData calldata swapData, uint256 amountOutMin, address to, bytes memory data ) external override returns (uint256 amountOut, uint256 shareOut) { // Get Curve tokens' indexes & addresses uint256[] memory tokenIndexes = abi.decode(data, (uint256[])); address tokenIn = curvePool.coins(tokenIndexes[0]); address tokenOut = curvePool.coins(tokenIndexes[1]); // Get tokens' amounts (uint256 amountIn, ) = _getAmounts( swapData.amountData, swapData.tokensData.tokenInId, swapData.tokensData.tokenOutId, yieldBox ); // Retrieve tokens from sender or from YieldBox amountIn = _extractTokens( swapData.yieldBoxData, yieldBox, tokenIn, swapData.tokensData.tokenInId, amountIn, swapData.amountData.shareIn ); // Swap & compute output amountOut = _swapTokensForTokens( int128(int256(tokenIndexes[0])), int128(int256(tokenIndexes[1])), amountIn, amountOutMin ); if (swapData.yieldBoxData.depositToYb) { _safeApprove(tokenOut, address(yieldBox), amountOut); (, shareOut) = yieldBox.depositAsset( swapData.tokensData.tokenOutId, address(this), to, amountOut, 0 ); } else { IERC20(tokenOut).safeTransfer(to, amountOut); } }
VS
add deadline
related parameter
Timing
#0 - c4-pre-sort
2023-08-05T12:43:09Z
minhquanym marked the issue as duplicate of #1513
#1 - c4-judge
2023-09-29T21:46:34Z
dmvt marked the issue as satisfactory
🌟 Selected for report: jasonxiale
Also found by: Madalad
453.755 USDC - $453.76
token mights stuck in MagnetarMarketModule contract if the asset doesn't support cross-chain operation
[MagnetarMarketModule._withdrawToChain] will check if the asset supports a cross chain operation in L703-L709, if the asset doesn't support, the function will return.
Taking one of the _withdrawToChain
callers MagnetarMarketModule.depositRepayAndRemoveCollateralFromMarket as an example:
While MagnetarMarketModule.depositRepayAndRemoveCollateralFromMarket
is called, the function will call MagnetarMarketModule._depositRepayAndRemoveCollateralFromMarket
, supposed everything goes well, the control flow will fall into L258-L289.
Suppose collateralAmount
in L258 is not zero, and withdrawCollateralParams.withdraw is true, in such case, collateralWithdrawReceiver
will be address(this)
. After calling marketInterface.removeCollateral, address(this)
which is MagnetarMarketModule
contract will own the asset.
Since withdrawCollateralParams.withdraw
is true, MagnetarMarketModule._withdrawToChain will be called.
212 function _depositRepayAndRemoveCollateralFromMarket( 213 address market, 214 address user, 215 uint256 depositAmount, 216 uint256 repayAmount, 217 uint256 collateralAmount, 218 bool extractFromSender, 219 ICommonData.IWithdrawParams calldata withdrawCollateralParams 220 ) private { ... 256 // performs a removeCollateral operation on the market 257 // if `withdrawCollateralParams.withdraw` it uses `withdrawTo` to withdraw collateral on the same chain or to another one 258 if (collateralAmount > 0) { 259 address collateralWithdrawReceiver = withdrawCollateralParams 260 .withdraw 261 ? address(this) 262 : user; 263 uint256 collateralShare = yieldBox.toShare( 264 marketInterface.collateralId(), 265 collateralAmount, 266 false 267 ); 268 marketInterface.removeCollateral( 269 user, 270 collateralWithdrawReceiver, 271 collateralShare 272 ); 273 274 //withdraw 275 if (withdrawCollateralParams.withdraw) { 276 _withdrawToChain( 277 yieldBox, 278 collateralWithdrawReceiver, 279 marketInterface.collateralId(), 280 withdrawCollateralParams.withdrawLzChainId, 281 LzLib.addressToBytes32(user), 282 collateralAmount, 283 collateralShare, 284 withdrawCollateralParams.withdrawAdapterParams, 285 payable(this), 286 address(this).balance 287 ); 288 } 289 } 290 }
In L703-L709, the function will return which leaves the asset in the MagnetarMarketModule
contract
677 function _withdrawToChain( 678 IYieldBoxBase yieldBox, 679 address from, 680 uint256 assetId, 681 uint16 dstChainId, 682 bytes32 receiver, 683 uint256 amount, 684 uint256 share, 685 bytes memory adapterParams, 686 address payable refundAddress, 687 uint256 gas 688 ) private { ... 702 // make sure the asset supports a cross chain operation 703 try 704 IERC165(address(asset)).supportsInterface( 705 type(ISendFrom).interfaceId 706 ) 707 {} catch { 708 return; <---------------- here 709 } 710 ...
VS
If the asset doesn't support cross-chain, maybe we can perform a withdraw as L690-L699
--- MagnetarMarketModule.sol 2023-08-04 17:22:52.272395098 +0800 +++ MagnetarMarketModule_new.sol 2023-08-04 17:34:57.230800699 +0800 @@ -705,6 +705,13 @@ type(ISendFrom).interfaceId ) {} catch { + yieldBox.withdraw( + assetId, + from, + LzLib.bytes32ToAddress(receiver), + amount, + share + ); return; }
Other
#0 - c4-pre-sort
2023-08-09T07:23:26Z
minhquanym marked the issue as primary issue
#1 - c4-sponsor
2023-08-25T17:45:27Z
0xRektora marked the issue as sponsor confirmed
#2 - c4-judge
2023-09-30T14:37:40Z
dmvt marked the issue as selected for report
🌟 Selected for report: GalloDaSballo
Also found by: 0xfuje, GalloDaSballo, Ruhum, Vagner, jasonxiale, kutugu, mojito_auditor
58.8874 USDC - $58.89
https://github.com/Tapioca-DAO/tapioca-yieldbox-strategies-audit/blob/05ba7108a83c66dada98bc5bc75cf18004f2a49b/contracts/compound/CompoundStrategy.sol#L115 https://github.com/Tapioca-DAO/tapioca-yieldbox-strategies-audit/blob/05ba7108a83c66dada98bc5bc75cf18004f2a49b/contracts/compound/CompoundStrategy.sol#L145-L145
Contract CompoundStrategy uses exchangeRateStored
to determinate the exchange range in here and here.
But according to cToken's API document, This function does not accrue interest before calculating the exchange rate
, so function CompoundStrategy._currentBalance and CompoundStrategy._withdraw use stale value.
CompoundStrategy._currentBalance:
function _currentBalance() internal view override returns (uint256 amount) { uint256 shares = cToken.balanceOf(address(this)); uint256 pricePerShare = cToken.exchangeRateStored(); <--------------- Here uint256 invested = (shares * pricePerShare) / (10 ** 18); uint256 queued = wrappedNative.balanceOf(address(this)); return queued + invested; }
function _withdraw( address to, uint256 amount ) internal override nonReentrant { uint256 available = _currentBalance(); require(available >= amount, "CompoundStrategy: amount not valid"); uint256 queued = wrappedNative.balanceOf(address(this)); if (amount > queued) { uint256 pricePerShare = cToken.exchangeRateStored(); <--------------- Here uint256 toWithdraw = (((amount - queued) * (10 ** 18)) / pricePerShare); cToken.redeem(toWithdraw); INative(address(wrappedNative)).deposit{ value: address(this).balance }(); } require( wrappedNative.balanceOf(address(this)) >= amount, "CompoundStrategy: not enough" ); wrappedNative.safeTransfer(to, amount); emit AmountWithdrawn(to, amount); }
VS
You shoud use exchangeRateCurrent()
instead
Math
#0 - c4-pre-sort
2023-08-06T12:42:35Z
minhquanym marked the issue as duplicate of #411
#1 - c4-pre-sort
2023-08-06T12:45:02Z
minhquanym marked the issue as duplicate of #1330
#2 - c4-judge
2023-09-26T14:59:41Z
dmvt marked the issue as satisfactory
🌟 Selected for report: mojito_auditor
Also found by: KIntern_NA, Udsen, bin2chen, chaduke, jasonxiale, kutugu, peakbolt, rvierdiiev
37.0991 USDC - $37.10
TapOFT.extractTAP uses wrong check to determine the number of tokens it can mint for specified week, which causes it mints more tokens than it's supposed.
According to comments in line 55 and the source code, emissionForWeek should be the amount of emitted TAP for a specific week
However while emissionForWeek
is used in function TapOFT.extractTAP, is misused as the limit of one tx
function extractTAP(address _to, uint256 _amount) external notPaused { require(msg.sender == minter, "unauthorized"); require(_amount > 0, "amount not valid"); uint256 week = _timestampToWeek(block.timestamp); require(emissionForWeek[week] >= _amount, "exceeds allowable amount"); <-------------- Here is the wrong check _mint(_to, _amount); mintedInWeek[week] += _amount; emit Minted(msg.sender, _to, _amount); }
VS
--- TapOFT.sol 2023-08-02 22:52:48.149365288 +0800 +++ TapOFT_New.sol 2023-08-02 22:53:29.974237715 +0800 @@ -222,7 +222,7 @@ require(_amount > 0, "amount not valid"); uint256 week = _timestampToWeek(block.timestamp); - require(emissionForWeek[week] >= _amount, "exceeds allowable amount"); + require(emissionForWeek[week] >= _amount + mintedInWeek[week], "exceeds allowable amount"); _mint(_to, _amount); mintedInWeek[week] += _amount; emit Minted(msg.sender, _to, _amount);
Invalid Validation
#0 - c4-pre-sort
2023-08-04T23:10:03Z
minhquanym marked the issue as duplicate of #728
#1 - c4-judge
2023-09-27T11:57:24Z
dmvt marked the issue as satisfactory
🌟 Selected for report: GalloDaSballo
Also found by: KIntern_NA, jasonxiale, ladboy233
141.3621 USDC - $141.36
https://github.com/Tapioca-DAO/tap-token-audit/blob/59749be5bc2286f0bdbf59d7ddc258ddafd49a9f/contracts/option-airdrop/AirdropBroker.sol#L535 https://github.com/Tapioca-DAO/tap-token-audit/blob/59749be5bc2286f0bdbf59d7ddc258ddafd49a9f/contracts/options/TapiocaOptionBroker.sol#L555
There are some weird token that has decimals larger than 18(e.g. YAM-V2 has 24), if such token is used in the protocol, the function will revert.
Take TapiocaOptionBroker._getDiscountedPaymentAmount as an example:
In L555, the paymentAmount
is calculated as paymentAmount = paymentAmount / (10 ** (18 - _paymentTokenDecimals));
, if _paymentTokenDecimals is larger than 18, the function will revert.
function _getDiscountedPaymentAmount( uint256 _otcAmountInUSD, uint256 _paymentTokenValuation, uint256 _discount, uint256 _paymentTokenDecimals ) internal pure returns (uint256 paymentAmount) { // Calculate payment amount uint256 rawPaymentAmount = _otcAmountInUSD / _paymentTokenValuation; paymentAmount = rawPaymentAmount - muldiv(rawPaymentAmount, _discount, 100e4); // 1e4 is discount decimals, 100 is discount percentage paymentAmount = paymentAmount / (10 ** (18 - _paymentTokenDecimals)); <-------------- Here }
VS
Under/Overflow
#0 - c4-pre-sort
2023-08-07T03:02:13Z
minhquanym marked the issue as duplicate of #1104
#1 - c4-judge
2023-09-30T12:54:48Z
dmvt marked the issue as satisfactory