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: 8/246
Findings: 4
Award: $766.20
🌟 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/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L79-L81 https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L98 https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/Reth.sol#L221-L223 https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/SfrxEth.sol#L122-L124 https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/WstEth.sol#L93-L95
The main issue is that users are able to control derivative.balance()
. (derivative means all of Reth
, SfrxEth
and WstEth
)
We can easily find that all of Reth
, SfrxEth
and WstEth
has the balance() function which is linked to the normal ERC20 token's balanceOf
function.
So if user transfer that token (rethAddress()
, SFRX_ETH_ADDRESS
, WST_ETH
) to individual derivative contract manually, he can increase derivative.balance()
.
There can be several attacking scenarios. Here PoC is just one example of it.
It's about "First staker can make preDepositPrice
too huge, so next users' mintAmount
can be affected".
Step | Activity | totalSupply | underlyingValue | Attacker balance |
---|---|---|---|---|
(orginal) | 0 | 0 | 0 | |
Step 1 | Attacker stake 1 ETH = (10**18) | about 10**18 | about 10**18 | |
Step 2 | Attacker make his SafEth balance to 1 by unstaking | 1 | 1 | |
Step 3 | Attacker make derivatives[0].balance() to 2 * 10**18 by manual tranfer | 1 | 2 * 10**18 | 1 |
Step 4 | UserB stake 1 ETH = (10**18) |
Let's say that attacker is the first staker. Attacker stake 1 ETH
Let's call the attacker's balance is b
(it will be around 10**18
).
Attacker unstake b - 1
.
Then, his balance and totalSupply
will be 1.
By manual tranfer of rethAddress()
(or SFRX_ETH_ADDRESS
, WST_ETH
) token to individual derivative contracts, we can make derivative.balance()
to over 2 * 10**18
. It costs about 2 ETH
.
Let's say that UserB (normal user) want to stake 1 ETH
.
underlyingValue
(declared in L68) will be about 2 * 10**18
.
So, preDepositPrice
(in L81) is about 2 * 10**36
(because totalSupply
is 1).
We can assume that totalStakeValueEth
is around 10**18
(which UserB deposited).
In L98, mintAmount = (totalStakeValueEth * 10**18) / preDepositPrice
is around 10**18 * 10**18 / (2 * 10**36)
.
This value is arithmetically zero by Math.floor.
Eventually, UserB stake 1 ETH, but get no SafEth
.
Let's imagine that there are numbers of users like UserB.
If the Attacker unstake his remained 1 SafEth
, he can get back all deposited ETH which can cover the manual amount. And he gets huge profit.
VSCode
In individual derivatives, we need to update balance()
function.
We need to calculate on only deposited amount. Not calculate manual tranfering.
#0 - c4-pre-sort
2023-04-04T12:42:25Z
0xSorryNotSorry marked the issue as duplicate of #715
#1 - c4-judge
2023-04-21T14:55:59Z
Picodes marked the issue as satisfactory
🌟 Selected for report: adriro
Also found by: 0x52, T1MOH, anodaram, cloudjunky, hassan-truscova
734.235 USDC - $734.24
// Line 241 return (sqrtPriceX96 * (uint(sqrtPriceX96)) * (1e18)) >> (96 * 2);
In case of sqrtPriceX96
is higher than expected, it can cause an arithmetic overflow.
// Line 240 (uint160 sqrtPriceX96, , , , , , ) = pool.slot0();
In this code, sqrtPriceX96
is normally around 296.
We should take care that there is possibility of it can be bigger than 4.3 * 296.
Then,
sqrtPriceX96 * sqrtPriceX96 * 1e18 > 4.3 * 2**96 * 4.3 * 2**96 * 1e18 = (18.49 * 1e18) * 2**192 > 2**64 * 2**192 = 2**256
VSCode
There are several ways to fix it. Here is one example which is the too simple.
return (sqrtPriceX96 * (uint(sqrtPriceX96)) * (5**18)) >> (96 * 2 - 18);
We can use 1e18 = 5**18 * 2**18
.
#0 - c4-pre-sort
2023-04-04T14:43:13Z
0xSorryNotSorry marked the issue as duplicate of #693
#1 - c4-judge
2023-04-21T16:40:42Z
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
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L144-L154 https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L84-L96
(This issue is on both rebalanceToWeights
and stake
in the same way. Let's only talk on rebalanceToWeights
In function SafEth.rebalanceToWeights
, it withdraws totally ethAmountToRebalance
eth and re-deposit it to the derivatives.
Just notice of a simple math knowledge.
Math.floor(a) + Math.floor(b)
is sometimes Math.floor(a + b) - 1
.
Since line 149 uses dividing function, the total deposited eth amount can be less than ethAmountToRebalance
.
In that case the minor amount of eth will stay in the contract.
function rebalanceToWeights() external onlyOwner { uint256 ethAmountBefore = address(this).balance; for (uint i = 0; i < derivativeCount; i++) { if (derivatives[i].balance() > 0) derivatives[i].withdraw(derivatives[i].balance()); } uint256 ethAmountAfter = address(this).balance; uint256 ethAmountToRebalance = ethAmountAfter - ethAmountBefore; for (uint i = 0; i < derivativeCount; i++) { if (weights[i] == 0 || ethAmountToRebalance == 0) continue; uint256 ethAmount = (ethAmountToRebalance * weights[i]) / totalWeight; // Price will change due to slippage derivatives[i].deposit{value: ethAmount}(); } emit Rebalanced(); }
For example, let's say that
derivativeCount = 2
ethAmountToRebalance = 10**18 + 1
(which is too small, but it's possible case)
weights = [1, 1]
ethAmount
is 5 * 10**17 for each derivatives.
10**18 + 1 - 5 * 10**17 - 5 * 10**17 = 1
eth is still remain on the smart contract.
VSCode
For the last derivative, we can deposit all remained eth instead of calculating based on the weights.
#0 - c4-pre-sort
2023-04-02T10:56:26Z
0xSorryNotSorry marked the issue as low quality report
#1 - c4-pre-sort
2023-04-04T16:25:35Z
0xSorryNotSorry marked the issue as duplicate of #455
#2 - c4-judge
2023-04-24T21:20:06Z
Picodes marked the issue as satisfactory
🌟 Selected for report: Rolezn
Also found by: 0x3b, 0xGordita, 0xSmartContract, 0xhacksmithh, 0xnev, 0xpanicError, 4lulz, Angry_Mustache_Man, ArbitraryExecution, Aymen0909, Bason, BlueAlder, EvanW, Franfran, HHK, Haipls, IgorZuk, JCN, KrisApostolov, Madalad, MiksuJak, MiniGlome, RaymondFam, ReyAdmirado, Rickard, Sathish9098, Udsen, adriro, alexzoid, anodaram, arialblack14, c3phas, carlitox477, ch0bu, chaduke, codeslide, d3e4, dicethedev, ernestognw, fatherOfBlocks, georgits, hunter_w3b, inmarelibero, lukris02, mahdirostami, maxper, pavankv, pixpi, rotcivegaf, smaul, tank, tnevler, wen, yac
10.7864 USDC - $10.79
totalWeight
weights[_derivativeIndex] = _weight; uint256 localTotalWeight = 0; for (uint256 i = 0; i < derivativeCount; i++) localTotalWeight += weights[i]; totalWeight = localTotalWeight;
weights[derivativeCount] = _weight; derivativeCount++; uint256 localTotalWeight = 0; for (uint256 i = 0; i < derivativeCount; i++) localTotalWeight += weights[i]; totalWeight = localTotalWeight;
It seems like current code assume that derivativeCount
is not big.
But in case of derivativeCount
is big, we need to optimize these for loops.
Like this:
// in adjustWeight(L169-L173) totalWeight -= weights[_derivativeIndex]; weights[_derivativeIndex] = _weight; totalWeight += weights[_derivativeIndex]; // in addDerivative(L187-L193) weights[derivativeCount] = _weight; derivativeCount++; totalWeight += weights[_derivativeIndex];
ethAmountToRebalance = 0
before the for loopfor (uint i = 0; i < derivativeCount; i++) { if (weights[i] == 0 || ethAmountToRebalance == 0) continue; uint256 ethAmount = (ethAmountToRebalance * weights[i]) / totalWeight; // Price will change due to slippage derivatives[i].deposit{value: ethAmount}(); }
Optimized Code by avoiding multiple checking of ethAmountToRebalance != 0
.
if (ethAmountToRebalance != 0) { for (uint i = 0; i < derivativeCount; i++) { if (weights[i] == 0) continue; uint256 ethAmount = (ethAmountToRebalance * weights[i]) / totalWeight; // Price will change due to slippage derivatives[i].deposit{value: ethAmount}(); } }
10 ** 18
without declaring it as a constant.In 4 solidity files which are in the scope, we can see totally 20 times of 10 ** 18
.
If we declare a constant variable of this, we can reduce the gas.
#0 - c4-pre-sort
2023-03-31T11:56:48Z
0xSorryNotSorry marked the issue as low quality report
#1 - c4-sponsor
2023-04-10T20:04:13Z
elmutt marked the issue as sponsor confirmed
#2 - c4-judge
2023-04-23T19:14:11Z
Picodes marked the issue as grade-b