7Block Labs
Smart Contracts

ByAUJay

Smart Contract Design, Smart Contract Verification, and Smart Contract Formal Verification Best Practices


Why this matters now

With the Dencun upgrade coming in March 2024 and all the new changes in Solidity and verification tools, some of those “old but gold” patterns are now a bit risky to use. Take EIP‑6780, for instance--it’s pretty much put the brakes on SELFDESTRUCT for contracts that are meant to last a while, which messes up those CREATE2-redeploy upgrade patterns. If you're still using that approach, it’s time to switch to proxy or diamond architectures.

Then there's EIP‑1153, which brought in low-cost transient storage for state that’s used per transaction--super handy for things like reentrancy locks. And let’s not forget MCOPY (EIP‑5656), which has really shifted how we should think about memory-heavy code. So, make sure your design and verification plans take all this into account. Check out more details over at (blog.ethereum.org).


Part I -- Design best practices that age well in 2026

1) Architecture choices post‑Dencun

  • Just a heads up: don’t depend on SELFDESTRUCT for that “reset and redeploy at the same address” trick anymore. Since March 13, 2024, it doesn’t wipe out code or storage for contracts made in earlier transactions--it only moves ETH now. For upgrades, you’ll need to stick with standard proxies (like Transparent or UUPS/1967) or go for a diamond setup (EIP‑2535). (blog.ethereum.org)
  • The diamond pattern (EIP‑2535) works great for larger, modular systems. If you’re planning to use it, make sure to set up DiamondCut access control, include a loupe facet, and keep your storage strategy consistent (think AppStorage or per-facet diamond storage) to steer clear of any storage collisions. (eips.ethereum.org)

Practical Guardrails:

  • Keep an eye on DiamondCut events and make sure to limit upgrades to governance windows if your product or governance model permits it. Check out more info here.
  • Make sure to document your storage strategy, like using AppStorage at slot 0, and stick to an order-only append policy for all your state structs. You can read up on that here.

2) Leverage new EVM features for security and gas

  • Transient Storage (EIP‑1153): You can use TSTORE and TLOAD for reentrancy locks and per-transaction allowances. The cool thing is they clear out at the end of each transaction, which means you don’t have to worry about the hassle of storage refunds. If you’re migrating to Solidity 0.8.28 or later, remember to budget for these transient state variables of value types. Check it out here: (eips.ethereum.org).
  • MCOPY (EIP‑5656): Say goodbye to those manual memory copy loops and the tedious identity-precompile roundtrips! With MCOPY, you can handle large memory operations like encoding/decoding and ABI packing much more efficiently. Get the details here: (eips.ethereum.org).
  • PUSH0 (EIP‑3855): This one's a gem! Compiler backends are using PUSH0 to create smaller bytecode and cut down on gas costs. Make sure to stick with modern compilers that target Cancun or newer versions for the best results. More info can be found here: (eips.ethereum.org).
  • Beacon Block Root in EVM (EIP‑4788): If you're building trust-minimized bridges, staking products, or anything related to restaking, you can now reference beacon roots through on-chain contracts to verify consensus states using Merkle proofs. Just be sure to plan out your data structures and verification costs in advance! Dive deeper here: (eips.ethereum.org).

3) Compiler, EVM versioning, and project defaults

  • Make sure to pin the compiler and EVM version in your CI. Starting with 0.8.25, the default EVM is “cancun”. Then, 0.8.26 introduced require(bool, Error) for custom errors through IR; 0.8.28 opened up transient storage variables; and 0.8.29 brought in the experimental EOF (Osaka) backend along with some nifty SMTChecker upgrades. Remember to use via‑IR and upgrade solc intentionally. (soliditylang.org)
  • Keep a solid storage layout discipline: generate and compare the storage layout for every PR before any upgrades. You can use Foundry’s forge inspect storageLayout alongside solc’s JSON output to help gate merges. (learnblockchain.cn)

Here’s a handy snippet for your foundry.toml profile that you might find useful:

[profile.default]
optimizer = true
optimizer_runs = 200

[profile.dev]
optimizer = false

[profile.test]
optimizer = true
optimizer_runs = 100

This setup gives you a solid foundation for your different profiles. Just adjust the settings based on what you're working on!

[profile.default]
evm_version = "cancun"
via_ir = true
ast = true
build_info = true
extra_output = ["storageLayout"]
ffi = true  # needed by some upgrade validators

Foundry CLI - inspect Command

The inspect command in Foundry CLI is super handy for diving into the details of your smart contracts. It helps you peek under the hood and gather all sorts of useful information.

Usage

Here’s how you use it:

forge inspect <contract> [options]

Options

  • --json: This will give you output in JSON format, which is great for further processing.
  • --help: Need a little more guidance? Just add this option, and you’ll get a nice help message.

