The Governance contract implements a two-step proposal execution process through a TimelockController. For proposals requiring ETH transfers, the TimelockController is designed to receive ETH only through its executeBatch function, which is marked as payable. The timelock contract intentionally lacks a receive() function to prevent direct ETH transfers outside the execution flow.
The Governance#_executeProposal function calls TimelockController#executeBatch without transferring the required ETH value. The architectural design requires ETH to be sent along with the executeBatch call, as this is the only way to fund the timelock contract for subsequent proposal execution.
When a proposal includes transactions that require ETH value transfers (non-zero values array), the timelock contract will attempt to forward these values to the target addresses. However, since no ETH was transferred during the executeBatch call, these transactions will fail due to insufficient balance.
High. Any proposal that includes ETH value transfers will fail to execute, effectively breaking a core governance functionality. This could prevent critical protocol upgrades or parameter changes that require ETH transfers.
Medium. While not all proposals require ETH transfers, this is a common requirement in DeFi governance (e.g., funding protocol initiatives, paying contributors). The issue will consistently manifest whenever such proposals are attempted.
User creates a proposal with:
target = recipient address
value = 1 ETH
calldata = empty bytes
Proposal passes voting and is queued in Governance#_queueProposal
After timelock delay, user calls Governance#execute
Governance#_executeProposal calls TimelockController#executeBatch without sending ETH
TimelockController#executeBatch attempts to forward 1 ETH to recipient but fails due to insufficient balance, as no ETH was provided in the call
Modify _executeProposal to calculate and forward the total ETH value required:
Also add a receive() function to accept ETH:
Note: Since the TimelockController is designed to only receive ETH through executeBatch, no additional receive functions are needed. The fix ensures ETH is properly forwarded through the intended execution path.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.