ChainScore Labs
LABS
Guides

Automated Proposal Execution in DeFi DAOs

Chainscore © 2025
core-concepts

Core Concepts of Proposal Automation

Foundational components enabling secure, trust-minimized execution of DAO governance decisions.

01

Execution Triggers

On-chain events that initiate automated proposal execution. These are predefined conditions, such as a specific block timestamp being reached, a governance vote passing a quorum threshold, or an external price oracle updating. This removes the need for manual intervention, ensuring timely and reliable execution of passed proposals.

02

Conditional Logic

If-then logic encoded into the execution pathway. For example, a proposal might execute a token swap only if the DAI/USDC exchange rate is above 0.995 on a specific DEX. This allows for complex, multi-step proposals that adapt to real-time on-chain state, increasing sophistication and capital efficiency.

03

Multisig & Safe Modules

Smart contract modules that attach to Gnosis Safe or other multisigs to enable automated execution. The module holds execution logic and permissions, allowing a Safe controlled by DAO signers to perform actions autonomously once conditions are met. This separates the treasury's security model from the automation logic.

04

Execution Strategies

Pre-defined transaction bundles that are executed atomically. A strategy could involve claiming rewards from a liquidity pool, swapping a portion for a stablecoin, and depositing the remainder into a yield vault in a single transaction. This minimizes slippage, reduces gas costs, and eliminates execution risk between steps.

05

Time-locks & Delays

Mandatory waiting periods between a vote passing and execution. This is a critical security mechanism that allows token holders to exit or react if they disagree with a passed proposal's execution. It provides a final buffer against malicious proposals or rapid, undesirable state changes.

06

Fallback & Revocation

Safety mechanisms to cancel or override automated execution. This includes emergency multisig revocation of a queued proposal or circuit-breaker functions that halt all automation if anomalous conditions are detected. These are essential for managing risk and responding to unforeseen vulnerabilities or market events.

Standard Automated Execution Workflow

Process overview for executing on-chain proposals via an automated executor contract.

1

Proposal Creation and Queuing

Initiate the governance process by creating and queuing a proposal for execution.

Detailed Instructions

Proposals are created through the DAO's governance portal (e.g., Tally, Snapshot for signaling) and must be queued for execution on-chain. This involves submitting the encoded transaction data to the Timelock Controller contract, which enforces a mandatory delay. The proposer must have the PROPOSER_ROLE and pay any required gas for the queue transaction.

  • Sub-step 1: Encode the target contract call data (e.g., abi.encodeWithSignature("upgradeTo(address)", newImplementation)).
  • Sub-step 2: Call queue on the Timelock, passing the target address, value, calldata, and a unique salt.
  • Sub-step 3: Verify the transaction is successfully queued by checking the event log for Queue(bytes32 indexed txId, address indexed target, uint256 value, string signature, bytes data, uint256 eta).
solidity
// Example: Queuing a proposal via Ethers.js const eta = (await timelock.getTimestamp(txId)).toNumber(); await timelock.queue(target, value, signature, calldata, eta);

Tip: The salt is typically a bytes32 hash of the proposal details to guarantee idempotency and prevent replay attacks.

2

Monitoring the Timelock Delay

Wait for the mandatory delay period to elapse before the proposal becomes executable.

Detailed Instructions

After queuing, the proposal enters a mandatory delay period (e.g., 48-72 hours). This is a critical security feature allowing tokenholders to review the action and potentially cancel it if malicious. The delay is enforced by the Timelock's internal scheduling. The minimum delay is a configurable parameter set by the DAO and can be updated via governance.

  • Sub-step 1: Record the eta (Estimated Time of Arrival) returned in the Queue event, which is block.timestamp + delay.
  • Sub-step 2: Monitor block timestamps. The transaction cannot be executed before the eta.
  • Sub-step 3: Use a keeper or off-chain script to poll getTimestamp(bytes32 txId) on the Timelock contract to check if block.timestamp >= eta.
solidity
// Solidity check within an executor contract function canExecute(bytes32 txId) public view returns (bool) { uint256 eta = timelock.getTimestamp(txId); return eta != 0 && block.timestamp >= eta && !timelock.isOperationDone(txId); }

