Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
LABS
Guides

How to Design a Proposal Dependency Check System

Build a system to automatically identify smart contract dependencies, verify permissions and balances, and simulate execution impacts for governance proposals.
Chainscore © 2026
introduction
INTRODUCTION

How to Design a Proposal Dependency Check System

A proposal dependency check system ensures governance proposals are executed in a safe, logical order, preventing conflicts and protocol failures. This guide explains the core concepts and design patterns.

In on-chain governance, proposals often modify interdependent protocol parameters or smart contract states. A dependency check system validates that a new proposal does not conflict with pending or active changes. For example, a proposal to increase a vault's debt ceiling should fail if a separate, unexecuted proposal plans to deprecate that vault. Without such checks, proposals can execute in a harmful order, leading to logical race conditions, fund lockups, or system instability.

The core mechanism involves defining and verifying proposal dependencies. Each proposal is annotated with metadata specifying its preconditions and conflicts. A common pattern uses a registry contract that maintains a graph of proposal relationships. Before a proposal is queued for execution, the system traverses this graph to check for circular dependencies and direct conflicts with proposals in a PENDING, ACTIVE, or QUEUED state. This is analogous to package managers like npm or apt resolving version conflicts.

Implementing checks requires a structured data model. A proposal object should include fields like dependsOn (array of proposal IDs that must execute first) and conflictsWith (array of IDs it cannot coexist with). The verification function, often called validateProposalDependencies, must perform both a forward check (are my dependencies satisfied?) and a reverse check (does any existing proposal conflict with me?). This ensures integrity from both perspectives.

Consider a real-world example from Compound Governance. Proposal 62, which updated cToken collateral factors, needed to execute after Proposal 61, which upgraded the oracle. A dependency system would have enforced this. In your design, you might implement a checkDependencies function that reverts if a proposal's blockNumber for execution is set before its dependencies' estimated completion, using a time-lock pattern for additional safety.

Beyond basic validation, advanced systems incorporate state simulation. Before final submission, a proposal's effects can be simulated against the current chain state plus the effects of all pending proposals. Frameworks like Tenderly or Foundry's forge script can be integrated into the governance front-end or backend to provide a pre-check, warning users of potential storage slot collisions or reverts. This adds a layer of defense-in-depth.

Finally, the user experience must be considered. The system should provide clear, actionable feedback. Instead of a generic "dependency failed" error, it should return: "Proposal #105 conflicts with pending proposal #102 on storage slot 0x123... for the Vault.sol contract." Integrating these checks creates a robust governance framework that protects the protocol and empowers informed community decision-making.

prerequisites
PREREQUISITES

How to Design a Proposal Dependency Check System

A proposal dependency check system ensures governance proposals cannot be executed until their required conditions are met. This guide covers the core design patterns for building one.

A proposal dependency check system is a smart contract mechanism that enforces execution prerequisites for on-chain governance proposals. It acts as a gatekeeper, preventing a proposal's execution logic from running unless all specified dependencies are satisfied. This is critical for managing complex, multi-step governance processes, such as deploying a new vault after a token vote or upgrading a protocol only after a timelock expires. The core contract typically implements a function like checkDependencies(address proposalId) that returns a boolean, which the main governance executor calls before proceeding.

The primary design patterns are state-based and action-based dependencies. A state-based dependency checks for a specific on-chain condition, like a token's price being above a certain threshold on a decentralized oracle (e.g., Chainlink) or a contract holding a minimum balance of ETH. An action-based dependency requires a prior transaction to have occurred, such as the successful execution of another governance proposal. This creates proposal execution graphs, where Proposal B cannot execute until Proposal A has succeeded, enabling safe, sequential upgrades.

To implement a state check, your dependency contract must query external data. For price feeds, integrate with an oracle like Chainlink's AggregatorV3Interface. For contract state, use simple view functions. The check should be gas-efficient and revert-proof; it must not fail due to external calls reverting but instead return false. Use low-level staticcall for safety and implement circuit breakers for oracle failures. Always assume external dependencies may be unavailable.

Action-based dependencies require tracking proposal execution status. This is often done by having the dependency contract be notified by the governance executor or by querying the executor's state. For example, after Proposal A executes, the governance module could call dependencyContract.recordCompletion(proposalAId). Proposal B's dependency check would then verify this recorded state. Ensure this record-keeping is permissioned to prevent spoofing, typically allowing only the trusted governance executor address to update completion status.

