Blanc/Deployed bytecode/Curve 3Crv

Curve 3Crv.

The liquidity-provider token of Curve’s 3pool for DAI, USDC and USDT: 2,276 bytes compiled by Vyper 0.2.4 in September 2020. The pool is its minter; it mints 3Crv to depositors and burns it on withdrawal.

Blanc proves that every successful call to these bytes, for every selector, is exactly a step of a token model — storage, events and return bytes included; that after any valid history the storage is that model run over exactly the committed calls; and that supply always equals the sum of balances. A model proved to match the bytes is also reliable documentation, and this one records a policy a modern ERC-20 user might not expect.

ArtifactCurve 3CrvEthereum mainnet
Address
0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490
Runtime
2,276 bytes; Keccak-256 0xb731b7a9a74c6c60d715f0c791d663f1530743a48337e7fab9cf3481afdd9feb
Compiler
Vyper 0.2.4
Created
block 10,809,467 (6 September 2020), transaction 0xa7d9…94f8, deployer nonce 42
Proofs
Blanc/Lift/Curve3Crv/, 9,745 lines of Lean

Safety, after any history

Exactly the committed calls, and conservation.

After any configured history from a checkpoint where the bytes are installed and the storage abstracts a model state, the code is intact and the final storage abstracts the state reached by running the model over exactly the settlement-committed calls to the token’s writers, in order — and that state conserves supply: totalSupply equals the sum of balances. The statement is short enough to read whole:

theorem c3crv_history_committed_derived {ca : Adr} {cfg : ChainConfig}
    {checkpoint future : BlockChain} {initial : Blanc.Curve3Crv.State}
    {initialKeys : Key → Prop}
    (trace : ConfiguredHistoryTrace cfg checkpoint future)
    (installed : some (checkpoint.state.getCode ca).toList = c3crvSem.image)
    (invariant : VyInv (checkpoint.state.getStor ca) initial initialKeys)
    (fresh : FreshKeys initialKeys (historyTouchedKeys ca trace)) :
    some (future.state.getCode ca).toList = c3crvSem.image ∧
      ∃ s, InvRun initial (committedInvocations ca trace) s ∧
        runInvocations initial (committedInvocations ca trace) = some s ∧
        VyInv (future.state.getStor ca) s
          (Key.extend initialKeys (invocationKeys (committedInvocations ca trace))) ∧
        Blanc.Curve3Crv.Conserved s

Besides the history itself, two hypotheses about the start and one about the hashes. fresh is trace-local: the mapping rows the history touches — rolled-back frames included — are tracked already or land on unused slots, and distinct rows land on distinct slots. No filter on static frames is needed, because no writer can succeed in one: every writer reaches an SSTORE or a LOG3, which a static frame forbids, and Blanc proves it from the frame’s own run (c3crv_writer_nonstatic).

Refinement

Every successful call is a step of the model.

From a fresh entry, a successful frame of the deployed runtime is exactly the model’s step for the decoded call. For a writer, the storage abstraction moves to the model’s next state, the logs are the model’s events, every other account’s storage is untouched, and the return bytes are the model’s: the ABI word true for transfer, transferFrom, approve, mint and burnFrom, empty for set_minter and set_name; the step for set_name also consumes the minter’s answer to owner(). For a view, storage and logs are unchanged and the return bytes equal the model’s output. Unknown selectors and short calldata have no successful run (c3crv_frame_refines).

Documentation that cannot drift

A token’s real policy.

Each of the following is a theorem about the model, carried to the bytes by the refinement theorems above.

theorem transferFrom_spends_max_allowance {ctx : Ctx} {f d v : B256} {s : State}
    {out : Out} (h : step ctx (.transferFrom f d v) s = .ok out)
    (hm : ctx.sender ≠ s.minter) (hv : v ≠ 0) :
    out.1.allowances f.toAdr ctx.sender ≠ s.allowances f.toAdr ctx.sender

Constructed executions

Live, at an exact cost.

A call the model accepts — any function but set_name — is a real execution ending in the model’s step, with exact logs and return bytes, at an exact cost: there is a constant c such that for every residual G above the 2,300-gas call stipend, the call succeeds when given G + c and leaves exactly G (c3crv_step_exec). The proofs construct each c; the exported theorem names no formula. The same holds at the state any configured history reaches (c3crv_history_live). set_name asks the minter’s owner() and is live given the callee premise OwnerCallOk, that the answer names the caller.

Where a history can start

Deployment.

Under every covered fork, the recorded creation input, run as a CREATE from the recorded deployer at nonce 42, installs the runtime at the token’s mainnet address and leaves storage that abstracts the model’s initial state — name, symbol, 18 decimals, zero supply, and the deployer as minter — with no hash premise: the constructor’s write of 0 at keccak(3 ‖ caller) is discharged by kernel evaluation (curve_deploy_covered). The recorded deployment therefore satisfies the history theorem’s checkpoint predicate. It is a modeled deployment: the 2020 transaction’s inclusion is not proved.

Stated, so it is not inferred

Not claimed.

  • A closed cost formula; the cost is exact but existential.
  • Logs and return bytes at the history level; they are stated frame by frame.
  • set_name without the owner-call premise; transaction-level liveness.
  • That today’s 3Crv storage abstracts a model state: that needs the holders and allowance pairs recovered from its Transfer and Approval logs, and a complete enumeration of its storage showing nothing else is stored.

We found no earlier machine-checked verification of this contract. The claim map’s 3Crv section lists every theorem with its premises, file and line.