<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Certora Formal Verification]]></title><description><![CDATA[Certora Formal Verification]]></description><link>https://alexzoid.com</link><generator>RSS for Node</generator><lastBuildDate>Wed, 16 Sep 2026 09:52:51 GMT</lastBuildDate><atom:link href="https://alexzoid.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Certora FV Practical Guide: Insights from the Badger eBTC Competition]]></title><description><![CDATA[Introduction
This article originally was published about one year ago (in Jan 2024). I’ve slightly edited it and fixed sources to support the latest (7.26.0) Prover version.
In 2023, I got involved in formal verification by joining Certora community ...]]></description><link>https://alexzoid.com/certora-formal-verification-practical-guide</link><guid isPermaLink="true">https://alexzoid.com/certora-formal-verification-practical-guide</guid><category><![CDATA[Formal Verification]]></category><category><![CDATA[certora]]></category><category><![CDATA[Solidity]]></category><category><![CDATA[defi]]></category><dc:creator><![CDATA[Alex Zoid]]></dc:creator><pubDate>Wed, 19 Mar 2025 08:41:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742373907945/08c2ed89-e4d3-4ac8-bbc8-23a23310237c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p><em>This article originally was published about one year ago (in Jan 2024). I’ve slightly edited it and fixed</em> <a target="_blank" href="https://github.com/alexzoid-eth/2023-10-badger-fv/tree/main/certora">sources</a> <em>to support the latest (7.26.0) Prover version.</em></p>
<p>In 2023, I got involved in formal verification by joining Certora <a target="_blank" href="https://www.certora.com/contests">community contests</a> and moving to a top position on the <a target="_blank" href="https://www.certora.com/leaderboard">leaderboard</a>. I learned a lot and developed a unique workflow and mindset, which I want to share in this article. I'll use the <a target="_blank" href="https://code4rena.com/audits/2023-10-badger-ebtc-audit-certora-formal-verification-competition">Badger eBTC Competition</a> as an example in this article. This competition was an exciting challenge where participants were motivated to develop and validate comprehensive properties of 4 smart contracts: <a target="_blank" href="https://github.com/alexzoid-eth/2023-10-badger/blob/main/packages/contracts/contracts/EBTCToken.sol">EBTCToken</a>, <a target="_blank" href="https://github.com/alexzoid-eth/2023-10-badger/blob/main/packages/contracts/contracts/ActivePool.sol">ActivePool</a>, <a target="_blank" href="https://github.com/alexzoid-eth/2023-10-badger/blob/main/packages/contracts/contracts/CollSurplusPool.sol">CollSurplusPool</a>, and <a target="_blank" href="https://github.com/alexzoid-eth/2023-10-badger/blob/main/packages/contracts/contracts/SortedCdps.sol">SortedCdps</a>.</p>
<p>In this article, rather than reiterating technical details about formal verification, predicate logic, and the Certora Prover - which are thoroughly explained in the latest <a target="_blank" href="https://docs.certora.com/projects/tutorials/en/latest/lesson1_prerequisites/index.html">Certora tutorials</a> - I will share a practical workflow and my insights. Additionally, I'll include a collection of helpful resources and links to guide you through the process.</p>
<blockquote>
<p>"Formal verification might sound hard, but you don't need to be a math expert to use it." <a target="_blank" href="https://twitter.com/CertoraInc/status/1731719978771227071">(c)</a></p>
</blockquote>
<p>Simply put, the formal verification process involves crafting properties (similar to writing tests) and submitting them alongside compiled Solidity smart contracts to a remote prover. This prover essentially transforms the contract bytecode and your rules into a mathematical model and determines the validity of your rules.</p>
<p>For those new to Certora Prover, it's crucial to understand that:</p>
<ol>
<li><p>The prover operates at the bytecode level. It even <a target="_blank" href="https://www.certora.com/blog/vyper-announcement">works</a> with the Vyper language as well.</p>
</li>
<li><p>Unlike fuzz testing, where functions are repeatedly executed with varying parameters, the prover efficiently translates the contract's bytecode and rules into a mathematical model that proves every possible code execution.</p>
</li>
<li><p>Variables, including the contract state, blockchain environment, and return values of unresolved external calls, are assigned a range of all possible values. It's your responsibility to define these bounds.</p>
</li>
</ol>
<h2 id="heading-configuration">Configuration</h2>
<p>To begin, it's essential to prepare all configuration files. The recommended file structure for configs, historically used in projects like <a target="_blank" href="https://github.com/alexzoid-eth/2023-10-badger-fv/tree/main/certora">this one</a>, is organized as follows:</p>
<pre><code class="lang-solidity">certora\
    harness\
    specs\
    confs\
    mutations\
</code></pre>
<p>Each folder has a specific role, which I will discuss in detail in the following sections.</p>
<h3 id="heading-harness">Harness</h3>
<blockquote>
<p>Prover interacts with <code>external</code> (<code>public</code>) functions and variables. Access to <code>internal</code> can be provided via harness contracts.</p>
</blockquote>
<p>Essentially, a harness is a wrapper inherited from the contract under test. Its primary function is to provide useful features, like access to <code>internal</code> variables and functions, and to overcome certain limitations of the CVL language. While its use isn't obligatory (the prover can directly interact with the primary contracts), I advise establishing it before implementing properties. This preliminary setup is likely to streamline the process and save time in subsequent stages.</p>
<p>For each contract you intend to test, create a corresponding harness contract. Additionally, if necessary, add mock contracts to simulate external calls. These harness contracts are the ones the Prover will interact with.</p>
<p>Here is an example of the file structure for harness contracts and mocks:</p>
<pre><code class="lang-solidity">certora\harness\
├── ActivePoolHarness.sol         <span class="hljs-comment">// Harness for `ActivePool.sol`</span>
├── CollateralTokenTester.sol     <span class="hljs-comment">// Mock for `CollateralToken`</span>
├── CollSurplusPoolHarness.sol    <span class="hljs-comment">// Harness for `CollSurplusPool.sol`</span>
├── DummyERC20A.sol               <span class="hljs-comment">// Mock for an `ERC20` token</span>
├── DummyERC20B.sol               <span class="hljs-comment">// Mock for another `ERC20` token</span>
├── DummyERC20Impl.sol            <span class="hljs-comment">// Basic `ERC20` implementation</span>
├── EBTCTokenHarness.sol          <span class="hljs-comment">// Harness for `EBTCToken.sol`</span>
└── SortedCdpsHarness.sol         <span class="hljs-comment">// Harness for `SortedCdps.sol`</span>
</code></pre>
<p>A simple harness file is just a derivative of the tested contract. It doesn't add new functionality at the moment, but inherits the original contract's features. An example can be found <a target="_blank" href="https://github.com/alexzoid-eth/2023-10-badger/blob/main/certora/harness/ActivePoolHarness.sol#L1-L18">here</a>, showing the <code>ActivePoolHarness.sol</code>:</p>
<pre><code class="lang-solidity"><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>

<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> 0.8.17;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"../../packages/contracts/contracts/ActivePool.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">ActivePoolHarness</span> <span class="hljs-keyword">is</span> <span class="hljs-title">ActivePool</span> </span>{ 

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params">
        <span class="hljs-keyword">address</span> _borrowerOperationsAddress,
        <span class="hljs-keyword">address</span> _cdpManagerAddress,
        <span class="hljs-keyword">address</span> _collTokenAddress,
        <span class="hljs-keyword">address</span> _collSurplusAddress,
        <span class="hljs-keyword">address</span> _feeRecipientAddress
    </span>) <span class="hljs-title">ActivePool</span>(<span class="hljs-params">
        _borrowerOperationsAddress, _cdpManagerAddress, _collTokenAddress, _collSurplusAddress, _feeRecipientAddress
    </span>) </span>{ }
}
</code></pre>
<h3 id="heading-specs">Specs</h3>
<p>Create a dedicated specification file for each contract you're testing. These files should be placed in the <code>spec</code> directory as follows:</p>
<pre><code class="lang-solidity">certora\specs\
├── ActivePool.spec
├── CollSurplusPool.spec
├── EBTCToken.spec
└── SortedCdps.spec
</code></pre>
<p>I recommend structuring each specification file into distinct sections, separated by comment lines. Each section serves a specific purpose:</p>
<pre><code class="lang-solidity"><span class="hljs-comment">/////////////////// METHODS ///////////////////////</span>

methods {
}

<span class="hljs-comment">///////////////// DEFINITIONS /////////////////////</span>

<span class="hljs-comment">////////////////// FUNCTIONS //////////////////////</span>

<span class="hljs-comment">///////////////// GHOSTS &amp; HOOKS //////////////////</span>

