Platform: Code4rena
Start Date: 22/09/2022
Pot Size: $30,000 USDC
Total HM: 12
Participants: 133
Period: 3 days
Judge: 0xean
Total Solo HM: 2
Id: 165
League: ETH
Rank: 33/133
Findings: 3
Award: $83.85
🌟 Selected for report: 0
🚀 Solo Findings: 0
39.1574 USDC - $39.16
https://github.com/code-423n4/2022-09-frax/blob/main/src/OperatorRegistry.sol#L54
For every call addValidator() function which computed contains the addValidator value of a given Validator is listed in validators.push[] array, the gas consumption can be more expensive each time that a new Validator address is appended to the array, until reaching an "Out of Gas" error or a "Block Gas Limit" in the worst scenario.
There is no upper limit on validators.push[[], it increments each time when new Validator is added. Eventually, as the count of Validators increases, the gas cost of smart contract calls will rise and there is no implemented function to reduce the array size.
https://github.com/code-423n4/2022-09-frax/blob/main/src/OperatorRegistry.sol#L54
'validators.push(validator);'
manual code review
Array's length should be checked.
#0 - FortisFortuna
2022-09-26T16:31:29Z
Adding a maxLoops parameter or similar can help mitigate this for sure.
#1 - FortisFortuna
2022-09-26T17:22:42Z
#2 - 0xean
2022-10-11T21:40:21Z
This isn't an exact dupe of #17 but please reference #224 for more info on it.
🌟 Selected for report: rotcivegaf
Also found by: 0x040, 0x1f8b, 0x4non, 0xNazgul, 0xSmartContract, 0xf15ers, 8olidity, Aymen0909, B2, Bahurum, Bnke0x0, Ch_301, CodingNameKiki, Deivitto, Diana, Funen, IllIllI, JC, JLevick, KIntern_NA, Lambda, OptimismSec, PaludoX0, RockingMiles, Rolezn, Sm4rty, Soosh, Tagir2003, Tointer, TomJ, Triangle, Trust, V_B, Waze, Yiko, __141345__, a12jmx, ajtra, asutorufos, ayeslick, aysha, bbuddha, bharg4v, bobirichman, brgltd, bytera, c3phas, cryptostellar5, cryptphi, csanuragjain, datapunk, delfin454000, durianSausage, exd0tpy, gogo, got_targ, jag, joestakey, karanctf, ladboy233, leosathya, lukris02, mics, millersplanet, natzuu, neko_nyaa, obront, oyc_109, parashar, peritoflores, rbserver, ret2basic, rokinot, ronnyx2017, rvierdiiev, sach1r0, seyni, sikorico, slowmoses, tnevler, yasir, yongskiws
28.3211 USDC - $28.32
address(0x0)
when assigning values to address
state variables2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::34 => timelock_address = _timelock_address; 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::96 => timelock_address = _timelock_address; 2022-09-frax/src/OperatorRegistry.sol::41 => timelock_address = _timelock_address;
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
2022-09-frax/src/frxETHMinter.sol::114 => receive() external payable {
approve is subject to a known front-running attack. Consider using safeApprove instead:
2022-09-frax/src/frxETHMinter.sol::75 => frxETHToken.approve(address(sfrxETHToken), msg.value);
transfer()
/transferFrom()
not checkedNot all IERC20 implementations revert() when there’s a failure in transfer()/transferFrom(). The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually making a payment
2022-09-frax/src/frxETHMinter.sol::200 => require(IERC20(tokenAddress).transfer(owner, tokenAmount), "recoverERC20: Transfer failed");
There exist ERC20 tokens that charge a fee for every transfer()
 or transferFrom()
.
If this tokens are unsupported, ensure there is proper documentation about it.
2022-09-frax/src/frxETHMinter.sol::200 => require(IERC20(tokenAddress).transfer(owner, tokenAmount), "recoverERC20: Transfer failed");
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.
2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/OperatorRegistry.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/frxETH.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/frxETHMinter.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/sfrxETH.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/xERC4626.sol::4 => pragma solidity ^0.8.0;
Contracts implementing access control's, e.g. owner
, should consider implementing a Two-Step Transfer pattern.
Otherwise it's possible that the role mistakenly transfers ownership to the wrong address, resulting in a loss of the role.
2022-09-frax/src/frxETHMinter.sol::56 => address _owner,
2022-09-frax/src/sfrxETH.sol::55 => return convertToAssets(1e18);
2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/OperatorRegistry.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/frxETH.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/frxETHMinter.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/sfrxETH.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/xERC4626.sol::4 => pragma solidity ^0.8.0;
2022-09-frax/src/frxETH.sol::1 => // SPDX-License-Identifier: GPL-2.0-or-later
Use a solidity version of at least 0.8.13 to get the ability to use using for with a list of free functions
2022-09-frax/src/xERC4626.sol::21 => using SafeCastLib for *;
indexed
fieldsEach event should use three indexed fields if there are three or more fields
2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::102 => event TokenMinterBurned(address indexed from, address indexed to, uint256 amount); 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::103 => event TokenMinterMinted(address indexed from, address indexed to, uint256 amount); 2022-09-frax/src/OperatorRegistry.sol::210 => event ValidatorAdded(bytes pubKey, bytes withdrawalCredential); 2022-09-frax/src/OperatorRegistry.sol::212 => event ValidatorRemoved(bytes pubKey, uint256 remove_idx, bool dont_care_about_ordering); 2022-09-frax/src/OperatorRegistry.sol::213 => event ValidatorsPopped(uint256 times); 2022-09-frax/src/OperatorRegistry.sol::214 => event ValidatorsSwapped(bytes from_pubKey, bytes to_pubKey, uint256 from_idx, uint256 to_idx); 2022-09-frax/src/frxETHMinter.sol::206 => event EmergencyERC20Recovered(address tokenAddress, uint256 tokenAmount); 2022-09-frax/src/frxETHMinter.sol::207 => event ETHSubmitted(address indexed sender, address indexed recipient, uint256 sent_amount, uint256 withheld_amt); 2022-09-frax/src/frxETHMinter.sol::209 => event DepositSent(bytes indexed pubKey, bytes withdrawalCredential); 2022-09-frax/src/frxETHMinter.sol::211 => event WithheldETHMoved(address indexed to, uint256 amount);
2022-09-frax/src/frxETHMinter.sol::49 => bool public submitPaused; 2022-09-frax/src/frxETHMinter.sol::50 => bool public depositEtherPaused;
2022-09-frax/src/xERC4626.sol::1 => // SPDX-License-Identifier: MIT
public
functions not called by the contract should be declared external
insteadContracts are allowed to override their parents’ functions and change the visibility from public to external.
2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::53 => function minter_burn_from(address b_address, uint256 b_amount) public onlyMinters { 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::59 => function minter_mint(address m_address, uint256 m_amount) public onlyMinters { 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::65 => function addMinter(address minter_address) public onlyByOwnGov { 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::76 => function removeMinter(address minter_address) public onlyByOwnGov { 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::94 => function setTimelock(address _timelock_address) public onlyByOwnGov { 2022-09-frax/src/OperatorRegistry.sol::53 => function addValidator(Validator calldata validator) public onlyByOwnGov { 2022-09-frax/src/OperatorRegistry.sol::69 => function swapValidator(uint256 from_idx, uint256 to_idx) public onlyByOwnGov { 2022-09-frax/src/OperatorRegistry.sol::82 => function popValidators(uint256 times) public onlyByOwnGov { 2022-09-frax/src/OperatorRegistry.sol::93 => function removeValidator(uint256 remove_idx, bool dont_care_about_ordering) public onlyByOwnGov { 2022-09-frax/src/OperatorRegistry.sol::197 => function numValidators() public view returns (uint256) { 2022-09-frax/src/sfrxETH.sol::54 => function pricePerShare() public view returns (uint256) { 2022-09-frax/src/xERC4626.sol::45 => function totalAssets() public view override returns (uint256) { 2022-09-frax/src/xERC4626.sol::78 => function syncRewards() public virtual {
2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/OperatorRegistry.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/frxETH.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/frxETHMinter.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/sfrxETH.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/xERC4626.sol::4 => pragma solidity ^0.8.0;
🌟 Selected for report: pfapostol
Also found by: 0x040, 0x1f8b, 0x4non, 0x5rings, 0xA5DF, 0xNazgul, 0xSmartContract, 0xmatt, 0xsam, Amithuddar, Aymen0909, B2, Ben, Bnke0x0, Chom, CodingNameKiki, Deivitto, Diana, Fitraldys, Funen, IllIllI, JAGADESH, JC, Metatron, Ocean_Sky, PaludoX0, Pheonix, RaymondFam, ReyAdmirado, RockingMiles, Rohan16, Rolezn, Satyam_Sharma, Sm4rty, SnowMan, SooYa, Tagir2003, TomJ, Tomio, Triangle, V_B, Waze, __141345__, ajtra, albincsergo, asutorufos, aysha, beardofginger, bobirichman, brgltd, bulej93, bytera, c3phas, ch0bu, cryptostellar5, cryptphi, d3e4, delfin454000, dharma09, drdr, durianSausage, emrekocak, erictee, fatherOfBlocks, gogo, got_targ, imare, jag, karanctf, ladboy233, leosathya, lukris02, medikko, mics, millersplanet, natzuu, neko_nyaa, oyc_109, peanuts, prasantgupta52, rbserver, ret2basic, rokinot, ronnyx2017, rotcivegaf, sach1r0, samruna, seyni, slowmoses, tnevler, wagmi, zishansami
16.3728 USDC - $16.37
immutable
2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::16 => address public timelock_address; 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::19 => address[] public minters_array; // Allowed to mint 2022-09-frax/src/OperatorRegistry.sol::38 => address public timelock_address; 2022-09-frax/src/frxETHMinter.sol::38 => uint256 public constant DEPOSIT_SIZE = 32 ether; // ETH 2.0 minimum deposit size 2022-09-frax/src/frxETHMinter.sol::39 => uint256 public constant RATIO_PRECISION = 1e6; // 1,000,000 2022-09-frax/src/frxETHMinter.sol::41 => uint256 public withholdRatio; // What we keep and don't deposit whenever someone submit()'s ETH 2022-09-frax/src/frxETHMinter.sol::42 => uint256 public currentWithheldETH; // Needed for internal tracking 2022-09-frax/src/frxETHMinter.sol::49 => bool public submitPaused; 2022-09-frax/src/frxETHMinter.sol::50 => bool public depositEtherPaused; 2022-09-frax/src/xERC4626.sol::27 => uint32 public lastSync; 2022-09-frax/src/xERC4626.sol::30 => uint32 public rewardsCycleEnd; 2022-09-frax/src/xERC4626.sol::33 => uint192 public lastRewardAmount;
If variables occupying the same slot are both written the same function or by the constructor, avoids a separate Gsset (20000 gas). Reads of the variables are also cheaper
2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::16 => address public timelock_address; 2022-09-frax/src/OperatorRegistry.sol::38 => address public timelock_address;
<array>.length
should not be looked up in every loop of a for
loopEven memory arrays incur the overhead of bit tests and bit shifts to calculate the array length. Storage array length checks incur an extra Gwarmaccess (100 gas) PER-LOOP.
2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::84 => for (uint i = 0; i < minters_array.length; i++){ 2022-09-frax/src/OperatorRegistry.sol::114 => for (uint256 i = 0; i < original_validators.length; ++i) {
++i/i++
should be unchecked{++i}
/unchecked{++i}
when it is not possible for them to overflow, as is the case when used in for
and while
loops2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::84 => for (uint i = 0; i < minters_array.length; i++){ 2022-09-frax/src/OperatorRegistry.sol::63 => for (uint256 i = 0; i < arrayLength; ++i) { 2022-09-frax/src/OperatorRegistry.sol::84 => for (uint256 i = 0; i < times; ++i) { 2022-09-frax/src/OperatorRegistry.sol::114 => for (uint256 i = 0; i < original_validators.length; ++i) { 2022-09-frax/src/frxETHMinter.sol::129 => for (uint256 i = 0; i < numDeposits; ++i) {
require()
/revert()
strings longer than 32 bytes cost extra gas2022-09-frax/src/frxETHMinter.sol::167 => require(amount <= currentWithheldETH, "Not enough withheld ETH in contract");
> 0
costs more gas than != 0
when used on a uint
in a require()
statement2022-09-frax/src/frxETHMinter.sol::79 => require(sfrxeth_recieved > 0, 'No sfrxETH was returned'); 2022-09-frax/src/frxETHMinter.sol::126 => require(numDeposits > 0, "Not enough ETH in contract");
2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::84 => for (uint i = 0; i < minters_array.length; i++){ 2022-09-frax/src/OperatorRegistry.sol::63 => for (uint256 i = 0; i < arrayLength; ++i) { 2022-09-frax/src/OperatorRegistry.sol::84 => for (uint256 i = 0; i < times; ++i) { 2022-09-frax/src/OperatorRegistry.sol::114 => for (uint256 i = 0; i < original_validators.length; ++i) { 2022-09-frax/src/frxETHMinter.sol::63 => withholdRatio = 0; // No ETH is withheld initially 2022-09-frax/src/frxETHMinter.sol::64 => currentWithheldETH = 0; 2022-09-frax/src/frxETHMinter.sol::94 => uint256 withheld_amt = 0; 2022-09-frax/src/frxETHMinter.sol::129 => for (uint256 i = 0; i < numDeposits; ++i) {
++i
costs less gas than i++
, especially when it’s used in forloops (--i
/i--
too)2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::84 => for (uint i = 0; i < minters_array.length; i++){
uints
/ints
smaller than 32 bytes (256 bits) incurs overheadWhen 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.
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Use a larger size then downcast where needed
2022-09-frax/src/sfrxETH.sol::42 => constructor(ERC20 _underlying, uint32 _rewardsCycleLength) 2022-09-frax/src/sfrxETH.sol::64 => uint8 v, 2022-09-frax/src/sfrxETH.sol::80 => uint8 v, 2022-09-frax/src/xERC4626.sol::24 => uint32 public immutable rewardsCycleLength; 2022-09-frax/src/xERC4626.sol::27 => uint32 public lastSync; 2022-09-frax/src/xERC4626.sol::30 => uint32 public rewardsCycleEnd; 2022-09-frax/src/xERC4626.sol::33 => uint192 public lastRewardAmount; 2022-09-frax/src/xERC4626.sol::37 => constructor(uint32 _rewardsCycleLength) { 2022-09-frax/src/xERC4626.sol::48 => uint192 lastRewardAmount_ = lastRewardAmount; 2022-09-frax/src/xERC4626.sol::49 => uint32 rewardsCycleEnd_ = rewardsCycleEnd; 2022-09-frax/src/xERC4626.sol::50 => uint32 lastSync_ = lastSync; 2022-09-frax/src/xERC4626.sol::79 => uint192 lastRewardAmount_ = lastRewardAmount; 2022-09-frax/src/xERC4626.sol::80 => uint32 timestamp = block.timestamp.safeCastTo32(); 2022-09-frax/src/xERC4626.sol::89 => uint32 end = ((timestamp + rewardsCycleLength) / rewardsCycleLength) * rewardsCycleLength;
require()
/revert()
checks should be refactored to a modifier or function2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::66 => require(minter_address != address(0), "Zero address detected"); 2022-09-frax/src/frxETHMinter.sol::171 => require(success, "Invalid transfer");
require()
or revert()
statements that check input arguments should be at the top of the functionChecks that involve constants should come before checks that involve state variables
2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::66 => require(minter_address != address(0), "Zero address detected"); 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::77 => require(minter_address != address(0), "Zero address detected"); 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::95 => require(_timelock_address != address(0), "Zero address detected"); 2022-09-frax/src/OperatorRegistry.sol::203 => require(_timelock_address != address(0), "Zero address detected");
revert()
/require()
strings to save deployment gas2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::41 => require(msg.sender == timelock_address || msg.sender == owner, "Not owner or timelock"); 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::46 => require(minters[msg.sender] == true, "Only minters"); 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::66 => require(minter_address != address(0), "Zero address detected"); 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::68 => require(minters[minter_address] == false, "Address already exists"); 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::77 => require(minter_address != address(0), "Zero address detected"); 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::78 => require(minters[minter_address] == true, "Address nonexistant"); 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::95 => require(_timelock_address != address(0), "Zero address detected"); 2022-09-frax/src/OperatorRegistry.sol::46 => require(msg.sender == timelock_address || msg.sender == owner, "Not owner or timelock"); 2022-09-frax/src/OperatorRegistry.sol::137 => require(numVals != 0, "Validator stack is empty"); 2022-09-frax/src/OperatorRegistry.sol::182 => require(numValidators() == 0, "Clear validator array first"); 2022-09-frax/src/OperatorRegistry.sol::203 => require(_timelock_address != address(0), "Zero address detected"); 2022-09-frax/src/frxETHMinter.sol::79 => require(sfrxeth_recieved > 0, 'No sfrxETH was returned'); 2022-09-frax/src/frxETHMinter.sol::87 => require(!submitPaused, "Submit is paused"); 2022-09-frax/src/frxETHMinter.sol::88 => require(msg.value != 0, "Cannot submit 0"); 2022-09-frax/src/frxETHMinter.sol::122 => require(!depositEtherPaused, "Depositing ETH is paused"); 2022-09-frax/src/frxETHMinter.sol::126 => require(numDeposits > 0, "Not enough ETH in contract"); 2022-09-frax/src/frxETHMinter.sol::140 => require(!activeValidators[pubKey], "Validator already has 32 ETH"); 2022-09-frax/src/frxETHMinter.sol::160 => require (newRatio <= RATIO_PRECISION, "Ratio cannot surpass 100%"); 2022-09-frax/src/frxETHMinter.sol::167 => require(amount <= currentWithheldETH, "Not enough withheld ETH in contract"); 2022-09-frax/src/frxETHMinter.sol::171 => require(success, "Invalid transfer"); 2022-09-frax/src/frxETHMinter.sol::193 => require(success, "Invalid transfer"); 2022-09-frax/src/frxETHMinter.sol::200 => require(IERC20(tokenAddress).transfer(owner, tokenAmount), "recoverERC20: Transfer failed");
payable
If a function modifier such as onlyOwner 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.
2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::53 => function minter_burn_from(address b_address, uint256 b_amount) public onlyMinters { 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::59 => function minter_mint(address m_address, uint256 m_amount) public onlyMinters { 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::65 => function addMinter(address minter_address) public onlyByOwnGov { 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::76 => function removeMinter(address minter_address) public onlyByOwnGov { 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::94 => function setTimelock(address _timelock_address) public onlyByOwnGov { 2022-09-frax/src/OperatorRegistry.sol::53 => function addValidator(Validator calldata validator) public onlyByOwnGov { 2022-09-frax/src/OperatorRegistry.sol::61 => function addValidators(Validator[] calldata validatorArray) external onlyByOwnGov { 2022-09-frax/src/OperatorRegistry.sol::69 => function swapValidator(uint256 from_idx, uint256 to_idx) public onlyByOwnGov { 2022-09-frax/src/OperatorRegistry.sol::82 => function popValidators(uint256 times) public onlyByOwnGov { 2022-09-frax/src/OperatorRegistry.sol::93 => function removeValidator(uint256 remove_idx, bool dont_care_about_ordering) public onlyByOwnGov { 2022-09-frax/src/OperatorRegistry.sol::181 => function setWithdrawalCredential(bytes memory _new_withdrawal_pubkey) external onlyByOwnGov { 2022-09-frax/src/OperatorRegistry.sol::190 => function clearValidatorArray() external onlyByOwnGov { 2022-09-frax/src/OperatorRegistry.sol::202 => function setTimelock(address _timelock_address) external onlyByOwnGov { 2022-09-frax/src/frxETHMinter.sol::159 => function setWithholdRatio(uint256 newRatio) external onlyByOwnGov { 2022-09-frax/src/frxETHMinter.sol::166 => function moveWithheldETH(address payable to, uint256 amount) external onlyByOwnGov { 2022-09-frax/src/frxETHMinter.sol::177 => function togglePauseSubmits() external onlyByOwnGov { 2022-09-frax/src/frxETHMinter.sol::184 => function togglePauseDepositEther() external onlyByOwnGov { 2022-09-frax/src/frxETHMinter.sol::191 => function recoverEther(uint256 amount) external onlyByOwnGov { 2022-09-frax/src/frxETHMinter.sol::199 => function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyByOwnGov {
Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value
2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/OperatorRegistry.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/frxETH.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/frxETHMinter.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/sfrxETH.sol::2 => pragma solidity ^0.8.0; 2022-09-frax/src/xERC4626.sol::4 => pragma solidity ^0.8.0;
The comparison operators >= and <= use more gas than >, <, or ==. Replacing the >= and ≤ operators with a comparison operator that has an opcode in the EVM saves gas.
Replace the comparison operator and reverse the logic to save gas using the suggestions above.
2022-09-frax/src/sfrxETH.sol::50 => if (block.timestamp >= rewardsCycleEnd) { syncRewards(); } 2022-09-frax/src/xERC4626.sol::52 => if (block.timestamp >= rewardsCycleEnd_) {
calldata
instead of memory
for function parametersUse calldata instead of memory for function parameters. Having function arguments use calldata instead of memory can save gas.
There are several cases of function arguments using memory instead of calldata
2022-09-frax/src/OperatorRegistry.sol::181 => function setWithdrawalCredential(bytes memory _new_withdrawal_pubkey) external onlyByOwnGov {
Checking non-zero transfer values can avoid an expensive external call and save gas.
While this is done at some places, it’s not consistently done in the solution.
2022-09-frax/src/frxETHMinter.sol::200 => require(IERC20(tokenAddress).transfer(owner, tokenAmount), "recoverERC20: Transfer failed");
2022-09-frax/src/frxETHMinter.sol::170 => (bool success,) = payable(to).call{ value: amount }(""); 2022-09-frax/src/frxETHMinter.sol::192 => (bool success,) = address(owner).call{ value: amount }("");
bools
for storage incurs overhead2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::20 => mapping(address => bool) public minters; // Mapping is also used for faster verification 2022-09-frax/src/frxETHMinter.sol::43 => mapping(bytes => bool) public activeValidators; // Tracks validators (via their pubkeys) that already have 32 ETH in them 2022-09-frax/src/frxETHMinter.sol::49 => bool public submitPaused; 2022-09-frax/src/frxETHMinter.sol::50 => bool public depositEtherPaused;
private
rather than public
for constants, saves gasIf needed, the value can be read from the verified contract source code. Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table
2022-09-frax/src/frxETHMinter.sol::38 => uint256 public constant DEPOSIT_SIZE = 32 ether; // ETH 2.0 minimum deposit size 2022-09-frax/src/frxETHMinter.sol::39 => uint256 public constant RATIO_PRECISION = 1e6; // 1,000,000
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{...}})
2022-09-frax/src/frxETH.sol::41 => {} 2022-09-frax/src/sfrxETH.sol::45 => {}
if (<x> == true) => if (<x>), if (<x> == false) => if (!<x>)
2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::46 => require(minters[msg.sender] == true, "Only minters"); 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::68 => require(minters[minter_address] == false, "Address already exists"); 2022-09-frax/src/ERC20/ERC20PermitPermissionedMint.sol::78 => require(minters[minter_address] == true, "Address nonexistant");
public/external function names and public member variable names can be optimized to save gas. See this link for an example of how it works. Below are the interfaces/abstract contracts that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup. Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shifted
2022-09-frax/src/xERC4626.sol::20 => abstract contract xERC4626 is IxERC4626, ERC4626 {