Tip: For mainnet, consider using a service like Chainlink Keepers or Gelato to automate the execution check, avoiding reliance on manual monitoring.

3

Automated Execution Trigger

The executor contract automatically calls the Timelock to execute the queued transaction.

Detailed Instructions

Once the delay has passed, an automated executor contract (with the EXECUTOR_ROLE or PROPOSER_ROLE) triggers the execution. This contract contains the logic to verify conditions and call execute on the Timelock. It is typically funded with gas and may implement circuit breakers or conditional checks (e.g., token price thresholds) before proceeding.

  • Sub-step 1: The executor's checkUpkeep function validates that block.timestamp >= eta and the operation is not already done.
  • Sub-step 2: If checks pass, the performUpkeep function calls timelock.execute(target, value, signature, calldata, eta).
  • Sub-step 3: The executor must handle potential reverts, such as a failed underlying call or a canceled proposal, and may emit its own events for off-chain tracking.
solidity
// Simplified executor performUpkeep function function performUpkeep(bytes32 txId, address target, uint256 value, bytes memory data, uint256 eta) external { require(block.timestamp >= eta, "Delay not met"); require(!timelock.isOperationDone(txId), "Already executed"); // Optional: Add custom pre-execution logic here timelock.execute(target, value, "", data, eta); // Empty signature for raw call }

Tip: Ensure the executor contract has a sufficient gas limit allowance for the execution, as complex governance transactions can be expensive.

4

Post-Execution Verification and State Update

Confirm successful execution and update any off-chain tracking systems.

Detailed Instructions

After the execute transaction is mined, verify its success and update relevant systems. The Timelock will emit an Execute event and mark the operation as done. This step ensures finality and triggers any downstream processes, such as updating a proposal's status in a front-end dashboard or notifying stakeholders.

  • Sub-step 1: Listen for the Execute(bytes32 indexed txId, address indexed target, uint256 value, bytes data) event from the Timelock.
  • Sub-step 2: Verify the transaction receipt status is true (success) and confirm the target contract's state change (e.g., by calling a getter function).
  • Sub-step 3: Update the proposal status in any off-chain database or UI from "Queued" to "Executed". The executor contract may also increment an internal nonce or emit a completion event.
javascript
// Example: Verifying execution with Ethers.js const receipt = await tx.wait(); const executedEvent = receipt.events.find(e => e.event === 'Execute'); if (executedEvent && receipt.status === 1) { console.log(`Proposal ${executedEvent.args.txId} executed successfully.`); // Update API/database status here }

Tip: Implement idempotent status updates to handle cases where the verification step might be run multiple times, ensuring data consistency.

Comparison of Automated Executor Models

A technical comparison of different approaches for automating on-chain proposal execution in DAOs.

FeatureGelato NetworkOpenZeppelin DefenderCustom Keeper Script

Execution Trigger

Off-chain relayers via webhook or time-based

Scheduled tasks or on-chain event listeners

Self-hosted cron job or event listener

Gas Fee Payment

Relayer pays, reimbursed via fee model (e.g., 1Balance)

Task sponsor's wallet pays directly

Operator's wallet or designated safe pays

Execution Speed

~15-30 seconds after trigger

~1-2 minutes for scheduled tasks

Depends on script polling interval

Cost Model

Subscription + gas reimbursement + premium

Pay-as-you-go gas costs + Defender plan fee

Infrastructure cost + gas costs

Failure Handling

Automatic retries, on-chain error logging

Manual retry, detailed logs & alerts in UI

Manual intervention required, custom alerting needed

Max Gas Limit

Configurable, typically up to block gas limit

Configurable per task, up to block gas limit

Defined in script/tx parameters

Multi-chain Support

EVM chains + Polygon, Arbitrum, Optimism, etc.

EVM chains + selected L2s via relayer network

Any chain the operator's node connects to

Smart Contract Integration

Requires dedicated GelatoExecutable contract

Uses OpenZeppelin's TimelockController or custom

Any contract with the required function signatures

Implementation Patterns and Use Cases

Understanding Automated Execution

Automated proposal execution allows a DAO's approved decisions to be carried out automatically by a smart contract, without requiring a trusted human to manually trigger the transaction. This reduces delays and centralization risk.