Security is paramount. The dependency check must be immutable once a proposal is live, or attackers could change the rules mid-process. Consider storing dependency parameters (like a price threshold or required proposal ID) within the proposal payload itself upon submission. Use event emission for transparency, logging both dependency checks and their results. Thoroughly test with forked mainnet simulations using tools like Foundry or Hardhat to ensure checks behave correctly under real-world conditions, including chain reorganizations and oracle downtime.

Integrate your system with popular governance frameworks. For OpenZeppelin Governor, override the _execute function to include your dependency check. For Compound's Governor Bravo style, add the check in the execute function before the main execution logic. Provide clear error messages using require( checkDependencies(proposalId), "DependencyCheck: Prerequisites not met" );. Document the dependencies for each proposal in the governance forum, creating a clear audit trail from discussion to on-chain enforcement.

key-concepts-text
SYSTEM DESIGN

Key Concepts for Dependency Checking

A proposal dependency check system ensures governance actions are executed in a safe, logical order. This guide covers the core concepts for designing such a system in a DAO or on-chain protocol.

A dependency check system validates that a governance proposal can only be executed if its logical prerequisites have been met. This prevents invalid state transitions, such as upgrading a contract before its new logic is deployed or spending from a treasury that hasn't been funded. In smart contract governance, these checks are enforced on-chain, making execution atomic and trustless. The core mechanism involves each proposal declaring its dependencies—typically the proposalId of a prior proposal—and the executor contract verifying their status before allowing execution.

The primary design pattern is the dependency graph, where proposals are nodes and dependencies are directed edges. Systems must prevent cycles, as a circular dependency would deadlock execution. A common implementation uses a mapping, such as mapping(uint256 => uint256[]) public dependencies, to store an array of prerequisite proposal IDs for each new proposal. During execution, the system performs a recursive or iterative check to confirm all dependencies are in an executed state. Platforms like OpenZeppelin Governor provide modular hooks where this logic can be integrated.

Consider a DAO managing a protocol upgrade: Proposal A deploys a new V2Logic contract, Proposal B points a ProxyAdmin to the new implementation, and Proposal C calls an initialization function on the upgraded contract. Here, B depends on A, and C depends on B. The dependency system ensures C cannot execute unless B has succeeded, which itself requires A's execution. This enforces a safe sequential workflow. Without it, a malicious or erroneous proposal could try to interact with a non-existent or incorrect contract state.

Key implementation details include defining a clear state model for proposals (e.g., pending, succeeded, executed, canceled). Only proposals in an executed state should satisfy a dependency. The check function must also handle failed or canceled proposals gracefully; dependencies that are canceled should likely fail the check, requiring the dependent proposal to be updated or canceled. Gas optimization is critical, as deep dependency chains could hit block gas limits. Using a non-recursive, iterative check and storing dependency status in a bitmap can reduce costs.

Beyond linear chains, systems can support complex topologies like fan-in (multiple proposals depending on one) or fan-out (one proposal with many dependencies). For fan-out, all dependencies must be met. It's advisable to implement a view function, isExecutable(uint256 proposalId), that allows users to simulate the check before attempting a costly on-chain execution. This improves UX and reduces failed transactions. Auditing the dependency logic is paramount, as flaws could allow proposals to bypass critical safeguards or permanently lock the governance system.

In practice, integrating dependency checks requires modifying your governance contract's execute function. A typical snippet adds a pre-execution hook:

solidity
function _beforeExecute(uint256 proposalId) internal override {
    super._beforeExecute(proposalId);
    uint256[] storage deps = dependencies[proposalId];
    for (uint256 i = 0; i < deps.length; i++) {
        require(state(deps[i]) == ProposalState.Executed, "Dep not executed");
    }
}

This ensures the rule is enforced autonomously, making the governance process more robust and reliable.

system-components
PROPOSAL DEPENDENCY CHECKS

Core System Components

A robust dependency check system prevents invalid proposals from reaching the voting stage, ensuring protocol stability and voter confidence.

01

Smart Contract State Validation

Check that all referenced smart contracts are in a valid, executable state before a proposal is created. This includes verifying that contract addresses are not blacklisted, are not paused, and that their bytecode matches the expected hash. For example, a proposal to upgrade a vault contract should first confirm the new contract's code is verified on Etherscan and passes a basic security audit. Use static calls to simulate the proposal's execution path and catch reverts early.

  • Key Checks: Contract existence, bytecode hash, pause state, admin permissions.
  • Tool: Use eth_call RPC for dry-run simulations.
02

On-Chain Parameter Thresholds

