web3.path

PHASE 17 EVM production · 16–24 hours

Production Contract Engineering

This is the bridge between “I can write Solidity” and “I can safely change a protocol repository under review.” Every section ends in evidence that belongs in a pull request, not just a tutorial screenshot.

Goal — produce a reproducible, verified, threat-modelled EVM release with unit, fuzz, invariant, fork, static-analysis, and deployment evidence.

1. Standard toolchain: Foundry first

Hardhat remains useful for plugins and some frontend stacks. For contract development, make Foundry the default feedback loop: forge for build/test/scripts, cast for RPC inspection, and anvil for a local node.

mkdir vault-protocol && cd vault-protocol
forge init --force
forge install OpenZeppelin/openzeppelin-contracts
forge test -vvv
anvil --chain-id 31337

# Pin the fork block: reproducible tests, not a moving-mainnet test.
export ETH_RPC_URL="https://your-archive-rpc"
forge test --fork-url "$ETH_RPC_URL" --fork-block-number 22000000 -vvv
Rule — do not call an RPC-dependent test “deterministic” unless it pins both the chain and block number. Keep endpoint secrets outside git.

2. Contract design as an API

Design itemWhat must be explicitEvidence
AuthorityWho can pause, upgrade, mint, change oracle, or move funds; which actions use a Safe/timelockAuthority map + tests for every role
Asset accountingUnits, decimals, rounding direction, accounting invariant, maximum lossNatSpec + invariant tests
External callsTrusted/untrusted callbacks, failure policy, token compatibility, reentrancy boundaryInteraction matrix + adversarial mocks
EventsState transitions needed by users/indexers; indexed filters; no secretsEvent assertions + ABI docs
Upgrade decisionImmutable or proxy; upgrade authority; migration and rollback planStorage report + simulation

Use interfaces, libraries, custom errors, NatSpec, immutable, constant, and small cohesive contracts. Know receive/fallback, ABI encoding, logs/topics, call, delegatecall, and CREATE2 before reviewing a protocol that uses them.

2b. EVM execution & gas diagnosis

# Opcode-level trace for a failing tx
cast run $TX_HASH --rpc-url $ETH_RPC_URL

# Gas report per function
forge test --gas-report

# ABI calldata edge case: dynamic types offset must be word-aligned
cast calldata "swap(uint256,address,bytes)" 100 $ADDR 0x

Lab: labs/phase-17-vault/ — starter implements cap/pause; solution includes CREATE2 factory + CI.

3. Test pyramid: examples are not enough

// test/Vault.t.sol
function testFuzz_DepositWithdrawConservesAssets(uint96 rawAssets) public {
    uint256 assets = bound(uint256(rawAssets), 1, 1_000_000e6);
    deal(address(usdc), alice, assets);
    vm.startPrank(alice);
    usdc.approve(address(vault), assets);
    uint256 shares = vault.deposit(assets, alice);
    uint256 received = vault.redeem(shares, alice, alice);
    vm.stopPrank();
    assertLe(received, assets); // fees/rounding may lower, never create assets.
}

function invariant_totalAssetsCoversRedeemableShares() public view {
    assertGe(vault.totalAssets(), vault.convertToAssets(vault.totalSupply()));
}

4. Secure integration patterns

5. Upgradeability and deterministic deployment

Default to immutable contracts. If an upgrade is justified, choose UUPS or transparent proxies deliberately; test initialization exactly once, authorization, storage-layout compatibility, and the post-upgrade invariant. For deterministic deployments, record the CREATE2 deployer, salt, init-code hash, expected address, and constructor/config artifact.

# Fails CI when a storage-layout change is unsafe.
forge inspect src/Vault.sol:Vault storage-layout > artifacts/vault-storage-layout.json
forge snapshot
slither . --exclude-dependencies
forge test --match-path "test/invariant/*" --fuzz-runs 10000

6. Client, account abstraction, and service boundaries

7. Practical lab — release a guarded ERC-4626 vault

Deliverable
  1. Build an ERC-4626 vault around a mock USDC using OpenZeppelin. Add a deposit cap, AccessControl roles, two-step admin transfer, pause, and a withdrawal queue/pull-payment path where appropriate.
  2. Write NatSpec for public/external methods plus a one-page authority map and invariant list.
  3. Implement unit, fuzz, handler-invariant, and pinned-fork tests. Include donation, reentrancy-callback, unauthorized upgrade, non-standard-token, and EIP-712 replay cases.
  4. Run Slither and triage every finding. Add a gas snapshot and coverage report; do not treat coverage percentage as security proof.
  5. Deploy with a versioned Foundry script to Sepolia or Base Sepolia, verify source, transfer admin to a Safe, and save deploy inputs/addresses/transaction hashes in the repository.

8. Definition of done

Checklist — a reviewer can clone the repository, install pinned dependencies (toolchain), run all tests, reproduce the deployment address/bytecode, see verified source, understand every privileged action, and reproduce the monitoring setup without asking for private context. Launch drill: docs/LAUNCH_DRILL.md.
← Phase 16Phase 18: Protocol Engineering →