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: 228/246
Findings: 1
Award: $3.49
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: monrel
Also found by: 0xRajkumar, 0xfusion, AkshaySrivastav, Bahurum, Brenzee, Cryptor, Dug, Haipls, Koolex, Krace, MiloTruck, RaymondFam, RedTiger, ToonVH, Tricko, Vagner, aga7hokakological, anodaram, bart1e, bin2chen, bytes032, carrotsmuggler, ck, d3e4, giovannidisiena, igingu, juancito, mahdirostami, mert_eren, n33k, nemveer, parsely, pavankv, sashik_eth, shaka, sinarette, ulqiorra, yac
3.4908 USDC - $3.49
Because of the way the underlyingValue
is calculated in the stake()
function an user who has all or a majority of the shares can manipulate the balances of the derivate contracts and steal the ether of any new user, if he has enough funds.
Bob is the first user of the protocol and stakes 3 ether with the stake()
function, minting 3 safEth tokens and having 100% of the shares. Alice wants to use the protocol too and stakes 1 ether with the stake()
function. Bob sees the transaction in the mempool and front-run the transaction, transferring an amount of wstEth/SfrxEth/Reth to the specific derivate contract, modifying the balanceOf that contract. The amount that needs to be sent to any of the contract can be calculated pretty easily by Bob since he needs to make it so the preDepositPrice
is bigger than the totalStakeValueEth
so when the mintAmount
is calculated for Alice, it will get rounded to 0, because of the EVM rounding, creating a precision loss for Alice. For this specific case, Bob needs to transfer as much as 0.1 wstEth/SfrxEth/Reth to any of the 3 derivates contracts specifically, changing the underlyingValue
and implicitly the preDepositPrice
, which would be $(1e18 * 3.1)/3=> 1.06 * 1e18$. The totalStakeValueEth
for Alice would be ~1, since she deposited 1 ether, so the mintAmount
is $(11e18)/(1.061e18) => 0$ after the rounding. Alice funds will get transfered to the derivates contract but the amount of shares minted would be 0, so Bob can call the unstake()
function, right after Alice stakes her ETH, with all of his shares, getting the balances of all the derivates contracts since he owns 100% of the shares. If Bob has enough funds to transfer to the contracts he can sandwich attack most of the users by just transferring before they stake and unstaking right after.
Manual review, Remix Debugger Tool
The calculations that are done in the stake()
function needs to be redone so there will not be such precision loss, or the business logic of the needs to be rethought so it will not use the balanceOf(address(this)
since that can be easily manipulated by malicious users that have enough funds.
#0 - c4-pre-sort
2023-04-04T12:44:55Z
0xSorryNotSorry marked the issue as duplicate of #715
#1 - c4-judge
2023-04-21T14:56:30Z
Picodes marked the issue as satisfactory