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: 114/133
Findings: 1
Award: $12.81
🌟 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.8108 USDC - $12.81
Enumerating storage array length inside a loop creates an extra SLOAD operation for each iteration after the first. It is more gas efficient to use a temporary in-function variable to store the length, then read that value in the loop.
In ERC20PermitPermissionedMint.sol's removeMinter() function the loop uses minters_array.length as a boundary. As minters_array's length only increases, this means that the cost of removing a minter will increase over use. Instead of the current loop code, the following would be more optimal and scale over time:
uint256 minters_array_len = minters_array.length; for (uint i = 0; i < minters_array_len; ++i){ if (minters_array[i] == minter_address) { minters_array[i] = address(0); // This will leave a null in the array and keep the indices the same break; } }
The code above also uses ++i instead of i++ to save 3 gas per iteration.
Another instance also exists in OperatorRegistry.sol
The minters mapping uses an address => bool mapping. When a minter is added, the address value is set to true. Removing a minter requires an array iteration that will become more expensive whenever minters are added or removed. Instead of a true/false value, the minters mapping could be used to store an array reference as a uint256. By storing the reference, this improves scalability and eliminates the need for a loop.
To support this the following changes need to be made in ERC20PermitPermissionedMint.sol.
In the constructor:
constructor( address _creator_address, address _timelock_address, string memory _name, string memory _symbol ) ERC20(_name, _symbol) ERC20Permit(_name) Owned(_creator_address) { timelock_address = _timelock_address; minters_array.push(address(0)); // Needed for lookups to work properly }
In addMinter():
// Adds whitelisted minters function addMinter(address minter_address) public onlyByOwnGov { require(minter_address != address(0), "Zero address detected"); require(minters[minter_address] == 0, "Address already exists"); minters[minter_address] = minters_array.length; // will match on push minters_array.push(minter_address); emit MinterAdded(minter_address); }
In removeMinter():
// Remove a minter function removeMinter(address minter_address) public onlyByOwnGov { require(minter_address != address(0), "Zero address detected"); require(minters[minter_address] != 0, "Address nonexistant"); // 'Delete' from the array by setting the address to 0x0 uint256 idx = minters[minter_address]; minters_array[idx] = address(0); // Leave a null in array and match indices // Delete from the mapping delete minters[minter_address]; emit MinterRemoved(minter_address); }