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: 130/246
Findings: 2
Award: $23.92
๐ Selected for report: 0
๐ Solo Findings: 0
๐ 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
13.1298 USDC - $13.13
Number | Issue details | Instances |
---|---|---|
L-1 | Lock pragmas to specific compiler version. | 4 |
L-2 | It is not checked if derivativeCount > 0 which can cause a Panic Error if something happens during deployment. | 1 |
L-3 | No input validation for the derivativeIndex of adjustWeight function. | 1 |
Total: 3 issues.
Number | Issue details | Instances |
---|---|---|
NC-1 | Missing timelock for critical changes. | 14 |
NC-2 | For modern and more readable code, update import usages. | 31 |
NC-3 | Add parameter to emitted event. | 1 |
NC-4 | Use of bytes.concat() instead of abi.encodePacked() . | 6 |
Total: 4 issues.
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 EthPM package. Otherwise, the developer would need to manually update the pragma in order to compile locally.
Ethereum Smart Contract Best Practices - Lock pragmas to specific compiler version. solidity-specific/locking-pragmas
File: 2023-03-asymmetry/contracts/SafEth/SafEth.sol
2: pragma solidity ^0.8.13;
File: 2023-03-asymmetry/contracts/SafEth/derivatives/Reth.sol
2: pragma solidity ^0.8.13;
File: 2023-03-asymmetry/contracts/SafEth/derivatives/SfrxEth.sol
2: pragma solidity ^0.8.13;
File: 2023-03-asymmetry/contracts/SafEth/derivatives/WstEth.sol
2: pragma solidity ^0.8.13;
derivativeCount > 0
which will cause a Panic Error if something happens during deployment.Not checking if derivativeCount
is greater than 0, will cause a Panic Error.
Since this involves users getting their transactions reverted, until admin notices, the impact is high as this may cause grief to users.
But this will only happen, if something happens during deployment (deploy.ts
file) and the addDerivative
function (low probability) fails to execute properly.
So this makes it a LOW severity issue.
When calling the stake
function, it is not checked whether the derivativeCount
is greater than zero.
function stake() external payable { require(pauseStaking == false, "staking is paused"); require(msg.value >= minAmount, "amount too low"); require(msg.value <= maxAmount, "amount too high"); uint256 underlyingValue = 0; // Getting underlying value in terms of ETH for each derivative for (uint i = 0; i < derivativeCount; i++) underlyingValue += (derivatives[i].ethPerDerivative(derivatives[i].balance()) * derivatives[i].balance()) / 10 ** 18; uint256 totalSupply = totalSupply(); uint256 preDepositPrice; // Price of safETH in regards to ETH if (totalSupply == 0) preDepositPrice = 10 ** 18; // initializes with a price of 1 else preDepositPrice = (10 ** 18 * underlyingValue) / totalSupply; uint256 totalStakeValueEth = 0; // total amount of derivatives worth of ETH in system for (uint i = 0; i < derivativeCount; i++) { uint256 weight = weights[i]; IDerivative derivative = derivatives[i]; if (weight == 0) continue; uint256 ethAmount = (msg.value * weight) / totalWeight; // This is slightly less than ethAmount because slippage uint256 depositAmount = derivative.deposit{value: ethAmount}(); uint derivativeReceivedEthValue = (derivative.ethPerDerivative( depositAmount ) * depositAmount) / 10 ** 18; totalStakeValueEth += derivativeReceivedEthValue; } // mintAmount represents a percentage of the total assets in the system uint256 mintAmount = (totalStakeValueEth * 10 ** 18) / preDepositPrice; _mint(msg.sender, mintAmount); emit Staked(msg.sender, msg.value, mintAmount); }
If the addDerivative
is not called, the previous function will be reverted because the loops on lines 71-75 and 84-96 will not execute, so the underlyingValue
will be 0 and since
else preDepositPrice = (10 ** 18 * 0) / totalSupply;
the preDepositPrice
will be set to 0 too.
So totalStakeValueEth = 0
and division by 0 for mintAmount
on line 98 which will cause the transaction to revert.
This check is also lacking in the following functions:
unstake
, rebalanceToWeights
, adjustWeight
Add the missing checks.
derivativeIndex
of adjustWeight
function.Owner could set the _derivativeIndex
to 0 by mistake, which would cause an error such as the above to occur.
function adjustWeight( uint256 _derivativeIndex, uint256 _weight ) external onlyOwner { weights[_derivativeIndex] = _weight; uint256 localTotalWeight = 0; for (uint256 i = 0; i < derivativeCount; i++) localTotalWeight += weights[i]; totalWeight = localTotalWeight; emit WeightChange(_derivativeIndex, _weight); }
Add a check like the following:
require(_derivatineIndex != 0, ZeroDerivativeIndexError);
A timelock should be added to functions that perform critical changes.
TODO: Add Timelock for the following functions.
File: 2023-03-asymmetry/contracts/SafEth/SafEth.sol
138: function rebalanceToWeights() external onlyOwner { 168: ) external onlyOwner { 185: ) external onlyOwner { 205: ) external onlyOwner { 214: function setMinAmount(uint256 _minAmount) external onlyOwner { 223: function setMaxAmount(uint256 _maxAmount) external onlyOwner { 232: function setPauseStaking(bool _pause) external onlyOwner { 241: function setPauseUnstaking(bool _pause) external onlyOwner {
File: 2023-03-asymmetry/contracts/SafEth/derivatives/Reth.sol
58: function setMaxSlippage(uint256 _slippage) external onlyOwner { 107: function withdraw(uint256 amount) external onlyOwner {
File: 2023-03-asymmetry/contracts/SafEth/derivatives/SfrxEth.sol
51: function setMaxSlippage(uint256 _slippage) external onlyOwner { 60: function withdraw(uint256 _amount) external onlyOwner {
File: 2023-03-asymmetry/contracts/SafEth/derivatives/WstEth.sol
48: function setMaxSlippage(uint256 _slippage) external onlyOwner { 56: function withdraw(uint256 _amount) external onlyOwner {
Solidity code is cleaner in the following way: On the principle that clearer code is better code, you should import the things you want to use. Specific imports with curly braces allow us to apply this rule better. Check out this article
Import like this: import {contract1 , contract2} from "filename.sol";
File: 2023-03-asymmetry/contracts/SafEth/SafEth.sol
4: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5: import "../interfaces/IWETH.sol"; 6: import "../interfaces/uniswap/ISwapRouter.sol"; 7: import "../interfaces/lido/IWStETH.sol"; 8: import "../interfaces/lido/IstETH.sol"; 9: import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 10: import "./SafEthStorage.sol"; 11: import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
File: 2023-03-asymmetry/contracts/SafEth/derivatives/Reth.sol
4: import "../../interfaces/IDerivative.sol"; 5: import "../../interfaces/frax/IsFrxEth.sol"; 6: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7: import "../../interfaces/rocketpool/RocketStorageInterface.sol"; 8: import "../../interfaces/rocketpool/RocketTokenRETHInterface.sol"; 9: import "../../interfaces/rocketpool/RocketDepositPoolInterface.sol"; 10: import "../../interfaces/rocketpool/RocketDAOProtocolSettingsDepositInterface.sol"; 11: import "../../interfaces/IWETH.sol"; 12: import "../../interfaces/uniswap/ISwapRouter.sol"; 13: import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 14: import "../../interfaces/uniswap/IUniswapV3Factory.sol"; 15: import "../../interfaces/uniswap/IUniswapV3Pool.sol";
File: 2023-03-asymmetry/contracts/SafEth/derivatives/SfrxEth.sol
4: import "../../interfaces/IDerivative.sol"; 5: import "../../interfaces/frax/IsFrxEth.sol"; 6: import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 7: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 8: import "../../interfaces/curve/IFrxEthEthPool.sol"; 9: import "../../interfaces/frax/IFrxETHMinter.sol";
File: 2023-03-asymmetry/contracts/SafEth/derivatives/WstEth.sol
4: import "../../interfaces/IDerivative.sol"; 5: import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 6: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7: import "../../interfaces/curve/IStEthEthPool.sol"; 8: import "../../interfaces/lido/IWStETH.sol";
No parameter has been passed to an emitted event. Add some parameter to better depict what has been done on the blockchain.
Consider passing a parameter to the event.
File: 2023-03-asymmetry/contracts/SafEth/SafEth.sol
154: emit Rebalanced();
bytes.concat()
instead of abi.encodePacked()
.Rather than using abi.encodePacked
for appending bytes, since version 0.8.4
, bytes.concat()
is enabled.
Since version 0.8.4
for appending bytes, bytes.concat()
can be used instead of abi.encodePacked()
.
File: 2023-03-asymmetry/contracts/SafEth/derivatives/Reth.sol
70: abi.encodePacked("contract.address", "rocketTokenRETH") 125: abi.encodePacked("contract.address", "rocketDepositPool") 136: abi.encodePacked( 162: abi.encodePacked("contract.address", "rocketDepositPool") 191: abi.encodePacked("contract.address", "rocketTokenRETH") 233: abi.encodePacked("contract.address", "rocketTokenRETH")
#0 - c4-sponsor
2023-04-10T20:49:36Z
elmutt marked the issue as sponsor confirmed
#1 - c4-judge
2023-04-24T18:42:57Z
Picodes marked the issue as grade-b
๐ 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
Number | Issue details | Instances |
---|---|---|
G-1 | Usage of uint s/int s smaller than 32 bytes (256 bits) incurs overhead. | 1 |
G-2 | Multiple accesses of a mapping/array should use a local variable cache. | 7 |
G-3 | >= costs less gas than > . | 2 |
G-4 | <x> += <y> costs more gas than <x> = <x> + <y> for state variables | 4 |
G-5 | Using fixed bytes is cheaper than using string . | 5 |
Total: 5 issues.
uint
s/int
s smaller than 32 bytes (256 bits) incurs overhead.When using elements that are smaller than 32 bytes, your contractโs gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
Use uint256
instead.
File: 2023-03-asymmetry/contracts/SafEth/derivatives/Reth.sol
86: uint24 _poolFee,
The instances below point to the second+ access of a value inside a mapping/array, within a function. Caching a mapping's value in a local storage or calldata variable when the value is accessed multiple times, saves ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations. Caching an array's struct avoids recalculating the array offsets into memory/calldata. Similar findings
Use a local variable cache.
File: 2023-03-asymmetry/contracts/SafEth/SafEth.sol
73: (derivatives[i].ethPerDerivative(derivatives[i].balance()) * 74: derivatives[i].balance()) / 115: uint256 derivativeAmount = (derivatives[i].balance() * 118: derivatives[i].withdraw(derivativeAmount); 141: if (derivatives[i].balance() > 0) 142: derivatives[i].withdraw(derivatives[i].balance()); 152: derivatives[i].deposit{value: ethAmount}();
>=
costs less gas than >
.The compiler uses opcodes GT
and ISZERO
for solidity code that uses >
, but only requires LT
for >=
, which saves 3 gas
Use >=
instead if appropriate.
File: 2023-03-asymmetry/contracts/SafEth/SafEth.sol
141: if (derivatives[i].balance() > 0)
File: 2023-03-asymmetry/contracts/SafEth/derivatives/Reth.sol
200: require(rethBalance2 > rethBalance1, "No rETH was minted");
<x> += <y>
costs more gas than <x> = <x> + <y>
for state variablesUsing the addition/subtraction operator instead of plus-equals/minus-equals saves 113 gas
Use <x> = <x> + <y>
or <x> = <x> - <y>
instead.
File: 2023-03-asymmetry/contracts/SafEth/SafEth.sol
72: underlyingValue += 95: totalStakeValueEth += derivativeReceivedEthValue; 172: localTotalWeight += weights[i]; 192: localTotalWeight += weights[i];
string
.As a rule of thumb, use bytes for arbitrary-length raw byte data and string
for arbitrary-length string (UTF-8) data. If you can limit the length to a certain number of bytes, always use one of bytes1
to bytes32
because they are much cheaper.
Use fixed bytes instead of string
.
File: 2023-03-asymmetry/contracts/SafEth/SafEth.sol
49: string memory _tokenName, 50: string memory _tokenSymbol
File: 2023-03-asymmetry/contracts/SafEth/derivatives/Reth.sol
50: function name() public pure returns (string memory) {
File: 2023-03-asymmetry/contracts/SafEth/derivatives/SfrxEth.sol
44: function name() public pure returns (string memory) {
File: 2023-03-asymmetry/contracts/SafEth/derivatives/WstEth.sol
41: function name() public pure returns (string memory) {
#0 - c4-sponsor
2023-04-10T20:08:51Z
elmutt marked the issue as sponsor confirmed
#1 - c4-judge
2023-04-23T19:13:55Z
Picodes marked the issue as grade-b