Validate that proposal parameters fall within governance-defined safety limits. This prevents proposals that could destabilize the protocol, such as setting a fee to 100% or a timelock delay to zero. Implement checks against a stored configuration contract that holds min/max values for critical parameters like quorum, votingDelay, or fundingAmount.

  • Example: A proposal to change the DAO's quorum must be between 5% and 30% of the total token supply.
  • Implementation: Read thresholds from a singleton ParameterStore contract during proposal submission.
03

Cross-Contract Dependency Graphs

Map and validate the web of interactions a proposal will trigger. A single governance action often touches multiple contracts (e.g., a Treasury payout may call a Payment contract, which then pulls from a Vault). Build a directed graph of the proposed calldata to identify all dependent contracts and check their states recursively. Tools like Tenderly's simulation API or Foundry's forge script can trace calls.

  • Process: 1. Parse proposal calldata. 2. Simulate execution to build call graph. 3. Validate each node in the graph.
  • Benefit: Catches second-order failures in complex multi-step proposals.
04

Temporal and Sequential Dependencies

Enforce rules based on proposal timing and order. Some actions require a cooldown period after a previous vote, or must be executed in a specific sequence. For instance, a proposal to upgrade a core module may be invalid if another upgrade for a dependent module is still in its timelock. Implement checks against a registry of activeProposals and their executionETA.

  • Common Rules: Minimum time between similar proposals, mandatory execution order for protocol migrations.
  • Data Structure: Maintain a mapping of proposalType to lastExecutedTimestamp.
05

Off-Chain Data & Oracle Feeds

Verify the integrity and freshness of any off-chain data a proposal depends on, such as price oracles or merkle roots. Proposals that execute based on stale or manipulated data can lead to significant losses. Require that referenced oracles are registered, have recent updates (within the last heartbeat interval, e.g., 1 hour), and are not frozen.

  • Checks: Oracle heartbeat, deviation thresholds, circuit breaker status.
  • Example: A proposal to rebalance a treasury based on ETH price must use a Chainlink oracle updated within the last 3600 blocks.
06

Resource & Gas Budget Enforcement

Ensure proposals do not exceed system resource limits, preventing blocks from being too full or execution from failing due to out-of-gas errors. Calculate the cumulative gas cost of the proposal's execution path and compare it against a governance-set maxExecutionGas limit. This is critical for multi-step proposals that interact with many contracts.

  • Implementation: Run a gas estimation (eth_estimateGas) on the full proposal during submission.
  • Limit: Set a conservative cap (e.g., 5 million gas) to protect against block congestion and high fees.
DEPENDENCY CLASSIFICATION

Types of Proposal Dependencies

A comparison of dependency types for governance proposals, detailing their characteristics and implementation considerations.

Dependency TypeSequentialParallelConditional

Execution Order

Strictly linear

Independent

Contingent on condition

Blocking Behavior

Blocks subsequent proposals

Non-blocking

Blocks until condition met

Implementation Complexity

Low

Low

High

Use Case Example

Treasury release after budget approval

Multiple unrelated parameter updates

Proposal requiring a specific on-chain state

Failure Handling

Cascading failure

Isolated failure

Condition timeout or reversion

Typical Check Interval

On-chain, per block

On-chain, at proposal creation

Off-chain oracle or scheduled check

Gas Cost Impact

Low to moderate

Low

High (oracle calls)

Common in DAOs

step-1-static-analysis
FOUNDATION

Step 1: Perform Static Analysis

Static analysis is the process of examining a smart contract's code without executing it. This first step is critical for identifying potential vulnerabilities, design flaws, and dependencies before any transaction is simulated or executed on-chain.

The goal of static analysis in a proposal dependency check system is to programmatically audit the bytecode or source code of a target contract. This involves analyzing the contract's structure, function signatures, storage layout, and, most importantly, its external calls. Tools like Slither, Mythril, or Securify are commonly used for this purpose. They can detect patterns associated with common vulnerabilities such as reentrancy, integer overflows, and improper access control, providing a foundational security assessment.

A key output of this analysis is the call graph and control flow graph. The call graph maps all external function calls (CALL, DELEGATECALL, STATICCALL) the contract makes to other addresses. The control flow graph shows the execution paths within the contract. By analyzing these graphs, you can identify external dependencies—the specific contracts and functions the proposal will interact with. For example, a governance proposal to upgrade a vault might call Vault.upgradeTo(address newImplementation), which is a direct dependency.

To implement this, you would typically use a script that integrates an analysis tool. For instance, using the Slither Python API, you can extract all external calls from a contract artifact. The code snippet below demonstrates a simplified approach:

python
import json
from slither import Slither

with open('proposal_contract.json') as f:
    artifact = json.load(f)

