Platform: Code4rena
Start Date: 24/03/2023
Pot Size: $49,200 USDC
Total HM: 20
Participants: 246
Period: 6 days
Judge: Picodes
Total Solo HM: 1
Id: 226
League: ETH
Rank: 220/246
Findings: 2
Award: $4.68
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: adriro
Also found by: 0xMirce, 0xRajkumar, 0xepley, BPZ, Bahurum, Bauer, Co0nan, Emmanuel, Franfran, HollaDieWaldfee, IgorZuk, MiloTruck, NoamYakov, RedTiger, Ruhum, T1MOH, Tricko, ad3sh_, auditor0517, bin2chen, carrotsmuggler, eyexploit, handsomegiraffe, igingu, jasonxiale, koxuan, lukris02, monrel, nadin, peanuts, rbserver, rvierdiiev, shaka, sinarette, tnevler, y1cunhui
4.5426 USDC - $4.54
WstEth.ethPerDerivative
returns stETH amount instead of ETH. stETH
price is nearly same as ETH but not the same.
At the moment of writing, 1ETH = 1.000965519156061458 stETH and ethPerDerivative
will return a little different value.
WstEth.ethPerDerivative
returns stETH amount while it should return ETH amount.
function ethPerDerivative(uint256 _amount) public view returns (uint256) { return IWStETH(WST_ETH).getStETHByWstETH(10 ** 18); }
stETH
is not the same as ETH in value at the moment. So WstEth.withdraw
swaps stETH to ETH during withdrawal.
IStEthEthPool(LIDO_CRV_POOL).exchange(1, 0, stEthBal, minOut);
The exact implementation is to get stETH price from the curve pool used in withdraw.
Manual Review
Get ETH value the curve pool. We can use get_dy
method of the pool.
uint256 stETH = IERC20(WST_ETH).balanceOf(address(this)); return IStEthEthPool(LIDO_CRV_POOL).get_dy(1, 0, stETH);
#0 - c4-pre-sort
2023-04-04T17:13:51Z
0xSorryNotSorry marked the issue as duplicate of #588
#1 - c4-judge
2023-04-23T11:07:04Z
Picodes changed the severity to 3 (High Risk)
#2 - c4-judge
2023-04-24T20:45:05Z
Picodes marked the issue as satisfactory
🌟 Selected for report: HHK
Also found by: 019EC6E2, 0Kage, 0x52, 0xRobocop, 0xTraub, 0xbepresent, 0xepley, 0xfusion, 0xl51, 4lulz, Bahurum, BanPaleo, Bauer, CodeFoxInc, Dug, HollaDieWaldfee, IgorZuk, Lirios, MadWookie, MiloTruck, RedTiger, Ruhum, SaeedAlipoor01988, Shogoki, SunSec, ToonVH, Toshii, UdarTeam, Viktor_Cortess, a3yip6, auditor0517, aviggiano, bearonbike, bytes032, carlitox477, carrotsmuggler, chalex, deliriusz, ernestognw, fs0c, handsomegiraffe, igingu, jasonxiale, kaden, koxuan, latt1ce, m_Rassska, n1punp, nemveer, nowonder92, peanuts, pontifex, roelio, rvierdiiev, shalaamum, shuklaayush, skidog, tank, teddav, top1st, ulqiorra, wait, wen, yac
0.1353 USDC - $0.14
Users can get more rETH by manipulating poolPrice()
using flashloan.
Reth.deposit()
swaps from ETH to rETH if the rETH pool is full by checking poolCanDeposit()
.
In this case, ETH will be swapped to rETH using poolPrice()
function poolPrice() private view returns (uint256) { address rocketTokenRETHAddress = RocketStorageInterface( ROCKET_STORAGE_ADDRESS ).getAddress( keccak256( abi.encodePacked("contract.address", "rocketTokenRETH") ) ); IUniswapV3Factory factory = IUniswapV3Factory(UNI_V3_FACTORY); IUniswapV3Pool pool = IUniswapV3Pool( factory.getPool(rocketTokenRETHAddress, W_ETH_ADDRESS, 500) ); (uint160 sqrtPriceX96, , , , , , ) = pool.slot0(); return (sqrtPriceX96 * (uint(sqrtPriceX96)) * (1e18)) >> (96 * 2); }
But it uses the raw price instead of TWAP so it can be manipulated easily.
Reth.deposit()
will work with uniswap.Manual Review
We should use the TWAP price of uniswap instead of raw price.
#0 - c4-pre-sort
2023-04-04T11:34:46Z
0xSorryNotSorry marked the issue as duplicate of #601
#1 - c4-judge
2023-04-21T16:14:41Z
Picodes marked the issue as satisfactory
#2 - c4-judge
2023-04-21T16:15:17Z
Picodes marked the issue as duplicate of #1125