Example

Let’s say you've got a contract called MyContract. You can inspect it by running:

forge inspect MyContract

This will pull up all sorts of cool details about MyContract, helping you understand it better.

For more specifics and advanced use cases, check out the official Foundry documentation.

4) Upgradeability patterns with safety rails

  • Go for UUPS proxies if you want to keep your deployments sleek; don’t forget to run OZ Upgrades validations and storage layout checks in your CI. When it comes to diamonds, stick to strict cut permissions and make sure to publish those loupe interfaces. (docs.openzeppelin.com)
  • Make sure to kick off Slither upgradeability checks early on; they can catch ID collisions, initializer misuse, and layout drift as you move between versions. (github.com)

5) Operational controls built‑in

  • Set up pause and guardian roles that come with clear limits. We need to demonstrate that the “pause” function can stop risky flows while still allowing for emergency withdrawals. Check out the details in Part III.
  • Get the monitoring plan ready for launch. Just a heads up: OpenZeppelin Defender is winding down (new signups were turned off on June 30, 2025, and it's set to shut down completely on July 1, 2026). We should transition to the open-source Monitor/Relayer or look into other options, and make sure to connect it to Forta alerts. (blog.openzeppelin.com)

Part II -- Verification you can ship: static + dynamic + runtime specs

The aim here isn't just to "run all the tools." It's really about "proving the properties that matter for your protocol's business logic" and then automating that proof in your Continuous Integration (CI) setup.

1) Static analysis, first

  • Slither baseline: Make sure to run this during pre-commit with a well-thought-out allowlist. Don’t forget to add slither-check-upgradeability for your proxies and diamonds. Check it out here.
  • Mythril: An awesome tool for diving into EVM bytecode, particularly useful for areas with inline assembly or custom Yul code. Remember to use Docker to keep things consistent. More info can be found here.

2) Fuzzing and invariants that simulate mainnet chaos

  • Foundry invariants: It's a good idea to bump up the depth settings beyond the usual defaults for your stateful systems. Make sure to set explicit target selectors to cover those risky sequences. Plus, don’t forget to enable call_override to test reentrancy surfaces. Key settings to pay attention to include runs, depth, fail_on_revert, dictionary_weight, and include_storage. (learnblockchain.cn)
  • Coverage: Go ahead and use forge coverage, but keep in mind its limitations--like assembly lines and forked tests potentially underreporting results. It's smart to keep a stable version of Foundry in your CI pipeline to steer clear of those pesky nightly regressions. (github.com)
  • Echidna: Think about incorporating property-based fuzzing for those protocol invariants that really need long random sequences and shrinking. You can easily hook it up in your CI using the official GitHub action. (github.com)

Example Echidna Property:

Echidna is a testing tool for smart contracts, and writing properties is a great way to ensure your contract behaves as expected. Here’s a simple example of how you might define a property for an Echidna test.

Property: Total Supply

Let’s say we have a token contract, and we want to make sure the total supply doesn’t exceed a certain limit. Below is a sample property you might write:

pragma solidity ^0.8.0;

contract Token {
    uint256 public totalSupply = 1000000;

    function mint(uint256 amount) public {
        totalSupply += amount;
    }
}

Now, here’s how you’d define a property for this:

// Echidna property to check total supply
property total_supply_within_limits {
    @public
    function echidna_total_supply_within_limits() public view returns (bool) {
        return totalSupply <= 1000000;
    }
}

This simple property checks that the total supply of tokens never goes over 1,000,000. If you run Echidna with this property, it’ll help catch any issues where you might accidentally mint too many tokens.

Summary

Using Echidna to test properties like this helps keep your smart contracts safe and reliable. Just make sure to write properties that cover the most critical behaviors of your contract!

/// @notice total supply equals sum of balances
function echidna_supply_invariant() public view returns (bool) {
    return token.totalSupply() == sumBalances();
}

(github.com)

3) Runtime specification checks with Scribble

Annotate your Solidity code with invariants and postconditions that can be checked during fuzzing and testing. This approach helps you get a head start on writing specifications and provides better insights for auditors. For more details, check out this blog post from ConsenSys Diligence.

Example Scribble Annotations:

  • Annotation 1: This is where I noted some key points that really stood out to me while reading. It’s always helpful to highlight the main ideas!
  • Annotation 2: I jotted down a quick reminder to check out this topic later. You never know when you might need it!
  • Annotation 3: Here’s a little side note that popped into my head. It’s great to capture those random thoughts before they slip away.
  • Annotation 4: This part is super interesting! I made sure to underline it, so I can revisit it easily when I need to.

Feel free to use these examples to get inspired for your own annotations!

