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

Setting Up a Formal Verification Process for Upgrades

This guide provides a practical workflow for integrating formal verification into your smart contract upgrade pipeline. It covers writing specifications, running verifiers, and interpreting results to prevent critical bugs.
Chainscore © 2026
introduction
INTRODUCTION

Setting Up a Formal Verification Process for Upgrades

A systematic approach to mathematically proving the correctness of smart contract upgrades before deployment.

Formal verification is the process of using mathematical logic to prove or disprove the correctness of a system's intended behavior. For smart contract upgrades, this means creating a formal specification—a precise, mathematical description of what the new contract should do—and then using automated theorem provers or model checkers to verify that the compiled bytecode adheres to this specification. Unlike traditional testing, which checks a finite set of inputs, formal verification aims to prove properties hold for all possible inputs and execution paths, providing a higher assurance of security and correctness.

The core components of a formal verification process are the specification, the implementation, and the verification tool. The specification is written in a formal language like TLA+, Coq, or the native language of a tool like Certora Prover or K-Framework. It defines critical properties such as: totalSupply never decreases, only the owner can pause the contract, or token transfers correctly update balances. The implementation is your Solidity or Vyper code. The verification tool, such as Certora, SMTChecker, or Foundry's forge prove, then mathematically checks if the implementation satisfies all specified properties.

To set up this process, you must first identify and formalize your contract's invariants and security properties. Key invariants often include access control rules, state machine correctness, and financial integrity (e.g., sum(balances) == totalSupply). For an upgrade, you must verify these properties for both the new implementation logic and the upgrade mechanism itself, ensuring the proxy's storage layout is compatible and the upgrade function is secure. Integrating this into a CI/CD pipeline requires tools that can run these proofs automatically on each commit or pull request, failing the build if any property is violated.

A practical workflow involves using a dedicated verification framework. For example, with the Certora Prover, you write specifications in its CVL (Certora Verification Language) and run them against your Solidity. With Foundry, you can write invariant tests in Solidity and use forge test --invariant to fuzz them, or use forge prove for formal verification via the SMTChecker. The process is iterative: a failed proof provides a counterexample—a specific input and state that breaks the property—which you must analyze to fix the bug or refine the specification.

The primary benefit is risk mitigation. Formal verification can catch subtle, complex bugs that evade conventional testing and audits, such as reentrancy in unexpected states, arithmetic overflow in edge cases, or violations of protocol invariants after multiple interactions. It is particularly crucial for upgrades because you are modifying a live, value-bearing system. While resource-intensive, establishing this process creates a verification artifact—a machine-checkable proof of correctness—that enhances trust with users, auditors, and the broader community, making it a cornerstone of professional smart contract development.

prerequisites
PREREQUISITES

Setting Up a Formal Verification Process for Upgrades

Before implementing formal verification for smart contract upgrades, you need to establish a robust foundation. This involves selecting the right tools, defining specifications, and structuring your codebase for analysis.

The first prerequisite is selecting a formal verification framework. For Ethereum smart contracts, the most established tool is K, a semantics-based framework used by projects like the Ethereum Foundation. For Solidity, you can use Certora Prover, a commercial tool that integrates with development environments, or Halmos, a symbolic execution tool for Foundry. For Move-based chains like Aptos or Sui, the Move Prover is built into the language. Your choice dictates the specification language and integration workflow. For example, Certora uses a custom CVL (Certora Verification Language) to write rules.

Next, you must define formal specifications. These are precise, mathematical statements of what your contract should do, separate from the implementation code. Specifications typically include: invariants (properties that must always hold, like total supply consistency), rules (describing state transitions for functions), and hook conditions (pre- and post-conditions). Writing these requires a deep understanding of the contract's intended behavior. A common starting invariant for an upgradeable ERC-20 might be: totalSupply() == sum(balanceOf[user]). Tools like the Certora Prover will attempt to mathematically prove these specifications hold for all possible inputs and states.