<span class="hljs-comment">///////////////// PROPERTIES //////////////////////</span>
</code></pre>
<ul>
<li><p><strong>METHODS</strong> (<a target="_blank" href="https://docs.certora.com/en/latest/docs/cvl/methods.html">link</a>): This section includes additional information about contract methods. While not always necessary, it's good practice to declare all external methods of the tested contract and any linked contracts here.</p>
</li>
<li><p><strong>DEFINITIONS</strong> (<a target="_blank" href="https://docs.certora.com/en/latest/docs/confluence/anatomy/definitions.html">link</a>): Similar to macros in other languages, you can define constants or simple expressions in this section.</p>
</li>
<li><p><strong>FUNCTIONS</strong> (<a target="_blank" href="https://docs.certora.com/en/latest/docs/confluence/anatomy/functions.html">link</a>): In CVL, a function can either be invoked within a rule or serve as a stub for a contract's external function.</p>
</li>
<li><p><strong>GHOSTS &amp; HOOKS</strong> (<a target="_blank" href="https://docs.certora.com/en/latest/docs/cvl/ghosts.html">Ghosts</a> &amp; <a target="_blank" href="https://docs.certora.com/en/latest/docs/cvl/hooks.html">Hooks</a>): Mostly contains shadow copies of storage variables. Its importance lies in providing access to <code>private</code> variables and facilitating the implementation of effective invariants and tracking things that didn't exist in the original contract (like the sum of balances).</p>
</li>
<li><p><strong>PROPERTIES</strong>: This section is where the <a target="_blank" href="https://docs.certora.com/en/latest/docs/cvl/rules.html">rules</a> and <a target="_blank" href="https://docs.certora.com/en/latest/docs/cvl/invariants.html">invariants</a> are defined. It's the core of your specification.</p>
</li>
</ul>
<p>I will delve into each of these blocks in more detail further in the article. For now, just incorporate this structured comment block into each specification file.</p>
<h3 id="heading-confs">Confs</h3>
<blockquote>
<p>Remember, the prover verifies specification against one main contract, with others being linked to it.</p>
</blockquote>
<p>Initially, it's necessary to create separate configuration files for each contract:</p>
<pre><code class="lang-solidity">certora\confs\
├── ActivePool_verified.conf
├── CollSurplusPool_verified.conf
├── EBTCToken_verified.conf
└── SortedCdps_verified.conf
</code></pre>
<p>The suffix <code>_verified</code> in each file name is a convention from Certora community contests. It signifies that the configuration is intended to prove the declared functionality. Another common suffix is <code>_violated</code>, indicating that the configuration is designed to demonstrate an actual bug (where a rule is violated, revealing a flaw).</p>
<p>Let's examine <code>ActivePool_verified.conf</code> for a clearer understanding (see the <a target="_blank" href="https://github.com/alexzoid-eth/2023-10-badger-fv/blob/main/certora/confs/ActivePool_verified.conf">example</a>):</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"files"</span>: [
        <span class="hljs-string">"certora/harness/ActivePoolHarness.sol"</span>,
        <span class="hljs-string">"certora/harness/CollateralTokenTester.sol"</span>,
        <span class="hljs-string">"certora/harness/CollSurplusPoolHarness.sol"</span>,
        <span class="hljs-string">"certora/harness/DummyERC20A.sol"</span>,
        <span class="hljs-string">"certora/harness/DummyERC20B.sol"</span>,
    ],
    <span class="hljs-attr">"link"</span>: [

        <span class="hljs-string">"ActivePoolHarness:collateral=CollateralTokenTester"</span>,
        <span class="hljs-string">"ActivePoolHarness:collSurplusPoolAddress=CollSurplusPoolHarness"</span>,

        <span class="hljs-string">"CollSurplusPoolHarness:activePoolAddress=ActivePoolHarness"</span>,
        <span class="hljs-string">"CollSurplusPoolHarness:collateral=CollateralTokenTester"</span>,
    ],
    <span class="hljs-attr">"verify"</span>: <span class="hljs-string">"ActivePoolHarness:certora/specs/ActivePool.spec"</span>,
    <span class="hljs-attr">"loop_iter"</span>: <span class="hljs-string">"3"</span>,
    <span class="hljs-attr">"optimistic_loop"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"rule_sanity"</span>: <span class="hljs-string">"basic"</span>,
    <span class="hljs-attr">"msg"</span>: <span class="hljs-string">"ActivePoolHarness"</span>,
    <span class="hljs-attr">"parametric_contracts"</span>: [ <span class="hljs-string">"ActivePoolHarness"</span> ]
}
</code></pre>
<p>The configuration contains several key blocks:</p>
<ol>
<li><p><code>files</code> Block: Lists all contracts to be compiled, all of which can be utilized in our specifications.</p>
</li>
<li><p><code>link</code> Block: Defines static links between contracts listed in <code>files</code>. This is crucial because the constructor logic is not 'executed' before proving rules. Therefore, static links in configuration files globally set immutable variables and dependencies.</p>
</li>
<li><p><code>verify</code>: Specifies the main contract to be verified against the specification.</p>
</li>
<li><p><strong>Additional Options</strong>: For a complete list of flags and options, refer to the official <a target="_blank" href="https://docs.certora.com/en/latest/docs/prover/cli/options.html">documentation</a>.</p>
</li>
</ol>
<p>An important consideration in configuration is the linking of variables. While it's not mandatory, I strongly recommend linking <code>immutable</code> variables, which are set in the <code>constructor</code>. This is crucial because the prover does not account for <code>constructor</code> logic. For external calls to dependent contracts and mocks, linking is not mandatory, but I advise doing so to avoid unexpected results.</p>
<p>While it's not mandatory to create a configuration file (since all options can be passed via command line parameters to <code>certoruRun</code>), employing a separate <a target="_blank" href="https://docs.certora.com/en/latest/docs/prover/cli/conf-file-api.html">configuration file</a> is considered best practice.</p>
<h3 id="heading-mutations">Mutations</h3>
<p>The final directory in our setup is for mutations, which are altered versions of the contract files, designed to test specific rules. There are two main types of mutations:</p>
<ol>
<li><p><strong>Manual Mutations</strong>: These are typically modified contract files adapted to prove a specific rule. My advice is to create and test these mutations immediately after implementing each rule. This proactive approach helps ensure that each rule is not only implemented correctly but is also effectively doing its job.</p>
</li>
<li><p><strong>Gambit Mutations</strong>: To gather coverage information, a special engine <code>Gambit</code> is used (see <a target="_blank" href="https://docs.certora.com/en/latest/docs/gambit/index.html">documentation</a>). It generates numerous mutated files, each of which is then tested against your specifications. The idea here is to see if rules are violated in these mutated scenarios.</p>
</li>
</ol>
<p>Here’s how your mutations directory structure might initially look:</p>
<pre><code class="lang-solidity">certora\mutations\
├── ActivePool
├── CollSurplusPool
├── EBTCToken
└── SortedCdps
</code></pre>
<h2 id="heading-execution">Execution</h2>
<p>For newcomers:</p>
<ul>
<li><p>install prover with <code>pip3 install certora-cli</code>, also I recommend check updates from time to time with <code>pip3 install certora-cli --upgrade</code></p>
</li>
<li><p>request a Certora licence key via <a target="_blank" href="https://www.certora.com/signup?plan=prover">website</a> or <a target="_blank" href="https://discord.com/channels/795999272293236746/1080511450075893800">discord</a> and set it as <code>CERTORAKEY</code> environment variable</p>
</li>
</ul>
<p>Setup appropriate solidity compiler version, for <code>Badger eBTC</code> it is <code>0.8.17</code>, with command <code>solc-select install 0.8.17 &amp;&amp; solc-select use 0.8.17</code></p>
<p>Let's add the first rule and check that everything is done as it should. Add a rule <code>sanity</code> to the <code>certora\specs\ActivePool.spec</code>.</p>
<pre><code class="lang-solidity"><span class="hljs-comment">///////////////// PROPERTIES //////////////////////</span>

rule sanity(method f, env e, calldataarg args) {
    f(e, args);
    satisfy(<span class="hljs-literal">true</span>);
}
</code></pre>
<p>This basic rule ensures that all external functions can execute without causing a revert. Typically, the prover overlooks any executions leading to a revert. However, using the <code>satisfy</code> system function, we can confirm if a specific condition holds <code>true</code> in at least one scenario. In other words, it checks that each <code>external</code> function can successfully execute without reverting in at least one possible case.</p>
<p>Now we can execute a prover with <code>certoraRun certora/confs/ActivePool_verified.conf</code> from the root directory.</p>
<p>Output will be something like this:</p>
<pre><code class="lang-bash">// ... Compilation messages ...

Connecting to server...

Job submitted to server

