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: 70/246
Findings: 3
Award: $79.44
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: ladboy233
Also found by: 0xkazim, 0xnev, Bauer, J4de, Matin, UniversalCrypto, cryptothemex, jasonxiale, juancito, koxuan, latt1ce, neumo
48.6252 USDC - $48.63
When withdrawing, the way the SfrxEth
contract calculates the value of minOut
before exchanging frxEth for ETH in the curve pool is wrong.
... 74 uint256 minOut = (((ethPerDerivative(_amount) * _amount) / 10 ** 18) * 75 (10 ** 18 - maxSlippage)) / 10 ** 18; ...
There is a division before multiplication that may cause a loss of precision and thus a minimum value for the exchange operation lower than epected, because the 18 less significant positions of the (ethPerDerivative(_amount) * _amount)
operation are cropped. This means the impact of this issue is not high, as the amount must be small for the calculation to be impacted significantly, and a sandwich attack to steal user funds probably would not be very profitable to the attacker.
Lets assume the amount to withdraw is 9 * (10 ** 8) wei. ethPerDerivative(_amount)
would be slightly greater than that, because it calculates it based on the price_oracle
of the curve pool:
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/SfrxEth.sol#L111-L117
At the time of writing, this value is 998772810487987829, so ethPerDerivative(_amount) * _amount
would be smaller than 10 ** 18, making minOut = 0
.
Manual review.
Change the calculation putting the division as the last operation:
... 74 uint256 minOut = ethPerDerivative(_amount) * _amount * (10 ** 18 - maxSlippage) / 10**36; ...
The operation could revert because of an overflow if _amount
is in the order of 340 billion ETH or greater, so I assume it to be safe.
#0 - c4-pre-sort
2023-04-04T16:39:16Z
0xSorryNotSorry marked the issue as duplicate of #1044
#1 - c4-judge
2023-04-24T21:15:36Z
Picodes marked the issue as satisfactory
🌟 Selected for report: RaymondFam
Also found by: 7siech, LeoGold, Phantasmagoria, SunSec, adeolu, anodaram, aviggiano, chaduke, d3e4, eyexploit, jasonxiale, juancito, koxuan, mojito_auditor, n33k, neumo, rotcivegaf, yac
17.681 USDC - $17.68
When a user calls the stake
function of the SafEth
contract, the value in ETH that he/she sends in is split among the various derivatives added to the contract. The portion of ETH staked to each derivative depends on the weight that derivative has.
The following snippet is the loop over the derivatives configured in the contract where the amount sent is split:
... 84 for (uint i = 0; i < derivativeCount; i++) { 85 uint256 weight = weights[i]; 86 IDerivative derivative = derivatives[i]; 87 if (weight == 0) continue; 88 uint256 ethAmount = (msg.value * weight) / totalWeight; 89 90 91 // This is slightly less than ethAmount because slippage 92 uint256 depositAmount = derivative.deposit{value: ethAmount}(); 93 uint derivativeReceivedEthValue = (derivative.ethPerDerivative( 94 depositAmount 95 ) * depositAmount) / 10 ** 18; 96 totalStakeValueEth += derivativeReceivedEthValue; 97 } ...
As we can see, the amount to stake for each derivative is calculated in line 88
, where the amount sent in by the user is multiplied by the weight of the derivative and then divided by the sum of all weights.
Then, in line 92
the amount is deposited through the derivative contract.
The issue here is that, after the execution of the loop, there are chances that not all ETH sent in by the user may have been deposited to the derivatives, there may be dust ETH lying in the SafEth
contract that cannot be withdrawn, as there is no mechanism set in the contract for that purpose. And it will keep accumulating as more calls to stake
are sent.
Let's assume Alice has 1000 wei and wants to stake it through the SafEth
contract (I will assume here that this amount can be staked, for the sake of simplicity of the calculations). The contract is configured with three derivatives, with weights 3
, 1
and 3
(so the value of totalWeights
is 7
). The contract has an initial balance of 0 ETH
.
When Alice calls stake
:
2 wei
Manual review.
Either refund the user the dust amount that was not staked after the loop or stake in the last derivative of the loop the remaining ETH from msg.value after staking in the previous derivatives.
#0 - c4-pre-sort
2023-04-04T16:28:36Z
0xSorryNotSorry marked the issue as duplicate of #455
#1 - c4-judge
2023-04-24T21:15:00Z
Picodes marked the issue as satisfactory