Your codebase must be structured for verification. This means writing modular, clean code with minimal complexity. Avoid unbounded loops, complex recursion, and inline assembly, as these can make formal verification intractable or impossible. Use the using statement for libraries and clearly separate logic from storage. Furthermore, you must have a comprehensive suite of standard unit and integration tests. Formal verification complements but does not replace testing; tests provide concrete examples and help debug specifications. A well-tested codebase ensures the specifications you write are aligned with the actual desired behavior.

Finally, integrate the verification tool into your CI/CD pipeline. This automates the proof process for every commit and pull request. For a Foundry project using Halmos, your foundry.toml would include a [invariant] section, and you would run halmos --function check_invariant in your CI script. For Certora, you would set up a script to run certoraRun with your spec files. The goal is to fail the build if any specification is violated, creating a safety net that ensures proposed upgrades do not break core protocol properties before they are deployed on-chain.

key-concepts-text
KEY CONCEPTS: SPECIFICATIONS AND VERIFIERS

Setting Up a Formal Verification Process for Upgrades

A structured methodology for ensuring smart contract upgrades preserve intended behavior and security properties.

A formal verification process for upgrades provides a mathematical guarantee that a new contract implementation satisfies a set of formal specifications. This is distinct from testing, which only checks for errors in a finite set of cases. The core components are the specification (a precise, machine-readable statement of what the contract should do) and the verifier (a tool that proves or disproves the specification holds). For upgrades, the critical step is verifying that the new implementation is a behavioral refinement of the original, meaning it does not violate any of the original's guarantees while potentially adding new, safe functionality.

The process begins by writing formal specifications for the original, audited contract. These are often expressed as invariants (properties that must always hold, like totalSupply consistency), state transition rules (how functions change storage), and access control policies. Tools like the Certora Prover use specification languages (CVL) to encode these rules. For an upgrade, you write specifications for the new contract and then use the verifier to check equivalence or refinement against the old contract's specs. This catches subtle bugs that unit tests miss, such as reentrancy introduced via a new callback or a precision error in a modified formula.

Implementing this requires integrating verification into your development pipeline. A common workflow is: 1) Write and verify specs for V1, 2) Develop V2 against the same spec suite, 3) Formally prove V2 refines V1, and 4) Add and verify any new specs for V2's added features. This can be automated in CI/CD using tools like Foundry with the forge command for invariant testing and Certora or Halmos for formal verification. The output is a verification report that either confirms all properties hold or provides a counterexample—a specific transaction sequence that breaks an invariant—which developers must then fix.

Consider upgrading an ERC20 token to include a fee-on-transfer mechanism. Key specifications for the original contract would include invariants like totalSupply == sum(balances) and allowances[from][spender] >= value. For the upgrade, you must prove that the new transfer function, which deducts a fee, still satisfies the core invariant that totalSupply correctly reflects the sum of all balances post-fee (which may be sent to a treasury). The verifier would mathematically check all possible states and inputs to ensure no scenario violates this. This process provides high confidence that the upgrade does not inadvertently break token economics or create arbitrage opportunities.

While powerful, formal verification has limitations. It requires significant expertise to write correct and complete specifications—a flawed spec leads to false security. The computational complexity can be high for large contracts, requiring abstraction or modular verification. It also cannot verify properties outside its mathematical model, like oracle reliability or market conditions. Therefore, it should complement, not replace, audits, fuzzing, and testnets. For teams managing critical DeFi protocols or cross-chain bridges, however, establishing a formal verification gate for upgrades is becoming a best practice for mitigating upgrade risks.

verification-workflow-steps
UPGRADE SAFETY

The Formal Verification Workflow

A systematic approach to mathematically proving the correctness of smart contract upgrades before deployment.

02

Model and Abstract the System

Formal verifiers cannot analyze the entire Ethereum Virtual Machine. You must create a simplified model of the system under test.

  • Abstract away irrelevant details (e.g., precise gas calculations).
  • Define harness contracts that isolate the component being verified.
  • Model external dependencies (like oracles or other contracts) with summary functions that specify their possible behaviors. This step is critical for making verification tractable.