Follow your job at https://prover.certora.com
Once the job is completed, the results will be available at https://prover.certora.com/output/52567/3828709713ec4504b61f3e8a2f824703?anonymousKey=1a9b6143a36bed6d9ab0c1afd325747842949db9
</code></pre>
<p>You got a shareable link to result of your rule execution. It's comfortable to use, but keep in mind that all associated files like your specification, configs and contracts are available as well. If you want to securely share your result with Certora team in debug purpose, you can simply remove <code>anonymousKey</code> from the url.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742370399076/ab394204-74b1-4177-8a54-1f530894876e.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-preparation">Preparation</h2>
<p>Before diving into property formulation, it's beneficial to make some preparations.</p>
<h3 id="heading-separating-functionality">Separating Functionality</h3>
<p>I recommend viewing your contracts from two perspectives: firstly as the main contract under test, and secondly as a potential externally linked contract. This approach leads to a logical division of functionality. In the externally linked part, include all contract setup operations, and in the tested contract, import these operations.</p>
<p>For instance, in our project scope, we have four contracts: <code>EBTCToken</code>, <code>ActivePool</code>, <code>CollSurplusPool</code>, and <code>SortedCdps</code>. Each of these can be both the focus of testing and a contract linked externally. Let's illustrate this separation:</p>
<pre><code class="lang-solidity">certora\specs\
├── base
│   ├── activePool.spec
│   ├── collSurplusPool.spec
│   ├── eBTCToken.spec
│   └── sortedCdps.spec
├── ActivePool.spec
├── CollSurplusPool.spec
├── EBTCToken.spec
└── SortedCdps.spec
</code></pre>
<p>In this structure, we use the root <code>spec</code> directory for testing the contracts, and the <code>base</code> directory for specifications meant for importing. Begin by adding the <code>base</code> directory and the relevant files. For example, insert <code>import "./base/activePool.spec";</code> at the beginning of <code>ActivePool.spec</code> and do the same for the other files.</p>
<h3 id="heading-methods-declaration">Methods Declaration</h3>
<p>The next step involves declaring all external methods in the <code>METHODS</code> block. This is not just a best practice but also a way to achieve specific behaviors in testing. For instance, an external method not declared in this block is assumed to interact with the current blockchain environment (<code>block</code>, <code>msg</code>). By declaring a method as <code>envfree</code> in the methods block, you explicitly indicate that it does not rely on the blockchain environment, leading to clearer specifications.</p>
<p>The methods block is a complex topic and goes beyond the scope of this summary, so for a more in-depth understanding, refer to the <a target="_blank" href="https://docs.certora.com/en/latest/docs/cvl/methods.html">Methods Block</a> and <a target="_blank" href="https://docs.certora.com/en/latest/docs/user-guide/multicontract/index.html">Working with Multiple Contracts</a> documentation.</p>
<h3 id="heading-unresolved-calls">Unresolved Calls</h3>
<p>When using the Prover, it's crucial to understand how it deals with calls to unresolved functions. By default, the Prover adopts a strategy known as "havocing," where it assumes that these unresolved calls could result in almost any state change. This broad assumption can introduce unexpected behavior into your specification. Therefore, I strongly advise against leaving any calls unresolved.</p>
<p>To identify these unresolved calls, refer to the <code>Contracts Call Resolutions</code> <a target="_blank" href="https://prover.certora.com/output/52567/3828709713ec4504b61f3e8a2f824703?anonymousKey=1a9b6143a36bed6d9ab0c1afd325747842949db9">section</a> of the Prover's output.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742370450399/222a80f5-1133-46ed-a1f8-da60f258a4d4.png" alt class="image--center mx-auto" /></p>
<p>To effectively handle these unresolved calls, follow the guidance provided in the <a target="_blank" href="https://docs.certora.com/en/latest/docs/user-guide/multicontract/index.html#handling-unresolved-method-calls">Handling Unresolved Method Calls</a> section of the Certora documentation. This resource provides detailed steps and best practices for managing such scenarios to ensure your specification behaves as intended.</p>
<h3 id="heading-shadowing-storage">Shadowing Storage</h3>
<p>Creating a shadow copy of all storage variables (in <code>GHOSTS &amp; HOOKS</code> block) is a strategic step before formulating properties. This approach streamlines the process, allowing you to focus on developing invariants.</p>
<p>There are three main reasons to adopt this strategy:</p>
<ol>
<li><p><strong>Extended Functionality Tracking</strong>: It allows for tracking additional metrics or states that were not originally included in the contract. For example, it enables the calculation and monitoring of aggregate values like the sum of balances, which might not be directly available in the original contract.</p>
</li>
<li><p><strong>Access to Private Variables</strong>: Without altering the original contract code, creating a shadow copy is the only way to access <code>private</code> variables.</p>
</li>
<li><p><strong>Quantifiers Limitations</strong>: Direct calls to contract functions are not supported in <a target="_blank" href="https://docs.certora.com/projects/tutorials/en/latest/lesson1_prerequisites/propositional_logic.html#quantifiers">quantifiers</a>.</p>
</li>
</ol>
<p>A typical shadow copy involves three components:</p>
<ol>
<li><p><strong>The Ghost Variable</strong>: Represents the shadow copy of the actual storage variable.</p>
</li>
<li><p><strong>Read Access Hook</strong>: Utilizes the <code>Sload</code> hook for read operations.</p>
</li>
<li><p><strong>Write Access Hook</strong>: Uses the <code>Sstore</code> hook for write operations.</p>
</li>
</ol>
<p>As an example, consider the shadow copy for <code>address public feeRecipientAddress;</code> from <a target="_blank" href="https://github.com/alexzoid-eth/2023-10-badger-fv/blob/main/packages/contracts/contracts/ActivePool.sol#L29">ActivePool.sol</a>. It includes two ghost variables (<code>ghostFeeRecipientAddress</code> and <code>ghostFeeRecipientAddressPrev</code>) along with <code>Sload</code> and <code>Sstore</code> hooks. Keeping track of the variable's previous value is useful for analyzing state transitions.</p>
<p>Here is how it looks in <a target="_blank" href="https://github.com/alexzoid-eth/2023-10-badger-fv/blob/main/certora/specs/base/activePool.spec#L45-L64">practice</a>:</p>
<pre><code class="lang-solidity"><span class="hljs-comment">//</span>
<span class="hljs-comment">// Ghost copy of `feeRecipientAddress`</span>
<span class="hljs-comment">//</span>

ghost <span class="hljs-keyword">address</span> ghostFeeRecipientAddress {
    init_state axiom ghostFeeRecipientAddress <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
}

ghost <span class="hljs-keyword">address</span> ghostFeeRecipientAddressPrev {
    init_state axiom ghostFeeRecipientAddressPrev <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
}

hook Sload <span class="hljs-keyword">address</span> val _ActivePool.feeRecipientAddress {
    <span class="hljs-built_in">require</span>(ghostFeeRecipientAddress <span class="hljs-operator">=</span><span class="hljs-operator">=</span> val);
}

