Platform: Code4rena
Start Date: 05/10/2023
Pot Size: $33,050 USDC
Total HM: 1
Participants: 54
Period: 6 days
Judge: hansfriese
Id: 294
League: ETH
Rank: 37/54
Findings: 1
Award: $8.19
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: windhustler
Also found by: 0xhex, 0xta, JCK, K42, MatricksDeCoder, MrPotatoMagic, SAQ, SY_S, SovaSlava, aslanbek, d3e4, danb, hunter_w3b, lukejohn
8.1878 USDC - $8.19
abi.encode(_token, _delegate)
abi.encode must be replaced with abi.encodePacked as there is little or no chances collision given passing in 2 addresses. abi.encode adds padding which adds to the gas costs as opposed to abi.encodePacked which tightly packs the
Minimal proxies, for deploying contract ERC20ProxyDelegator results insubstantial gas savings. Clones are small and inexpensive smart contracts that delegate calls to an implementation contract, eliminating the need to deploy the entire contract repeatedly for each delegateMulti.
Instead make use of created proxies, that point to the shared contract logic. This approach reduces gas costs significantly, making the deployment process more efficient and cost-effective. e.g ERC1167 is very gas efficient implementation contract is directly stored in the contract code, eliminating the need for additional storage loads (sload).
https://github.com/code-423n4/2023-10-ens/blob/1adbe2cce191140657b8bccffab85103953bdccb/contracts/ERC20MultiDelegate.sol#L87 In above cases Math.max(sourcesLength, targetsLength) is computed twice in same function instead of just storing the value first time in a memory variable and reuse e.g
uint256 maxSize = Math.max(sourcesLength, targetsLength)
Instead of importing entire Open Zeppelin Math library can just implement the two needed functions as internal functions or copy and create them in own library and import that library to use in these contracts.
Solmate Owned contracts are generally always more gas efficient and smaller compared to Open Zeppelin contracts. We can see that the Solmate Owned contract is smaller than Open Zeppelin on this link
https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol
It may be ideal to use do while loops as do while loop will cost less gas since the condition is not being checked for the first iteration leading to them being cheaper than for loops
\\change from format for (uint i= 0; i<num; i++) { ...doStuff} \\ change to format uint i; do { ...doStuff} while(i < num)
https://github.com/code-423n4/2023-10-ens/blob/1adbe2cce191140657b8bccffab85103953bdccb/hardhat.config.js#L27 There is scope for gas savings by exploring the optimal optimizer runs settings for Solidity
Not setting optimized value can result in using optimizer settings that are no best for reducing gas costs for the users. The more the runs e.g 10,000 the cheaper the gas costs for functions for users. The smaller the runs, the cheaper the deployment costs. It's essential to put users first in order to attract more and better usage of protocol.
Important library Address which is attached to address type is not important as none of its functionality like sendValue, verifyCallResult etc are used on the address type.
ERC20Votes public token; // vs ERC20Votes private token;
Above variable by being public leads to a free getter function which is not really necessary as the value can be read directly from blockchain. Gas savings can be made by making the variable private
function _delegateMulti( uint256[] calldata sources, uint256[] calldata targets, uint256[] calldata amounts )
Taking in address inputs as uint256 just results in further computation that adds to gas costs of casting the inputs into address
address source = transferIndex < sourcesLength ? address(uint160(sources[transferIndex])) : address(0); address target = transferIndex < targetsLength ? address(uint160(targets[transferIndex])) : address(0);
Just take them in as addresses
function _delegateMulti( address[] calldata sources, address[] calldata targets, uint256[] calldata amounts )
for ( uint transferIndex = 0; transferIndex < Math.max(sourcesLength, targetsLength); transferIndex++ ) {
Can be rewritten as no need to reassing transferIndex to its default value 0 at start
for ( uint transferIndex; transferIndex < Math.max(sourcesLength, targetsLength); transferIndex++ ) {
https://github.com/code-423n4/2023-10-ens/blob/1adbe2cce191140657b8bccffab85103953bdccb/contracts/ERC20MultiDelegate.sol#L198 Instead of the below
bytes memory bytecode = abi.encodePacked( type(ERC20ProxyDelegator).creationCode, abi.encode(_token, _delegate) ); bytes32 hash = keccak256( abi.encodePacked( bytes1(0xff), address(this), uint256(0), // salt keccak256(bytecode) ) ); return address(uint160(uint256(hash)));
Use cheaper assembly in the form below updating it appropriately for the correct variables, values and logic
function predictDeterministicAddress( address implementation, bytes32 salt, address deployer ) internal pure returns (address predicted) { /// @solidity memory-safe-assembly assembly { let ptr := mload(0x40) mstore(add(ptr, 0x38), deployer) mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff) mstore(add(ptr, 0x14), implementation) mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73) mstore(add(ptr, 0x58), salt) mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37)) predicted := keccak256(add(ptr, 0x43), 0x55) } }
Above format in assembly does the create2 and hashing in memory which saves gas
#0 - c4-pre-sort
2023-10-13T14:13:09Z
141345 marked the issue as sufficient quality report
#1 - c4-judge
2023-10-24T17:02:43Z
hansfriese marked the issue as grade-b