7Block Labs
Blockchain Development

ByAUJay

web3 contract development service: Writing Specs That Prevent Audit Ping-Pong

Short description: Stop wasting weeks in back‑and‑forth with auditors. This guide shows exactly how to write audit‑ready smart‑contract specifications in 2025—mapped to current Ethereum upgrades, concrete invariants, machine‑checkable requirements, and CI hooks—so your first audit pass sticks.


Why audit ping‑pong happens (and how to kill it)

Audit “ping‑pong” is the multi‑week cycle of questions about vague requirements, misread threat models, and moving invariants. You stop it by handing auditors a spec that:

  • encodes intent as testable properties (not prose),
  • matches today’s chain semantics and toolchain, and
  • ships with reproducible tests and upgrade procedures.

Below is a battle‑tested spec blueprint aligned to the 2024–2025 changes teams most often miss.


What changed in 2024–2025 that your spec must reflect

  • Blobs and blob fees are live and bigger now. EIP‑4844 added blob transactions; Dencun hit mainnet on March 13, 2024. Pectra (May 7, 2025) increased blob throughput (target 6, max 9 blobs) and adjusted fee responsiveness; calldata also got a floor price for data‑heavy txs via EIP‑7623. Your gas/DA assumptions, fee formulas, and rollup settlement specs must capture this. (blog.ethereum.org)
  • Solidity defaults and features changed. Solidity 0.8.30 set EVM version “prague” as default (Pectra), after 0.8.29 introduced experimental EOF support and custom storage layouts. Specs should pin compiler/EVM versions and state EOF usage explicitly. (soliditylang.org)
  • Upgradeability and storage layout got safer patterns. Namespaced storage (ERC‑7201) plus compiler support from 0.8.20 helps avoid collisions across facets/modules. Reference the annotation in code and spec. (eips.ethereum.org)
  • AA keeps evolving. ERC‑4337 is mainstream; validation rules and native‑AA proposals (e.g., RIP‑7560, EIP‑7702 “set code for EOAs”) affect wallet/signature assumptions. Specs must call out which path you support. (docs.erc4337.io)
  • Opcode semantics changed. SELFDESTRUCT behavior changed (EIP‑6780) and transient storage (EIP‑1153 TLOAD/TSTORE) exists; some “old patterns” no longer apply. Specs must forbid relying on legacy destruct semantics and must bound transient‑storage usage. (eips.ethereum.org)
  • BLS12‑381 precompiles (EIP‑2537) shipped with Pectra; if you verify BLS sigs on L1, your spec should account for these precompiles and their gas. (eips.ethereum.org)

The 7Block Labs audit‑ready spec template

Copy these sections into your internal PRD. Each line prevents a common audit stall.

  1. Problem statement and non‑goals
  • Define the minimal business objective and what is explicitly out of scope (e.g., “no cross‑chain minting v1”).
  1. Canonical external references
  • List exact standards you implement: ERC‑20/721/6909/4337/2612 etc., with links and any deltas. (eips.ethereum.org)
  1. Chain, EVM, compiler matrix
  • Example: “Mainnet, EVM=prague, Solidity 0.8.30; no EOF bytecode in v1.” Include any L2s and differences. (soliditylang.org)
  1. Threat model (SCSVS + EthTrust mapping)
  • Map risks to requirements using SCSVS v2 and EthTrust v3 IDs; include an exceptions register if you accept residual risks. (github.com)
  1. Roles, auth, and upgrade governance
  • Owner/admin/signer model, timelocks, Safe multisig thresholds, emergency procedures, and the exact upgrade flow (UUPS/Transparent/Beacon; who signs what, in what Safe, with which delay). Reference Defender/Upgrades plugin usage. (docs.openzeppelin.com)
  1. Storage layout and upgrade rules
  • Declare ERC‑7201 namespaces used, base slots, and “no reordering/no shrinking” rules. Commit to running storage‑layout validation in CI on every PR. (eips.ethereum.org)
  1. Invariants (machine‑checkable)
  • Encode as properties you’ll test with Foundry invariants/Echidna. Provide math assertions (e.g., supply conservation) and safety properties (e.g., “no external call before state write” except X). (learnblockchain.cn)
  1. Economic and fee assumptions
  • If you rely on calldata, show how EIP‑7623 floors affect your costs. If you rely on blobs, state your blob budget and failover logic. If using blobbasefee onchain, specify usage. (eips.ethereum.org)
  1. Event model and off‑chain integration
  • Every state‑changing path emits an event; specify event names/args and idempotent replays.
  1. Testing and CI hooks
  • Include Foundry commands, fuzz parameters, replay of counterexamples, static analysis (Slither), and coverage targets. (getfoundry.sh)
  1. Deployment and migration plan
  • Scripts, dry‑run networks, and rollback criteria. If upgradeable, include oz‑upgrades validation steps. (docs.openzeppelin.com)
  1. Observability and on‑call
  • Prominent metrics/events to monitor, alert thresholds, pause/unpause runbooks.