hook Sstore _ActivePool.feeRecipientAddress <span class="hljs-keyword">address</span> val {
    ghostFeeRecipientAddressPrev <span class="hljs-operator">=</span> ghostFeeRecipientAddress;
    ghostFeeRecipientAddress <span class="hljs-operator">=</span> val;
}
</code></pre>
<p>For detailed information, please refer to the <a target="_blank" href="https://docs.certora.com/en/latest/docs/cvl/ghosts.html">Ghosts</a> and <a target="_blank" href="https://docs.certora.com/en/latest/docs/cvl/hooks.html#load-and-store-hooks">Load and Store Hooks</a> sections in the documentation.</p>
<p>Though setting up hooks can be labor-intensive, this crucial preparation step significantly enhances the efficiency of your verification process in the long run.</p>
<h2 id="heading-thinking-about-properties">Thinking about Properties</h2>
<p>As we reach this phase, it's time to start formulating our properties. However, before diving into code, it's crucial to take a moment to systematically conceptualize your properties.</p>
<p>Begin by framing your properties in simple English (<a target="_blank" href="https://github.com/alexzoid-eth/2023-10-badger-fv/blob/main/packages/contracts/specs/PROPERTIES.md">PROPERTIES.md</a>). This approach helps in clearly defining what you aim to achieve before any coding begins. For guidance on this process, consider this insightful article <a target="_blank" href="https://fuzzy.fyi/blog/quick-tips-to-start-your-next-invariant-test-campaign">Quick tips to start your next invariant test campaign</a> and posts (<a target="_blank" href="https://twitter.com/agfviggiano/status/1687854392202997760">Post #1</a> and <a target="_blank" href="https://twitter.com/agfviggiano/status/1735235127171551320">Post #2</a>), which offer detailed explanations.</p>
<p>Certora's team has identified five primary <a target="_blank" href="https://github.com/Certora/Tutorials/blob/master/06.Lesson_ThinkingProperties/Categorizing_Properties.pdf">categories of properties</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742370517525/4bd7ec9d-b697-4fe2-9249-ce199c26d30f.png" alt class="image--center mx-auto" /></p>
<p>I suggest starting with the <code>Valid States</code> category. These properties are crucial when linking your specification to another contract. They encompass initial setups like constructor configurations, initial storage variable values, correctness of linked lists, and summaries of user balances relative to the total balance. From an external contract's viewpoint, these invariants are essential for proper setup and utilization.</p>
<p>Place these properties in the <code>base</code> directory. For instance, many <a target="_blank" href="https://github.com/alexzoid-eth/2023-10-badger-fv/blob/main/certora/specs/base/sortedCdps.spec#L248-L417">properties</a> in <code>SortedCdps</code> are designed to ensure a correctly structured list, fitting into the <code>Valid States</code> category. Beginning with this category is strategic, as many other property types often rely on having a valid state as a foundation.</p>
<p>After that, progress from <code>High-level</code> properties to <code>Unit Tests</code>, transitioning from those with broader project impact to more specific ones.</p>
<h2 id="heading-developing-properties">Developing Properties</h2>
<p>When constructing properties in formal verification, we mainly deal with two types: <a target="_blank" href="https://docs.certora.com/en/latest/docs/cvl/invariants.html">Invariants</a> and <a target="_blank" href="https://docs.certora.com/en/latest/docs/cvl/rules.html">Rules</a>.</p>
<p>In simple terms, an <code>invariant</code> functions as follows: it establishes an initial condition for the contract's environment, then an external function of the contract is executed. After execution, the prover checks whether the contract state still meets the invariant's criteria.</p>
<p>Consider the basic invariant <a target="_blank" href="https://github.com/alexzoid-eth/2023-10-badger-fv/blob/main/certora/specs/base/sortedCdps.spec#L260-L261">sortedCdpsMaxSizeGtZero</a>:</p>
<pre><code class="lang-solidity">invariant sortedCdpsMaxSizeGtZero() _SortedCdps.maxSize() <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>
    filtered { f <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> <span class="hljs-operator">!</span>HARNESS_REPLACED_OR_VIEW_FUNCTIONS(f) }
</code></pre>
<p>Here, the <code>filtered</code> block narrows down the set of external functions to be tested. The prover assumes <code>_SortedCdps.maxSize() != 0</code> before and checks this condition after the function execution.</p>
<p>On the other hand, a <code>rule</code> is structured differently and comprises three segments: setting up the environment, executing the function, and then verifying the post-execution environment with <code>assert</code> or <code>satisfy</code>. The rule is meaningful only when it includes an <code>assert</code> or <code>satisfy</code> statement.</p>
<p>For instance, look at the simple rule <a target="_blank" href="https://github.com/alexzoid-eth/2023-10-badger-fv/blob/main/certora/specs/SortedCdps.spec#L126-L136">reInsertNotAffectSize</a>:</p>
<pre><code class="lang-solidity"><span class="hljs-comment">// reInsert() should not change list size</span>
rule reInsertNotAffectSize(env e, <span class="hljs-keyword">bytes32</span> _id, <span class="hljs-keyword">uint256</span> _newNICR, <span class="hljs-keyword">bytes32</span> _prevId, <span class="hljs-keyword">bytes32</span> _nextId) {

    mathint sizeBefore <span class="hljs-operator">=</span> ghostSize;

    reInsert(e, _id, _newNICR, _prevId, _nextId);

    mathint sizeAfter <span class="hljs-operator">=</span> ghostSize;

    <span class="hljs-built_in">assert</span>(sizeBefore <span class="hljs-operator">=</span><span class="hljs-operator">=</span> sizeAfter);
}
</code></pre>
<p>It begins by saving the initial state of <code>ghostSize</code>, executes the <code>reInsert</code> function, and concludes with an <code>assert</code> to confirm that the list size remains unchanged.</p>
<p>When writing your properties, adopt a methodical approach by breaking down complex rules into simpler ones. This strategy of decomposition, coupled with constraining the range of possible values, can significantly speed up the rule execution process.</p>
<p>To test a specific property, use the <code>--rule</code> flag. This tests only the selected rule, saving time: <code>certoraRun certora/confs/ActivePool_verified.conf --rule sanity</code>.</p>
<p>For additional information on CVL (Certora Verification Language), I recommend paying attention to official <a target="_blank" href="https://docs.certora.com/en/latest/docs/cvl/index.html">Documentation</a>, video of <a target="_blank" href="https://www.youtube.com/watch?v=DtVj788m3Qo">Practical Introduction</a>, and exploring a range of <a target="_blank" href="https://github.com/Certora/Examples">examples</a> and <a target="_blank" href="https://github.com/Kirkeelee/Certora-examples">real projects</a>.</p>
<p>If you encounter any questions or need further clarification, the <a target="_blank" href="https://discord.com/channels/795999272293236746/1104825071450718338">Certora Discord help-desk</a> is an excellent resource for information and support.</p>
<h2 id="heading-quality-assurance">Quality Assurance</h2>
<p>Ensuring the quality of your properties is as crucial as testing in traditional programming. To gain a deeper understanding of this process, I recommend two insightful videos: <a target="_blank" href="https://www.youtube.com/watch?v=PjUua2Hi1GA&amp;t=433s">Checking Specifications - What's the Quality of My Rules?</a> and <a target="_blank" href="https://www.youtube.com/watch?v=mntP0_EN-ZQ">Webinar: How to Prevent Prover Timeouts</a>.</p>
<p>The QA process for formal verification can be categorized into three main stages:</p>
<h3 id="heading-manual-mutations">Manual Mutations</h3>
<p>A manual mutation involves altering the contract code, typically represented as the original contract file with specific modifications. To validate the effectiveness of your property, it should pass with the unaltered contract code and fail (or be violated) when tested with the manual mutation. This approach confirms that your rule is not superficial and functions as intended.</p>
<p>One practical method for testing with manual mutations is leveraging <code>git</code> commands. For instance, after introducing a mutation in <code>ActivePool.sol</code>, run <code>certoraRun</code> as usual. Then, use <code>git restore packages/contracts/contracts/ActivePool.sol</code> to revert to the original contract file.</p>
<p>It’s advisable to perform this test for each property immediately after its development.</p>
<h3 id="heading-coverage-information">Coverage Information</h3>
<p>Historically, mutation testing in Solidity relied on <a target="_blank" href="https://github.com/Certora/gambit">Gambit</a>, an open-source mutation generator (<a target="_blank" href="https://docs.certora.com/en/latest/docs/gambit/index.html">documentation</a>). Nowadays, <code>Gambit</code> has been integrated into the more comprehensive <code>certoraMutate</code> engine (<a target="_blank" href="https://docs.certora.com/en/latest/docs/gambit/mutation-verifier.html">documentation</a>), which you should focus on. This tool combines the functionalities of <code>Gambit</code>, <code>certoraRun</code>, a server infrastructure designed for extensive testing, and a user-friendly <a target="_blank" href="https://mutation-testing.certora.com/">dashboard</a>.</p>
<p>Configuring <code>certoraMutate</code> is similar to setting up the prover using <code>conf</code> files. For example, the configuration for <code>ActivePool.sol</code>:</p>
<pre><code class="lang-json"><span class="hljs-string">"mutations"</span>: { 
    <span class="hljs-attr">"gambit"</span>: {                                                                   
        <span class="hljs-attr">"filename"</span> : <span class="hljs-string">"../../../packages/contracts/contracts/ActivePool.sol"</span>,
        <span class="hljs-attr">"num_mutants"</span>: <span class="hljs-number">0</span>
    },                                                                           
    <span class="hljs-attr">"manual_mutants"</span>: {                                                          
        <span class="hljs-attr">"file_to_mutate"</span>: <span class="hljs-string">"../../../packages/contracts/contracts/ActivePool.sol"</span>,
        <span class="hljs-attr">"mutants_location"</span>: <span class="hljs-string">"../../mutations/ActivePool"</span>
    }                                                                            
},
</code></pre>
<p>Execute with <code>certoraMutate certora/confs/ActivePool_verified.conf</code>.</p>
<p>Setting <code>num_mutants</code> to zero means only manual mutations will be executed. The process involves replacing the original <code>ActivePool.sol</code> with each file in the <a target="_blank" href="https://github.com/alexzoid-eth/2023-10-badger-fv/tree/main/certora/mutations/ActivePool">../../mutations/ActivePool</a> directory and running <code>certoraRun</code>.</p>
<p>Alternatively, for more in-depth coverage analysis, you can add the <code>--coverage_info [none|basic|advanced]</code> flag to <code>certoraRun</code>. The <code>advanced</code> option provides more detailed insights but is slower. An example of this can be seen <a target="_blank" href="https://prover.certora.com/output/52567/79c0d8b34f934d4bac6142136a68ee3f?anonymousKey=d4c7e909bc09c9acaee84c109ed83c3aab93a2d0">here</a>, where <code>certoraRun certora/confs/ActivePool_verified.conf --rule sanity --coverage_info advanced</code> was executed. To view this, first click <code>Job Info</code> on the left panel, then <code>Unsat Core page</code> on the right side of the window.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742370570098/cbbea602-db3c-43c4-98f1-c342e0bff3df.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-identifying-problems">Identifying Problems</h3>
<p>To guarantee the quality of your property specifications, consider using the <code>--rule_sanity</code> option, which performs automatic checks to identify common errors in specifications. Examples include unreachable <code>assert</code> statements due to reverts, <code>asserts</code> that are always <code>true</code>, invariants that invariably pass, or superfluous <code>require</code> and <code>assert</code> statements.</p>
<p>For detailed insights into these checks, refer to the <a target="_blank" href="https://docs.certora.com/en/latest/docs/prover/checking/sanity.html">sanity checks documentation</a>.</p>
<h2 id="heading-automation">Automation</h2>
<p>Minimizing the time spent on proving properties and testing them with manual mutations is crucial. To facilitate this, I developed several scripts for efficient workflow management.</p>
<ol>
<li><p><strong>Generating Manual Mutations</strong>:</p>
<ul>
<li><p>The script <a target="_blank" href="https://github.com/alexzoid-eth/2023-10-badger-fv/blob/main/certora/mutations/addMutation.sh">certora/mutations/addMutation.sh</a> assists in creating manual mutations. After manually editing the contract file, this script can generate a mutation. It requires two input parameters: the name of the configuration and the relative path to the contract file.</p>
</li>
<li><p>Example usage: <code>./certora/mutations/addMutation.sh ActivePool ./packages/contracts/contracts/ActivePool.sol</code></p>
</li>
<li><p>The script copies the mutated contract file into the <code>certora/mutations/ActivePool/</code> directory and adds a mutation comment, similar to the output of the <code>git diff</code> command.</p>
</li>
</ul>
</li>
</ol>
<p>    See <a target="_blank" href="https://github.com/alexzoid-eth/2023-10-badger-fv/blob/main/certora/mutations/ActivePool/2.sol#L46-L83">certora/mutations/ActivePool/2.sol</a> for an example mutation:</p>
<pre><code class="lang-solidity">        <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params">
            <span class="hljs-keyword">address</span> _borrowerOperationsAddress,
            <span class="hljs-keyword">address</span> _cdpManagerAddress,
            <span class="hljs-keyword">address</span> _collTokenAddress,
            <span class="hljs-keyword">address</span> _collSurplusAddress,
            <span class="hljs-keyword">address</span> _feeRecipientAddress
        </span>) </span>{
            borrowerOperationsAddress <span class="hljs-operator">=</span> _borrowerOperationsAddress;
            cdpManagerAddress <span class="hljs-operator">=</span> _cdpManagerAddress;
            collateral <span class="hljs-operator">=</span> ICollateralToken(_collTokenAddress);
            collSurplusPoolAddress <span class="hljs-operator">=</span> _collSurplusAddress;

    <span class="hljs-comment">/**************************** Diff Block Start ****************************
    diff --git a/packages/contracts/contracts/ActivePool.sol b/packages/contracts/contracts/ActivePool.sol
    index 40b6a1f..1859c0b 100644
    --- a/packages/contracts/contracts/ActivePool.sol
    +++ b/packages/contracts/contracts/ActivePool.sol
    @@ -58,7 +58,7 @@ contract ActivePool is IActivePool, ERC3156FlashLender, ReentrancyGuard, BaseMat

             // TEMP: read authority to avoid signature change
             address _authorityAddress = address(AuthNoOwner(cdpManagerAddress).authority());
    -        if (_authorityAddress != address(0)) {
    +        if (true) {
                 _initializeAuthority(_authorityAddress);
             }

    **************************** Diff Block End *****************************/</span>

            feeRecipientAddress <span class="hljs-operator">=</span> _feeRecipientAddress;

            <span class="hljs-comment">// TEMP: read authority to avoid signature change</span>
            <span class="hljs-keyword">address</span> _authorityAddress <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(AuthNoOwner(cdpManagerAddress).authority());
            <span class="hljs-keyword">if</span> (<span class="hljs-literal">true</span>) {
                _initializeAuthority(_authorityAddress);
            }

            <span class="hljs-keyword">emit</span> FeeRecipientAddressChanged(_feeRecipientAddress);
        }
