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: 48/246
Findings: 3
Award: $118.64
π Selected for report: 2
π Solo Findings: 0
π Selected for report: brgltd
Also found by: 0xbepresent, 0xepley, 0xnev, BPZ, Breeje, Polaris_tow, SadBase, SaeedAlipoor01988, eyexploit, ladboy233, latt1ce, peanuts, rbserver
52.8279 USDC - $52.83
Lack of deadline for uniswap AMM
The ISwapRouter.exactInputSingle params (used in the rocketpool derivative) does not include a deadline currently.
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter .ExactInputSingleParams({ tokenIn: _tokenIn, tokenOut: _tokenOut, fee: _poolFee, recipient: address(this), amountIn: _amountIn, amountOutMinimum: _minOut, sqrtPriceLimitX96: 0 });
The following scenario can happen:
Because Front-running is a key aspect of AMM design, deadline is a useful tool to ensure that your tx cannot be βsaved for laterβ.
Due to the removal of the check, it may be more profitable for a validator to deny the transaction from being added until the transaction incurs the maximum amount of slippage.
Manual review.
The Reth.deposit()
function should accept a user-input deadline
param that should be passed along to Reth.swapExactInputSingleHop() and ISwapRouter.exactInputSingle().
#0 - c4-pre-sort
2023-04-04T14:48:41Z
0xSorryNotSorry marked the issue as duplicate of #1087
#1 - c4-judge
2023-04-22T10:17:55Z
Picodes marked the issue as satisfactory
#2 - c4-judge
2023-04-22T10:18:20Z
Picodes marked the issue as selected for report
11.1318 USDC - $11.13
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L63-L101 https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L182-L195
There should be at least one derivative in the system before staking is allowed
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L63-L101
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L182-L195
If a user calls SafEth.stake()
after SafEth.sol
gets deployed but before any derivative contract is added, the user will receive zero funds in return and will lose his ether send in the transaction. There won't be any way to receive the funds back.
SafEth.sol
is deployed but before SafEth.addDerivative()
gets called, e.g there's no derivative in SafEth.sol
._mint()
will accept the input zero for amount.https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L63-L101
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L182-L195
Manual review
Validate that there's at least one derivative in SafEth.stake()
, e.g.
diff --git a/SafEth.sol.orig b/SafEth.sol --- a/SafEth.sol.orig +++ b/SafEth.sol @@ -64,6 +64,7 @@ contract SafEth is require(pauseStaking == false, "staking is paused"); require(msg.value >= minAmount, "amount too low"); require(msg.value <= maxAmount, "amount too high"); + require(derivativeCount > 0, "no derivative"); uint256 underlyingValue = 0;
Alternatively, validate that the amount to be minted is bigger than zero, also in SafEth.stake()
e.g.
diff --git a/SafEth.sol.orig b/SafEth.sol --- a/SafEth.sol.orig +++ b/SafEth.sol @@ -96,6 +96,7 @@ contract SafEth is // mintAmount represents a percentage of the total assets in the system uint256 mintAmount = (totalStakeValueEth * 10 ** 18) / preDepositPrice; + require(mintAmount > 0, "error"); _mint(msg.sender, mintAmount); emit Staked(msg.sender, msg.value, mintAmount);
#0 - c4-pre-sort
2023-04-04T19:17:31Z
0xSorryNotSorry marked the issue as duplicate of #363
#1 - c4-judge
2023-04-21T16:29:52Z
Picodes marked the issue as satisfactory
π Selected for report: brgltd
Also found by: 0x3b, 0xAgro, 0xGusMcCrae, 0xNorman, 0xRajkumar, 0xSmartContract, 0xTraub, 0xWagmi, 0xWaitress, 0xffchain, 0xhacksmithh, 0xkazim, 0xnev, 3dgeville, ArbitraryExecution, Aymen0909, BRONZEDISC, Bason, Bloqarl, BlueAlder, Brenzee, CodeFoxInc, CodingNameKiki, Cryptor, DadeKuma, DevABDee, Diana, Dug, Englave, Gde, Haipls, HollaDieWaldfee, Ignite, Infect3d, Jerry0x, Josiah, Kaysoft, Koko1912, KrisApostolov, Lavishq, LeoGold, Madalad, PNS, Rappie, RaymondFam, RedTiger, Rickard, Rolezn, Sathish9098, SunSec, T1MOH, UdarTeam, Udsen, Viktor_Cortess, Wander, adriro, ak1, alejandrocovrr, alexzoid, arialblack14, ayden, bin2chen, brevis, btk, c3phas, carlitox477, catellatech, ch0bu, chaduke, ck, climber2002, codeslide, descharre, dingo2077, ernestognw, fatherOfBlocks, favelanky, georgits, helios, hl_, inmarelibero, juancito, ks__xxxxx, lopotras, lukris02, m_Rassska, mahdirostami, maxper, nadin, navinavu, nemveer, p_crypt0, peanuts, pipoca, pixpi, qpzm, rbserver, reassor, roelio, rotcivegaf, scokaf, siddhpurakaran, slvDev, smaul, tnevler, tsvetanovv, turvy_fuzz, vagrant, wen, yac, zzzitron
54.6785 USDC - $54.68
Currently it's possible to set any value for the weights. Some combinations for weights could result in issues while calculating ethAmount
.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L169
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L187
For example, assuming the minimum value for msg.value, three derivatives and strange values for the weights.
msg.value = 5e17 = 0.5e18 weight1 = 5e17 = 0.5e18 weight2 = 19e18 = 19e18 weight3 = 19e19 = 190e18 ethAmount = (msg.value * weight) / totalWeight 5e17 * 5e17 / (5e17 + 19e18 + 19e19)
This would result in 1193317422434367.5
which would round down in solidity.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L88
Add checks for min and max values for weights.
In case a derivative gets added by mistake or with incorrect parameters, currently this derivative would remain stuck in SafEth.sol
.
Consider adding a method that allows removing derivatives from SafEth.sol
.
SafEth.unstake()
There is a reentrancy possibility in SafEth.unstake()
where the tokens are burned only after the derivative withdraw.
If the derivatives[i].withdraw()
external call where to reenter into SafEth.unstake()
, the safEthAmount
is still not updated, since the _burn()
is only called after, and the function doesn't contain a nonReentranct modifier.
Note: this would only be an issue for a malicious derivative contract.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L113-L120
Call _burn()
before derivatives[i].withdraw()
in SafEth.unstake()
.
There are multiple instances of loops executing external calls where the number of iterations is unbounded and controlled by the number of derivatives. This is not an issue on the current setup, since there are only three derivatives.
However, if a large amount of derivative gets added, functionalities like stake()
and unstake()
could run out of gas and revert.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L84-L96
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L113-L119
Limit the maximum number of derivatives that can be added.
Multiple functions in the project emit an event as the last statement. Wherever possible, consider emitting events before external calls. In case of reentrancy, funds are not at risk (for external call + event ordering), however emitting events after external calls can damage frontends and monitoring tools in case of reentrancy attacks.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L63-L101
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L108-L129
All contracts in scope are floating the pragma version.
Locking the pragma helps to ensure that contracts do not accidentally get deployed using an outdated compiler version.
Note that pragma statements can be allowed to float when a contract is intended for consumption by other developers, as in the case with contracts in a library or a package.
Input addresses should be checked against address(0) to prevent unexpected behavior.
Misdeployed values can cause failure of integrations. One addition that can be made is to add setter functions for the owner to update these addresses if necessary.
When the system is in pause mode, e.g. staking and unstaking is blocked, consider adding a check to prevent new derivatives from being added, e.g.
require(!pausedStaking && !pauseUnstaking, "error");
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L182-L195
Lack of two-step procedure for critical operations leaves them error-prone.
Consider adding a two-steps pattern and a timelock on critical changes to avoid modifying the system state.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L223-L226
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L232-L235
Adding an event will facilitate offchain monitoring when changing system parameters.
Events that mark critical parameter changes should contain both the old and the new value.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L165-L175
Add a check ensuring that the new value if different than the current value to avoid emitting unnecessary events.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L214-L217
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L223-L226
derivatives[i].balance()
can be cached on the following instance.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L141-L142
Unsigned integers will already be initalized with zero on their declaration, e.g. there's no need to manually assign zero.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L68
Multiplying by 10**18
and dividing by 10**18
is not needed on L215 of Reth.sol
.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L215
The derivative contracts don't have all functions and branches covered.
It is crucial to write tests with possibly 100% coverage for smart contracts. It is recommended to write tests for all possible code flows.
SafEth.adjustWeight()
contains an incorrect @notice.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L158
Large code bases, or code with complicated math, or complicated interactions between multiple contracts, should implement fuzzing tests.
Fuzzers such as Echidna require the test writer to come up with invariants which should not be violated under any circumstances, and the fuzzer tests various inputs and function calls to ensure that the invariants always hold.
Even code with 100% code coverage can still have bugs due to the order of the operations a user performs, and fuzzers, with properly and extensively-written invariants, can close this testing gap significantly.
SafEth.adjustWeight()
there's no need to loop all derivativesIt's possible to decrease the old weight and increase the new weight to compute localTotalWeight
and update totalWeight
.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L165-L175
Consider renaming the variable totalSupply
in SafEth.stake()
, since it's being shadowed by ERC20Upgradeable.totalSupply()
.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L77
Some functions return named variables, others return explicit values.
Following function returns an explicit value.
Following function return returns a named variable.
Consider adopting the same approach throughout the codebase to improve the explicitness and readability of the code.
Consider grouping the imports, e.g. first libraries, then interfaces, the storage.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L4-L11
The solidity documentation recommends the following order for functions:
constructor receive function (if exists) fallback function (if exists) external public internal private
The receive() functions are currently in the bottom on the contract.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L246
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L244
The solidity documentation recommends a maximum of 120 characters.
Consider adding a limit of 120 characters or less to prevent large lines.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L142
Scientific notation can be used for better code readability, e.g. consider using using 10e18
and 10e17
instead of 10**18
and 10**17
.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L54-L55
Consider also adding the name of the warning being disabled, e.g. // solhint-disable-next-line warning-name
.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L123-L126
variable == false
with !variable
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L64
Consider using only one approach, e.g. only uint256.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L91-L92
The following instance can use a ternary expression instead of a conditional.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L78-L81
The package @balancer-labs/balancer-js is deprecated and balancer recommends to use the @balancer-labs/sdk package instead. Also, this package is currently unused on the tests and deployment setups. Consider removing this package.
This is not a vulnerability, but removing this package is beneficial to the project, since unnecessary packages incurs overhead and increases the project download time and size.
https://github.com/code-423n4/2023-03-asymmetry/blob/main/package.json#L78
#0 - c4-sponsor
2023-04-07T21:56:21Z
toshiSat marked the issue as sponsor confirmed
#1 - c4-judge
2023-04-24T17:20:25Z
Picodes marked the issue as grade-a
#2 - c4-judge
2023-04-24T19:20:37Z
Picodes marked the issue as selected for report
#3 - Picodes
2023-05-05T09:36:19Z
For the report:
14-15-16-20 are GAS findings more than QA 19 invalid