Key Components

  • Proposal Contract: The smart contract that holds the logic and parameters for the action to be executed, like a token transfer or parameter update.
  • Executor Module: A permissioned contract (like a Safe's Zodiac module or a Governor's Timelock) that is authorized to call the target contract after a proposal passes.
  • Trigger Condition: The specific event (e.g., a successful on-chain vote, a specific timestamp) that allows the executor to run the encoded transaction.

Common Use Case

When a Uniswap DAO governance proposal to adjust a fee parameter passes, the transaction data is queued in a Timelock. After the waiting period, anyone can call the execute function to apply the change, automating the final step.

security-considerations

Critical Security Considerations

Automated proposal execution introduces unique attack vectors and trust assumptions. This section details the core security models and risks that DAO contributors must evaluate.

01

Smart Contract Risk

The executor contract is the most critical component, holding the power to move treasury funds. Its code must be formally verified and extensively audited for reentrancy, access control flaws, and logic errors. A single bug can lead to irreversible fund loss, making immutable, battle-tested contracts essential for high-value operations.

02

Oracle Reliability

Automation often depends on price oracles and keeper networks to trigger execution. Manipulated oracle data (e.g., a flash loan attack on a DEX price) can cause incorrect proposal execution. Reliance on centralized keeper services also introduces a liveness risk and potential censorship vector for time-sensitive proposals.

03

Governance Attack Surface

Automation expands the governance attack surface. Attackers may exploit proposal creation flaws, spam the queue, or manipulate vote outcomes to pass malicious payloads. The time-lock between vote conclusion and execution is a critical defense, allowing community review of the final calldata before funds are moved.

04

Parameterization & Scope

Incorrect parameterization of execution conditions is a common failure mode. Setting overly broad calldata, incorrect reward amounts, or faulty trigger logic can drain funds legally. Proposals must strictly define and limit the executor's scope of action to the minimum necessary for the intended operation.

05

Multi-sig & Access Control

The access control layer managing the executor, often a multi-sig, must balance security with liveness. A configuration with too few signers is insecure, while too many can cause operational delays. Key management for signers and secure, off-chain signing ceremonies are vital to prevent private key compromise.

06

Contingency & Revocation

A robust system requires emergency revocation mechanisms. This includes the ability for a designated security council or a new governance vote to halt the executor, cancel queued transactions, or recover funds if a vulnerability is discovered. Without these safeguards, a live exploit cannot be stopped.

Smart Contract Audit Checklist

A systematic process for reviewing automated proposal execution contracts.

1

Review Access Control and Permissions

Verify the authorization model for proposal execution.

Detailed Instructions

Begin by mapping all privileged functions that can trigger execution, such as executeProposal(bytes32 proposalId). Identify the roles and modifiers (e.g., onlyGovernance, onlyExecutor) that guard them. Check for any centralization risks like a single EOA with a DEFAULT_ADMIN_ROLE that can upgrade contracts or change parameters unilaterally.

  • Sub-step 1: Trace the inheritance chain for access control libraries like OpenZeppelin's AccessControl or Ownable.
  • Sub-step 2: Confirm that critical state-changing functions, such as setting execution delays or fee parameters, are also properly permissioned.
  • Sub-step 3: Verify that role-administration functions (e.g., grantRole) are themselves protected and not callable by the role being granted.
solidity
// Example: Check for a timelock or multisig requirement. function executeProposal(bytes32 proposalId) external onlyExecutor afterDelay(proposalId) { // Execution logic }

Tip: Use static analysis tools like Slither to automatically detect missing or incorrect modifiers.

2

Analyze Proposal State Machine and Finality

Ensure proposals move through defined, secure states.

Detailed Instructions

Examine the state transitions for a proposal (e.g., Pending, Approved, Queued, Executed, Cancelled). The core risk is re-entrancy or replay attacks where a proposal can be executed more than once. Verify that the contract enforces finality, typically by setting a state to Executed before performing external calls.

  • Sub-step 1: Audit the condition checks in the execution function. It should require a specific state like Queued and check that block.timestamp >= executionEta.
  • Sub-step 2: Ensure the state is updated to Executed before any external calls (Checks-Effects-Interactions pattern).
  • Sub-step 3: Check for any paths where a proposal could be cancelled or expired after execution has begun but before it completes.
