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: 29/54
Findings: 2
Award: $16.12
π Selected for report: 0
π Solo Findings: 0
π Selected for report: thekmj
Also found by: 0x3b, 33BYTEZZZ, Bauchibred, Chom, Dravee, J4X, Limbooo, Maroutis, MiloTruck, MrPotatoMagic, SBSecurity, Sathish9098, Tadev, ZanyBonzy, adam-idarrha, adriro, btk, hyh, lukejohn, nmirchev8, peakbolt, radev_sw, rvierdiiev
5.4311 USDC - $5.43
Count | Title |
---|---|
[L-01] | No check if source == target in _processDelegation |
[L-02] | No check if amount == 0 |
[L-03] | Tokens and Ether mistakenly sent to contracts cannot be recovered |
[L-04] | Everyone can delegate to themselves |
[L-05] | Delegator can pass an address of a proxy which has already delegated to him |
Count | Title |
---|---|
[NC-01] | Missing event for important changes |
[N-02] | Unnecessary library usage |
Total Non-Critical Issues | 2 |
---|
_processDelegation
If we provide the same source and target addresses, the function will execute successfully but won't result in any modifications.
This is because it invokes the token.transferFrom()
function, which merely deducts the specified amount from the source address and then immediately credits it back to the same address. This behavior occurs because the default implementation of ERC20:transferFrom
does not validate whether the source and target addresses are the same.
Maybe add a check if source == target
to prevent the user from paying for gas.
163 function transferBetweenDelegators( 164 address from, 165 address to, 166 uint256 amount 167 ) internal { + require(from != to) 169 address proxyAddressFrom = retrieveProxyContractAddress(token, from); 170 address proxyAddressTo = retrieveProxyContractAddress(token, to); 171 token.transferFrom(proxyAddressFrom, proxyAddressTo, amount); 172 }
amount == 0
There is no validation whether amount transferred is greater than 0. As a result many proxies will be deployed but they wonβt have any funds and therefore will result in a waste of gas and not giving any value to the delegator.
The ERC20MultiDelegate
and ERC20ProxyDelegator
contracts lack functions for sweep/recover
mistakenly sent tokens or Ether.
Users might assume they should transfer their ENS tokens to the ERC20MultiDelegate
, expecting it to forward them to the proxy.
Alternatively, they may see any proxy address from the mempool and send their tokens there, under the assumption that this action delegates their votes.
Consider adding this type of function and restrict it to only be callable by the protocol admin.
The _delegateMulti()
function allows self-delegation because it doesn't check if the target is equal to msg.sender
.The ERC20Votes:delegate
function also lacks this check, resulting in the ability to self-delegate which goes against the contract's intended purpose.
Consider adding a check if msg.sender == target
.
Anyone can establish a "chain delegation" by passing a proxy linked to their own address as the target. In doing so, they delegate to the proxy address (target), which in turn creates another proxy, ultimately leading to the delegation being routed back to the original delegator.
No events are emitted when delegate from user to proxy and from proxy to user.
In _reimburse()
:
144 function _reimburse(address source, uint256 amount) internal { 145 // Transfer the remaining source amount or the full source amount 146 // (if no remaining amount) to the delegator 147 address proxyAddressFrom = retrieveProxyContractAddress(token, source); 148 token.transferFrom(proxyAddressFrom, msg.sender, amount); + emit DelegationProcessed(proxyAddressFrom, msg.sender, amount); 149 }
In createProxyDelegatorAndTransfer()
:
155 function createProxyDelegatorAndTransfer( 156 address target, 157 uint256 amount 158 ) internal { 159 address proxyAddress = deployProxyDelegatorIfNeeded(target); 160 token.transferFrom(msg.sender, proxyAddress, amount); + emit DelegationProcessed(msg.sender, proxyAddress, amount); 161 }
ERC20MultiDelegate
contract uses the OZ Address
library for the address type, but has never used any of its features.
These lines can safely be removed from the contract:
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
using Address for address;
#0 - 141345
2023-10-13T13:23:11Z
L-03 is dup of https://github.com/code-423n4/2023-10-ens-findings/issues/62
L-05 is dup of https://github.com/code-423n4/2023-10-ens-findings/issues/61
#1 - c4-pre-sort
2023-10-13T13:25:45Z
141345 marked the issue as sufficient quality report
#2 - c4-judge
2023-10-24T15:47:59Z
hansfriese marked the issue as grade-b
π Selected for report: polarzero
Also found by: 0x3b, 0xSmartContract, 0xnev, ABA, Bulletprime, J4X, Limbooo, MrPotatoMagic, SBSecurity, Sathish9098, asui, d3e4, hyh, ihtishamsudo, inzinko, marchev, pfapostol, radev_sw, rahul, ro1sharkm
10.6913 USDC - $10.69
ENS is a decentralized naming system for wallets websites, and & more. However, contracts that are currently being audited are related to the governance part of the organization. The unique approach allows token holder to delegate his votes to multiple users with the help of proxy contracts.
ENS is built around various standards and modules. But the currently reviewed contract is more related to:
ERC20Votes
: A smart contract standard that includes two main components, ERC20
and Votes
, which provides generic way for the delegator to give his voting tokens to another user.ERC1155
: Multi-Token standard which offers a comprehensive range of essential features, the most important ones for this ecosystem are _mintBatch
, _burnBatch
, which are used to respectively mint tokens for a user who is delegating his voting power to delegate or burn the same tokens which in the context of the protocol is used to reimburse the owner of delegated tokens, and balanceOf
whose function, in this case, is to track the balance of delegate and the tokens he is being delegated of.Ownable
: AccessControl standard which sets the owner of ERC20MultiDelegate
contract and exposes the important onlyOwner
modifier used in the setUri
function.delegateMulti()
function which is used either to transfer votes between delegates, reimburse the delegator, or delegator to give his votes to multiple delegates.const delegates = [deployer, alice, bob, charlie]; const amounts = delegates.map(() => delegatorTokenAmount.div(delegates.length) );
await multiDelegate.delegateMulti([], delegates, amounts);
These 2 code snippets are used in multiple places and extracting them into functions will reduce the complexity of the tests. Also new tests can be added in order to validate the edge cases of the ERC1155
given the fact that some of the functions are used differently than for what they are intended.
Registry
and Resolver
which donβt give any particular value to the tests and therefore increase the complexity and test execution time.The architecture of ERC20MultiDelegate
is well-designed with unique approach which allows utilizing the ERC1155
and proxies by handling delegation to more than one people at the same time. However there are small improvements that could be made:
processDelegation
function, by adding them contract will be more gas efficient and unnecessary ERC20ProxyDelegator
deployments will be avoided.ERC20MultiDelegate
, now they are stuck inside forever.setUri
function which has onlyOwner modifier we can state there is little to none centralisation in the reviewed contracts, even on the contrary, with the whole idea of using a proxy and ERC1155 together which allows users to delegate their votes to multiple people at the same time results increased decentralization of the whole ENS governance.The ERC20MultiDelegate
contract is innovative as it uses ERC1155
tokens to create a unique composite token structure involving delegators and delegate(proxies). This clever approach removes the need for traditional data structures like mappings and arrays to handle user-delegate connections. The idea that a single proxy can store votes from multiple users is intriguing, and the entire system operates on the basis of minted ERC1155 tokens.
However, as previously mentioned, the absence of a check to verify if the transferred amount is 0 could potentially allow users to spam multiple proxies. On the flip side, the mechanisms employed in the contract are indeed fascinating, providing valuable learning experiences for all involved.
ERC20MultiDelegate
has no any particular risks. Sponsor has shown some areas where problems might occur, but after a thorough investigation, we were unable to find anything critical, which indicates that the developers have done their job by avoiding unnecessary complexity and thereby shrinking the attack surfaceTo improve the ERC20MultiDelegate
and ERC20ProxyDelegator
consider adding the changes that we recommended at the #Architecture Recommendations point:
transferFrom
functions to be more than 0ERC20MultiDelegate
and ERC20ProxyDelegator
) are well-designed and their innovative approach by utilizing Proxies and ERC1155 in order to allow votes to be delegated to multiple users at once, without any unnecessary complexity, will be great addition to the whole ENS ecosystem and for the ETH community itself.40 hours
#0 - c4-pre-sort
2023-10-14T08:44:41Z
141345 marked the issue as sufficient quality report
#1 - c4-judge
2023-10-24T16:43:32Z
hansfriese marked the issue as grade-b