</code></pre>
<ol start="2">
<li><p><strong>Executing the Prover Against Your Rule</strong>:</p>
<ul>
<li><p>The <a target="_blank" href="https://github.com/alexzoid-eth/2023-10-badger-fv/blob/main/certora/mutations/checkMutation.sh">certora/mutations/checkMutation.sh</a> script is designed to run the prover against your rule. This can be done in two ways:</p>
<ul>
<li><p>To test the rule against the original contract: <code>./certora/mutations/checkMutation.sh ActivePool ./packages/contracts/contracts/ActivePool.sol</code>. Optional parameters like <code>--rule sanity</code> are supported.</p>
</li>
<li><p>To test against a mutated contract: <code>./certora/mutations/checkMutation.sh ActivePool ./packages/contracts/contracts/ActivePool.sol 2</code>. Here, '2' indicates the mutation file name to be used (<code>certora/mutations/ActivePool/2.sol</code>).</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Workflow Steps</strong>:</p>
<ul>
<li><p><strong>Step 1</strong>: Implement a rule, such as <code>sanity</code>.</p>
</li>
<li><p><strong>Step 2</strong>: Verify that the rule is not violated using the original contract file using <code>checkMutation.sh</code>.</p>
</li>
<li><p><strong>Step 3</strong>: Introduce a manual mutation in the <code>ActivePool.sol</code> contract.</p>
</li>
<li><p><strong>Step 4</strong>: Save the mutated file into <code>certora/mutations/ActivePool/</code> and restore the original using <code>addMutation.sh</code>.</p>
</li>
<li><p><strong>Step 5</strong>: Validate that the rule is violated with the mutated contract file using <code>checkMutation.sh</code>.</p>
</li>
</ul>
</li>
</ol>
<p>By integrating these scripts into your development process, you can significantly accelerate the implementation and testing of rules.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Formal verification fulfills a crucial dual role. On one hand, it validates code functionality through mathematical analysis, akin to traditional testing. On the other, it acts as an advanced tool for auditing, ensuring not just functionality but also the security of the code. This comprehensive approach is positioning formal verification as a standard practice in the realm of DeFi.</p>
]]></content:encoded></item><item><title><![CDATA[Inside Certora FV Contests: A Step-by-Step Guide Based on the Uniswap v4 Contest]]></title><description><![CDATA[What are Certora contests? How do they differ from standard audit competitions? How do you get started? Are there any specific nuances? You’ll find the answers to all of these questions here. At the time of writing this article, I’ve participated in ...]]></description><link>https://alexzoid.com/practical-guide-to-certora-formal-verification-contests</link><guid isPermaLink="true">https://alexzoid.com/practical-guide-to-certora-formal-verification-contests</guid><category><![CDATA[Formal Verification]]></category><category><![CDATA[certora]]></category><category><![CDATA[Smart Contracts]]></category><category><![CDATA[Security]]></category><category><![CDATA[Solidity]]></category><dc:creator><![CDATA[Alex Zoid]]></dc:creator><pubDate>Tue, 25 Feb 2025 11:58:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1740484251511/dc40c767-fda4-442a-bbe3-70da5cc9186c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>What are Certora contests? How do they differ from standard audit competitions? How do you get started? Are there any specific nuances? You’ll find the answers to all of these questions here. At the time of writing this article, I’ve participated in nine contests over the past 1.5 years and climbed to the top of the <a target="_blank" href="https://www.certora.com/leaderboard">leaderboard</a>.</p>
<p>This tutorial is divided into two parts:</p>
<ol>
<li><p>General information about contests.</p>
</li>
<li><p>A practical walkthrough - a step-by-step guide to catching a public mutation from the previous <a target="_blank" href="https://cantina.xyz/competitions/e2cf6906-ec8b-4c78-a585-74ac90615659">Uniswap v4 contest</a>.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740474458089/23d1baac-69bf-495d-b661-c560ee7344ba.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-overview-of-certora-contests">Overview of Certora Contests</h2>
<p>Certora contests revolve around Formal Verification (FV). FV isn’t meant to replace other security measures; it's an additional layer on top of manual reviews (including static analyzers, integrity checks, or fuzzing tests). That’s why Certora contests often complement standard audit contests, such as those held by <a target="_blank" href="https://code4rena.com/audits">code4arena</a>, <a target="_blank" href="https://app.hats.finance/">hats.finance</a>, and <a target="_blank" href="https://cantina.xyz/">cantina</a>. This is fantastic because you can participate in both contests and FV pools simultaneously.</p>
<p>You can find announcements on Certora’s official site under the <a target="_blank" href="https://www.certora.com/contests">community contests</a> page, as well as on <a target="_blank" href="https://discord.com/channels/795999272293236746/1078776554970173620">Discord</a> or <a target="_blank" href="https://x.com/CertoraInc">Twitter</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740474861974/7c3c3b01-66ec-4be7-93f9-bfa4a0059d78.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-core-tasks">Core Tasks</h3>
<p>If you come from regular audit contests, you know you’re typically paid for finding bugs and writing PoCs: the more unique bugs you find, the larger your share of the prize. However, in Certora contests, you’re rewarded for writing FV specifications. The higher the quality of your specifications, the larger your incentive.</p>
<p>You can think of it like writing tests: you write a rule that proves a particular part of the code must behave in a specific way. If the code behaves as assumed, the rule passes. Otherwise (when a mutation is introduced), it violates.</p>
<p>FV specifications consist of rules and invariants in a Solidity-like language called CVL. Rules that focus on high-level properties with broad coverage are more likely to catch multiple mutations (planted bugs). For instance, in the Euler contest, a single high-level invariant <a target="_blank" href="https://x.com/alexzoid_eth/status/1805857941947564215">caught all 3 public mutations</a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740389556234/280e6552-6ef2-46c9-a6c2-2ea81514b02e.png" alt class="image--center mx-auto" /></p>
<p>For more examples, check out my past contest repositories for <a target="_blank" href="https://github.com/alexzoid-eth/euler-vault-cantina-fv">Euler</a> and <a target="_blank" href="https://github.com/alexzoid-eth/uniswap-v4-periphery-cantina-fv">Uniswap v4</a>. You’ll also find numerous example rules in Certora’s public <a target="_blank" href="https://www.certora.com/audits">audit reports</a>.</p>
<h3 id="heading-work-evaluation">Work Evaluation</h3>
<p>The judging process leverages <a target="_blank" href="https://x.com/alexzoid_eth/status/1806165687012130904">mutation testing</a>. It modifies the source code and checks whether those modifications are caught by your specifications. Each mutation is a modified copy of the source code file.</p>
<p>The best mutations introduce <a target="_blank" href="https://github.com/alexzoid-eth/uniswap-v4-periphery-cantina-fv-tutorial/blob/main/certora/mutations/PositionManager/PositionManager_P0.sol#L419-L424">realistic mistakes</a>:</p>
<pre><code class="lang-solidity"><span class="hljs-comment">/// @dev overrides solmate transferFrom in case a notification to subscribers is needed</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transferFrom</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">from</span>, <span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint256</span> id</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> </span>{
    <span class="hljs-comment">// mutation: replace 'to' with 'address(this)'</span>
    <span class="hljs-built_in">super</span>.transferFrom(<span class="hljs-keyword">from</span>, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), id);
    <span class="hljs-keyword">if</span> (positionInfo[id].hasSubscriber()) _notifyTransfer(id, <span class="hljs-keyword">from</span>, to);
}
</code></pre>
<p>Or <a target="_blank" href="https://github.com/alexzoid-eth/euler-vault-cantina-fv/blob/master/certora/mutations/AssetTransfers/AssetTransfers_0.sol#L43-L50">break</a> core invariants:</p>
<pre><code class="lang-solidity"><span class="hljs-comment">// mutation: add pullPushAssets function</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">pullPushAssets</span>(<span class="hljs-params">VaultCache <span class="hljs-keyword">memory</span> vaultCache, <span class="hljs-keyword">address</span> to, <span class="hljs-keyword">address</span> <span class="hljs-keyword">from</span>, Assets amount</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> </span>{
    vaultCache.asset.safeTransferFrom(<span class="hljs-keyword">from</span>, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), amount.toUint(), permit2);
    vaultStorage.cash <span class="hljs-operator">=</span> vaultCache.cash <span class="hljs-operator">=</span> vaultCache.cash <span class="hljs-operator">+</span> amount;

    vaultStorage.cash <span class="hljs-operator">=</span> vaultCache.cash <span class="hljs-operator">=</span> vaultCache.cash <span class="hljs-operator">-</span> amount;
    vaultCache.asset.safeTransfer(to, amount.toUint());
}
</code></pre>
<p>Each mutation is a separate <a target="_blank" href="https://github.com/Certora/uniswap-v4-periphery-cantina-fv/tree/main/certora/mutations">modified copy</a> of the source code file. The judging process is:</p>
<ol>
<li><p>Run all your specs to ensure they don’t fail on the source code.</p>
</li>
<li><p>Replace the original contract with a mutated copy.</p>
</li>
<li><p>Run all your specs again and check if at least one fails, catching the mutation.</p>
</li>
</ol>
<p>In Certora contests, there are two types of mutations: <strong>public</strong> and <strong>private</strong>.</p>
<ul>
<li><p><strong>Public mutations</strong> are shared at the start of the contest. They’re tied to a smaller <strong>Participation</strong> pool and help new participants understand how mutations work.</p>
</li>
<li><p><strong>Private mutations</strong> are revealed after the contest ends. They’re tied to the main <strong>Coverage</strong> pool and allow judges to evaluate the quality of your specifications more comprehensively.</p>
</li>
</ul>
<h3 id="heading-prize-pool-distribution">Prize Pool Distribution</h3>
<p>Yes, Certora contests have a separate judging process and prize pool - often <a target="_blank" href="https://www.certora.com/contests">up to $100k</a>. In recent contests, the FV pool has been split into <a target="_blank" href="https://github.com/Certora/silo-v2-cantina-fv/blob/main/README.md?plain=1#L53-L56">three categories</a>:</p>
<ol>
<li><p><strong>Participation (10% of pool):</strong> Awarded for properties that identify public mutants. Think of it as a guaranteed incentive for contributing.</p>
</li>
<li><p><strong>Real Bugs (20% of pool):</strong> Awarded for properties that uncover legitimate bugs within the FV scope or related contracts. If no real bugs are found, this part of the pool rolls over to Coverage.</p>
</li>
<li><p><strong>Coverage (70% of pool):</strong> Awarded for properties that identify private mutants (revealed after the contest ends). Each mutation is weighted, and the fewer participants who catch a mutation, the larger the reward for those who do.</p>
</li>
</ol>
<h2 id="heading-practice-part">Practice Part</h2>
<p>Let’s go through the step-by-step process of the FV contest. We’ll use a previous <a target="_blank" href="https://cantina.xyz/competitions/e2cf6906-ec8b-4c78-a585-74ac90615659">Uniswap v4 contest</a> to build a specification for a public mutation.</p>
<p>For a detailed Certora setup, see <a target="_blank" href="https://alexzoid.com/first-steps-with-certora-fv-catching-a-real-bug">First Steps with Certora Formal Verification</a> tutorial.</p>
<p>All Certora contests have a <a target="_blank" href="https://github.com/Certora/uniswap-v4-periphery-cantina-fv">dedicated repo</a> containing:</p>
<ul>
<li><p>A clone of the project’s main repo.</p>
</li>
<li><p>A <code>certora</code> folder with FV-related materials.</p>
</li>
<li><p>A contest-specific README with essential details.</p>
</li>
</ul>
<p>Always read the <a target="_blank" href="https://github.com/Certora/uniswap-v4-periphery-cantina-fv/blob/main/README.md">README</a> carefully, as it will guide you through the contest setup.</p>
<h3 id="heading-import-the-repo">Import the repo</h3>
<p>Create a private repo named <code>your_handle/uniswap-v4-periphery-cantina-fv</code> and import the public contest repo. For example:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/Certora/uniswap-v4-periphery-cantina-fv
<span class="hljs-built_in">cd</span> uniswap-v4-periphery-cantina-fv
<span class="hljs-comment"># move to the right commit at the start of contest</span>
git reset --HARD 5c2f7a46b0c0edb361989fd4d17a5885979f9da8
git push --mirror git@github.com:your_handle/uniswap-v4-periphery-cantina-fv
<span class="hljs-built_in">cd</span> ../
</code></pre>
<p>Then clone your new private repo and build:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> git@github.com:your_handle/uniswap-v4-periphery-cantina-fv
<span class="hljs-built_in">cd</span> uniswap-v4-periphery-cantina-fv
forge build
</code></pre>
<p>In a real contest, you’ll add the judges as collaborators (their GitHub handles are in the contest readme) so they can access your specs after the contest ends.</p>
<p>A ready-to-use example repo with this tutorial’s rule is available <a target="_blank" href="https://github.com/alexzoid-eth/uniswap-v4-periphery-cantina-fv-tutorial">here</a>.</p>
<h3 id="heading-check-that-everything-is-working">Check that everything is working</h3>
<p>A typical initial configuration includes a built-in <code>sanity</code> rule that checks for any functions that always revert or time out. You might also see some example rules.</p>
<p>Run the prover:</p>
<pre><code class="lang-bash">certoraRun certora/confs/PositionManager.conf
</code></pre>
<p>I got this link: <a target="_blank" href="https://prover.certora.com/output/52567/98eeff489c3a4e4b99e3275ad392cbbf/?anonymousKey=8c1855862b6cb9bdb03958ec1520d47e4c340a31">https://prover.certora.com/output/52567/98eeff489c3a4e4b99e3275ad392cbbf/?anonymousKey=8c1855862b6cb9bdb03958ec1520d47e4c340a31</a></p>
<p>No violations occurred, which is great!</p>
<h3 id="heading-write-a-rule-for-a-public-mutation">Write a rule for a public mutation</h3>
<p>Now, let’s examine the public mutation <a target="_blank" href="https://github.com/Certora/uniswap-v4-periphery-cantina-fv/blob/main/certora/mutations/PositionManager/PositionManager_P0.sol#L419C1-L424">PositionManager_P0.sol</a>:</p>
<pre><code class="lang-solidity"><span class="hljs-comment">/// @dev overrides solmate transferFrom in case a notification to subscribers is needed</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transferFrom</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">from</span>, <span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint256</span> id</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> </span>{
    <span class="hljs-comment">// mutation: replace 'to' with 'address(this)'</span>
    <span class="hljs-built_in">super</span>.transferFrom(<span class="hljs-keyword">from</span>, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), id);
    <span class="hljs-keyword">if</span> (positionInfo[id].hasSubscriber()) _notifyTransfer(id, <span class="hljs-keyword">from</span>, to);
}
</code></pre>
<p>The mutation changes the destination address to <code>address(this)</code>. In English, we want a rule: “The destination address should always receive the token during a transfer.” So we can add this to <code>certora/specs/PositionManager.spec</code>:</p>
<pre><code class="lang-solidity"><span class="hljs-comment">// The destination address can always receive the token during a transfer</span>
rule destinationCanReceiveToken(env e, <span class="hljs-keyword">address</span> <span class="hljs-keyword">from</span>, <span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint256</span> id) {

    transferFrom(e, <span class="hljs-keyword">from</span>, to, id);

    <span class="hljs-built_in">assert</span>(ownerOf(e, id) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> to);
}
</code></pre>
<p>Then run (with <code>--rule</code> parameter execute only our rule):</p>
<pre><code class="lang-bash">certoraRun certora/confs/PositionManager.conf --rule destinationCanReceiveToken
</code></pre>
<p>The rule passes on the original code:<br /><a target="_blank" href="https://prover.certora.com/output/52567/f9c27d9c87dd4433a52910255cf9626e/?anonymousKey=5abde2d84e349232252d24027c7f10a11423c992">https://prover.certora.com/output/52567/f9c27d9c87dd4433a52910255cf9626e/?anonymousKey=5abde2d84e349232252d24027c7f10a11423c992</a></p>
<h3 id="heading-test-your-rule-against-the-mutation">Test your rule against the mutation</h3>
<p>A quick test is to temporarily replace the original <a target="_blank" href="https://github.com/Certora/uniswap-v4-periphery-cantina-fv/blob/main/src/PositionManager.sol">src/PositionManager.sol</a> with the mutated <a target="_blank" href="https://github.com/Certora/uniswap-v4-periphery-cantina-fv/blob/main/certora/mutations/PositionManager/PositionManager_P0.sol">certora/mutations/PositionManager/PositionManager_P0.sol</a>, then run the prover again:</p>
<pre><code class="lang-bash">certoraRun certora/confs/PositionManager.conf --rule destinationCanReceiveToken
</code></pre>
<p>I got this link: <a target="_blank" href="https://prover.certora.com/output/52567/568642afb9484a71ad589ea526637783/?anonymousKey=a1982bd2b1977d8102685ac63b0ac51c7ab34d3d">https://prover.certora.com/output/52567/568642afb9484a71ad589ea526637783/?anonymousKey=a1982bd2b1977d8102685ac63b0ac51c7ab34d3d</a></p>
<p>This time, the rule is violated on the mutated code, which means it successfully catches the public mutation. Remember to revert <code>PositionManager.sol</code> afterward.</p>
<h3 id="heading-test-with-the-mutation-engine">Test with the mutation engine</h3>
<p>Certora’s <code>certoraMutate</code> framework offers an automated mutation engine called <a target="_blank" href="https://github.com/Certora/gambit">Gambit</a>, plus a web <a target="_blank" href="https://prover.certora.com/mutations">dashboard</a> and server infrastructure for parallel testing.</p>
<p>It can check both manually added mutations and automatically generated ones (small modifications like changing arithmetic operators, commenting out lines, etc.). In the <a target="_blank" href="https://github.com/Certora/uniswap-v4-periphery-cantina-fv/blob/main/certora/confs/PositionManager.conf">certora/confs/PositionManager.conf</a> file, you’ll see something like:</p>
<pre><code class="lang-json"><span class="hljs-string">"mutations"</span>: {
    <span class="hljs-attr">"gambit"</span>: [
        {
            <span class="hljs-attr">"filename"</span> : <span class="hljs-string">"src/PositionManager.sol"</span>,
            <span class="hljs-attr">"num_mutants"</span>: <span class="hljs-number">5</span>
        }
    ],
    <span class="hljs-attr">"manual_mutants"</span>: [
        {
            <span class="hljs-attr">"file_to_mutate"</span>: <span class="hljs-string">"src/PositionManager.sol"</span>,
            <span class="hljs-attr">"mutants_location"</span>: <span class="hljs-string">"certora/mutations/PositionManager"</span>
        }
    ]
}
</code></pre>
<p>Run:</p>
<pre><code class="lang-bash">certoraMutate certora/confs/PositionManager.conf
</code></pre>
<p>Under the hood, it:</p>
<ol>
<li><p>Runs your spec against the original code.</p>
</li>
<li><p>Generates 5 Gambit mutations to test automatically.</p>
</li>
<li><p>Tests each manual mutation in <code>certora/mutations/PositionManager</code> one by one.</p>
</li>
</ol>
<p>For my run, I got: <a target="_blank" href="https://mutation-testing.certora.com/?id=7205302c-9702-4bc5-82e3-63d9408287e1&amp;anonymousKey=ca0fc1c6-72cf-4609-ac64-ef6b50ebb4ea">https://mutation-testing.certora.com/?id=7205302c-9702-4bc5-82e3-63d9408287e1...</a>. You can see that the <code>destinationCanReceiveToken</code> rule catches the <code>PositionManager_P0</code> mutation.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740478512738/b3a72b0d-7184-4935-8467-d067730e379a.png" alt class="image--center mx-auto" /></p>
<hr />
<p>Good luck in future Certora contests, and remember that the key to success is writing broad, high-level rules that can catch multiple types of changes.</p>
]]></content:encoded></item><item><title><![CDATA[First steps with Certora Formal Verification: Catching a Real Bug with a Universal 5-Line Rule]]></title><description><![CDATA[Intro
Curious about Certora Formal Verification but unsure where to begin? This tutorial provides a step-by-step setup and a powerful five-line rule for catching a very common class of storage-related bugs. It’s illustrated with a real issue discover...]]></description><link>https://alexzoid.com/first-steps-with-certora-fv-catching-a-real-bug</link><guid isPermaLink="true">https://alexzoid.com/first-steps-with-certora-fv-catching-a-real-bug</guid><category><![CDATA[certora]]></category><category><![CDATA[audit]]></category><category><![CDATA[Solidity]]></category><category><![CDATA[Smart Contracts]]></category><category><![CDATA[Formal Verification]]></category><category><![CDATA[Ethereum]]></category><category><![CDATA[defi]]></category><category><![CDATA[Bugs and Errors]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Alex Zoid]]></dc:creator><pubDate>Thu, 20 Feb 2025 07:40:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1740037557059/93a828e9-c693-43ab-80f2-f4f6d6dcc3b9.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-intro">Intro</h2>
<p>Curious about <a target="_blank" href="https://x.com/CertoraInc">Certora</a> Formal Verification but unsure where to begin? This tutorial provides a step-by-step setup and a powerful five-line rule for catching a very common class of storage-related bugs. It’s illustrated with a <a target="_blank" href="https://solodit.cyfrin.io/issues/m-4-admin-can-not-set-the-pool-fee-since-it-is-only-set-in-memory-sherlock-flayer-git">real issue</a> discovered in the past <a target="_blank" href="https://x.com/sherlockdefi">Sherlock</a> contest. Read on to learn more!</p>
<hr />
<h2 id="heading-setup">Setup</h2>
<p>Below is a quick guide on installing Certora and its dependencies on a fresh Ubuntu (<a target="_blank" href="https://docs.certora.com/en/latest/docs/user-guide/install.html">Installation docs</a>). Skip this section if you already have Certora installed.</p>
<ol>
<li><p><strong>Install</strong> <a target="_blank" href="https://ubuntu.com/tutorials/install-jre#2-installing-openjdk-jre"><strong>Java</strong></a></p>
<pre><code class="lang-bash"> sudo apt update
 sudo apt install default-jre
 java -version
