Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: low
Valid

Insufficient ETH Forwarding in Governance Execution Mechanism Causes Proposal Failures

Insufficient ETH Forwarding in Governance Execution Mechanism Causes Proposal Failures

Relevant Context

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.

Finding Description

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.

Impact Explanation

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.

Likelihood Explanation

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.

Proof of Concept

  1. User creates a proposal with:

    • target = recipient address

    • value = 1 ETH

    • calldata = empty bytes

  2. Proposal passes voting and is queued in Governance#_queueProposal

  3. After timelock delay, user calls Governance#execute

  4. Governance#_executeProposal calls TimelockController#executeBatch without sending ETH

  5. TimelockController#executeBatch attempts to forward 1 ETH to recipient but fails due to insufficient balance, as no ETH was provided in the call

Recommendation

Modify _executeProposal to calculate and forward the total ETH value required:

function _executeProposal(uint256 proposalId) internal {
ProposalCore storage proposal = _proposals[proposalId];
// Calculate total ETH value needed
uint256 totalValue = 0;
for(uint256 i = 0; i < proposal.values.length; i++) {
totalValue += proposal.values[i];
}
// Execute with required ETH value
_timelock.executeBatch{value: totalValue}(
proposal.targets,
proposal.values,
proposal.calldatas,
bytes32(0),
proposal.descriptionHash
);
proposal.executed = true;
emit ProposalExecuted(proposalId, msg.sender, block.timestamp);
}

Also add a receive() function to accept ETH:

receive() external payable {}

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.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Governance.execute lacks payable modifier and ETH forwarding mechanism, preventing proposals with ETH transfers from being executed through TimelockController

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Governance.execute lacks payable modifier and ETH forwarding mechanism, preventing proposals with ETH transfers from being executed through TimelockController

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!