04

Analyze Counterexamples

When a property is violated, the verifier provides a counterexample—a concrete sequence of transactions and state that breaks the rule.

  • Triage: Determine if it's a real bug, a specification error, or a modeling artifact.
  • Iterate: Refine the spec or fix the code based on findings. This is the core feedback loop for improving correctness.
  • Document all analyzed counterexamples for audit trails.
06

Produce an Audit Report

Generate a final, human-readable report summarizing the verification effort.

  • List all proven properties and their descriptions.
  • Document the scope and limitations of the model (what was abstracted away).
  • Include counterexample analysis for any violations found and resolved. This report is crucial for external auditors and governance proposals.
writing-act-specifications
FORMAL VERIFICATION

Step 1: Writing Specifications in Act

This guide explains how to write formal specifications for smart contract upgrades using the Act specification language, establishing the foundation for a verifiable upgrade process.

Formal verification begins with writing a precise, mathematical specification of what a smart contract should do. This is distinct from writing the implementation code itself. For upgrades, you must specify the intended behavior of both the original and upgraded contracts, as well as the properties that must be preserved during the transition. The Act specification language provides a syntax for this, allowing you to define invariants (properties that must always hold), state transitions, and function behaviors in a logic-based format. Think of it as writing a detailed, unambiguous blueprint before construction.

A core concept is defining the storage layout invariant. When upgrading a contract, its persistent state (stored variables) must remain accessible and consistent. In Act, you can specify that the storage footprint of the new contract is a superset of the old one, or that specific variable mappings are preserved. For example, an upgrade to a token contract must guarantee that all user balances from the old contract are correctly migrated and remain unchanged unless explicitly altered by the new logic. This prevents catastrophic bugs like lost funds during migration.

You also need to specify functional correctness for the new methods introduced in the upgrade. For instance, if adding a new feeTransfer function, your Act spec would define its post-conditions: sum(balances) == old(sum(balances)) - fee and feeBalance == old(feeBalance) + fee. The Act documentation provides the full grammar for writing these Hoare-style pre- and post-conditions. Writing these specs forces you to rigorously define the upgrade's intended effect, surfacing ambiguous requirements early.

Finally, the specification must encode the safety properties for the upgrade mechanism itself. If using a proxy pattern like Transparent or UUPS, you need to specify that: (1) only the authorized owner can execute the upgrade, (2) the upgrade function correctly points the proxy to the new implementation address, and (3) no storage collisions occur. By formally defining these constraints in Act, you create a verifiable target for the next step: using a tool like the Act tool or Certora Prover to mathematically check if your implementation code satisfies all specifications.

running-certora-prover
FORMAL VERIFICATION

Step 2: Running the Certora Prover

This guide walks through executing the Certora Prover to formally verify a smart contract's specifications against its implementation.

After writing your specification in a .spec file, you run the Certora Prover using the certoraRun command. This command is executed from your project's root directory. The basic syntax is certoraRun CONTRACT_FILE --verify CONTRACT_NAME:SPECIFICATION_FILE. For example, to verify a Vault.sol contract against a Vault.spec file, you would run: certoraRun contracts/Vault.sol --verify Vault:specs/Vault.spec. This command compiles the contract, parses the specification, and submits the verification job to Certora's cloud service or a local prover.

The certoraRun command accepts numerous flags to customize the verification. Key options include --msg "Custom run message" to tag your run, --rule RULE_NAME to verify a single rule instead of all rules in the spec, and --settings -enableEqualitySaturation=false for advanced solver tuning. For projects with dependencies, you must specify them using --solc_remap or provide a foundry.toml/hardhat.config.js file. A typical command for a complex project might look like: certoraRun contracts/Controller.sol --verify Controller:specs/Controller.spec --solc solc8.19 --rule onlyOwnerCanPause --msg "Run for audit report 2024-05-27".