Make your spec machine‑checkable: examples you can copy

Below are spec snippets paired with code/test scaffolds that auditors can run without interpretation.

A. Upgradeable contract with namespaced storage (UUPS + ERC‑7201)

Spec fragment

  • Pattern: UUPS via ERC1967Proxy. Access: only UpgradeAdmin multisig can upgrade via _authorizeUpgrade.
  • Storage: Namespaced via ERC‑7201. No variable reordering; new fields append only; gaps reserved.
  • CI: On each PR, run OpenZeppelin storage‑layout validation; reject if incompatible.
  • Rollback: Use Defender proposeUpgrade; require 2/3 multisig and 48h timelock. (docs.openzeppelin.com)

Code stub

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

// @custom:storage-location erc7201:MyApp.Storage.V1
struct MyStorageV1 {
    uint128 feeBps;
    address treasury;
    uint256[46] __gap;
}

// Minimal UUPS pattern (OpenZeppelin Contracts Upgradeable recommended for prod)
abstract contract UUPSLike {
    function _authorizeUpgrade(address) internal virtual;
}

contract MyModule is UUPSLike {
    // keccak256(keccak256("MyApp.Storage.V1") - 1) & ~bytes32(uint256(0xff))
    bytes32 private constant SLOT = 0x...; // compute per EIP‑7201

    function _s() internal pure returns (MyStorageV1 storage $) {
        assembly { $.slot := SLOT }
    }

    function setFee(uint128 bps) external {
        require(bps <= 1000, "fee too high");
        _s().feeBps = bps;
    }

    function _authorizeUpgrade(address) internal override {
        require(msg.sender == 0xMultiSig..., "not admin");
    }
}

CI snippet (Foundry + oz‑upgrades)

# foundry.toml
[profile.default]
ffi = true
ast = true
build_info = true
extra_output = ["storageLayout"]
# Validate storage layout compatibility on PR
npx oz-upgrades validate --build-info ./out/build-info --contracts src/**/*.sol

Why auditors like this: it codifies storage rules (7201) and proves you actually run the same validation they will. (eips.ethereum.org)


B. ERC‑6909 “minimal multi‑token” instead of ERC‑1155

Spec fragment

  • Standard: ERC‑6909. No callbacks; hybrid operator/approval model.
  • Invariants:
    • For each id: sum(balances[id]) == totalSupply[id].
    • Operators cannot transfer > allowance for (owner,id).
    • Mint/burn only via MINTER_ROLE.
  • Events: Transfer, Approval per id.

Rationale: ERC‑6909 cuts complexity that often causes audit issues in 1155 callbacks. Include these invariants as Foundry tests. (eips.ethereum.org)


C. Permits in 2025: pick ERC‑2612 vs. Permit2 and document risks

Spec fragment

  • If token you control: implement ERC‑2612 verbatim; include EIP‑712 domain separator details and nonces.
  • If interacting with arbitrary tokens: use Uniswap’s Permit2 (address 0x000000000022D473030F116dDEE9F6B43aC78BA3) for consistent UX, but enumerate signature‑phishing risks and approval scopes in the spec and UI copy.
  • Tests: EOA secp256k1 signatures and ERC‑1271 contract signatures should both pass when applicable. (eips.ethereum.org)