/// invariant totalSupply() == __sum(balances);
/// if_succeeds {:msg "onlyOwner"} msg.sender == owner();
function mint(address to, uint256 amount) external { ... }

(diligence.consensys.io)

4) Solidity SMTChecker in CI

Enable the model checker to demonstrate simple safety properties across unbounded inputs and transaction sequences. This includes areas like arithmetic, bounds, and certain reentrancy patterns. Begin with assertions and assumptions, then expand step by step. You can dive into more details here.

Example CI Step (solc >= 0.8.29 Suggested):

Here's a quick look at a CI step you might want to set up, especially if you're working with solc version 0.8.29 or later:

steps:
  - name: Install dependencies
    run: npm install

  - name: Run Solidity compiler
    run: npx solc --version

  - name: Compile contracts
    run: npx truffle compile

Feel free to tweak this setup to match your project's needs!

solc --model-checker-engine chc \
     --model-checker-targets assert,overflow,outOfBounds,balance \
     --model-checker-timeout 600 \
     --via-ir --evm-version cancun \
     --standard-json < compile.json

(soliditylang.org)


Part III -- Formal verification that moves the needle

When the contract handles important stuff like asset custody, liquidations, and governance, it’s a good idea to bump it up to formal verification. This ensures that the proofs are solid across all states and sequences.

1) Certora Prover (EVM)

  • Create CVL rules that ensure safety properties and implement parametric rules to cover all public methods. With CLI v5.0, parametric rules are now applied by default across contracts, which really ups your coverage game. The latest update in 8.5.1 also brings in Sui support and improves spec ergonomics. Check it out here: (docs.certora.com)

Example CVL Rule (Supply Conservation):

sum(inflow) = sum(outflow)

This rule simply states that the total amount of inflow must equal the total amount of outflow. In other words, everything that comes in has to go out, ensuring that we're not running a deficit or surplus. It's all about keeping things balanced!

rule totalSupply_is_sum_of_balances(env e) {
  require e.msg.sender != address(0);
  mathint sum = 0;
  forall (address a) { sum += token.balanceOf(a); }
  assert token.totalSupply() == sum;
}

(Certora Docs)

Tips to Steer Clear of False Confidence

  • Make sure to use multi_assert_check to pinpoint any counterexamples for each assertion. Check out the details in the docs.certora.com for more info.
  • It's a good idea to model your external dependencies clearly. For example, think about the underlying ERC‑20 token that can change its balances without your contract being involved. Using v5's parametric coverage is great for catching those sneaky cross‑contract violations. You can read more about it here.

2) Move Prover (Aptos/Sui)

For targets that aren’t EVM-based, Move's spec language (MSL) and prover provide thorough proofs on modules. You can use struct invariants, function invariants, and global invariants to ensure asset conservation and manage access control. Check it out here: (aptos.dev)

Example MSL Invariant:

Invariant Example = 
    forall x: Int :: (x > 0) ==> (x + 1 > x)

This simple invariant states that for all integers x, if x is greater than 0, then x + 1 is also greater than x. It’s a straightforward way to demonstrate how MSL can be used to express certain properties about numbers.

spec module MyToken {
  invariant forall a: address where exists<Coin>(a): global<Coin>(a).value >= 0;
}

(aptos.dev)

3) What to prove, concretely

  • Asset accounting: We make sure that totalSupply equals the sum of all balances, and there’s absolutely no room for any unauthorized minting or burning of tokens.
  • Withdrawal/repayment math: We pay close attention to rounding and fee paths. Just a heads up, MCOPY‑based encoders work just like our reference ones. Check it out here: (eips.ethereum.org).
  • Invariant liveness under pause: When we hit pause, it blocks all mints and transfers. But don’t worry--emergencyWithdraw will always work, no matter what.
  • Upgrade safety: We ensure that the storage layout is preserved, the initializer can only be called once, and there are no pesky function ID collisions. You can read more about it here: (github.com).

Part IV -- Monitoring, response, and lifecycle security

  • Alerts: You can subscribe to Forta bots, whether it’s for the general or premium feeds, to keep an eye on governance, access control, shady activity, and any financial oddities. Integration is super easy via Slack, Telegram, or Webhooks, plus you can tap into the GraphQL API too. And if you’re looking to publish your own bots, don’t forget to stake! (docs.forta.network)
  • Platform Shift: With Defender wrapping up operations in 2026, it's time to think ahead and plan those migrations to OpenZeppelin’s open-source Monitor/Relayer or set up your own infrastructure. Keep an eye on migration guides and timelines, and make sure they're all in your runbooks. (blog.openzeppelin.com)
  • Incident Drills: Don’t forget to draft, practice, and monitor those diamondCut/proxy upgrade windows. Pre-stage your pause guardians and run “break-glass” transactions on forks before they hit the mainnet.

