Platform: Code4rena
Start Date: 23/05/2022
Pot Size: $50,000 USDC
Total HM: 44
Participants: 99
Period: 5 days
Judge: hickuphh3
Total Solo HM: 11
Id: 129
League: ETH
Rank: 75/99
Findings: 2
Award: $77.89
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: MiloTruck
Also found by: CertoraInc, PP1004, SmartSek, VAD37, WatchPug, berndartmueller, cccz, minhquanym, oyc_109, sorrynotsorry, unforgiven
77.7947 USDC - $77.79
EIP-4626 have fatal flaw that when totalAssets
can be manipulated then so token share price.
User can create new BathToken
and inflate share price so high that deposit will always return 0 share regardless of amount token transfer. Basically, give free token to first user deposit in BathToken
pool
BathHouse.openBathTokenSpawnAndSignal()
with initialLiquidity = 1e1 DAI.TotalSupply
= 1e1 and TotalAssets
= 1e1underlyingBalance()
return current balance as 1000e18.TotalSupply
= 1e1 and TotalAssets
= 1000e18convertToShares
formular assetsTransfered * totalSupply / totalAssets
. Which always return 0 if transfer less than 1000 DAI.The current easiest workaround is new BathToken can only be created by owner. Owner can create pool with large amount of initial liquidity, enough to prevent share price manipulation.
The underlying problem still is totalAssets
can be manipulated by anyone then so token share price.
BathToken.underlyingBalance()
return balanceOf current address with a global variable _totalAsset
_totalAsset
amount.BathPair.sol
. Create new onlyPair
function to update new rewards balance.IERC20(underlyingToken).balanceOf(address(this))
to _totalAsset
. Depositor will get their new share price fairlyThis help prevents anyone from manipulate share price of token through totalAssets
and only strategist can do so.
#0 - KenzoAgada
2022-06-06T04:51:41Z
Duplicate of #323.
🌟 Selected for report: berndartmueller
Also found by: 0x1f8b, 0xDjango, 0xsomeone, ACai, Bahurum, BouSalman, CertoraInc, Deivitto, Dravee, GimelSec, IllIllI, JMukesh, Kaiziron, PP1004, Ruhum, SmartSek, VAD37, WatchPug, _Adam, aez121, antonttc, blockdev, broccolirob, camden, cccz, cryptphi, defsec, dipp, ellahi, fatherOfBlocks, gzeon, horsefacts, ilan, jayjonah8, joestakey, kenta, kenzo, minhquanym, oyc_109, pauliax, pedroais, peritoflores, sashik_eth, shenwilly, simon135, throttle, xiaoming90, z3s
0.1022 USDC - $0.10
The majority of project use interface call IERC20(tokenAddress).transfer()
to transfer token without checking return value. With the exception of RubiconMarket.sol
EIP-20 implement function transfer(address _to, uint256 _value) public returns (bool success)
have a boolean return false value on failed transfer. Majority of ERC20 token never return false value. So most of the time people can get away with not checking return value.
BathToken.sol
ignore transfer return value. If the underlying token return false somehow, attacker can manipulate that pool to deposit no token but still receive share from the pool. (Unlikely to happen)Use safeTransfer library by OpenZeppelin.
Replace all ERC20().transfer and transferFrom with library call for all core files (RubiconMarket.sol
, RubiconRouter.sol
, BathToken.sol
, etc...)
#0 - bghughes
2022-06-04T01:14:45Z
Duplicate of #316