Why auditors like this: it ties your auth surface to a standard and forces you to specify signature formats and replay protections.


D. Account Abstraction (4337) with future‑proofing

Spec fragment

  • Current path: ERC‑4337 with EntryPoint v0.x, supports UserOperation validation per ERC‑7562 rules; state acceptance criteria: simulation determinism, max verification gas, and anti‑grief constraints.
  • Future path: note EIP‑7702/RIP‑7560 implications; upgrades to native AA require a migration doc and entrypoint deprecation plan. (docs.erc4337.io)

E. Dencun/Pectra fee semantics baked into acceptance criteria

Spec fragment

  • If your contracts estimate blob vs. calldata path onchain, require:
    • use of block.blobbasefee where applicable,
    • calldata floor accounted via EIP‑7623,
    • reject operations when DA cost exceeds a governance cap (specify cap and unit).
  • Add post‑upgrade playbook to adjust DA caps after EIP‑7691 shifts fee dynamics. (eips.ethereum.org)

Turn invariants into code the auditor can run

Foundry makes your spec executable: every property below is “green or red,” not negotiable prose.

Core Foundry config

# foundry.toml
[profile.default]
fuzz = { runs = 1000 }
invariant = { runs = 128, depth = 128 }
ffi = true
ast = true
build_info = true
extra_output = ["storageLayout"]

Property examples

// Supply conservation for ERC-6909
function invariant_totalSupplyMatchesBalances(uint256 id) public {
    uint256 sum;
    address[] memory holders = registry.holders(id);
    for (uint256 i; i < holders.length; i++) sum += token.balanceOf(holders[i], id);
    assertEq(sum, token.totalSupply(id));
}

// No-external-call-before-state-write (simple CEI guard sample)
function invariant_noExternalBeforeState() public {
    vm.recordLogs();
    hevm.prank(attacker);
    try system.doSomething() {} catch {}
    Vm.Log[] memory logs = vm.getRecordedLogs();
    assertTrue(orderingChecker.logsShowStateWriteBeforeExternal(logs));
}

Helpful Foundry features for reproducibility:

  • Rerun failures only:
    forge test --rerun
  • Persist fuzz/invariant counterexamples and auto‑replay next runs
  • Inline per‑test configs (e.g., bump runs/depth on heavy invariants) (foundry-book.zksync.io)

Static + Fuzzing:

  • Run Slither for detectors/prints in CI.
  • Use Echidna for property fuzzing of invariants that require multi‑step sequences; port crashes back into Foundry with fuzz‑utils. (github.com)