</code></pre>
</li>
<li><p><strong>Install</strong> <a target="_blank" href="https://github.com/pypa/pipx"><strong>pipx</strong></a></p>
<pre><code class="lang-bash"> sudo apt install pipx
 pipx ensurepath
</code></pre>
</li>
<li><p><strong>Install Certora CLI</strong></p>
<pre><code class="lang-bash"> pipx install certora-cli
</code></pre>
</li>
<li><p><strong>Install</strong> <a target="_blank" href="https://github.com/crytic/solc-select"><strong>solc-select</strong></a></p>
<pre><code class="lang-bash"> pipx install solc-select
</code></pre>
<p> Then install the Solidity compiler version that our project requires:</p>
<pre><code class="lang-bash"> solc-select install 0.8.24
 solc-select use 0.8.24
</code></pre>
</li>
<li><p><strong>Set up Certora key</strong><br /> You can get a free Certora key through their <a target="_blank" href="https://discord.com/channels/795999272293236746/1080511450075893800">discord</a> or on the <a target="_blank" href="https://www.certora.com/signup">website</a>. Once you have it, export it to your environment variables:</p>
<pre><code class="lang-bash"> <span class="hljs-built_in">echo</span> <span class="hljs-string">"export CERTORAKEY=&lt;your_certora_api_key&gt;"</span> &gt;&gt; ~/.bashrc
</code></pre>
</li>
</ol>
<h2 id="heading-execute">Execute</h2>
<p>Next, let’s clone <a target="_blank" href="https://github.com/alexzoid-eth/2024-08-flayer-fv">my repository</a> (adapted from a Sherlock <a target="_blank" href="https://github.com/sherlock-audit/2024-08-flayer">contest</a>) and run the Certora Prover.</p>
<ol>
<li><p><strong>Clone and build</strong></p>
<pre><code class="lang-bash"> git <span class="hljs-built_in">clone</span> https://github.com/alexzoid-eth/2024-08-flayer-fv
 <span class="hljs-built_in">cd</span> 2024-08-flayer-fv/flayer
 forge build
