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: 24/54
Findings: 1
Award: $78.89
🌟 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
78.8909 USDC - $78.89
In the last comparison there is no need to write 'else if', because if we have reached this point, it means that transferIndex is definitely less than targetsLength. And you can simply write 'else'.
if (transferIndex < Math.min(sourcesLength, targetsLength)) { // Process the delegation transfer between the current source and target delegate pair. _processDelegation(source, target, amount); } else if (transferIndex < sourcesLength) { // Handle any remaining source amounts after the transfer process. _reimburse(source, amount); - } else if (transferIndex < targetsLength) { + } else { // Handle any remaining target amounts after the transfer process. createProxyDelegatorAndTransfer(target, amount); }
You can calculate the minimum length of arrays once and use this value, rather than calculating it at each iteration.
// Iterate until all source and target delegates have been processed. + uint minLen = sourcesLength < targetsLength ? sourcesLength: targetsLength; for ( uint transferIndex = 0; transferIndex < Math.max(sourcesLength, targetsLength); transferIndex++ ) { address source = transferIndex < sourcesLength ? address(uint160(sources[transferIndex])) : address(0); address target = transferIndex < targetsLength ? address(uint160(targets[transferIndex])) : address(0); uint256 amount = amounts[transferIndex]; - if (transferIndex < Math.min(sourcesLength, targetsLength)) { + if (transferIndex < minLen) {
https://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol#L84C2-L99C1
If we understand that the array element is empty, then we do not need to assign the value address(0) to the variable because this is already the default value.
- address source = transferIndex < sourcesLength - ? address(uint160(sources[transferIndex])) - : address(0); - address target = transferIndex < targetsLength - ? address(uint160(targets[transferIndex])) - : address(0); + address source; + address target; + if(transferIndex < sourcesLength) { + source = address(uint160(sources[transferIndex])); + } + if(transferIndex < targetsLength) { + target = address(uint160(targets[transferIndex])); + }
https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L92 https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L95
It is not necessary to convert the hex value into bytes1. You can specify it through hex"ff" (without 0x prefix).
bytes32 hash = keccak256( abi.encodePacked( - bytes1(0xff), + hex"ff", address(this), uint256(0), // salt keccak256(bytecode) ) );
It is not necessary to use library functions for simple mathematics. We can insert the calculation directly into the contract. And create variable maxLen, and use it instead call library function.
- import "@openzeppelin/contracts/utils/math/Math.sol"; ... // Create variable maxLen + uint maxLen = (sourcesLength >= targetsLength ? sourcesLength : targetsLength); // place 1 require( - Math.max(sourcesLength, targetsLength) == amountsLength, + maxLen == amountsLength, "Delegate: The number of amounts must be equal to the greater of the number of sources or targets" ); // place 2 for ( uint transferIndex = 0; - transferIndex < Math.max(sourcesLength, targetsLength); + transferIndex < maxLen; transferIndex++ ) {
The contract does not use functions from the Address library, so we can remove the line with applying library to type address and remove import the library to contract. This will reduce the cost of deploying a contract.
- import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; contract ERC20MultiDelegate is ERC1155, Ownable { - using Address for address; + // delete line
https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L4 https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L26
There is no need to check additionally the balance in the _processDelegation() function, because this point is checked in the token burning function - _burnBatch(). If the user specifies a larger balance than he delegated, the contract will not be able to burn more tokens than he has (ERC-1155).
We can delete assert() and function getBalanceForDelegate(). So, it will decrease deploy cost.
function _processDelegation( address source, address target, uint256 amount ) internal { - uint256 balance = getBalanceForDelegate(source); - assert(amount <= balance); - function getBalanceForDelegate( - address delegate - ) internal view returns (uint256) { - return ERC1155(this).balanceOf(msg.sender, uint256(uint160(delegate))); - }
There is no point in emitting an event 'ProxyDeployed', because users do not directly interact with the proxy contract(ERC20ProxyDelegator) and they do not need its address. If there is no proxy for the target address, it is created without user intervention and the user does not need to care whether there is a proxy for the target address or not. This information does not carry any semantic load.
/** ### EVENTS ### */ - event ProxyDeployed(address indexed delegate, address proxyAddress); event DelegationProcessed( address indexed from, address indexed to, uint256 amount ); ... function deployProxyDelegatorIfNeeded( address delegate ) internal returns (address) { address proxyAddress = retrieveProxyContractAddress(token, delegate); // check if the proxy contract has already been deployed uint bytecodeSize; assembly { bytecodeSize := extcodesize(proxyAddress) } // if the proxy contract has not been deployed, deploy it if (bytecodeSize == 0) { new ERC20ProxyDelegator{salt: 0}(token, delegate); - emit ProxyDeployed(delegate, proxyAddress); } return proxyAddress; }
We can use msg.value (as 0) because function delegateMulti() is not payable. Value of msg.value always will be 0.
// case 1 - if (bytecodeSize == 0) { - new ERC20ProxyDelegator{salt: 0}(token, delegate); + if (bytecodeSize == msg.value) { + new ERC20ProxyDelegator{salt: msg.value}(token, delegate); // case 2 bytes32 hash = keccak256( abi.encodePacked( bytes1(0xff), address(this), - uint256(0), // salt + msg.value, // salt keccak256(bytecode) ) );
https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L210 https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L185 https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L186
#0 - c4-pre-sort
2023-10-13T14:16:14Z
141345 marked the issue as sufficient quality report
#1 - c4-judge
2023-10-24T16:57:02Z
hansfriese marked the issue as grade-a