slither = Slither(artifact['bytecode']['object'])
for contract in slither.contracts:
    for function in contract.functions:
        for call in function.internal_calls + function.low_level_calls:
            print(f"Function {function.name} calls: {call}")

This provides a machine-readable list of dependencies for the next step.

Beyond simple call detection, advanced static analysis can perform taint analysis or symbolic execution to understand how user-controlled inputs (like proposal parameters) flow through the contract to these external calls. This helps answer critical questions: Can an attacker manipulate a parameter to redirect a DELEGACALL to a malicious contract? Does the proposal's success depend on the state of an external protocol that could change? Answering these questions statically reduces the risk of runtime surprises.

Finally, the results of the static analysis must be formatted into a structured dependency report. This report should list each external contract address (or address placeholder if unknown), the function signatures called, and the type of call (e.g., regular CALL vs. DELEGATECALL). This report becomes the primary input for Step 2: Dynamic State Analysis, where the actual on-chain state of these dependencies is evaluated.

step-2-state-validation
PROPOSAL DESIGN

Step 2: Validate State Pre-conditions

Before a governance proposal can be executed, the protocol must verify that the current on-chain state meets all necessary conditions. This step prevents proposals from failing or causing unintended side effects.

A proposal dependency check system is a set of on-chain or off-chain validations that ensure a proposal's execution logic will succeed. This is distinct from checking a proposal's format or the voter's intent; it validates the state of the blockchain itself. Common pre-conditions include verifying that a vault has sufficient liquidity for a withdrawal, a contract is not paused, a specific price oracle is not stale, or that a critical external dependency (like a bridge) is operational. Without these checks, a proposal can pass governance but then revert during execution, wasting gas and creating governance overhead.

The most robust approach is to encode these checks directly into the proposal's execution function using require or revert statements. For example, a proposal to rebalance a treasury might start with require(getReserveRatio() > MIN_SAFE_RATIO, "Insufficient collateral");. This makes the check atomic with the action itself. However, this requires the proposal logic to be permissionlessly executable, which isn't always the case in upgradeable or manager-controlled systems. For off-chain validation, tools like Tenderly simulations or custom scripts can simulate proposal execution against a forked mainnet state to predict failures.

Design your checks to be specific and minimal. Instead of a generic "system is healthy" check, validate the exact variables your proposal logic depends on. Use established on-chain data sources like Chainlink oracles for price and heartbeat checks, or internal contract view functions for protocol state. For complex dependencies, consider implementing a dedicated checkProposalPrerequisites(uint proposalId) view function that returns a boolean and a revert reason, which can be called by both off-chain tools and the on-chain executor.

A critical pattern is handling time-sensitive state. A proposal to execute an arbitrage opportunity based on a current price discrepancy is only valid while that discrepancy exists. In such cases, the proposal should include a deadline or a condition that reverts if the market moves, similar to a limit order. The block.timestamp or a specific block number can be used as a hard expiry. This prevents a proposal from being executed days later under completely different and potentially harmful market conditions.

Finally, document all pre-conditions clearly in the proposal description. Voters need to understand not just what the proposal does, but under which conditions it is safe to execute. Transparency here builds trust and allows the community to independently verify the state before voting. A well-designed dependency system turns governance from a hopeful vote into a predictable and secure state transition mechanism.

step-3-execution-simulation
PROPOSAL DEPENDENCY CHECK

Step 3: Simulate Execution on a Fork

Before a governance proposal is finalized, you must verify its dependencies will execute correctly on-chain. This step involves simulating the proposal's execution on a forked version of the live network.

A fork simulation is a critical safety check that executes your proposal's transactions against a temporary, isolated copy of the blockchain state. This is done using tools like Hardhat or Foundry, which allow you to fork the mainnet at a specific block. The simulation runs the proposal's payload—such as a call to a Timelock contract or a direct contract interaction—and observes the resulting state changes and events without spending real gas or affecting the live network. This process validates that the encoded calldata is correct and the target contracts are in the expected state.

To set up a simulation, you need the RPC URL of an archive node (e.g., from Alchemy or Infura) and the target block number. In Hardhat, you configure your network to fork from this endpoint. You then write a test script that impersonates the governance executor (like a Governor contract) using hardhat_impersonateAccount, funds it with ETH if needed, and sends the proposal transaction. The key outputs to verify are: a successful transaction receipt, the emitted events (checking for ProposalExecuted), and the final state of any contracts involved (e.g., Did the Timelock queue get updated?).