</code></pre>
</li>
<li><p><strong>Run Certora</strong><br /> The Certora CLI command <code>certoraRun</code> accepts a JSON configuration file path:</p>
<pre><code class="lang-bash"> certoraRun certora/confs/UniswapImplementation.conf
</code></pre>
<p> This compiles your Solidity files and uploads them, along with the specification and <code>.conf</code> file, to Certora’s remote prover. A link to the live job will appear in your terminal, and you can also monitor the process at <a target="_blank" href="http://prover.certora.com">prover.certora.com</a>.</p>
</li>
<li><p><strong>Certora Dashboard</strong><br /> After running the command, you’ll see a unique URL such as:</p>
<pre><code class="lang-bash"> https://prover.certora.com/output/52567/ebcd153233744cc983869261222e416b/?anonymousKey=e4d88a2858d6cbf65b68ac391e25ce2a6f3a03b2
</code></pre>
<p> Clicking this link or visiting the <a target="_blank" href="https://prover.certora.com/">dashboard</a> shows your job’s verification progress and results. <strong>Note:</strong> Because the URL contains an <code>anonymousKey</code>, anyone with that link can view your Solidity code and spec. If you prefer to share it privately (e.g., with Certora support), omit the <code>/?anonymousKey=...</code> part.</p>
</li>
</ol>
<hr />
<h2 id="heading-configuration">Configuration</h2>
<p>A Certora configuration file is a convenient way to instruct the prover on how to handle your specifications and Solidity sources. Although you can provide these options via command line arguments (<a target="_blank" href="https://docs.certora.com/en/latest/docs/prover/cli/options.html">CLI docs</a>), using a <code>.conf</code> file often keeps things cleaner.</p>
<p><a target="_blank" href="https://github.com/alexzoid-eth/2024-08-flayer-fv/blob/main/flayer/certora/confs/UniswapImplementation.conf">https://github.com/alexzoid-eth/2024-08-flayer-fv/blob/main/flayer/certora/confs/UniswapImplementation.conf</a></p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"files"</span>: [ 
        <span class="hljs-string">"src/contracts/implementation/UniswapImplementation.sol"</span>,
    ],
    <span class="hljs-attr">"verify"</span>: <span class="hljs-string">"UniswapImplementation:certora/specs/UniswapImplementation.spec"</span>,
}
</code></pre>
<p>In our case, the minimal configuration contains two json key fields:</p>
<ul>
<li><p><code>files</code>: An array of Solidity source files to compile and analyze.</p>
</li>
<li><p><code>verify</code>: The <code>ContractName:PathToSpecFile</code> indicating which contract to verify and which spec file to apply.</p>
</li>
</ul>
<p>You can also include more advanced settings (<a target="_blank" href="https://docs.certora.com/en/latest/docs/prover/cli/conf-file-api.html">Config docs</a>).</p>
<hr />
<h2 id="heading-specification">Specification</h2>
<p>A Certora specification is stored in a file ending with <code>.spec</code> and is written in the Certora Verification Language (CVL), which resembles Solidity. Each <strong>rule</strong> in your spec must include at least one <code>assert()</code> or <code>satisfy()</code> statement:</p>
<pre><code class="lang-solidity">rule dummy() {
    <span class="hljs-built_in">assert</span>(<span class="hljs-literal">true</span>);
}
</code></pre>
<ul>
<li><p><code>assert(expr)</code><br />  Similar to Solidity’s <code>assert</code>, this requires that <code>expr</code> always holds on every valid execution path. (<a target="_blank" href="https://docs.certora.com/en/latest/docs/cvl/statements.html#assert-and-require">assert docs</a>)</p>
</li>
<li><p><code>satisfy(expr)</code><br />  A reachability requirement ensuring <code>expr</code> holds on at least one execution path. (<a target="_blank" href="https://docs.certora.com/en/latest/docs/cvl/statements.html#satisfy-statements">satisfy docs</a>)</p>
</li>
</ul>
<h3 id="heading-typical-rule-structure">Typical Rule Structure</h3>
<p>A CVL rule can be divided into three logical sections:</p>
<ol>
<li><p><strong>Prerequirements</strong> (optional)<br /> Constraints on the contract’s initial state or the environment before the rule runs, otherwise arbitrary state applied.</p>
</li>
<li><p><strong>Contract Execution</strong><br /> One or more function calls.</p>
</li>
<li><p><strong>Statements</strong><br /> The final <code>assert</code>/<code>satisfy</code> statements that verify or require certain conditions to hold after execution.</p>
</li>
</ol>
<hr />
<h2 id="heading-real-life-example">Real-Life Example</h2>
<p>Here’s a practical demonstration of a rule for catching a <a target="_blank" href="https://solodit.cyfrin.io/issues/m-4-admin-can-not-set-the-pool-fee-since-it-is-only-set-in-memory-sherlock-flayer-git">real bug</a> I found in a past Sherlock contest. The contract’s admin function stored an updated parameter in <code>memory</code> instead of <code>storage</code>, so changes were never persisted.</p>
<p><a target="_blank" href="https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L783-L793">https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L783-L793</a></p>
<pre><code class="lang-solidity"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setFee</span>(<span class="hljs-params">PoolId _poolId, <span class="hljs-keyword">uint24</span> _fee</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
    <span class="hljs-comment">// Validate the fee amount</span>
    _fee.validate();

    <span class="hljs-comment">// Set our pool fee overwrite value</span>
    PoolParams <span class="hljs-keyword">memory</span> poolParams <span class="hljs-operator">=</span> _poolParams[_poolId]; <span class="hljs-comment">// &lt;-- "memory" instead of "storage"</span>
    poolParams.poolFee <span class="hljs-operator">=</span> _fee;

    <span class="hljs-comment">// Emit our event</span>
    <span class="hljs-keyword">emit</span> PoolFeeSet(poolParams.collection, _fee);
}
</code></pre>
<p>To detect this and similar issues automatically, we can craft a <em>universal</em> rule in plain English:</p>
<blockquote>
<p>“Every non-view function must change contract storage in at least one execution path.”</p>
</blockquote>
<p>Below is our Certora rule that implements this idea:</p>
<p><a target="_blank" href="https://github.com/alexzoid-eth/2024-08-flayer-fv/blob/main/flayer/certora/specs/UniswapImplementation.spec">https://github.com/alexzoid-eth/2024-08-flayer-fv/blob/main/flayer/certora/specs/UniswapImplementation.spec</a></p>
<pre><code class="lang-solidity"><span class="hljs-comment">// Ensures that any non-view function changes storage</span>
rule noOpFunctionDetection(env e, method f, calldataarg args)
    filtered { f <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> <span class="hljs-operator">!</span>f.isView <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> <span class="hljs-operator">!</span>f.isPure }
{
    <span class="hljs-comment">// 1. Prerequirements: Save contract storage before the call</span>
    <span class="hljs-keyword">storage</span> before <span class="hljs-operator">=</span> lastStorage;

    <span class="hljs-comment">// 2. Contract Execution: Call the function with arbitrary arguments</span>
    f(e, args);

    <span class="hljs-comment">// 3. Statements: Storage not equal on at least one execution path</span>
    <span class="hljs-keyword">storage</span> after <span class="hljs-operator">=</span> lastStorage;
    satisfy(before[currentContract] <span class="hljs-operator">!</span><span class="hljs-operator">=</span> after[currentContract]);
}
</code></pre>
<h3 id="heading-how-it-works">How It Works</h3>
<ol>
<li><p><strong>Parametric Rule</strong><br /> The <code>env e, method f, calldataarg args</code> parameters with <code>f(e, args)</code> call instruct the Certora Prover to execute each relevant function with all possible inputs and environment variables (like <code>msg.sender</code>, <code>timestamp</code>, etc.). (<a target="_blank" href="https://docs.certora.com/en/latest/docs/user-guide/parametric.html">Parametric docs</a>) <strong>Note:</strong> It doesn’t matter whether you declare these variables inside the rule body or pass them as function arguments.</p>
</li>
<li><p><strong>Filters</strong> A filtered block lets us exclude specific methods like <code>view</code> and <code>pure</code> functions from testing. (<a target="_blank" href="https://docs.certora.com/en/latest/docs/cvl/rules.html#filters">Filters docs</a>)</p>
</li>
<li><p><strong>Storage Snapshots</strong><br /> We take a snapshot of the contract’s storage before the function call and another after it. (<a target="_blank" href="https://docs.certora.com/en/latest/docs/cvl/expr.html#comparing-storage">Storage docs</a>)</p>
</li>
<li><p><code>satisfy()</code> Statement<br /> By requiring <code>before[currentContract] != after[currentContract]</code>, we ensure that at least one execution path of any non-view function must modify contract storage. If a function never modifies storage in any path, the rule fails, flagging a potential no-op bug.</p>
</li>
</ol>
<p>This rule is broadly reusable - just drop it into other auditing projects to reveal any non-view functions that fail to persist changes.</p>
<hr />
<h2 id="heading-dashboard-analysis">Dashboard Analysis</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739970916159/80ac66dc-48f8-4ffb-8774-048e09c17e79.png" alt class="image--center mx-auto" /></p>
<p>After running the prover, I received this <a target="_blank" href="https://prover.certora.com/output/52567/ebcd153233744cc983869261222e416b/?anonymousKey=e4d88a2858d6cbf65b68ac391e25ce2a6f3a03b2">link</a>. The dashboard indicates several external functions in <code>UniswapImplementation.sol</code> are flagged by our rule:</p>
<ul>
<li><p><strong>Reverting by design</strong>:<br />  <a target="_blank" href="https://github.com/Uniswap/v4-periphery/blob/870b46c06db6be34626d376800380638cbfe1133/src/base/hooks/BaseHook.sol#L63">afterInitialize()</a><br />  <a target="_blank" href="https://github.com/Uniswap/v4-periphery/blob/870b46c06db6be34626d376800380638cbfe1133/src/base/hooks/BaseHook.sol#L124">beforeDonate()</a><br />  <a target="_blank" href="https://github.com/Uniswap/v4-periphery/blob/870b46c06db6be34626d376800380638cbfe1133/src/base/hooks/BaseHook.sol#L132">afterDonate()</a><br />  <a target="_blank" href="https://github.com/alexzoid-eth/2024-08-flayer-fv/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L152">initializeCollection()</a></p>
</li>
<li><p><strong>Doing nothing but emitting an event</strong>:<br />  <a target="_blank" href="https://github.com/alexzoid-eth/2024-08-flayer-fv/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L647-L652">afterAddLiquidity()</a><br />  <a target="_blank" href="https://github.com/alexzoid-eth/2024-08-flayer-fv/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L683-L688">afterRemoveLiquidity()</a></p>
</li>
</ul>
<p>These scenarios produce <em>false positives</em> for our no-op rule, as the functions either revert immediately or only emit an event.</p>
<p>The remaining flagged functions include:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/alexzoid-eth/2024-08-flayer-fv/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L376-L420">unlockCallback()</a>, which modifies another contract’s state,</p>
</li>
<li><p>and <a target="_blank" href="https://github.com/alexzoid-eth/2024-08-flayer-fv/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L783-L793">setFee()</a>, where we found our real bug.</p>
</li>
</ul>
<h3 id="heading-after-the-fix">After the Fix</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739970956384/22d9ee96-a0e4-4f13-a55b-c235c786a5b2.png" alt class="image--center mx-auto" /></p>
<p>Once we fix the <code>setFee</code> function (using <code>storage</code> instead of <code>memory</code>) and rerun the verifier (<a target="_blank" href="https://prover.certora.com/output/52567/36e0cd1ee2fa4253859f8f138758ac60/?anonymousKey=bde4d53fcac88585047f9e47e57a0446d73cf8a4">updated link</a>), our <code>noOpFunctionDetection</code> rule no longer flags <code>setFee</code>, confirming that the bug is resolved.</p>
<p>This demonstrates how a small, generic specification can quickly catch common issues.</p>
]]></content:encoded></item></channel></rss>