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: 57/132
Findings: 5
Award: $559.88
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Ack
Also found by: 0x73696d616f, 0xrugpull_detector, ACai, BPZ, Breeje, CrypticShepherd, Kaysoft, carrotsmuggler, cergyk, kodyvim, ladboy233, offside0011
56.1709 USDC - $56.17
https://github.com/Tapioca-DAO/tapiocaz-audit/blob/master/contracts/tOFT/modules/BaseTOFTStrategyModule.sol#L152-L162 https://github.com/Tapioca-DAO/tapiocaz-audit/blob/master/contracts/tOFT/modules/BaseTOFTMarketModule.sol#L160-L169 https://github.com/Tapioca-DAO/tapiocaz-audit/blob/master/contracts/tOFT/modules/BaseTOFTOptionsModule.sol#L189-L200 https://github.com/Tapioca-DAO/tapiocaz-audit/blob/master/contracts/tOFT/modules/BaseTOFTLeverageModule.sol#L184-L194 https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/master/contracts/usd0/modules/USDOLeverageModule.sol#L169-L179 https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/master/contracts/usd0/modules/USDOMarketModule.sol#L168-L177 https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/master/contracts/usd0/modules/USDOOptionsModule.sol#L174-L185
All these contracts are module contracts for omni-chain operations like leverage, strategy, market, options.
Vulnerabiilies come from 2 reasons.
modules
parameter.For example, a hacker can call BaseTOFTStrategyModule.strategyDeposit()
passing modules
as malicious contract with SELFDESTRUCT.
contract MaliciousModule Is Ownable { function depositToYieldbox() { selfdestruct(owner()); } }
contract MaliciousAttacker Is Ownable { function attack() { BaseTOFTStrategyModule(strategyModule).strategyDeposit(new MaliciousModule(), ...); } }
function strategyDeposit( address module, uint16 _srcChainId, bytes memory _srcAddress, uint64 _nonce, bytes memory _payload, IERC20 _erc20 ) public { ... (bool success, bytes memory reason) = module.delegatecall(// @audit - might cause self destructions, function should not be public abi.encodeWithSelector( this.depositToYieldbox.selector, assetId, amount, share, _erc20, address(this), onBehalfOf ) );
Complete DoS of Omni-chain tokens by destroying leverage, strategy, option, market modules.
Manual Review
modules
address should be used for delegatecall.call/delegatecall
#0 - c4-pre-sort
2023-08-05T11:17:39Z
minhquanym marked the issue as duplicate of #146
#1 - c4-judge
2023-09-13T10:25:05Z
dmvt marked the issue as satisfactory
🌟 Selected for report: zzzitron
Also found by: 0xRobocop, 0xSky, 0xrugpull_detector, KIntern_NA, RedOneN, bin2chen, carrotsmuggler, chaduke, kutugu, minhtrng, mojito_auditor, plainshift, zzebra83
46.9428 USDC - $46.94
BigBang.init()
might set non-zero big debtStartPoint
value.
_isEthMarket = collateralId == penrose.wethAssetId(); if (!_isEthMarket) { debtRateAgainstEthMarket = _debtRateAgainstEth; maxDebtRate = _debtRateMax; minDebtRate = _debtRateMin; debtStartPoint = _debtStartPoint; }
If debtStartPoint
is greater than totalBorrow.elastic
, underflow will occur.
function getDebtRate() public view returns (uint256) { ... uint256 _currentDebt = totalBorrow.elastic; uint256 debtPercentage = ((_currentDebt - debtStartPoint) * DEBT_PRECISION) / (_maxDebtPoint - debtStartPoint); ... return debt; }
As getDebtRate()
is called by accure()
and liquidate()
. it will cause these functions to be reverted as well.
function _accrue() internal override { .... //update debt rate uint256 annumDebtRate = getDebtRate(); ... }
If totalBorrow.elastic
< debtStartPoint
, getDebtRate()
should return mintDebtRate
.
function getDebtRate() public view returns (uint256) { ... uint256 _currentDebt = totalBorrow.elastic; + if (_currentDebt < debtStartPoint) return minDebtRate; uint256 debtPercentage = ((_currentDebt - debtStartPoint) * DEBT_PRECISION) / (_maxDebtPoint - debtStartPoint); ... return debt; }
DoS
#0 - c4-pre-sort
2023-08-04T22:26:38Z
minhquanym marked the issue as duplicate of #1370
#1 - c4-pre-sort
2023-08-04T22:30:36Z
minhquanym marked the issue as duplicate of #1046
#2 - c4-judge
2023-09-18T13:13:52Z
dmvt changed the severity to 3 (High Risk)
#3 - c4-judge
2023-09-18T13:15:26Z
dmvt marked the issue as satisfactory
🌟 Selected for report: GalloDaSballo
Also found by: 0xrugpull_detector, GalloDaSballo, SaeedAlipoor01988, kaden, ladboy233, unsafesol
76.3356 USDC - $76.34
It is quite possible for some times Yearn strategy might return incurring losses.
As maxLoss
parameter is set to 0, YearnStrategy._withdraw()
does not tolerate any losses incurred.
It will make withdrawal from Yearn strategies blocked.
function _withdraw( address to, uint256 amount ) internal override nonReentrant { uint256 available = _currentBalance(); require(available >= amount, "YearnStrategy: amount not valid"); uint256 queued = wrappedNative.balanceOf(address(this)); if (amount > queued) { uint256 pricePerShare = vault.pricePerShare(); uint256 toWithdraw = (((amount - queued) * (10 ** vault.decimals())) / pricePerShare); vault.withdraw(toWithdraw, address(this), 0); // @audit - maxLoss = 0 } wrappedNative.safeTransfer(to, amount - 1); //rounding error emit AmountWithdrawn(to, amount); }
Withdrawal might be blocked in case of strategy loss.
It should use maxLoss
parameter which is updatable by owner.
function _withdraw( address to, uint256 amount ) internal override nonReentrant { uint256 available = _currentBalance(); require(available >= amount, "YearnStrategy: amount not valid"); uint256 queued = wrappedNative.balanceOf(address(this)); if (amount > queued) { uint256 pricePerShare = vault.pricePerShare(); uint256 toWithdraw = (((amount - queued) * (10 ** vault.decimals())) / pricePerShare); - vault.withdraw(toWithdraw, address(this), 0); + vault.withdraw(toWithdraw, address(this), maxLoss); } wrappedNative.safeTransfer(to, amount - 1); emit AmountWithdrawn(to, amount); }
DoS
#0 - c4-pre-sort
2023-08-05T08:54:47Z
minhquanym marked the issue as duplicate of #96
#1 - c4-judge
2023-09-13T09:08:49Z
dmvt marked the issue as satisfactory