Upon execution, the prover outputs a link to the Verification Report in the Certora Web UI. This report is the core deliverable. It details the status of each rule: verified (proven correct), violated (a counterexample is shown), or inconclusive (the prover could not decide). For a violated rule, the report provides a concrete counterexample—a sequence of transactions and state values that breaks the rule—which is invaluable for debugging. You must analyze these results to determine if the violation reveals a genuine bug in the contract or a flaw in the specification's logic.

VERIFICATION CHECKLIST

Common Properties to Verify for Upgrades

Key invariants and security properties to formally verify before and after a smart contract upgrade.

Property CategoryPre-Upgrade StatePost-Upgrade StateVerification Tool Example

Access Control Permissions

Only owner can pause

Only owner can pause

Slither, Certora

Critical State Invariants

Total supply = sum(balances)

Total supply = sum(balances)

Foundry (fuzzing), Halmos

Core Function Logic

swap() fee <= 0.3%

swap() fee <= 0.3%

Certora, KEVM

Emergency Function Behavior

pause() stops all transfers

pause() stops all transfers

Manticore, Echidna

Tokenomics & Rewards

Max inflation 5% per year

Max inflation 5% per year

Formal verification in Dafny

Upgrade Mechanism Itself

Timelock delay = 7 days

Timelock delay = 7 days

Manual audit, TLA+

Third-Party Integration Points

Oracle price feed accessible

Oracle price feed accessible

Slither, manual review

Gas Usage for Key Functions

transfer cost < 100k gas

transfer cost < 120k gas

Foundry gas snapshots

interpreting-results
STEP 3

Interpreting Formal Verification Results

Learn to analyze the output from formal verification tools to validate the safety and correctness of your smart contract upgrade.

After running a formal verification tool like Certora Prover, Solidity SMTChecker, or Halmos, you will receive a detailed report. This report is not a simple pass/fail; it is a structured analysis of your contract's logic against the specified properties. The core output consists of verification conditions (VCs). Each VC represents a logical statement that must be proven true for all possible inputs and states. A successful verification means all VCs are proven, indicating the property holds universally.

A failed verification is a critical finding, not a failure of the process. The tool provides a counterexample—a concrete set of inputs, transaction sequences, and state values that violate the property. For instance, a counterexample might show that under specific conditions, a user can drain a liquidity pool or that an admin function can be called by an unauthorized address. Analyzing this trace is essential; it either reveals a genuine bug in your upgrade logic or an over-constraining specification that is too strict.

Common result statuses include Safe (property proven), Violated (counterexample found), and Unknown. An Unknown result often means the solver reached its time or resource limits (a timeout). This can happen with complex, unbounded loops or heavy use of cryptographic operations like keccak256. In such cases, you may need to simplify the property, provide additional lemmas (helper proofs), or increase solver resources. Tools like Certora classify violations by severity, helping you prioritize HIGH-risk logical flaws over MEDIUM-level specification issues.