Storage and upgrade acceptance tests (the #1 audit stall)

Make these checks part of your “Definition of Done”:

  • Storage layout diff passes (no reorders/shrinks); namespaced slots declared for each module (ERC‑7201).
  • oz‑upgrades “validate” passes in CI; PRs that break layout fail.
  • UUPS/Transparent proxy choice documented;
    _authorizeUpgrade
    enforces multisig + delay; Defender proposeUpgrade used for approvals. (docs.openzeppelin.com)

Tip: Include a “storage map appendix” with slot addresses per namespace and growth strategy. Auditors will quote it back to you.


Security baselines auditors expect now

  • SWC coverage: Run tools against the SWC registry categories and note any false positives. Provide a mapping table in your spec appendix. (github.com)
  • EthTrust v3 and SCSVS v2: Declare certification level targets (S/M/Q) and list exceptions with justifications. (entethalliance.org)
  • Opcode semantics: Forbid relying on SELFDESTRUCT cleanup (documented), and cap transient storage usage; include tests. (eips.ethereum.org)

Deliver an “audit packet” instead of just code

Hand auditors a zip (or repo tag) containing:

  • Spec PDF/MD with all sections above.
  • Threat model with SCSVS/EthTrust mapping and an exceptions register. (github.com)
  • Test artifacts:
    forge test
    output, gas snapshots, coverage, invariant/fuzz configs, and replayed counterexamples. (learnblockchain.cn)
  • Static analysis reports: Slither JSON and any custom detectors you wrote. (github.com)
  • Upgrade packet: storage layout JSON, oz‑upgrades validation logs, Defender proposal URLs (or IDs) for staging. (docs.openzeppelin.com)

This turns review from “interpret intent” into “verify properties.”


Common spec anti‑patterns that guarantee ping‑pong (and how to fix)

  • “Implements ERC‑20 with permits” with no signature details. Fix: cite ERC‑2612, paste your EIP‑712 domain, and provide test vectors. If using Permit2, pin the address and spell out approval scopes and signature expiry rules. (eips.ethereum.org)
  • “Upgradeable proxy” with no governance flow. Fix: name the proxy pattern, admin roles, and the exact Defender/Safe process. (docs.openzeppelin.com)
  • “Will optimize calldata costs” with no Pectra math. Fix: show how EIP‑7623 floors interact with your payload sizes; if you ever switch to calldata from blobs, specify thresholds and fail‑closed behavior. (eips.ethereum.org)
  • “Uses AA” without validation rules. Fix: state ERC‑4337 conformance, EntryPoint version, and ERC‑7562 validation‑phase guarantees; flag future EIP‑7702 migration strategy. (docs.erc4337.io)
  • “Deletes contracts to reclaim gas.” Fix: explicitly prohibit relying on SELFDESTRUCT for state cleanup and show the cleanup function you actually use. (eips.ethereum.org)

Brief, in‑depth example: Rollup settlement contract spec hooks post‑Pectra

  • Blob DA budgeting: “The contract enforces blob posting when
    blobbasefee * blobsNeeded <= maxBlobSpend
    , else reverts and requires off‑chain retry. No calldata fallback in v1.”
  • Fee monitoring: “An on‑chain view exposes the effective ceiling from current
    block.blobbasefee
    for frontends; ops adjusts
    maxBlobSpend
    via timelocked governance.”
  • Tests: fuzz across blob fee volatility; fail when calldata would be cheaper than blobs under EIP‑7623 thresholds (guarded by a revert). (eips.ethereum.org)

Your next steps (copy/paste)

  • Add the twelve sections above to your PRD.
  • Wire the CI hooks (Foundry + oz‑upgrades + Slither).
  • Encode your invariants as Foundry/Echidna tests before hand‑off.
  • Produce the audit packet and tag the commit you’ll send.

If you want help, 7Block Labs runs “Spec‑to‑Audit” engagements: we write/retrofit your spec to the template, wire up the tests and oz‑upgrades validation, and package the audit kit so first‑pass findings are substantive, not clarifications.


References for the 2025 spec landscape

  • Dencun/Pectra timelines and EIPs: EF blog; EIP‑4844 (blobs), EIP‑7691 (blob throughput), EIP‑7623 (calldata cost). (blog.ethereum.org)
  • Solidity 0.8.29/0.8.30 releases (EOF experiments, prague default). (soliditylang.org)
  • ERC‑7201 Namespaced Storage; ERC‑6909 Minimal Multi‑Token. (eips.ethereum.org)
  • ERC‑4337 AA core docs; native AA proposals (RIP‑7560), EIP‑7702 (EOA code). (docs.erc4337.io)
  • EIP‑1153 Transient storage; EIP‑6780 SELFDESTRUCT change. (eips.ethereum.org)
  • BLOBBASEFEE opcode (EIP‑7516). (eips.ethereum.org)
  • Upgrades: OpenZeppelin Upgrades plugins, storage‑layout validation, Defender workflows. (docs.openzeppelin.com)
  • Security baselines: SWC registry; SCSVS v2; EthTrust v3. (github.com)
  • Testing: Foundry fuzz/invariant docs; Echidna fuzzer. (learnblockchain.cn)
  • Permits: ERC‑2612, EIP‑712, ERC‑1271, Uniswap Permit2 docs and address. (eips.ethereum.org)

Like what you're reading? Let's build together.

Get a free 30‑minute consultation with our engineering team.

Related Posts

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.

© 2025 7BlockLabs. All rights reserved.