Part V -- Concrete examples and checklists

A) Transient storage reentrancy lock (Solidity ≥ 0.8.28)

// Pseudocode: use transient storage to auto-clear at tx end
contract Vault {
    transient bool locked; // 0.8.28+

    modifier nonReentrant() {
        require(!locked, "reentrant");
        locked = true;
        _;
        // no manual clear needed; resets at end-of-tx per EIP-1153
    }
}

(eips.ethereum.org)

B) Foundry invariant campaign hardening

[profile.default]
# raise exploration for stateful protocols
[invariant]
runs = 10_000
depth = 50
fail_on_revert = true
call_override = true
dictionary_weight = 90

(getfoundry.sh)

C) Slither and upgrade checks in CI

slither . --exclude-informational
slither-check-upgradeability . ProxyName --proxy-filename ./contracts --proxy-name TransparentUpgradeableProxy

(github.com)

D) Echidna in GitHub Actions

- uses: crytic/echidna-action@v2
  with:
    files: src/
    contract: Invariants
    config: echidna.yaml
    format: json

(github.com)

E) Storage layout diff gate

  • Use the command forge inspect storageLayout on both the old and new implementations. Make sure the diff is append-only. You can automate this process with the OZ Upgrades Foundry plugin validations. Check it out here: learnblockchain.cn.

Common 2026 pitfalls you can avoid

  • If you’re still counting on SELFDESTRUCT for upgrades or ETH-burn patterns, heads up--EIP-6780 has changed the game. It’s time to switch over to proxies or diamonds. Check it out here.
  • Don’t let transient storage go to waste! If you’re still using SSTORE/SLOAD with reentrancy locks or per-tx allowances, consider making the switch to TSTORE/TLOAD. It’ll save you gas and minimize those annoying refund surprises. More details here.
  • Keep an eye on Dencun behavior! The blob gas changes (EIP-4844) are impacting L2 cost models, so don’t just hardcode fee assumptions into your on-chain accounting. Make sure to track blobbasefee via EIP-7516 if it's relevant for your protocol. Get more info here.
  • Staying updated with compiler changes is crucial! With features rolling out in 0.8.26 like custom-error requires, 0.8.28's transient variables, and 0.8.29's experimental EOF, remember to pin your versions, test them, and upgrade on purpose. Details can be found here.
  • If you’re relying on hosted Defender for the long haul, now’s the time to plan your migration. Look into open-source Monitor/Relayer or other alternatives before July 1, 2026. You can read more about it here.

Executive roll‑out plan (what to ask your team for next sprint)

  • Architecture decision record: We're weighing the pros and cons of using Proxy versus Diamond patterns. This includes our storage strategy, the governance window for upgrades, and how we’ll monitor our endpoints. Check it out here: (eips.ethereum.org).
  • Tooling baseline in CI: We’ve set up some solid tools for continuous integration, including Slither for upgradeability checks, Foundry tests coupled with invariants that have coverage, and Echidna properties for our top three invariants. Plus, we have an SMTChecker job and storageLayout diff gates to keep everything in check! More info here: (github.com).
  • Formal spec backlog: We’re looking at creating 5-10 CVL rules to cover our core value flows. Parametric rules are enabled, and we’ll be running nightly tests on forks to make sure everything’s good to go. Dive into the details here: (docs.certora.com).
  • Monitoring & response: We're using Forta subscriptions to stay on top of governance, ACLs, and any suspicious activities. There's also the OpenZeppelin Monitor migration playbook to guide us, and we’re practicing pause/guardian drills on forks to ensure we're prepared. Learn more here: (docs.forta.network).

Closing note

Design, verification, and formal methods should be a seamless process rather than a step-by-step approach. With the Dencun-era EVM updates and the latest compilers, you've got fresh building blocks at your disposal--make sure to use them intentionally. Combine these tools with invariant-driven tests and machine-verified specifications. The result? Not just a reduction in issues; you'll also enjoy quicker shipping times and greater confidence in your releases.

7Block Labs is here to help you focus on the properties that really count, craft specs that make a difference, and set up a CI pipeline that validates everything with every change.

Get a free security quick-scan of your smart contracts

Submit your contracts and our engineer will review them for vulnerabilities, gas issues and architecture risks.

7BlockLabs

Full-stack blockchain product studio: DeFi, dApps, audits, integrations.

7Block Labs is a trading name of JAYANTH TECHNOLOGIES LIMITED.

Registered in England and Wales (Company No. 16589283).

Registered Office address: Office 13536, 182-184 High Street North, East Ham, London, E6 2JA.

© 2026 7BlockLabs. All rights reserved.