To act on the results, follow a systematic triage. First, confirm the counterexample is reproducible by simulating the exact transaction sequence in a test (e.g., using Foundry's forge test with the provided calldata and storage state). If it's a true bug, fix the contract code. If the property is too strict, refine your formal spec. For example, a property stating "the total supply never decreases" would be violated by a legitimate burn function; you would need to adjust the spec to account for authorized burns. Iterate by re-running verification after each change.

Integrate these results into your upgrade governance. A clean verification report provides a high-assurance argument for security. It should be included in the audit trail alongside manual review and test coverage metrics. For teams using timelock or multisig upgrade processes, formal verification results offer objective, machine-checked evidence that the upgrade behaves as intended, significantly de-risking the deployment. This step transforms abstract mathematical proofs into actionable security insights for your protocol.

integrating-into-cicd
DEVELOPER WORKFLOW

Integrating Verification into CI/CD

Automate formal verification for smart contract upgrades to ensure security and correctness are maintained throughout the development lifecycle.

Integrating formal verification into your Continuous Integration and Continuous Deployment (CI/CD) pipeline transforms security from a manual audit into an automated gate. This ensures every proposed change to your smart contracts is automatically checked for adherence to critical invariants before it can be merged or deployed. Tools like the Foundry framework with its forge toolchain or the Certora Prover can be scripted to run verification tasks. A typical setup involves creating a verification script that defines the properties to check and then configuring your CI service (e.g., GitHub Actions, GitLab CI) to execute this script on every pull request targeting your main branch.

The core of the CI integration is a verification script. For a Foundry project, this might involve running forge prove on your specified contracts and properties. You define your invariants in Solidity files using the certora keyword or in a separate specification language. The script should fail the CI job if any property is violated, blocking the merge. It's crucial to version-control both your contract code and your verification specifications together. This guarantees that the properties being checked are always synchronized with the codebase they are meant to secure.

For upgradeable contracts using patterns like Transparent Proxy or UUPS, the verification process must account for the proxy storage layout and the initialization logic. Your formal spec should verify that the upgrade function preserves critical state invariants and that the new implementation's storage variables are appended correctly to avoid collisions. You can write properties asserting that user balances or total supply remain consistent across a simulated upgrade transaction. This automated check prevents a common class of upgrade-related bugs that could corrupt protocol state.

To make the process robust, your CI pipeline should test verification against multiple scenarios. This includes running proofs on the main implementation contract, the proxy admin functions, and any associated helper libraries. You can structure your CI job to run a quick, default set of rules on every commit and a more comprehensive, longer-running suite of properties on a nightly schedule or before production deployments. Integrating the results into your pull request interface, using status checks or comment bots, provides immediate feedback to developers.

Maintaining the verification suite is an ongoing process. As you add new features, you must also write new specifications to cover the novel logic. Treat your formal spec files with the same rigor as your test files—refactor them for clarity and ensure they are well-documented. A successful CI/CD integration means a failed verification is treated with the same severity as a failing unit test: it is a direct blocker to deployment, ensuring that proven security guarantees are never accidentally broken.

FORMAL VERIFICATION

Frequently Asked Questions

Common questions and troubleshooting for implementing a formal verification process for smart contract upgrades.

Formal verification is a mathematical method for proving or disproving the correctness of a smart contract against a formal specification. For upgrades, it's critical because it provides the highest level of assurance that a new contract version behaves exactly as intended and doesn't introduce regressions or security flaws.

Unlike testing, which checks specific cases, formal verification exhaustively analyzes all possible execution paths. This is essential for upgrade logic, where a single edge case in a proxy pattern or migration function can lead to fund loss. Projects like MakerDAO and Compound use tools like Certora Prover and K-Framework to formally verify their upgradeable contracts, significantly reducing the risk of catastrophic bugs post-deployment.

conclusion
IMPLEMENTATION

Conclusion and Next Steps

This guide has outlined the core components of a formal verification process for smart contract upgrades. The next step is to integrate these practices into your development lifecycle.

Formal verification is not a one-time audit but a continuous process that must be integrated into your development lifecycle. Establish a verification gate in your CI/CD pipeline that requires a successful proof for all changes to critical contract logic before deployment. Tools like the Certora Prover or Halmos can be automated to run on every pull request targeting your protocol's main branch. This ensures that safety properties are never regressed and that every upgrade maintains the system's core invariants.

To scale this process, invest in property engineering. Start by codifying the most critical, protocol-defining invariants—such as "total supply must be conserved" or "user balances cannot be created from nothing." Over time, expand your property library to cover more complex behaviors like fee distribution, reward calculations, and access control. Document each property's intent and scope. Frameworks like Foundry's invariant testing provide a practical bridge, allowing you to express properties in Solidity that can later be formalized.

Finally, treat verification results as living documentation. The set of proven properties serves as a precise, machine-checkable specification of your system's behavior. Share these results with auditors, stakeholders, and the community to build trust. For ongoing learning, study the verification reports of major protocols like MakerDAO or Aave, which are often public. Engage with the formal verification community through forums and workshops hosted by groups like the Certora Community or ETH Security to stay current on best practices and emerging tools.