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: 23/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
ERC20MultiDelegate.sol#L85-L108
Instead of the "swiss-army-knife" loop that handles every input via if-else and ternary operators, it will be better to separate the loop into smaller ones that won't have them.
The expected user input is: a amounts, s sources, t targets; a = max(s,t).
require( Math.max(sourcesLength, targetsLength) == amountsLength, "Delegate: The number of amounts must be equal to the greater of the number of sources or targets" );
[0 : min(s,t)]
. Ternary operator is removed: Sources and Targets are guaranteed to be provided; Only _processDelegation
will be invoked.for ( uint transferIndex = 0; transferIndex < minSourcesTargetsLength; transferIndex++ ) { address source = address(uint160(sources[transferIndex])); address target = address(uint160(targets[transferIndex])); uint256 amount = amounts[transferIndex]; _processDelegation(source, target, amount); }
[min(s,t) : max(s,t)]
.a) If s > t, we use a loop with _reimburse
. Only Sources are needed and they are guaranteed to be provided.
b) If s <= t, we use a loop with createProxyDelegatorAndTransfer
. Only Targets are needed and they are guaranteed to be provided.
if (sourcesLength > targetsLength) { for ( uint transferIndex = minSourcesTargetsLength; transferIndex < sourcesLength; transferIndex++ ) { address source = address(uint160(sources[transferIndex])); uint256 amount = amounts[transferIndex]; _reimburse(source, amount); } } else { for ( uint transferIndex = minSourcesTargetsLength; transferIndex < targetsLength; transferIndex++ ) { address target = address(uint160(targets[transferIndex])); uint256 amount = amounts[transferIndex]; createProxyDelegatorAndTransfer(target, amount); } }
Original version:
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)) { // 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) { // Handle any remaining target amounts after the transfer process. createProxyDelegatorAndTransfer(target, amount); } }
Optimized version:
uint256 minSourcesTargetsLength = Math.min( sourcesLength, targetsLength ); for ( uint transferIndex = 0; transferIndex < minSourcesTargetsLength; transferIndex++ ) { address source = address(uint160(sources[transferIndex])); address target = address(uint160(targets[transferIndex])); uint256 amount = amounts[transferIndex]; _processDelegation(source, target, amount); } if (sourcesLength > targetsLength) { for ( uint transferIndex = minSourcesTargetsLength; transferIndex < sourcesLength; transferIndex++ ) { address source = address(uint160(sources[transferIndex])); uint256 amount = amounts[transferIndex]; _reimburse(source, amount); } } else { for ( uint transferIndex = minSourcesTargetsLength; transferIndex < targetsLength; transferIndex++ ) { address target = address(uint160(targets[transferIndex])); uint256 amount = amounts[transferIndex]; createProxyDelegatorAndTransfer(target, amount); } }
The test creates 5 delegates, then moves their voting power to 5 other delegates.
solc = 0.8.21
(Sponsor has confirmed that they will be using the latest version)
runs = 200
| Deployment Cost | Deployment Size | | | | | | 2125456 | 11245 | | | | | | Function Name | min | avg | median | max | # calls | | delegateMulti | 885663 | 952517 | 952517 | 1019371 | 2 | | Deployment Cost | Deployment Size | | | | | | 2154292 | 11389 | | | | | | Function Name | min | avg | median | max | # calls | | delegateMulti | 884562 | 951248 | 951248 | 1017935 | 2 | | Savings | 1101 | 1269 | 1269 | 1436 | |
Deployment cost will increase by ~29k gas, which, as you can see, will pay off very quickly.
_token
parameter - it can be retrieved from the contract's bytecodeWith the G-02 optimization from the bot report, it's better to retrieve the token
from contract's bytecode inside the function's body, instead of passing it as a parameter every time the function is invoked.
- ERC20Votes public token; + ERC20Votes public immutable token;
function retrieveProxyContractAddress( - ERC20Votes _token, address _delegate ) private view returns (address) { bytes memory bytecode = abi.encodePacked( type(ERC20ProxyDelegator).creationCode, - abi.encode(_token, _delegate) + abi.encode(token, _delegate) ); bytes32 hash = keccak256( abi.encodePacked( bytes1(0xff), address(this), uint256(0), // salt keccak256(bytecode) ) ); return address(uint160(uint256(hash))); }
Remove token
parameter from the following lines:
147: address proxyAddressFrom = retrieveProxyContractAddress(token, source);
168: address proxyAddressFrom = retrieveProxyContractAddress(token, from); 169: address proxyAddressTo = retrieveProxyContractAddress(token, to);
176: address proxyAddress = retrieveProxyContractAddress(token, delegate);
| Deployment Cost | Deployment Size | | | | | | 2143100 | 11491 | | | | | | Function Name | min | avg | median | max | # calls | | delegateMulti | 882638 | 949097 | 949097 | 1015556 | 2 | | Deployment Cost | Deployment Size | | | | | | 2122226 | 11366 | | | | | | Function Name | min | avg | median | max | # calls | | delegateMulti | 882518 | 949017 | 949017 | 1015516 | 2 | | Savings | 120 | 80 | 80 | 40 | | Deployment savings - 20874 gas
ERC20MultiDelegate.sol#L133 ERC20MultiDelegate.sol#L169
function _processDelegation( address source, address target, uint256 amount ) internal { uint256 balance = getBalanceForDelegate(source); assert(amount <= balance); - deployProxyDelegatorIfNeeded(target); transferBetweenDelegators(source, target, amount); emit DelegationProcessed(source, target, amount); }
function transferBetweenDelegators( address from, address to, uint256 amount ) internal { address proxyAddressFrom = retrieveProxyContractAddress(token, from); - address proxyAddressTo = retrieveProxyContractAddress(token, to); + address proxyAddressTo = deployProxyDelegatorIfNeeded(to); token.transferFrom(proxyAddressFrom, proxyAddressTo, amount); }
| Deployment Cost | Deployment Size | | | | | | 2125456 | 11245 | | | | | | Function Name | min | avg | median | max | # calls | | delegateMulti | 885663 | 952517 | 952517 | 1019371 | 2 | | Deployment Cost | Deployment Size | | | | | | 2120056 | 11218 | | | | | | Function Name | min | avg | median | max | # calls | | delegateMulti | 873450 | 946410 | 946410 | 1019371 | 2 | | Savings | 12213 | 6107 | 6107 | 0 | | Deployment savings - 5400 gas
_processDelegation
optimizationsERC20MultiDelegate.sol#L124-L137 ERC20MultiDelegate.sol#L192-L196
assert
is redundant: every transaction that tries to transfer more tokens than the current balance will revert in _burnBatch
.
getBalanceForDelegate
is used only for this assert
and should be removed too.
function _processDelegation( address source, address target, uint256 amount ) internal { - uint256 balance = getBalanceForDelegate(source); - assert(amount <= balance); deployProxyDelegatorIfNeeded(target); transferBetweenDelegators(source, target, amount); emit DelegationProcessed(source, target, amount); }
- function getBalanceForDelegate( - address delegate - ) internal view returns (uint256) { - return ERC1155(this).balanceOf(msg.sender, uint256(uint160(delegate))); - }
| Deployment Cost | Deployment Size | | | | | | 2125456 | 11245 | | | | | | Function Name | min | avg | median | max | # calls | | delegateMulti | 885663 | 952517 | 952517 | 1019371 | 2 | | Deployment Cost | Deployment Size | | | | | | 2086012 | 11048 | | | | | | Function Name | min | avg | median | max | # calls | | delegateMulti | 879881 | 949626 | 949626 | 1019371 | 2 | | Savings | 5782 | 2891 | 2891 | 0 | | Deployment savings - 39444 gas
#0 - c4-pre-sort
2023-10-13T14:07:34Z
141345 marked the issue as sufficient quality report
#1 - c4-judge
2023-10-24T16:56:51Z
hansfriese marked the issue as grade-a