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: 10/54
Findings: 2
Award: $141.52
🌟 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
55.8454 USDC - $55.85
Project Name | ENS |
Repository | https://github.com/code-423n4/2023-10-ens |
Website | Link |
Link | |
Methods | Manual review |
Contract (1) | SLOC | Purpose | Libraries used |
---|---|---|---|
contracts/ERC20MultiDelegate.sol | 216 | ERC20Votes compatible multi-delegation contract to manage user votings | @openzeppelin/* |
ID | Title | Instances | Severity |
---|---|---|---|
L-01 | Inefficient token handling in delegateMulti() when a user wants to both reimburse and delegate MultiDelegate tokens | - | Low to Medium |
L-02 | ERC20MultiDelegate contract breaks ERC1155 specifications | - | Low to Medium |
L-03 | Unbounded for loop in _delegateMulti() . Set max value for the loop iterations (max values for sourcesLength[] and targetsLength[] ) | 1 | Low |
L-04 | Static Salt in deployProxyDelegatorIfNeeded() : Possibility of DoS due to Predictable Contract Address | 1 | Low |
L-05 | Unchecked Initialization in deployProxyDelegatorIfNeeded() : Risk of DoS due to Potential Malfunction in ERC20ProxyDelegator | 1 | Low |
L-06 | Front Running of ERC20ProxyDelegator Deployment | 1 | Low |
L-07 | Gas Concerns - DoS in deployProxyDelegatorIfNeeded() | 1 | Low |
NC-01 | Potential Reversion Issue with Certain ERC20 Token Approvals | 1 | Non Critical |
NC-02 | Unchecked Return Values for approve() | 1 | Non Critical |
NC-03 | Need for Comments on State Variables | 1 | Non Critical |
NC-04 | Absence of Event Emissions | 3 | Non Critical |
NC-05 | Missing address zero checks for sources[] and targets[] arrays in delegateMulti() function | 1 | Non Critical |
S-01 | Optimization for deployProxyDelegatorIfNeeded() function logic | - | Suggestion/Optimization |
S-02 | Optimization for transferring flow | - | Suggestion/Optimization |
Note: I will leave it to the judge and sponsor to determine the actual severity of issues that I have classified as Severity: Low to Medium
.
delegateMulti()
when a user wants to both reimburse
and delegate
MultiDelegate tokenshttps://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol#L57-L116 https://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol#L124-L137 https://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol#L155-L161
delegateMulti()
when a user wants to both reimburse
and delegate
MultiDelegate tokens / Inefficient System Design and Flow.MultiDelegate
tokens delegation in the future.Bob approve ERC20MultiDelegate
contract with 100000 ERC20Votes tokens
and after that delegate 100000 ERC20Votes to Alice
. For this purpose he calls delegateMulti()
function with the following parameters.
sources[]
: []targets[]
: [alice's address]arguments[]
: [100000]After some period of time, Bob decides to reimburse
his 100,000 ERC20Votes tokens to Alice and delegate
another 100,000 ERC20Votes tokens to Charlie in a single transaction
.
delegateMulti(sources: [alice's address], targets: [bob's address, charlie's address], amounts: [100000, 100000])
.await increaseTime(365 * 24 * 60 * 60); await token.mint(bob, mintAmount); await increaseTime(365 * 24 * 60 * 60); await token.mint(alice, mintAmount);
it("proof of concept", async () => { // For our example let's say that: // Bob: delegator const bobSigner = await ethers.provider.getSigner(bob); // -> Step 1: Bob approve ERC20MultiDelegate contract with 100000 ERC20Votes tokens and after that delegate 100000 ERC20Votes to Alice await token.connect(bobSigner).approve(multiDelegate.address, 100000); await multiDelegate.connect(bobSigner).delegateMulti([], [alice], [100000]); let bobToAliceMultiDelegateTokensBalance = await multiDelegate.balanceOf( bob, alice ); console.log( `Step 1. MultiDelegated tokens from Bob to Alice: ${bobToAliceMultiDelegateTokensBalance.toString()}` ); // 100000 expect(await bobToAliceMultiDelegateTokensBalance.toString()).to.equal( "100000" ); // -> Step 2: Bob decides to `reimburse` his 100,000 ERC20Votes tokens to Alice and `delegate` another 100,000 ERC20Votes tokens to Charlie in a single transaction. // For this purpose, Bob calls `delegateMulti(sources: [alice], targets: [bob, charlie], amounts: [100000, 100000])`. // As a result, Bob removes his delegated tokens to Alice and delegates new 100,000 ERC20Votes to Charlie. However... // However Bob doesn't actually receive back his 100,000 ERC20Votes tokens. Instead, these tokens are transferred to the ERC20ProxyDelegate contract. // Consequently, Bob is left with a negative impression of the protocol and has to execute another transaction (spend additional gas) to reclaim his ERC20Votes tokens. await token.connect(bobSigner).approve(multiDelegate.address, 100000); await multiDelegate .connect(bobSigner) .delegateMulti([alice], [bob, charlie], [100000, 100000]); bobToAliceMultiDelegateTokensBalance = await multiDelegate.balanceOf( bob, alice ); let bobToCharlieMultiDelegateTokensBalance = await multiDelegate.balanceOf( bob, charlie ); let bobToBobMultiDelegateTokensBalance = await multiDelegate.balanceOf( bob, bob ); console.log( `Step 2. MultiDelegated tokens from Bob to Alice: ${bobToAliceMultiDelegateTokensBalance.toString()}` ); // 0 console.log( `Step 2. MultiDelegated tokens from Bob to Charlie: ${bobToCharlieMultiDelegateTokensBalance.toString()}` ); // 100000 console.log( `Step 2. MultiDelegated tokens from Bob to Bob: ${bobToBobMultiDelegateTokensBalance.toString()}` ); // 100000 expect(await bobToAliceMultiDelegateTokensBalance.toString()).to.equal("0"); expect(await bobToCharlieMultiDelegateTokensBalance.toString()).to.equal( "100000" ); expect(await bobToBobMultiDelegateTokensBalance.toString()).to.equal( "100000" ); });
ERC20MultiDelegate
contract breaks ERC1155 specificationshttps://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol#L25-L216
ERC20MultiDelegate
contract does not adhere to ERC1155.ERC20MultiDelegate.sol
contract.Description: The ERC20MultiDelegate
contract implements ERC1155 (https://eips.ethereum.org/EIPS/eip-1155). This contract contains delegateMulti()
function that facilitates the process of transferring delegation amounts across multiple source and target delegate pairs. It serves as an interface for the _delegateMulti()
function, which contains the actual logic.
Inside the _delegateMulti()
function MultiDelegate tokens are minted and burned (transferred) via _mintBatch()
and _burnBatch()
functions.
Whenever there is a transfer, the standard requires checking the receiver accepts the transfer:
ERC20MultiDelegate
contract does not adhere to ERC1155.If the recipient implements ERC1155TokenReceiver, require that it accepts the transfer. If the recipient is a contract that does not implement a receiver, reject the operation.
for
loop in _delegateMulti()
: Set maximum limit for loop iterations (max values for sourcesLength
and targetsLength
)The function _delegateMulti()
processes the delegation transfer process for multiple source and target delegates. Currently, there are no explicit bounds set for the lengths of the sources
and targets
arrays. This lack of upper limit poses potential risks in terms of gas costs and unwanted system behaviors.
Denial of Service DoS
Gas Costs: If an excessively large number of delegates is supplied to the function, the unbounded loop can consume a lot of gas. In extreme cases, this might cause the transaction to fail due to reaching the block gas limit.
Denial-of-Service (DoS) Attack Vector: Malicious actors can attempt to exhaust the contract's functions by calling _delegateMulti()
with a large number of delegates, potentially causing regular users' transactions to fail or become more expensive.
Operational Delays: For a contract that is intended to process other transactions in a timely manner, long-loop iterations can cause operational delays.
To mitigate these risks, consider adding an explicit maximum bound for the lengths of the sources[]
and targets[]
arrays. This will ensure that the loop does not iterate an excessive number of times:
uint256 constant MAX_DELEGATES = 100; // Example value; adjust as needed require(sourcesLength <= MAX_DELEGATES, "Delegate: Too many source delegates provided"); require(targetsLength <= MAX_DELEGATES, "Delegate: Too many target delegates provided");
By introducing these checks, you can ensure that the _delegateMulti()
function remains efficient and less susceptible to potential abuse.
deployProxyDelegatorIfNeeded()
: Possibility of DoS due to Predictable Contract AddressThis vulnerability potentially impacts the upgradability of the ERC20ProxyDelegator
contract. In scenarios where there's a need to redeploy an ERC20ProxyDelegator
for a specific token
and delegate
combination due to, for instance, an upgrade or bug fix, the system wouldn't be able to deploy the new contract at the same address. As a result, the predictability and management of the system can be compromised. Furthermore, an attacker aware of this static salt can precompute and self-destruct the proxy contract's address, causing a Denial-of-Service (DoS) condition where the contract can't be redeployed at its expected address.
The deployProxyDelegatorIfNeeded
function is responsible for deploying a new ERC20ProxyDelegator
contract, if not already deployed, for a specific token
and delegate
combination. The deployment uses the CREATE2
opcode with a static salt (uint256(0)
). As a result, the address for a new proxy contract is predictable and always the same for the given token
and delegate
pair.
The ERC20ProxyDelegator
contract is a proxy delegator contract designed to vote on behalf of the original delegator. Upon deployment, it automatically delegates the votes and approves the utility contract to spend its tokens.
ERC20ProxyDelegator
for a specific token
and delegate
.ERC20ProxyDelegator
.ERC20ProxyDelegator
again for the same token
and delegate
. Since the contract's address is predictable and static, the system will try to deploy at the same address, which is already taken, leading to a failure in deployment.CREATE2
opcode. Instead, employ a mechanism to dynamically generate the salt, for example, by using a combination of the current block number, timestamp, or a nonce which gets incremented with every deployment.deployProxyDelegatorIfNeeded()
: Risk of DoS due to Potential Malfunction in ERC20ProxyDelegator
If there's a malfunction during the initialization of the ERC20ProxyDelegator
, or if an out-of-gas error occurs during its deployment, it could lead to the ERC20ProxyDelegator
contract being deployed without proper initialization. This can render the deployed contract useless, potentially halting any subsequent operations or interactions relying on this contract. Such a scenario can result in a Denial-of-Service (DoS) state for the affected operations.
The function deployProxyDelegatorIfNeeded
is tasked with deploying a new ERC20ProxyDelegator
contract when required. However, once the contract is deployed using the new
keyword, the function doesn't verify if the newly deployed contract has been correctly initialized.
The ERC20ProxyDelegator
contract, upon deployment, is expected to approve a specified amount of tokens for spending and delegate votes on behalf of the original delegator. If this initialization fails, the proxy delegator might not function as expected, causing subsequent operations to be disrupted.
ERC20ProxyDelegator
constructor, such as an invalid operation or an out-of-gas consuming loop.ERC20ProxyDelegator
using the deployProxyDelegatorIfNeeded
function.ERC20ProxyDelegator
contract gets deployed, it might not be correctly initialized, making it non-functional or leaving it in an undesirable state.ERC20ProxyDelegator
, verify its state. For instance, check if the expected approvals and delegations have been made. If not, the deployment should be considered a failure, and corrective measures should be taken.ERC20ProxyDelegator
DeploymentAn attacker can front-run the deployment of the ERC20ProxyDelegator
, leading to unexpected behavior for the original transaction sender. The contract, instead of being deployed by the legitimate transaction, gets deployed by the attacker's transaction. This could lead to scenarios where the expected contract logic doesn't apply as intended, potentially causing monetary or functionality disruptions for users.
Front-running is a scenario on public blockchains where a malicious actor can see pending transactions and then decide to place their own transaction with a higher gas price, ensuring it gets mined before the original transaction. In the context of the ERC20ProxyDelegator
deployment:
ERC20ProxyDelegator
contract.ERC20ProxyDelegator
is deployed by the attacker's transaction, not the original transaction. This can cause confusion and unexpected behavior, especially if the attacker's deployment has any deviations or manipulations.ERC20ProxyDelegator
.ERC20ProxyDelegator
contract will be deployed by the attacker's transaction, causing the original transaction to fail or behave unexpectedly.deployProxyDelegatorIfNeeded()
Excessive and unnecessary contract deployments could result in draining funds from the deploying address due to the high gas costs associated with contract creation. An attacker could intentionally cause frequent deployments, leading to increased transaction costs and potentially depleting the funds of the contract or the user that's bearing the deployment costs.
Contract deployment on the Ethereum network consumes a considerable amount of gas, especially when the contract contains complex logic or storage. In the provided code, the deployProxyDelegatorIfNeeded()
function deploys an instance of the ERC20ProxyDelegator
contract. If there are pathways in the system that allow this function to be triggered without necessary conditions, it exposes a vulnerability.
A malicious actor might discover ways to frequently call these pathways, forcing the system to deploy new contract instances repeatedly. Each deployment incurs gas costs, and over time, this can be a vector for a Denial of Service (DoS) attack, as the deploying entity might run out of funds to cover these costs.
deployProxyDelegatorIfNeeded()
can be triggered, preferably without stringent conditions or checks.ERC20ProxyDelegator
instances repeatedly.Several well-known ERC20 tokens, such as UNI
and COMP
, may revert when the approve
functions are called with values exceeding uint96
. This behavior deviates from the standard ERC20 implementation and might lead to unexpected results.
For instance, both UNI
and COMP
tokens feature special-case logic in their approve
functions. When the approval amount is uint256(-1)
, the allowance
is set to type(uint96).max
. Systems that anticipate the approved value to match the value in the allowances
mapping might face compatibility issues.
<i>This issue is present in the following location:</i>
17: _token.approve(msg.sender, type(uint256).max);
Full Contract Context:
contract ERC20ProxyDelegator { constructor(ERC20Votes _token, address _delegate) { _token.approve(msg.sender, type(uint256).max); _token.delegate(_delegate); } }
approve()
It is essential to note that not all IERC20
implementations utilize the revert()
mechanism to indicate failures in the approve()
function. Some instead return a boolean to signal any issues. By not verifying this returned value, certain operations might proceed as if they were successful, despite no actual approvals taking place.
<i>The issue occurs in the following segment:</i>
17: _token.approve(msg.sender, type(uint256).max);
Link: https://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol/#L17-L17
To enhance readability and assist future reviewers, it is advisable to provide detailed comments on crucial state variables. Such documentation clarifies their intended roles and usage.
<i>This recommendation is based on the following code:</i>
28: ERC20Votes public token;
Link: https://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol/#L28-L28
Event emissions play a vital role in tracking and auditing on-chain activities. The following functions currently lack event emissions, which may hinder transparency and monitoring:
Consider introducing appropriate events to facilitate a more transparent operation flow.
delegateMulti()
/ _delegateMulti()
For a function that is dealing with multiple delegations, it would be handy to have events that provide a clear indication of each delegation that took place:
event Delegated(address indexed source, address indexed target, uint256 amount);
This event will be emitted within the _processDelegation
function each time a delegation takes place. If _processDelegation
is a function in the same contract, you would insert emit Delegated(source, target, amount);
within that function.
_reimburse()
This function essentially sends the un-delegated or remaining tokens back to the user. An event for this would look something like:
event Reimbursed(address indexed source, address indexed user, uint256 amount);
You would emit this event within the _reimburse
function right after the transferFrom
:
token.transferFrom(proxyAddressFrom, msg.sender, amount); emit Reimbursed(source, msg.sender, amount);
createProxyDelegatorAndTransfer()
This function seems to be creating a proxy and then transferring a specified amount of tokens to this proxy. The event for this would look like:
event ProxyCreatedAndFunded(address indexed target, address proxyAddress, uint256 amount);
You would emit this event within the createProxyDelegatorAndTransfer
function right after the transferFrom
:
token.transferFrom(msg.sender, proxyAddress, amount); emit ProxyCreatedAndFunded(target, proxyAddress, amount);
sources[]
and targets[]
arrays in delegateMulti()
functionWithin the delegateMulti()
and _delegateMulti()
functions, the sources
and targets
arrays contain Ethereum addresses. These addresses are further used in delegation operations. However, there is currently no validation to prevent the inclusion of the Ethereum zero address (address(0)
) within these arrays. Using the zero address can lead to unexpected behaviors or errors, especially in delegation scenarios.
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]; ... }
In the snippet above, there is logic to set source
or target
to address(0)
if their respective lengths are exceeded. However, this does not prevent the user from manually providing the zero address in the sources
or targets
arrays.
By not ensuring the absence of the Ethereum zero address in the sources[]
and targets[]
arrays, a number of potential risks arise:
Introduce validation checks to ensure that none of the elements in the sources[]
and targets[]
arrays are equal to the Ethereum zero address. This can be done at the start of the _delegateMulti()
function. The check can look something like:
for (uint i = 0; i < sourcesLength; i++) { require(sources[i] != address(0), "Delegate: Source address cannot be the zero address"); } for (uint j = 0; j < targetsLength; j++) { require(targets[j] != address(0), "Delegate: Target address cannot be the zero address"); }
By integrating this check, the contract can safeguard against accidental or malicious use of the Ethereum zero address within the delegation process.
deployProxyDelegatorIfNeeded()
function logicIf the goal is only ever to retrieve the address (and not necessarily deploy every time), separating the retrieval logic and the deployment logic might be more efficient.
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; }
function retrieveProxyContractAddress( ERC20Votes _token, address _delegate ) private view returns (address) { 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))); }
During the delegation transfer process for multiple source and target delegates, it's essential to maintain an efficient process to save on gas costs and optimize the overall transaction process. It's been identified that the current transfer flow can be optimized by introducing two additional checks:
source
and target
delegates are different.Gas Efficiency: Avoiding unnecessary transfers can save on gas costs, as each unnecessary operation within the Ethereum Virtual Machine (EVM) incurs gas costs.
Logical Integrity: Transferring tokens between identical source and target addresses or transferring an amount of zero doesn't align with the logical intent of a transfer operation.
Preventing Unexpected Behaviors: Ensuring logical checks can prevent potential unwanted system behaviors, edge cases, or vulnerabilities.
Incorporate the following checks before calling the functions that execute the actual transferring:
The modified section within the _delegateMulti()
function can look like:
// ... (rest of the code) // Iterate until all source and target delegates have been processed. 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 (source != target && amount > 0) { // 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. if (source != address(0) && amount > 0) { _reimburse(source, amount); } } else if (transferIndex < targetsLength) { // Handle any remaining target amounts after the transfer process. if (target != address(0) && amount > 0) { createProxyDelegatorAndTransfer(target, amount); } } } // ... (rest of the code)
By implementing these checks, the contract becomes more optimized, reduces unnecessary operations, and ensures a logical flow of token transfers.
#0 - c4-pre-sort
2023-10-13T11:32:55Z
141345 marked the issue as sufficient quality report
#1 - 141345
2023-10-13T11:34:07Z
L-02 is dup of https://github.com/code-423n4/2023-10-ens-findings/issues/473
L-03 is dup of https://github.com/code-423n4/2023-10-ens-findings/issues/19
L-06 is dup of https://github.com/code-423n4/2023-10-ens-findings/issues/684
NC-01 is dup of https://github.com/code-423n4/2023-10-ens-findings/issues/239
NC-02 is dup of https://github.com/code-423n4/2023-10-ens-findings/issues/30
#2 - c4-judge
2023-10-24T15:54:26Z
hansfriese marked the issue as grade-a
🌟 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
85.6695 USDC - $85.67
# | Topic |
---|---|
1 | ENS Contest Documentation Analysis |
2 | Codebase Explanation, Examples Executions & Scenarios |
3 | Questions & Notes during the Audit |
4 | Potential Attack Vectors Discussed during the Audit |
5 | My opinion for the contract (Code Complexity, Systemic Risks, etc,) |
6 | Time Spent |
Ethereum Name Service (ENS) Summary
ENS is a decentralized naming service built on top of Ethereum, and designed to resolve a wide array of resources including blockchain addresses, decentralised content, and user profile information.
Purpose: ENS provides a decentralized naming system on the Ethereum blockchain. It associates human-readable names (e.g., 'alice.eth') with machine identifiers, such as Ethereum addresses, other crypto addresses, content hashes, and more. It can be likened to the Internet's DNS but is uniquely structured to work within Ethereum's capabilities.
Functionality:
Availability: Deployed on the Ethereum main network and various test networks. Compatible libraries and apps can recognize and work with the appropriate ENS deployment based on the network they are interacting with.
Components:
ENS Registry:
Resolvers:
User Interaction: Users can interact with ENS via the ENS Manager App or through various ENS-enabled applications.
In the above example, we're trying to find the Ethereum address pointed to by 'foo.eth'. First, we ask the registry which resolver is responsible for 'foo.eth'. Then, we query that resolver for the address of 'foo.eth'.
Delegation Mechanics:
Underlying Infrastructure:
Cryptographic and Standard Adherence:
delegateMulti()
:The delegateMulti()
function facilitates the process of transferring delegation amounts across multiple source and target delegate pairs. It serves as an interface for the _delegateMulti()
function, which contains the actual logic.
Check Input Validity:
Process Delegations:
Token Adjustments:
msg.sender
(the caller) corresponding to sources.msg.sender
for the targets.Let's take a hypothetical scenario to better understand its behavior:
Suppose there are 3 source delegates, 2 target delegates, and 3 amounts to transfer:
sources = [address1, address2, address3]; targets = [addressA, addressB]; amounts = [100, 50, 75];
Execution:
It would validate that there's at least one source or target and that the number of amounts (3 in this case) matches the larger list (sources, which has 3 addresses).
It will iterate 3 times (the length of the longest list).
i. For the first iteration:
ii. For the second iteration:
iii. For the third iteration:
The msg.sender
will have 225 (100 + 50 + 75) units burned because of the source addresses and 150 (100 + 50) units minted because of the target addresses.
sources = [address1, address1, address3]; targets = [addressA, addressA]; amounts = [100, 50, 75];
Here's a step-by-step breakdown:
sourcesLength = 3
, targetsLength = 2
, amountsLength = 3
.
The function checks that the amounts
array has the same length as the greater of sources
and targets
, which it does in this case. So, no require
statement is triggered.
The function will now iterate 3 times since Math.max(sourcesLength, targetsLength) = 3
.
In the first iteration:
source
is address1
target
is addressA
amount
is 100
_processDelegation()
function is called, and 100 tokens are transferred from address1
to addressA
.In the second iteration:
source
is address1
target
is addressA
amount
is 50
_processDelegation()
function is called again, and 50 more tokens are transferred from address1
to addressA
.In the third iteration:
source
is address3
target
is address(0)
(i.e., the zero address) because the targets
array has only 2 elements.amount
is 75
targets
array is exhausted but there are still sources left, the _reimburse()
function is called. This function essentially reimburses address3
with 75 tokens.sources = [address1, address1, address3]; targets = [addressA, addressB]; amounts = [100, 50, 75];
This case will proceed similarly to Second Example with one key difference:
source
is address1
target
is addressB
amount
is 50
_processDelegation()
function is called, transferring 50 tokens from address1
to addressB
.The third iteration remains the same as in Second Example, where address3
is reimbursed with 75 tokens.
retrieveProxyContractAddress()
function:retrieveProxyContractAddress()
function appears to compute the address of a contract before it is actually deployed on the Ethereum blockchain. This is commonly known as the "create2 address"._token
: An instance or the address of an ERC20Votes
token._delegate
: The address of a delegate.bytecode
for a new contract (ERC20ProxyDelegator
). The bytecode consists of the contract's creationCode
concatenated with the ABI-encoded parameters _token
and _delegate
.hash
using the keccak256 hashing function. The input to this hash function includes:
bytes1(0xff)
: A constant prefix.address(this)
: The address of the current contract (the contract from which retrieveProxyContractAddress
is called).uint256(0)
: A salt value, which is set to 0 here. This salt allows the generation of different addresses based on different inputs even when using the same bytecode. However, in this case, the salt is static and always 0.keccak256(bytecode)
: A hash of the previously mentioned bytecode.ERC20ProxyDelegator
contract will reside if deployed using the provided _token
and _delegate
parameters with the specified creation code and the given salt.Purpose: This function is used to determine the address of a proxy contract that represents a delegate's voting power in an ERC20Votes system before it's actually deployed.
deployProxyDelegatorIfNeeded()
function executionGet Predicted Proxy Address:
address proxyAddress = retrieveProxyContractAddress(token, delegate);
This line calls the retrieveProxyContractAddress()
function to get the expected address of the ERC20ProxyDelegator
contract for the given token
and delegate
combination. At this point, the contract might not have been deployed yet, but we can predict its address because of how Ethereum's CREATE2
opcode works.
Check If The Proxy Contract Is Already Deployed:
uint bytecodeSize; assembly { bytecodeSize := extcodesize(proxyAddress) }
Here, the code uses inline assembly to check the size of the bytecode at the predicted address. The extcodesize
opcode in the Ethereum Virtual Machine (EVM) returns the size of the bytecode of the contract deployed at the given address. If there's no contract at that address, it returns 0
.
Deploy the Proxy Contract If Not Already Deployed:
if (bytecodeSize == 0) { new ERC20ProxyDelegator{salt: 0}(token, delegate); emit ProxyDeployed(delegate, proxyAddress); }
If bytecodeSize
is 0
, it means the ERC20ProxyDelegator
contract hasn't been deployed at the predicted address. In that case, the code deploys the contract using the new
keyword with the given token
and delegate
as arguments. The {salt: 0}
syntax specifies a salt of 0
for the deployment, which matches what was used in the retrieveProxyContractAddress
function. Once the contract is deployed, an event ProxyDeployed
is emitted, notifying listeners of the new contract's address.
Return the Proxy Address:
return proxyAddress;
In summary, this function ensures that a unique ERC20ProxyDelegator
contract exists for each token
and delegate
combination. If it doesn't exist, it deploys one, and then returns the address of the proxy contract.
delegateMulti()
function, you can provide multiple source and target addresses, meaning you can perform multiple delegation transfers in a single transaction.delegateMulti()
function allows for multiple amounts to be specified, one for each delegate pair.delegateMultifunction
to delegate his token, this will trigger a proxy deployment (if needed) for each delegate the token holder has decided to delegate to. The token holder will send his token to the proxy (or from a proxy to another, but this is still tokens actually owned by the token holder), and ERC20MultiDelegate inherits from ERC1155 to keep track of delegated tokens actually owned by the token holder, in all different proxy delegate contracts. Here, ID of erc1155 _balances
mapping is the delegate address (and not a token). _burnBatch
and _mintBatch
are here used to update ERC1155 mappings and check if the caller of (delegateMulti) is legitimate._processDelegation
will be triggered:sources
and targets
arrays have the same lengthsources
array is shorter than the targets
arraytargets
array is shorter than the sources
array_reimburse
will be trigger:sources
array is longer than the targets
array.createProxyDelegatorAndTransfer
will be trigger:targets
array is longer than the sources
array.delegateMulti()
execution.delegateMulti()
execution.delegateMulti()
execution.delegateMulti()
execution.delegateMulti()
execution.ERC20Votes contract
(the fact that you must delegate your entire voting power to only one address. The ERC20MultiDelegate contract
overcomes this using ERC1155 and Proxy contracts.Day 1:
Day 2:
Day 3:
Day 4:
Day 5:
46 hours
#0 - c4-pre-sort
2023-10-14T08:44:02Z
141345 marked the issue as sufficient quality report
#1 - c4-judge
2023-10-24T16:53:47Z
hansfriese marked the issue as grade-a