solidity
// Example: Correct state transition and finality. require(proposals[proposalId].state == ProposalState.Queued, "!queued"); require(block.timestamp >= proposals[proposalId].eta, "!ready"); proposals[proposalId].state = ProposalState.Executed; // Effects first (bool success, ) = target.call{value: value}(data); // Interaction after require(success, "call failed");

Tip: Look for storage variable collisions where one proposal's state could inadvertently affect another's.

3

Validate Calldata and Target Address Handling

Inspect how execution payloads are processed and validated.

Detailed Instructions

Automated executors often handle arbitrary target addresses and calldata. The primary threat is malicious proposal payloads that could self-destruct the executor or drain funds. Scrutinize any validation or restrictions on target addresses and the functions they can call.

  • Sub-step 1: Check for a whitelist or blacklist of target addresses. If present, verify the admin functions for managing it are secure.
  • Sub-step 2: Analyze if the contract decodes and validates calldata. For example, does it prevent calls to selfdestruct or sensitive functions on critical system contracts?
  • Sub-step 3: Verify the handling of value (ETH) transfers. Ensure there are limits and that the contract's balance is sufficient to avoid failed executions.
solidity
// Example: A simple validation mechanism. function _isValidTarget(address _target) internal view returns (bool) { return _target != address(this) && _target != address(0); } // In execute function: require(_isValidTarget(target), "Invalid target");

Tip: Consider scenarios where a target is a malicious contract that re-enters the executor to manipulate proposal states.

4

Audit Failure Modes and Contingencies

Review how the system handles execution failures and edge cases.

Detailed Instructions

An execution can fail due to revert, insufficient gas, or changed conditions. The system must have clear failure handling to avoid locked states or lost funds. Examine the logic for gas limits and error propagation.

  • Sub-step 1: Check if the execute function uses a fixed gas limit (e.g., gasleft() - 5000) or forwards all gas. A low limit can cause executions to fail unpredictably.
  • Sub-step 2: Determine what happens on a failed low-level call. Does it revert the entire transaction, or does it emit an event and mark the proposal as failed?
  • Sub-step 3: Look for emergency pause or cancel functions that governance can use to halt a malicious or stuck proposal. Ensure these have appropriate time locks or delays themselves.
solidity
// Example: Handling a call failure without reverting the top-level transaction. (bool success, bytes memory returnData) = target.call{gas: gasLimit, value: value}(data); if (!success) { emit ExecutionFailed(proposalId, returnData); // State remains Queued for retry? Or moves to Failed? } else { proposals[proposalId].state = ProposalState.Executed; }

Tip: Test the contract's behavior with a target that uses all gas or reverts with a long error string.

5

Verify Integration with External Modules

Check dependencies on oracles, registries, and other contracts.

Detailed Instructions

Execution logic may depend on external data or contracts, such as a price oracle for conditional proposals or a governance token registry. These introduce dependency risks and potential oracle manipulation.

  • Sub-step 1: Map all external contract calls made during proposal validation or execution. Identify trusted actors (e.g., Chainlink Oracles) and assess their security assumptions.
  • Sub-step 2: For conditional executions (e.g., "execute if ETH > $3000"), audit the oracle integration for freshness, minimum answer precision, and circuit breaker mechanisms.
  • Sub-step 3: Review upgradeability patterns for referenced contracts. If the executor uses a registry.getGovernanceToken(), ensure the registry cannot be upgraded to a malicious address without governance oversight.
solidity
// Example: Conditional execution with an oracle. IAggregatorV3Interface oracle = IAggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419); (, int256 price, , , ) = oracle.latestRoundData(); require(price >= 3000 * 10**oracle.decimals(), "Condition not met"); // Proceed with execution

Tip: Use Echidna or Foundry fuzzing to test execution under various oracle-reported values.

Frequently Asked Technical Questions

The core architecture is a trust-minimized off-chain service that monitors DAO governance contracts for passed proposals. It uses a keeper network or a relayer to submit the encoded transaction data to the target contract. The executor's logic is defined in a smart contract that validates proposal state and execution conditions before broadcasting. For example, a typical flow involves listening for a ProposalExecuted event on a Snapshot space's execution contract, then calling execute() on the Aave governance contract with the specific proposalId and calldata.