Beyond basic execution, you must test for dependency failures. This means simulating scenarios where a prerequisite transaction hasn't yet been executed. For example, if Proposal B requires Proposal A to complete first, you should simulate B's execution without A's state change. The simulation should revert with a clear error, confirming the dependency is enforced. Tools like Tenderly or OpenZeppelin Defender offer advanced simulation with detailed traces, which are invaluable for debugging complex multi-call proposals and understanding gas usage before submission.

step-4-report-generation
IMPLEMENTATION

Step 4: Generate a Validation Report

After executing dependency checks, the system must compile results into a structured, actionable report for governance participants.

The validation report is the final output of your dependency check system, transforming raw data into a clear decision-making aid. It should present a consolidated view of all checks performed, their outcomes, and an overall proposal status. A well-structured report enables voters to quickly assess technical feasibility and potential risks without needing to interpret raw blockchain data or code themselves. The report's credibility hinges on its transparency—every check, data source, and validation rule must be explicitly documented.

A comprehensive report includes several key sections. The Executive Summary provides a high-level pass/fail status and a severity-weighted risk score. The Dependency Analysis details each checked contract or proposal, listing its address, the type of dependency (e.g., inherits from, calls function, references storage), and the verification result (verified, unverified, mismatch). For on-chain state checks, include the actual value read, the expected value, and the block number of the query. This audit trail is critical for trust.

For maximum utility, the report should offer actionable insights, not just data. Flag specific dependencies that introduce high risk, such as interactions with unaudited contracts or functions that could be front-run. If a dependency is a live governance proposal, link to its discussion page and current vote tally. Use code snippets to illustrate critical findings, like showing a function selector mismatch: Proposal calls: 0xabcdef12, Live contract has: 0x12345678. This level of detail helps technical voters perform their own due diligence.

The report generation logic should be deterministic and reproducible. All inputs—proposal calldata, blockchain state at a specific block, and the rule set—should be hashed and included in the report. This allows any third party to re-run the checks and verify the results. Consider outputting the report in a machine-readable format like JSON alongside a human-friendly HTML or Markdown version. This enables integration with voter dashboards and automated tools that can parse reports to guide voting strategies or trigger alerts.

Finally, consider the report's lifecycle. It should be timestamped and stored immutably, perhaps on IPFS or Arweave, with a content identifier (CID) recorded on-chain. This creates a permanent record of the pre-vote analysis for that specific proposal. Future systems could even compare reports across similar proposal types to identify recurring risk patterns or deviations in dependency management practices, adding a layer of longitudinal analysis to the governance process.

PROPOSAL DEPENDENCY CHECKS

Frequently Asked Questions

Common questions and technical clarifications for developers implementing a robust proposal dependency system in on-chain governance.

A proposal dependency check is a validation mechanism that ensures a new governance proposal can only be executed if a set of predefined conditions from prior proposals are met. This is critical for sequential governance, where the outcome of Proposal A (e.g., upgrading a core contract) must be confirmed before Proposal B (e.g., changing parameters on that upgraded contract) is valid.

Without dependency checks, proposals could execute in the wrong order, leading to failed transactions, wasted gas, or even protocol-breaking states. Systems like Compound's Governor Bravo and OpenZeppelin's Governor models implement these checks to enforce safe execution paths.

conclusion
IMPLEMENTATION SUMMARY

Conclusion and Next Steps

This guide has outlined the core components for building a robust proposal dependency check system. The next steps involve hardening the system for production and exploring advanced use cases.

You now have a functional blueprint for a system that can prevent conflicting or unsafe governance proposals. The core logic revolves around a DependencyChecker contract that validates proposals against a set of rules stored in a PolicyRegistry. By implementing checks like checkTokenAllowance, checkTimelock, and checkState, you can enforce critical safety constraints before a proposal goes to a vote. Remember to thoroughly test all dependency rules in a forked mainnet environment using frameworks like Foundry or Hardhat to simulate real-world conditions.

For production deployment, consider these enhancements: upgradability via a proxy pattern (e.g., Transparent or UUPS) to update policy rules without migrating governance, permissioned management of the policy registry using a multisig or the DAO itself, and event emission for off-chain indexing and alerting. Integrating with a snapshot service like OpenZeppelin Defender can automate the pre-check process, triggering validation automatically when a new proposal is submitted on platforms like Tally or Snapshot.

The concepts here extend beyond basic safety. You can design dependencies for complex cross-protocol interactions, such as ensuring a proposal to deposit treasury funds into a new Aave pool only executes if a prior proposal to approve the tokens has passed. Explore integrating with oracles like Chainlink for real-world data checks or using zk-proofs for private validation of certain conditions. The system's flexibility makes it a foundational component for secure, multi-step DAO operations.