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: 101/133
Findings: 1
Award: $12.87
🌟 Selected for report: 0
🚀 Solo Findings: 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
12.8681 USDC - $12.87
The unchecked keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas PER LOOP
src/ERC20/ERC20PermitPermissionedMint.sol:84: for (uint i = 0; i < minters_array.length; i++){
src/OperatorRegistry.sol:63: for (uint256 i = 0; i < arrayLength; ++i) { src/OperatorRegistry.sol:84: for (uint256 i = 0; i < times; ++i) { src/OperatorRegistry.sol:114: for (uint256 i = 0; i < original_validators.length; ++i) {
If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for address…). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
We can use uint number;
instead of uint number = 0;
src/ERC20/ERC20PermitPermissionedMint.sol:84: for (uint i = 0; i < minters_array.length; i++){
src/OperatorRegistry.sol:63: for (uint256 i = 0; i < arrayLength; ++i) { src/OperatorRegistry.sol:84: for (uint256 i = 0; i < times; ++i) { src/OperatorRegistry.sol:114: for (uint256 i = 0; i < original_validators.length; ++i) {
++i costs less gas as compared to i++ for unsigned integer, as per-increment is cheaper(its about 5 gas per iteration cheaper)
i++ increments i and returns initial value of i. Which means
uint i = 1; i++; // ==1 but i ==2
But ++i returns the actual incremented value:
uint i = 1; ++i; // ==2 and i ==2 , no need for temporary variable here
In the first case, the compiler has create a temporary variable (when used) for returning 1 instead of 2.
src/ERC20/ERC20PermitPermissionedMint.sol:84: for (uint i = 0; i < minters_array.length; i++){
Change post-increment to pre-increment.
0 is less efficient than != 0 for unsigned integers (with proof) != 0 costs less gas compared to > 0 for unsigned integers in require statements with the optimizer enabled (6 gas) Proof: While it may seem that > 0 is cheaper than !=, this is only true without the optimizer enabled and outside a require statement. If you enable the optimizer at 10k AND you’re in a require statement, this will save gas. You can see this tweet for more proof: https://twitter.com/gzeon/status/1485428085885640706
src/frxETHMinter.sol:79: require(sfrxeth_recieved > 0, 'No sfrxETH was returned'); src/frxETHMinter.sol:126: require(numDeposits > 0, "Not enough ETH in contract");
I suggest changing > 0 with != 0. Also, please enable the Optimizer.
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met). Starting from Solidity v0.8.4,there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures (e.g., revert("Insufficient funds.");),but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them. Custom errors are defined using the error statement, which can be used inside and outside of contracts (including interfaces and libraries).
src/frxETHMinter.sol:79: require(sfrxeth_recieved > 0, 'No sfrxETH was returned'); src/frxETHMinter.sol:87: require(!submitPaused, "Submit is paused"); src/frxETHMinter.sol:88: require(msg.value != 0, "Cannot submit 0"); src/frxETHMinter.sol:122: require(!depositEtherPaused, "Depositing ETH is paused"); src/frxETHMinter.sol:126: require(numDeposits > 0, "Not enough ETH in contract"); src/frxETHMinter.sol:140: require(!activeValidators[pubKey], "Validator already has 32 ETH"); src/frxETHMinter.sol:160: require (newRatio <= RATIO_PRECISION, "Ratio cannot surpass 100%"); src/frxETHMinter.sol:167: require(amount <= currentWithheldETH, "Not enough withheld ETH in contract"); src/frxETHMinter.sol:171: require(success, "Invalid transfer"); src/frxETHMinter.sol:193: require(success, "Invalid transfer"); src/frxETHMinter.sol:200: require(IERC20(tokenAddress).transfer(owner, tokenAmount), "recoverERC20: Transfer failed");
ERC20PermitPermissionedMint.sol
src/ERC20/ERC20PermitPermissionedMint.sol:41: require(msg.sender == timelock_address || msg.sender == owner, "Not owner or timelock"); src/ERC20/ERC20PermitPermissionedMint.sol:46: require(minters[msg.sender] == true, "Only minters"); src/ERC20/ERC20PermitPermissionedMint.sol:66: require(minter_address != address(0), "Zero address detected"); src/ERC20/ERC20PermitPermissionedMint.sol:68: require(minters[minter_address] == false, "Address already exists"); src/ERC20/ERC20PermitPermissionedMint.sol:77: require(minter_address != address(0), "Zero address detected"); src/ERC20/ERC20PermitPermissionedMint.sol:78: require(minters[minter_address] == true, "Address nonexistant"); src/ERC20/ERC20PermitPermissionedMint.sol:95: require(_timelock_address != address(0), "Zero address detected"); src/OperatorRegistry.sol:46: require(msg.sender == timelock_address || msg.sender == owner, "Not owner or timelock");
src/OperatorRegistry.sol:137: require(numVals != 0, "Validator stack is empty"); src/OperatorRegistry.sol:182: require(numValidators() == 0, "Clear validator array first"); src/OperatorRegistry.sol:203: require(_timelock_address != address(0), "Zero address detected");
I suggest replacing revert strings with custom errors.
lib/ERC4626/src/xERC4626.sol:71: storedTotalAssets += amount; src/frxETHMinter.sol:97: currentWithheldETH += withheld_amt;
https://github.com/code-423n4/2022-05-backd-findings/issues/108
Booleans are more expensive than uint256 or any type that takes up a full word because each write operation emits an extra SLOAD to first read the slot's contents, replace the bits taken up by the boolean, and then write back. This is the compiler's defense against contract upgrades and pointer aliasing, and it cannot be disabled.
Refer Here Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past
src/ERC20/ERC20PermitPermissionedMint.sol:20: mapping(address => bool) public minters; // Mapping is also used for faster verification src/sfrxETH.sol:63: bool approveMax, src/sfrxETH.sol:79: bool approveMax, src/frxETHMinter.sol:43: mapping(bytes => bool) public activeValidators; // Tracks validators (via their pubkeys) that already have 32 ETH in them src/frxETHMinter.sol:49: bool public submitPaused; src/frxETHMinter.sol:50: bool public depositEtherPaused;
https://code4rena.com/reports/2022-06-notional-coop#8-using-bools-for-storage-incurs-overhead