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: 42/246
Findings: 2
Award: $160.03
🌟 Selected for report: 1
🚀 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
42.0604 USDC - $42.06
Issue | Contexts | |
---|---|---|
LOW‑1 | IERC20 approve() Is Deprecated | 2 |
LOW‑2 | Missing Contract-existence Checks Before Low-level Calls | 5 |
LOW‑3 | Contracts are not using their OZ Upgradeable counterparts | 4 |
LOW‑4 | TransferOwnership Should Be Two Step | 4 |
LOW‑5 | Unused receive() Function Will Lock Ether In Contract | 4 |
LOW‑6 | No Storage Gap For Upgradeable Contracts | 1 |
LOW‑7 | Upgrade OpenZeppelin Contract Dependency | 1 |
LOW‑8 | Use safeTransferOwnership instead of transferOwnership function | 4 |
LOW‑9 | Consider the case where totalsupply is 0 | 2 |
Total: 27 contexts over 9 issues
Issue | Contexts | |
---|---|---|
NC‑1 | Add a timelock to critical functions | 8 |
NC‑2 | Avoid Floating Pragmas: The Version Should Be Locked | 4 |
NC‑3 | No need for == true or == false checks | 2 |
NC‑4 | Constants in comparisons should appear on the left side | 3 |
NC‑5 | Critical Changes Should Use Two-step Procedure | 8 |
NC‑6 | Duplicated require() /revert() Checks Should Be Refactored To A Modifier Or Function | 2 |
NC‑7 | Event emit should emit a parameter | 1 |
NC‑8 | Function writing that does not comply with the Solidity Style Guide | 4 |
NC‑9 | Imports can be grouped together | 31 |
NC‑10 | NatSpec return parameters should be included in contracts | 1 |
NC‑11 | No need to initialize uints to zero | 3 |
NC‑12 | Initial value check is missing in Set Functions | 8 |
NC‑13 | Missing event for critical parameter change | 3 |
NC‑14 | Implementation contract may not be initialized | 4 |
NC‑15 | NatSpec comments should be increased in contracts | 1 |
NC‑16 | Non-usage of specific imports | 22 |
NC‑17 | Use a more recent version of Solidity | 4 |
NC‑18 | Public Functions Not Called By The Contract Should Be Declared External Instead | 2 |
NC‑19 | Use bytes.concat() | 10 |
NC‑20 | Interchangeable usage of uint and uint256 | 3 |
Total: 124 contexts over 20 issues
approve()
Is Deprecatedapprove()
is subject to a known front-running attack. It is deprecated in favor of safeIncreaseAllowance()
and safeDecreaseAllowance()
. If only setting the initial allowance to the value that means infinite, safeIncreaseAllowance()
can be used instead.
https://docs.openzeppelin.com/contracts/3.x/api/token/erc20#IERC20-approve-address-uint256-
90: IERC20(_tokenIn).approve(UNISWAP_ROUTER, _amountIn);
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L90
59: IERC20(STETH_TOKEN).approve(LIDO_CRV_POOL, stEthBal);
Consider using safeIncreaseAllowance()
/ safeDecreaseAllowance()
instead.
Low-level calls return success if there is no code present at the specified address.
124: (bool sent, ) = address(msg.sender).call{value: ethAmountToWithdraw}(
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L124
110: (bool sent, ) = address(msg.sender).call{value: address(this).balance}(
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L110
84: (bool sent, ) = address(msg.sender).call{value: address(this).balance}(
63: (bool sent, ) = address(msg.sender).call{value: address(this).balance}(
76: (bool sent, ) = WST_ETH.call{value: msg.value}("");
In addition to the zero-address checks, add a check to verify that <address>.code.length > 0
The non-upgradeable standard version of OpenZeppelin’s library are inherited / used by the contracts. It would be safer to use the upgradeable versions of the library contracts to avoid unexpected behaviour.
4: import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L4
6: import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L6
7: import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6: import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/WstEth.sol#L6
Where applicable, use the contracts from @openzeppelin/contracts-upgradeable
instead of @openzeppelin/contracts
.
See https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/tree/master/contracts for list of available upgradeable contracts
Recommend considering implementing a two step process where the owner or admin nominates an account and the nominated account needs to call an acceptOwnership() function for the transfer of ownership to fully succeed. This ensures the nominated EOA account is a valid and active account.
53: function initialize: _transferOwnership(msg.sender);
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L53
43: function initialize: _transferOwnership(_owner);
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L43
37: function initialize: _transferOwnership(_owner);
34: function initialize: _transferOwnership(_owner);
Lack of two-step procedure for critical operations leaves them error-prone. Consider adding two step procedure on the critical functions.
receive()
Function Will Lock Ether In ContractIf the intention is for the Ether to be used, the function should call another function, otherwise it should revert
246: receive() external payable {}
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L246
244: receive() external payable {}
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L244
126: receive() external payable {}
97: receive() external payable {}
The function should call another function, otherwise it should revert
For upgradeable contracts, there must be storage gap to "allow developers to freely add new state variables in the future without compromising the storage compatibility with existing deployments". Otherwise it may be very difficult to write new implementation code. Without storage gap, the variable in child contract might be overwritten by the upgraded base contract if new variables are added to the base contract. This could have unintended and very serious consequences to the child contracts.
Refer to the bottom part of this article: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable
However, the contract doesn't contain a storage gap. The storage gap is essential for upgradeable contract because "It allows us to freely add new state variables in the future without compromising the storage compatibility with existing deployments". See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps for a description of this storage variable. While some contracts may not currently be sub-classed, adding the variable now protects against forgetting to add it in the future.
contract SafEth is Initializable, ERC20Upgradeable, OwnableUpgradeable, SafEthStorage
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L5
Recommend adding appropriate storage gap at the end of upgradeable contracts such as the below. Please reference OpenZeppelin upgradeable contract templates.
uint256[50] private __gap;
An outdated OZ version is used (which has known vulnerabilities, see: https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories).
Require dependencies to be at least version of 4.8.1 to include critical vulnerability patches.
"@openzeppelin/contracts": "^4.8.0"
https://github.com/code-423n4/2023-03-asymmetry/tree/main/package.json#L82
Update OpenZeppelin Contracts Usage in package.json and require at least version of 4.8.1 via >=4.8.1
safeTransferOwnership
instead of transferOwnership
functionUse safeTransferOwnership
which is safer. Use it as it is more secure due to 2-stage ownership transfer.
53: function initialize: _transferOwnership(msg.sender);
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L53
43: function initialize: _transferOwnership(_owner);
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L43
37: function initialize: _transferOwnership(_owner);
34: function initialize: _transferOwnership(_owner);
Use <a href="https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol">Ownable2Step.sol</a>
function acceptOwnership() external { address sender = _msgSender(); require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner"); _transferOwnership(sender); } }
Consider the case where totalsupply
is 0. When totalsupply
is 0, it should return 0 directly, because there will be an error of dividing by 0.
This would cause the affected functions to revert and as a result can lead to potential loss.
81: else preDepositPrice = (10 ** 18 * underlyingValue) / totalSupply;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L81
116: _safEthAmount) / safEthTotalSupply;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L116
Add check for zero value and return 0.
if ( totalSupply() == 0) return 0;
It is a good practice to give time for users to react and adjust to critical changes. A timelock provides more guarantees and reduces the level of trust required, thus decreasing risk for users. It also indicates that the project is legitimate (less risk of a malicious owner making a sandwich attack on a user). Consider adding a timelock to the following functions:
202: function setMaxSlippage(
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L202
214: function setMinAmount(uint256 _minAmount) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L214
223: function setMaxAmount(uint256 _maxAmount) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L223
232: function setPauseStaking(bool _pause) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L232
241: function setPauseUnstaking(bool _pause) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L241
58: function setMaxSlippage(uint256 _slippage) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L58
51: function setMaxSlippage(uint256 _slippage) external onlyOwner {
48: function setMaxSlippage(uint256 _slippage) external onlyOwner {
Avoid floating pragmas for non-library contracts.
While floating pragmas make sense for libraries to allow them to be included with multiple different versions of applications, it may be a security risk for application implementations.
A known vulnerable compiler version may accidentally be selected or security tools might fall-back to an older compiler version ending up checking a different EVM compilation that is ultimately deployed on the blockchain.
It is recommended to pin to a concrete compiler version.
Found usage of floating pragmas ^0.8.13 of Solidity in [SafEth.sol]
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L2
Found usage of floating pragmas ^0.8.13 of Solidity in [Reth.sol]
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L2
Found usage of floating pragmas ^0.8.13 of Solidity in [SfrxEth.sol]
Found usage of floating pragmas ^0.8.13 of Solidity in [WstEth.sol]
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/WstEth.sol#L2
== true
or == false
checksThere is no need to verify that == true
or == false
when the variable checked upon is a boolean as well.
64: require(pauseStaking == false, "staking is paused");
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L64
109: require(pauseUnstaking == false, "unstaking is paused");
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L109
Instead simply check for variable
or !variable
Doing so will prevent <a href="https://www.moserware.com/2008/01/constants-on-left-are-better-but-this.html">typo bugs</a>
79: if (totalSupply == 0)
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L79
87: if (weight == 0) continue;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L87
117: if (derivativeAmount == 0) continue;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L117
The critical procedures should be two step process.
See similar findings in previous Code4rena contests for reference: https://code4rena.com/reports/2022-06-illuminate/#2-critical-changes-should-use-two-step-procedure
202: function setMaxSlippage(
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L202
214: function setMinAmount(uint256 _minAmount) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L214
223: function setMaxAmount(uint256 _maxAmount) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L223
232: function setPauseStaking(bool _pause) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L232
241: function setPauseUnstaking(bool _pause) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L241
58: function setMaxSlippage(uint256 _slippage) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L58
51: function setMaxSlippage(uint256 _slippage) external onlyOwner {
48: function setMaxSlippage(uint256 _slippage) external onlyOwner {
Lack of two-step procedure for critical operations leaves them error-prone. Consider adding two step procedure on the critical functions.
require()
/revert()
Checks Should Be Refactored To A Modifier Or FunctionSaves deployment costs
66: require(sent, "Failed to send Ether"); 77: require(sent, "Failed to send Ether");
Some emitted events do not have any emitted parameters. It is recommended to add some parameter such as state changes or value changes when events are emitted
154: emit Rebalanced()
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L154
Order of Functions; ordering helps readers identify which functions they can call and to find the constructor and fallback definitions easier. But there are contracts in the project that do not comply with this.
https://docs.soliditylang.org/en/v0.8.17/style-guide.html
Functions should be grouped according to their visibility and ordered:
See Reth.sol
for example
Consider importing OZ first, then all interfaces, then all utils.
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";
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L4-L11
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";
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";
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";
It is recommended that Solidity contracts are fully annotated using NatSpec for all public interfaces (everything in the ABI). It is clearly stated in the Solidity official documentation. In complex projects such as Defi, the interpretation of all functions and their arguments and returns is important for code readability and auditability.
https://docs.soliditylang.org/en/v0.8.15/natspec-format.html
All in-scope contracts
Include return parameters in NatSpec comments
Recommendation Code Style: (from Uniswap3)
/// @notice Adds liquidity for the given recipient/tickLower/tickUpper position /// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends /// on tickLower, tickUpper, the amount of liquidity, and the current price. /// @param recipient The address for which the liquidity will be created /// @param tickLower The lower tick of the position in which to add liquidity /// @param tickUpper The upper tick of the position in which to add liquidity /// @param amount The amount of liquidity to mint /// @param data Any data that should be passed through to the callback /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback function mint( address recipient, int24 tickLower, int24 tickUpper, uint128 amount, bytes calldata data ) external returns (uint256 amount0, uint256 amount1);
There is no need to initialize uint
variables to zero as their default value is 0
68: uint256 underlyingValue = 0;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L68
170: uint256 localTotalWeight = 0;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L170
190: uint256 localTotalWeight = 0;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L190
A check regarding whether the current value and the new value are the same should be added
202: function setMaxSlippage( uint _derivativeIndex, uint _slippage ) external onlyOwner { derivatives[_derivativeIndex].setMaxSlippage(_slippage); emit SetMaxSlippage(_derivativeIndex, _slippage); }
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L202
214: function setMinAmount(uint256 _minAmount) external onlyOwner { minAmount = _minAmount; emit ChangeMinAmount(minAmount); }
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L214
223: function setMaxAmount(uint256 _maxAmount) external onlyOwner { maxAmount = _maxAmount; emit ChangeMaxAmount(maxAmount); }
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L223
232: function setPauseStaking(bool _pause) external onlyOwner { pauseStaking = _pause; emit StakingPaused(pauseStaking); }
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L232
241: function setPauseUnstaking(bool _pause) external onlyOwner { pauseUnstaking = _pause; emit UnstakingPaused(pauseUnstaking); } receive() external payable {} }
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L241
58: function setMaxSlippage(uint256 _slippage) external onlyOwner { maxSlippage = _slippage; }
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L58
51: function setMaxSlippage(uint256 _slippage) external onlyOwner { maxSlippage = _slippage; }
48: function setMaxSlippage(uint256 _slippage) external onlyOwner { maxSlippage = _slippage; }
When changing state variables events are not emitted. Emitting events allows monitoring activities with off-chain monitoring tools.
58: function setMaxSlippage(uint256 _slippage) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L58
51: function setMaxSlippage(uint256 _slippage) external onlyOwner {
48: function setMaxSlippage(uint256 _slippage) external onlyOwner {
OpenZeppelin recommends that the initializer modifier be applied to constructors. Per OZs Post implementation contract should be initialized to avoid potential griefs or exploits. https://forum.openzeppelin.com/t/uupsupgradeable-vulnerability-post-mortem/15680/5
38: constructor()
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L38
33: constructor()
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L33
27: constructor()
24: constructor()
It is recommended that Solidity contracts are fully annotated using NatSpec for all public interfaces (everything in the ABI). It is clearly stated in the Solidity official documentation. In complex projects such as Defi, the interpretation of all functions and their arguments and returns is important for code readability and auditability. https://docs.soliditylang.org/en/v0.8.15/natspec-format.html
All in-scope contracts
NatSpec comments should be increased in contracts
The current form of relative path import is not recommended for use because it can unpredictably pollute the namespace. Instead, the Solidity docs recommend specifying imported symbols explicitly. https://docs.soliditylang.org/en/v0.8.15/layout-of-source-files.html#importing-other-source-files
A good example:
import {OwnableUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; import {SafeCastLib} from "solmate/utils/SafeCastLib.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; import {IProducer} from "src/interfaces/IProducer.sol"; import {GlobalState, UserState} from "src/Common.sol";
5: import "../interfaces/IWETH.sol"; 6: import "../interfaces/uniswap/ISwapRouter.sol"; 7: import "../interfaces/lido/IWStETH.sol"; 8: import "../interfaces/lido/IstETH.sol"; 10: import "./SafEthStorage.sol";
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L5
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L6
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L7
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L8
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L10
4: import "../../interfaces/IDerivative.sol"; 5: import "../../interfaces/frax/IsFrxEth.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"; 14: import "../../interfaces/uniswap/IUniswapV3Factory.sol"; 15: import "../../interfaces/uniswap/IUniswapV3Pool.sol";
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L4
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L5
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L7
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L8
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L9
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L10
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L11
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L12
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L14
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L15
4: import "../../interfaces/IDerivative.sol"; 5: import "../../interfaces/frax/IsFrxEth.sol"; 8: import "../../interfaces/curve/IFrxEthEthPool.sol"; 9: import "../../interfaces/frax/IFrxETHMinter.sol";
4: import "../../interfaces/IDerivative.sol"; 7: import "../../interfaces/curve/IStEthEthPool.sol"; 8: import "../../interfaces/lido/IWStETH.sol";
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/WstEth.sol#L4
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/WstEth.sol#L7
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/WstEth.sol#L8
Use specific imports syntax per solidity docs recommendation.
<a href="https://blog.soliditylang.org/2022/05/18/solidity-0.8.14-release-announcement/">0.8.14</a>:
ABI Encoder: When ABI-encoding values from calldata that contain nested arrays, correctly validate the nested array length against calldatasize() in all cases. Override Checker: Allow changing data location for parameters only when overriding external functions.
<a href="https://blog.soliditylang.org/2022/06/15/solidity-0.8.15-release-announcement/">0.8.15</a>:
Code Generation: Avoid writing dirty bytes to storage when copying bytes arrays. Yul Optimizer: Keep all memory side-effects of inline assembly blocks.
<a href="https://blog.soliditylang.org/2022/08/08/solidity-0.8.16-release-announcement/">0.8.16</a>:
Code Generation: Fix data corruption that affected ABI-encoding of calldata values represented by tuples: structs at any nesting level; argument lists of external functions, events and errors; return value lists of external functions. The 32 leading bytes of the first dynamically-encoded value in the tuple would get zeroed when the last component contained a statically-encoded array.
<a href="https://blog.soliditylang.org/2022/09/08/solidity-0.8.17-release-announcement/">0.8.17</a>:
Yul Optimizer: Prevent the incorrect removal of storage writes before calls to Yul functions that conditionally terminate the external EVM call.
<a href="https://blog.soliditylang.org/2023/02/22/solidity-0.8.19-release-announcement/">0.8.19</a>: SMTChecker: New trusted mode that assumes that any compile-time available code is the actual used code, even in external calls. Bug Fixes:
pragma solidity ^0.8.13;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L2
pragma solidity ^0.8.13;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L2
pragma solidity ^0.8.13;
pragma solidity ^0.8.13;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/WstEth.sol#L2
Consider updating to a more recent solidity version.
Contracts are allowed to override their parents’ functions and change the visibility from external to public.
function ethPerDerivative(uint256 _amount) public view returns (uint256) {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L211
function ethPerDerivative(uint256 _amount) public view returns (uint256) {
bytes.concat()
Solidity version 0.8.4 introduces bytes.concat()
(vs abi.encodePacked(<bytes>,<bytes>)
)
70: abi.encodePacked("contract.address", "rocketTokenRETH") ) ) 233: abi.encodePacked("contract.address", "rocketTokenRETH") ) ) 125: abi.encodePacked("contract.address", "rocketDepositPool") ) ) 162: abi.encodePacked("contract.address", "rocketDepositPool") ) ) 136: abi.encodePacked( "contract.address", "rocketDAOProtocolSettingsDeposit" ) ) ) 125: abi.encodePacked("contract.address", "rocketDepositPool") ) ) 162: abi.encodePacked("contract.address", "rocketDepositPool") ) ) 191: abi.encodePacked("contract.address", "rocketTokenRETH") ) ) 70: abi.encodePacked("contract.address", "rocketTokenRETH") ) ) 233: abi.encodePacked("contract.address", "rocketTokenRETH") ) )
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L70
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L233
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L125
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L162
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L136
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L125
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L162
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L191
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L70
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L233
Use bytes.concat()
and upgrade to at least Solidity version 0.8.4 if required.
uint
and uint256
Some developers prefer to use uint256
because it is consistent with other uint
data types, which also specify their size, and also because making the size of the data explicit reminds the developer and the reader how much data they've got to play with, which may help prevent or detect bugs.
For example, if doing bytes4(keccak('transfer(address, uint)’)), you'll get a different method sig ID than bytes4(keccak('transfer(address, uint256)’)) and smart contracts will only understand the latter when comparing method sig IDs
setMaxSlippage( uint _derivativeIndex, uint _slippage ) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L202
adjustWeight( uint256 _derivativeIndex, uint256 _weight ) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L165
addDerivative( address _contractAddress, uint256 _weight ) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L182
#0 - c4-sponsor
2023-04-10T19:40:13Z
elmutt marked the issue as sponsor confirmed
#1 - c4-judge
2023-04-24T18:48:50Z
Picodes marked the issue as grade-a
🌟 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
117.9662 USDC - $117.97
Issue | Contexts | Estimated Gas Saved | |
---|---|---|---|
GAS‑1 | Setting the constructor to payable | 4 | 52 |
GAS‑2 | Duplicated require() /revert() Checks Should Be Refactored To A Modifier Or Function | 2 | 56 |
GAS‑3 | Empty Blocks Should Be Removed Or Emit Something | 4 | - |
GAS‑4 | Using delete statement can save gas | 3 | - |
GAS‑5 | Functions guaranteed to revert when called by normal users can be marked payable | 17 | 357 |
GAS‑6 | Use hardcoded address instead address(this) | 22 | - |
GAS‑7 | Optimize names to save gas | 3 | 66 |
GAS‑8 | <x> += <y> Costs More Gas Than <x> = <x> + <y> For State Variables | 4 | - |
GAS‑9 | Public Functions To External | 9 | - |
GAS‑10 | Save gas with the use of specific import statements | 22 | - |
GAS‑11 | Using unchecked blocks to save gas | 6 | 120 |
GAS‑12 | Use functions instead of modifiers | 1 | 100 |
GAS‑13 | Use solidity version 0.8.19 to gain some gas boost | 4 | 352 |
GAS‑14 | Save loop calls | 3 | - |
Total: 104 contexts over 14 issues
constructor
to payable
Saves ~13 gas per instance
38: constructor()
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L38
33: constructor()
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L33
27: constructor()
24: constructor()
require()
/revert()
Checks Should Be Refactored To A Modifier Or FunctionSaves deployment costs
66: require(sent, "Failed to send Ether"); 77: require(sent, "Failed to send Ether");
The code should be refactored such that they no longer exist, or the block should do something useful, such as emitting an event or reverting. If the contract is meant to be extended, the contract should be abstract and the function signatures be added without any default implementation. If the block is an empty if-statement block to avoid doing subsequent checks in the else-if/else conditions, the else-if/else conditions should be nested under the negation of the if-statement, because they involve different classes of checks, which may lead to the introduction of errors when the code is later modified (if(x){}else if(y){...}else{...} => if(!x){if(y){...}else{...}})
246: receive() external payable {}
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L246
244: receive() external payable {}
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L244
126: receive() external payable {}
97: receive() external payable {}
delete
statement can save gas68: uint256 underlyingValue = 0;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L68
170: uint256 localTotalWeight = 0;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L170
190: uint256 localTotalWeight = 0;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L190
payable
If a function modifier or require such as onlyOwner/onlyX is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2), DUP1(3), ISZERO(3), PUSH2(3), JUMPI(10), PUSH1(3), DUP1(3), REVERT(0), JUMPDEST(1), POP(2) which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost.
138: function rebalanceToWeights() external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L138
165: function adjustWeight( uint256 _derivativeIndex, uint256 _weight ) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L165
182: function addDerivative( address _contractAddress, uint256 _weight ) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L182
202: function setMaxSlippage( uint _derivativeIndex, uint _slippage ) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L202
214: function setMinAmount(uint256 _minAmount) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L214
223: function setMaxAmount(uint256 _maxAmount) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L223
232: function setPauseStaking(bool _pause) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L232
241: function setPauseUnstaking(bool _pause) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L241
58: function setMaxSlippage(uint256 _slippage) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L58
107: function withdraw(uint256 amount) external onlyOwner {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L107
156: function deposit() external payable onlyOwner returns (uint256) {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L156
51: function setMaxSlippage(uint256 _slippage) external onlyOwner {
60: function withdraw(uint256 _amount) external onlyOwner {
94: function deposit() external payable onlyOwner returns (uint256) {
48: function setMaxSlippage(uint256 _slippage) external onlyOwner {
56: function withdraw(uint256 _amount) external onlyOwner {
73: function deposit() external payable onlyOwner returns (uint256) {
Functions guaranteed to revert when called by normal users can be marked payable.
address(this)
Instead of using address(this)
, it is more gas-efficient to pre-calculate and use the hardcoded address
. Foundry's script.sol and solmate's LibRlp.sol
contracts can help achieve this.
References: https://book.getfoundry.sh/reference/forge-std/compute-create-address
https://twitter.com/transmissions11/status/1518507047943245824
111: uint256 ethAmountBefore = address(this).balance; 121: uint256 ethAmountAfter = address(this).balance;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L111
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L121
139: uint256 ethAmountBefore = address(this).balance; 144: uint256 ethAmountAfter = address(this).balance;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L139
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L144
96: recipient: address(this),
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L96
110: (bool sent, ) = address(msg.sender).call{value: address(this).balance}(
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L110
197: uint256 rethBalance1 = rocketTokenRETH.balanceOf(address(this)); 199: uint256 rethBalance2 = rocketTokenRETH.balanceOf(address(this));
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L197
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L199
222: return IERC20(rethAddress()).balanceOf(address(this));
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L222
63: address(this), 63: address(this) 63: address(this) 84: (bool sent, ) = address(msg.sender).call{value: address(this).balance}(
99: address(this) 101: frxETHMinterContract.submitAndDeposit{value: msg.value}(address(this)); 99: address(this)
123: return IERC20(SFRX_ETH_ADDRESS).balanceOf(address(this));
58: uint256 stEthBal = IERC20(STETH_TOKEN).balanceOf(address(this)); 63: (bool sent, ) = address(msg.sender).call{value: address(this).balance}(
74: uint256 wstEthBalancePre = IWStETH(WST_ETH).balanceOf(address(this)); 78: uint256 wstEthBalancePost = IWStETH(WST_ETH).balanceOf(address(this));
94: return IERC20(WST_ETH).balanceOf(address(this));
Use hardcoded address
Contracts most called functions could simply save gas by function ordering via Method ID. Calling a function at runtime will be cheaper if the function is positioned earlier in the order (has a relatively lower Method ID) because 22 gas are added to the cost of a function for every position that came before it. The caller can save on gas if you prioritize most called functions.
See more <a href="https://medium.com/joyso/solidity-how-does-function-name-affect-gas-consumption-in-smart-contract-47d270d8ac92">here</a>
All in-scope contracts
Find a lower method ID name for the most called functions for example Call() vs. Call1() is cheaper by 22 gas For example, the function IDs in the Gauge.sol contract will be the most used; A lower method ID may be given.
<x> += <y>
Costs More Gas Than <x> = <x> + <y>
For State Variables72: underlyingValue += 95: totalStakeValueEth += derivativeReceivedEthValue;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L72
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L95
172: localTotalWeight += weights[i];
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L172
192: localTotalWeight += weights[i];
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L192
The following functions could be set external to save gas and improve code quality. External call cost is less expensive than of public functions.
function name() public pure returns (string memory) {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L50
function ethPerDerivative(uint256 _amount) public view returns (uint256) {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L211
function balance() public view returns (uint256) {
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L221
function name() public pure returns (string memory) {
function ethPerDerivative(uint256 _amount) public view returns (uint256) {
function balance() public view returns (uint256) {
function name() public pure returns (string memory) {
function ethPerDerivative(uint256 _amount) public view returns (uint256) {
function balance() public view returns (uint256) {
The current form of relative path import is not recommended for use because it can unpredictably pollute the namespace. Instead, the Solidity docs recommend specifying imported symbols explicitly. https://docs.soliditylang.org/en/v0.8.15/layout-of-source-files.html#importing-other-source-files
A good example:
import {OwnableUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; import {SafeCastLib} from "solmate/utils/SafeCastLib.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; import {IProducer} from "src/interfaces/IProducer.sol"; import {GlobalState, UserState} from "src/Common.sol";
5: import "../interfaces/IWETH.sol"; 6: import "../interfaces/uniswap/ISwapRouter.sol"; 7: import "../interfaces/lido/IWStETH.sol"; 8: import "../interfaces/lido/IstETH.sol"; 10: import "./SafEthStorage.sol";
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L5
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L6
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L7
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L8
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L10
4: import "../../interfaces/IDerivative.sol"; 5: import "../../interfaces/frax/IsFrxEth.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"; 14: import "../../interfaces/uniswap/IUniswapV3Factory.sol"; 15: import "../../interfaces/uniswap/IUniswapV3Pool.sol";
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L4
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L5
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L7
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L8
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L9
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L10
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L11
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L12
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L14
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L15
4: import "../../interfaces/IDerivative.sol"; 5: import "../../interfaces/frax/IsFrxEth.sol"; 8: import "../../interfaces/curve/IFrxEthEthPool.sol"; 9: import "../../interfaces/frax/IFrxETHMinter.sol";
4: import "../../interfaces/IDerivative.sol"; 7: import "../../interfaces/curve/IStEthEthPool.sol"; 8: import "../../interfaces/lido/IWStETH.sol";
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/WstEth.sol#L4
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/WstEth.sol#L7
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/WstEth.sol#L8
Use specific imports syntax per solidity docs recommendation.
unchecked
blocks to save gasSolidity version 0.8+ comes with implicit overflow and underflow checks on unsigned integers. When an overflow or an underflow isn’t possible (as an example, when a comparison is made before the arithmetic operation), some gas can be saved by using an unchecked
block
122: uint256 ethAmountToWithdraw = ethAmountAfter - ethAmountBefore;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L122
174: ((10 ** 18 - maxSlippage))) / 10 ** 18); 201: uint256 rethMinted = rethBalance2 - rethBalance1;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L174
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L201
75: (10 ** 18 - maxSlippage)) / 10 ** 18;
60: uint256 minOut = (stEthBal * (10 ** 18 - maxSlippage)) / 10 ** 18;
79: uint256 wstEthAmount = wstEthBalancePost - wstEthBalancePre;
52: ERC20Upgradeable.__ERC20_init(_tokenName, _tokenSymbol);
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L52
Functions guaranteed to revert when called by normal users can be marked payable.
Upgrade to the latest solidity version 0.8.19 to get additional gas savings. See latest release for reference: https://blog.soliditylang.org/2023/02/22/solidity-0.8.19-release-announcement/
pragma solidity ^0.8.13;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L2
pragma solidity ^0.8.13;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/Reth.sol#L2
pragma solidity ^0.8.13;
pragma solidity ^0.8.13;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/derivatives/WstEth.sol#L2
Instead of calling derivatives[i]
3 times in each loop for fetching data, it can be saved as a variable.
for (uint i = 0; i < derivativeCount; i++) underlyingValue += (derivatives[i].ethPerDerivative(derivatives[i].balance()) * derivatives[i].balance()) / 10 ** 18;
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L71-L75
for (uint256 i = 0; i < derivativeCount; i++) { // withdraw a percentage of each asset based on the amount of safETH uint256 derivativeAmount = (derivatives[i].balance() * _safEthAmount) / safEthTotalSupply; if (derivativeAmount == 0) continue; // if derivative empty ignore derivatives[i].withdraw(derivativeAmount); }
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L113-L119
for (uint i = 0; i < derivativeCount; i++) { if (derivatives[i].balance() > 0) derivatives[i].withdraw(derivatives[i].balance()); }
https://github.com/code-423n4/2023-03-asymmetry/tree/main/contracts/SafEth/SafEth.sol#L140-L143
#0 - c4-sponsor
2023-04-10T19:59:57Z
elmutt marked the issue as sponsor confirmed
#1 - c4-judge
2023-04-23T19:22:59Z
Picodes marked the issue as grade-a
#2 - c4-judge
2023-04-23T19:46:32Z
Picodes marked the issue as selected for report