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: 151/246
Findings: 2
Award: $16.62
🌟 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
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L72 https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/WstEth.sol#L94
To stake ETH into safETH, one should call SafEth.stake()
method. The calculation of preDepositPrice is done like this:
preDepositPrice = (10 ** 18 * underlyingValue) / totalSupply;
Please note that the underlyingValue is the total value in ETH of all derivatives. Each derivative's underlyingValue is calculated as follows:
underlyingValue += (derivatives[i].ethPerDerivative(derivatives[i].balance()) * derivatives[i].balance()) / 10 ** 18;
And the derivative balance is calculated as follows (an example for WST_ETH):
function balance() public view returns (uint256) { return IERC20(WST_ETH).balanceOf(address(this)); }
An early malicious actor can send the derivative token directly to the derivative contract (e.g. WstEth), manipulating the underlyingValue calculation. Thus, manipulating the preDepositPrice which could possibly cause DoS to staking. Meaning that stakers can not stake any longer after the attack.
Given: Alice likes to join Asymmetry as a staker Bob is an early first staker (a malicious actor) For simplicity, safETH has only one derivative (e.g. WstEth) The price of 1 WST_ETH token = 1 ETH
Imagine the following scenario:
preDepositPrice = 10 ** 18; // initializes with a price of 1
// Calculate underlyingValue underlyingValue = WST_ETH price in ETH * WstEth derivative balance / 10 ** 18 underlyingValue = 1 ETH * 201 WST_ETH token / 10 ** 18 underlyingValue = 201 ETH // Calculate preDepositPrice preDepositPrice = (10 ** 18 * underlyingValue) / totalSupply preDepositPrice = (10 ** 18 * 201) / 1 safETH token preDepositPrice = 201 ETH // Calculate mint amount mintAmount = (totalStakeValueEth * 10 ** 18) / preDepositPrice => Alice staked 10, so totalStakeValueEth = 10 mintAmount = (10 * 10 ** 18) / 201 ETH mintAmount = 0
=> Alice staked 200, so totalStakeValueEth = 200 mintAmount = (200 * 10 ** 18) / 201 ETH mintAmount = 0
maxAmount = 200 * 10 ** 18; // initializing with 200 ETH as maximum
Please note that if the method reverts upon receiving zero safETH token, the impact still holds (i.e. Alice can not stake since the method will always revert).
Important: One could argue that this attack is expensive (although I don't beleive so), However, the attacker could always unstake and receive his funds back since the percentage of the asset is calculated based on the amount of safETH and the derivative's balance.
uint256 derivativeAmount = (derivatives[i].balance() * _safEthAmount) / safEthTotalSupply;
That's why I set the severity to high because:
Manual analysis
In the derivative contract, don't rely on balanceOf(address(this)), instead keep track of the balance in a state variable. This way, manipulating the price (in the way decribed above) is not possible anymore.
#0 - c4-pre-sort
2023-04-04T13:49:56Z
0xSorryNotSorry marked the issue as duplicate of #454
#1 - c4-judge
2023-04-21T16:21:10Z
Picodes marked the issue as duplicate of #1098
#2 - c4-judge
2023-04-24T21:00:49Z
Picodes marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2023-04-24T21:00:59Z
Picodes marked the issue as satisfactory