EIP

Building ERC-TRUST from Executable Semantics to Solidity

23 Oct 2026 · 19 min read

An ERC number does not mean its code was checked against its text. To make that check possible, ERC-TRUST changed its design: legacy forced transfers run only behind an exact-use ticket, regulatory state has one owner, the reference kernel cannot be upgraded, and ERC-3643 support is an evidence-backed profile. A FREEZE pilot then carried one action down to bytecode.

TL;DR

  • An ERC number does not mean the standard was verified: nothing in Ethereum's standards process checks by machine that a standard's text and its code behave alike.
  • To make that kind of Solidity formal verification possible, ERC-TRUST changed its own design first: an exact-use ticket in front of legacy forced transfers, one owner for regulatory state, a reference kernel that cannot be upgraded, and ERC-3643 support that counts only with evidence.
  • A pilot carried FREEZE from the verified model down to compiled bytecode, and when its first remote proof run passed only 2 of 13 rules, we corrected the model until all 13 passed without weakening any of them.
  • That settles the design layer; a proof covering every compiled execution is still ahead.

What an ERC Number Does Not Check

For a security token implementer, the trouble starts with one call. Both ERC-7943 and ERC-3643 define forcedTransfer(from, to, amount) [1, 2]. The calldata does not say whether the tokens are moving into court-ordered custody, being permanently taken in a confiscation, going back to the owner they were stolen from, or being sold off in a liquidation to repay a debt.

Those four have different legal effects. Custody can be released later, the other three close the case, and each one has to leave its own record. On chain they all look like the same balance movement, and neither an indexer nor an auditor can tell from the code which of them happened. ERC-8319 gives names to six such legal effects [3], and ERC-TRUST is the standard proposal that turns those names into typed execution.

An ERC number makes this gap easy to overlook, because many readers assume that a numbered standard was verified somewhere along the way. EIP-1, which defines the standards process, describes a Standards Track proposal as a design document, an implementation, and, where warranted, an update to the formal specification. No step in that process checks by machine that the text and the running code behave alike; implementations are usually checked with tests and audits. ERC-TRUST set out to verify the standard itself. Its normative sentences, machine-readable core, generated interfaces, reference implementation, runtime bytecode, conformance evidence, and remaining assumptions are treated as one auditable chain, an approach we described on Ethereum Research as whole-standard assurance.

The first stretch of that chain, told in Formalizing ERC-TRUST from Normative Rules to Executable Semantics, ended with an abstract model verified in Isabelle/HOL within its declared semantic domain. At that point we had a proved specification and no contract. Most Solidity formal verification starts from the other end: the code already exists, and the verifier writes a specification to fit it. Starting from the proved side changed what counted as a good design. A contract structure now had to be something the tools could prove before it could be something convenient, and five decisions came out differently because of that.

Five Design Changes Made for the Proof

A Legacy Call Does Not Say Which Action It Is

We decided early that the token would never guess a regulatory action from a legacy call. A rule that told seizure from confiscation by function name or argument shape could be written down, but it could never be proved, because the call carries nothing to prove it against. So setFrozenTokens and forcedTransfer left the public authority surface, and both selectors now run only as calls the token makes to itself.

A regulatory action arrives instead as a typed command from the account registered for its authority. The token validates the command in a fixed order, assesses the dependencies bound to it, and records one exact-use ticket: the command identifier, the selector, the calldata hash, and the dependency root and epoch current at preparation. When the token then calls its own sensitive selector in the same transaction, the selector compares every recorded field with the actual call and the current dependency state, and only on a full match does it consume the ticket and apply the transition. Reuse, altered calldata, a direct external call, a wrong selector, or a dependency change in between all revert with TrustRouteMismatch [4].

Exact-use route, reference implementation Even a registered authority cannot call the two sensitive selectors directly: a typed command validated in the same transaction leaves a ticket for exactly one call, and every call that does not match it reverts with nothing changed.
How a typed command reaches a sensitive ERC-7943 selector A registered authority has two ways toward the sensitive ERC-7943 selectors, setFrozenTokens and forcedTransfer. Its typed command goes to executeERC7943Action or executeERC7943Reversal, which runs eleven checks in a fixed order, from the domain through the action rules to a last check of the route that carries the request, and then assesses the bound inputs. The wrapper records one route ticket that holds the command identifier, the selector, the calldata hash, the dependency root and the epoch, and is good for one call in the same transaction. In that same transaction the token calls its own selector, which compares every ticket field with the call and the current dependency state. When every field matches, the ticket is consumed and the transition is applied, and the canonical receipt is emitted as the last log. Every other path joins one line that is rejected at the selector: the authority's direct external call, reuse of a consumed ticket, altered calldata, a wrong selector, and a dependency changed between preparation and the call. All of them revert with TrustRouteMismatch; the endpoint stays exactly as it was and nothing is stored or consumed. Registered authority typed command direct external callEvery other callreusealtered calldatawrong selectordependency changedrejected at the selector executeERC7943Action or executeERC7943Reversal 11 checks, fixed order domain action rules route then assesses bound inputs records one ticket self-call,same transaction Route ticket one call, same transaction command id selector calldata hash dependency root epoch setFrozenTokens forcedTransfer compares every field with this call and the dependency state consumed every field matches Ticket consumed, transition applied Canonical receipt, emitted as the last log Revert TrustRouteMismatch endpoint exactly as it was nothing stored or consumed
Registered authority
executeERC7943Actionor executeERC7943Reversal11 checks in fixed order, from domain through action rules to the route · then bound inputs assessed
Route ticketone call, same transactioncommand id · selector · calldata hash · dependency root · epoch
setFrozenTokens · forcedTransfer
Ticket consumed, transition appliedcanonical receipt as the last log
RevertTrustRouteMismatchendpoint exactly as it was · nothing stored
Figure 1How a Typed Command Reaches a Sensitive ERC-7943 Selector

The ticket did not start out this tight. In the pilot, a prepared route could wait for its matching call; the reference implementation later narrowed it to a ticket that lives only inside one transaction and never becomes standing authority. Because of that narrowing, an implementer can expose the ERC-7943 interface without handing its selectors to anyone. It also turns the route into something a verifier can state and prove: a wrong caller or altered calldata can neither use nor corrupt someone else's ticket.

Regulatory State Has Exactly One Owner

Where regulatory state lives had to be settled before any storage layout, because the answer decides what a proof can say. If a registry and a token each kept a copy of the same frozen amount, no proof could tell which copy is true; a proof can only make statements about one store. So a registry stays upstream, as a source of evidence and policy and as a router, and the token or an adapter is the single owner of regulatory state and receipts.

Pen sketch for Solidity formal verification: two frozen-amount copies crossed out, one owner circled

Two copies of one frozen amount, crossed out: regulatory state got one owner.

Today's native token owns ERC-20 balances, frozen targets and restriction flags, cases and their overlays, custody records, action records and receipts, replay keys, authorities, and dependency bindings, as its architecture document lists. Its four dependencies, for policy, identity, settlement, and entitlement, are read-only. Each binding fixes a dependency's address, runtime code identity, configuration digest, schema, and per-kind epoch, and the four fold in a fixed order into one dependency root; rebinding any of them advances a global epoch by one. A command carrying a stale root or epoch is rejected before any state is read, and a call fails closed when the code, configuration, return length, outcome word, command echo, or binding echo does not match.

A dependency upgrade therefore never slips in quietly. The epoch moves, and commands prepared under the old epoch stop executing. The same rule spares the proofs an awkward assumption, since failures can be classified without guessing how the return values of unresolved external calls relate to one another.

Why the Reference Kernel Cannot Be Upgraded

This decision cost the most. Five candidates were on the table: a single monolithic contract, a minimal immutable core with versioned modules and adapters, a UUPS or transparent proxy, a Diamond, and an interface-only standard with a modular reference implementation. We weighed them on how small the standard surface stays, whether upgrade authority can invalidate proofs, storage and selector risk, what a static prover and symbolic execution can express, compatibility with ERC-7943 and ERC-3643, gas and deployment limits, and the operational load on implementers.

A proxy was the familiar answer, and it stayed on the list until we counted what each upgrade would reopen. Initialization, storage layout, the frame of every function, and the upgrade path itself would all have to be proved again after every upgrade, and one admin key could void what had already been proved. A Diamond widens the proof surface further through dynamic routing and storage aliasing, which a surface this thin cannot justify. The monolith kept pulling policy and identity into the standard surface, and in the end it fit in exactly one place: a small pilot with a narrowed boundary between pure decisions and state application.

What we chose combines two layers. The standard layer is interface-only, so semantics, ABI, and observable results become normative while the structure stays neutral. The reference deployment uses a minimal immutable core, the kernel, with versioned, fail-closed adapters.

Two layers, five candidates The standard fixes only the interface and its meaning, and the reference deploys an immutable kernel; a proxy or a Diamond would reopen what was already proved at every change.
Five architecture candidates and what each would ask of the proof Five candidates for the ERC-TRUST architecture, compared qualitatively, without scores. On top sits the standard layer, an interface-only standard with a modular reference implementation: semantics, ABI and observable results are normative and the structure stays neutral. It was chosen. Below it, four structures for the reference deployment are drawn as schematic lanes over a deployment's life, each meeting the same two changes. A filled bar means the proof holds; a dashed bar means it is reopened and has to be proved again. Minimal immutable core with versioned adapters, chosen: at each change a rebind advances the epoch by one and only that binding's evidence briefly reopens, so the bar stays almost whole; a kernel change means a new deployment, and every reference endpoint reports proxySupported = false. Single contract, pilot only: each policy change means migrating state and redeploying, so the bar breaks and is proved again; it also meets the 24 KB size limit, and policy and identity leak into the standard surface. UUPS or transparent proxy, which conforms only if fully re-verified after each upgrade: every upgrade reopens the initialization, storage, frame and upgrade obligations, and one admin key can void what was proved. Diamond, dropped: every facet change reopens the proof over a wider surface because of dynamic routing and storage aliasing. The final design combines the two chosen layers; it is a proof-cost decision for the reference, not a rule that every conforming implementation must be immutable. Standard layer Interface-only standard, modular reference semantics, ABI and observable results normative · structure neutral Chosen Reference deployment candidates Minimal immutable core + versioned adapters Chosen rebind: epoch +1, only that binding’s evidence reopens kernel change: a new deployment · proxySupported = false Single contract Pilot only policy change: migrate state and redeploy 24 KB size limit · policy and identity leak into the standard surface UUPS or transparent proxy Conforms only if re-verified upgrade: initialization, storage, frame and upgrade reopen one admin key can void what was proved Diamond Dropped facet change: dynamic routing and storage aliasing widen it change change a deployment’s life, schematic proof holds reopened, proved again Five architecture candidates and what each would ask of the proof Five candidates for the ERC-TRUST architecture, compared qualitatively, without scores. On top sits the standard layer, an interface-only standard with a modular reference implementation: semantics, ABI and observable results are normative and the structure stays neutral. It was chosen. Below it, four structures for the reference deployment are drawn as schematic lanes over a deployment's life, each meeting the same two changes. A filled bar means the proof holds; a dashed bar means it is reopened and has to be proved again. Minimal immutable core with versioned adapters, chosen: at each change a rebind advances the epoch by one and only that binding's evidence briefly reopens, so the bar stays almost whole; a kernel change means a new deployment, and every reference endpoint reports proxySupported = false. Single contract, pilot only: each policy change means migrating state and redeploying, so the bar breaks and is proved again; it also meets the 24 KB size limit, and policy and identity leak into the standard surface. UUPS or transparent proxy, which conforms only if fully re-verified after each upgrade: every upgrade reopens the initialization, storage, frame and upgrade obligations, and one admin key can void what was proved. Diamond, dropped: every facet change reopens the proof over a wider surface because of dynamic routing and storage aliasing. The final design combines the two chosen layers; it is a proof-cost decision for the reference, not a rule that every conforming implementation must be immutable. Standard layer Chosen Interface-only standard, modular reference semantics, ABI and observable results normative · structure neutral Reference deployment candidates Minimal immutable core + versioned adapters Chosen rebind: epoch +1, only that binding’s evidence reopens kernel change: a new deployment proxySupported = false Single contract Pilot only policy change: migrate state and redeploy 24 KB size limit; policy and identity leak into the standard UUPS or transparent proxy Conforms only if re-verified upgrade: initialization, storage, frame and upgrade reopen one admin key can void what was proved Diamond Dropped facet change: dynamic routing and storage aliasing widen it change change a deployment’s life, schematic proof holds reopened, to be proved again
Figure 2Five Architecture Candidates and What Each Would Ask of the Proof

This sets the proof cost of the reference implementation and nothing more. It does not require every conforming implementation to be non-upgradeable [4]. A proxy-based implementation can still conform, provided it re-verifies fully after each upgrade before it claims conformance again. Every reference endpoint reports proxySupported = false, and a kernel change means a new deployment.

A Profile Is a Claim That Needs Evidence

ERC-TRUST treats ERC-3643 as an optional interoperability profile rather than a normative dependency. For tokens already issued under ERC-3643, an adapter and a ProfileGovernor form one conformance unit. The governor owns the token and seals, once, the expected token code identity, the Identity Registry, the Compliance contract, the chain, the adapter's exclusive Agent relationship, and the manifest of imported legacy state. After the seal it has no arbitrary-call, Agent-management, or registry-rebinding surface, as the profile document specifies.

Every endpoint reports one profile descriptor through trustProfile(), and the descriptor is only a declaration. Importing these contracts, reusing the function names, or matching an ERC-165 identifier does not confer Full status. When evidence is missing, stale, or contradictory, the classification is Unsupported, which is the safe reading of absent evidence and says nothing about anyone's intent.

The boundary ended up narrower than we first drew it. At design time, the adapter with its sealed governor was meant to be the route to Full. Later evidence showed that an adapter over an existing token cannot block an ordinary inbound transfer within the same transaction, so that route stayed Partial, and Full now opens only for a freshly deployed token with a transfer hook. We wrote the gap into the profile document as a known limitation, so an implementer putting ERC-TRUST on top of T-REX knows before deployment what the deployment can and cannot claim.

Our ERC-TRUST 1.2 update reports how the native token's freeze was later proven for every input on the compiled code, under two declared assumptions. We found no published formal verification of regulatory actions for ERC-3643, ERC-1400, ERC-1404 or ERC-7943, so to our knowledge ERC-TRUST is now the first security-token standard backed by machine-checked proof.

Every Surface Comes from One Machine-Readable Source

The last of the five came with the second version of the kernel. The domain constant, enums, request and receipt structs, hash preimages, the kernel interface with its ERC-165 identifier, shape rules, the case transition table, reason classes, and profile descriptors now live in one JSON source, described in the repository's kernel source notes. The Solidity interface, the JSON ABI, a human-readable rendering, TypeScript types, conformance vectors for six actions and three reversals, and the receipt schema are all generated from it. The generator's check mode rejects any drift between the generated copy and the copy the implementation imports, so changing one normative identifier without regenerating the interface fails the build.

An editor reviewing the standard can rely on the prose and the machine form staying in step, and an implementer can reproduce every identifier, hash, calldata, and receipt hash in the conformance vectors from the specification and the vectors alone.

Mapping Verified Meaning onto Solidity

With the design settled, we wrote the correspondence between the model and the contract. The retrieve relation maps concrete balances, frozen targets, restriction flags, custody records, case terminality, nonces, epochs, and receipts into an abstract state. Under its declared well-formedness and pinned-runtime premises it is unique and functional, and it rejects runtime substitution; its input is the current on-chain state and pinned configuration rather than any assumed execution history [4]. The idea goes back a long way. If a function carries concrete states to abstract ones, the claim that an implementation implements a specification can be argued one step at a time; refinement mappings rest on that idea [5].

Writing the relation also meant writing down the places where it does not translate on its own, and we gave each of them a name: canonical ABI decoding and the rejection of malformed lengths, enum values, addresses, and fixed-width words; storage projection; read-only dependency calls and the classification of their return data; creation, consumption, and deletion of the same-transaction ticket; action and reversal effects with rollback; failure stutter, the rule that a rejected or failed step changes no state at all; event order and the final canonical receipt; and the identity of creation and runtime bytecode, method identifiers, and storage layout [4]. Once every seam had a name, we could ask of each one which tool covered it.

The answers were uneven. In our Solidity formal verification setup, the Certora Prover checks quantified rules over every contract state and input at the Solidity level [6]. Kontrol symbolically executes the compiled bytecode on the KEVM semantics [7], which gives an independent cross-check of the source-level result. Foundry runs concrete and fuzz tests and hosts the falsification mutants. Event order fell through all three, and no tool in this scope proves compiler correctness, deployment identity, or the truth of external facts. So we kept one rule while putting their results together: passing tools do not add up to a stronger cross-layer theorem.

What the FREEZE Pilot Checked

Before building the whole design, we took one action all the way down. The FREEZE pilot joins, in one vertical slice, a native FREEZE command, the staged setFrozenTokens route mentioned above, the frozen floor check on ordinary transfers, and the final canonical receipt [8]. It was the smallest piece of Solidity formal verification we could find that still crossed every layer of the design.

Preserved FREEZE pilot Three tools checked different obligations of one four-stage slice, all on one pinned build; what the slice never touched stays outside the claim.
The FREEZE pilot slice and which tool checked what The preserved FREEZE pilot is a vertical slice of four stages: a native FREEZE command, the exact-use staged setFrozenTokens route, the frozen-floor check on ordinary transfers, and the canonical receipt as the last log. On the left, the static prover, Certora, checked 13 of 13 quantified rules at the Solidity level over every state and input: direct and staged freeze converge; rejected and failed commands revert with the state unchanged; a wrong caller, wrong calldata or stale binding keeps the target ticket; cancelled or consumed tickets cannot be used; transfer and transferFrom keep the frozen floor, canTransfer matches it exactly, and a successful transfer keeps its frame. It also classified all nine state-changing functions of the whole contract, and it has no rule on event order. On the right, two tools worked on the compiled bytecode. Kontrol on KEVM passed two proofs: on a policy failure the log and the state fingerprint stay unchanged, and the freeze event is followed by the receipt as the last log, checked through a custom K log module that people must review. Foundry ran 13 tests and 256 fuzz runs on the transfer gate and caught all five falsification mutants: an operational failure allowed to run, a consumed ticket kept, the caller and calldata binding removed, the frozen-floor check removed, and the event order reversed. All three stand on one pinned build that gives them the same bytes: solc 0.8.36, 200 optimizer runs, the IR pipeline, metadata hash and CBOR off, the prover held to the compiler's optimizer sequence, and two clean builds with identical hashes. Outside the pilot's claim stay forcedTransfer and full ERC-7943 compatibility, signature-based authorization, full ERC-TRUST refinement, a frozen ABI and production readiness. The contract reports itself as UNSUPPORTED with only the FREEZE bit set, and the only statement is pilot conjunct VERIFIED. Static prover 13 of 13 rules Certora, every state and input FREEZE pilot slice preserved snapshot Compiled bytecode 2 proofs · 5 of 5 mutants Kontrol on KEVM · Foundry, 13 tests Native FREEZEtyped command Exact-use routestaged setFrozenTokens Frozen flooron ordinary transfers Canonical receiptthe last log direct and staged converge failures revert, state unchanged wrong caller, calldata, binding:target ticket kept cancelled or consumed tickets:cannot be used transfer, transferFrom keepthe frozen floor canTransfer matches the floor successful transfer frame no event-order rule all 9 state-changing functionsclassified, whole contract proof: policy failure leaveslog and state unchanged mutant: operational failureallowed to run mutant: consumed ticket kept mutant: binding to caller andcalldata removed fuzz: 256 runs on the gate mutant: floor check removed proof: freeze event, then thereceipt as the last log via a custom K log check,which people must review mutant: event order reversed One pinned build · the same bytes for all three tools solc 0.8.36 · 200 optimizer runs · IR pipeline · metadata hash and CBOR off prover keeps the compiler’s optimizer sequence · two clean builds, identical hashes Outside the pilot’s claim self-report: UNSUPPORTED, FREEZE bit only forcedTransfer and full ERC-7943 signature-based authorization production readiness full ERC-TRUST refinement a frozen ABI only statement: pilot conjunct VERIFIED
Native FREEZEtyped command
Proverdirect and staged converge; failures revert, state unchanged
KEVMproof: policy failure leaves log and state unchanged
Foundrymutant caught: operational failure allowed to run
Exact-use routestaged setFrozenTokens
Proverwrong caller, calldata or binding keeps the target ticket; cancelled or consumed tickets cannot be used
Foundrymutants caught: consumed ticket kept; caller and calldata binding removed
Frozen flooron ordinary transfers
Provertransfer, transferFrom keep the floor; canTransfer matches it; successful transfer frame
Foundry256 fuzz runs on the gate; mutant caught: floor check removed
Canonical receiptthe last log
KEVMproof: freeze event, then the receipt as the last log, via a custom K log check that people must review
Foundrymutant caught: event order reversed
no static prover rule on event order
Proverwhole contract: all 9 state-changing functions classified
One pinned buildthe same bytes for all three tools: solc 0.8.36 · 200 optimizer runs · IR pipeline · metadata hash and CBOR off · two clean builds, identical hashes
Outside the pilot’s claim
Not claimed: forcedTransfer and full ERC-7943 Not claimed: signature-based authorization Not claimed: full ERC-TRUST refinement Not claimed: a frozen ABI Not claimed: production readiness
self-report: UNSUPPORTED, FREEZE bit only · only statement: pilot conjunct VERIFIED
Figure 3The FREEZE Pilot Slice and Which Tool Checked What

At its own point in time, the pilot left five results:

  • The static prover passed all 13 quantified rules and confirmed that none of the nine externally callable state-changing functions falls outside the declared classification.
  • Two bytecode proofs passed. The canonical receipt event is the last log, right after the freeze event, and after a policy failure the log holds only the constructor's Transfer while the state fingerprint stays unchanged.
  • Thirteen tests and 256 fuzz runs passed.
  • We broke the code in five declared ways, and each break was caught by the detector meant for it: removing the frozen floor check, keeping a consumed ticket, swapping the freeze event and the receipt, removing the caller and calldata binding, and letting an operational failure execute.
  • Two isolated clean builds produced identical artifact and bytecode hashes.

The only statement attached to these results is pilot conjunct VERIFIED. The pilot does not claim full ERC-7943 compatibility or forcedTransfer, signature-based authorization, full ERC-TRUST refinement, a frozen ABI, or a finished or production-ready reference implementation, and the contract says so itself: it reports its own profile as UNSUPPORTED with only the FREEZE bit set. Of everything the pilot produced, that self-report is the result we find most telling, because the contract declares its own limit.

Where We Got Stuck

Three Tools Had to See the Same Bytes

The design documents fixed only a lower bound on the compiler, and for a while that looked like enough. It was not. Unless the three tools used the same compiler and the same optimizer settings, the code the static prover checked and the code symbolic execution checked would be different bytes, and their results could not support each other. Without a pin, Solidity formal verification by three tools would have described three different programs.

So we pinned the exact compiler commit (solc 0.8.36), 200 optimizer runs, and the IR pipeline, and disabled metadata hashing and CBOR metadata. We also turned on the option that keeps the static prover's command-line tool from swapping in an optimizer sequence of its own. If different implementers are to rerun the same evidence and reach the same conclusion, the compiler pin is part of the evidence.

A Check the Tools Did Not Have

The canonical receipt has to be the last log, and at first we had no way to check that. The test framework's sequential event expectation kept only one expected event, and reading the full recorded log was not yet implemented.

So we wrote the check ourselves, as a K rule module that reads the KEVM log cell directly. It confirms that the last two logs are the freeze event and the receipt event, and that after a policy failure the only log is the constructor's Transfer. The module added something people now have to review, so we recorded it as an assertion adapter rather than a transition summary and listed it in the trusted base. With it, the normative sentence "the receipt is the last log" became a check that can fail on bytecode.

The First Remote Proof Run Passed Two of Thirteen Rules

The first remote proof job passed 2 of the 13 rules. Seven failed, and four tripped the vacuity check, which means those four were true only because nothing could reach them; the first run record still shows it. There is a quick way to turn red rules green, which is to make them say less. We took the slow way and read the counterexamples one at a time, asking each time whether the code was wrong or whether we had written down something we did not mean. In the end the fixes went into the model, and the properties kept their strength:

  • The route rules had claimed that all storage stays the same after a call with a wrong caller, wrong calldata, or a stale binding, which was more than we meant. We computed the route key from canonical calldata, so the rules now prove that the exact target ticket and its prepared record survive.
  • Arbitrary payloads on inactive paths, which carry no meaning in any real call, were projected to canonical zero values, so only active state is compared.
  • Operational failure became a deterministic event, a mismatch between the live policy code identity and the bound one, so no correlation between unresolved external return values has to be assumed.
  • The recipient's balance increase was written as checked arithmetic, so modular overflow cannot slip through even from symbolic storage states.

The final configuration has no optimistic arguments and no always-allow summaries. The final gate kept the basic vacuity check. The advanced check we used during diagnosis also flags deterministic postconditions in function bodies as tautological, so it stayed a diagnostic tool. We did not delete the failing run; it stays in the repository as diagnostic evidence. Two days later all 13 rules passed, and "a wrong caller can neither use nor corrupt someone else's ticket" was proved as a statement that means what it says.

Laptop screen: Solidity formal verification run summary NOT_VERIFIED, 2 of 13 rules passed

The first run record: 2 of 13 rules passed; the fixes went into the model.

Everything Passed, and It Was Still Not Done

Then every technical condition passed, and we still did not mark the work complete. One condition was left: the source, ABI, bytecode, specification, and report had to be bound to the same commit, so the status stayed at "verification passed, commit binding pending." There was a shortcut that would have closed it sooner, writing a predicted hash of the not-yet-created commit into the evidence files, and we did not take it. The enumerated content hashes served as the binding first, and the commit identifier was recorded afterwards as provenance, so anyone reproducing the result can confirm by hash that it is about exactly this code.

What Solidity Formal Verification Established at This Layer

By the end of this stage we could answer the three kinds of readers who usually look hardest at a new ERC.

An ERC editor wants to know whether the normative sentences can be verified at all. Here the identities of six actions and three reversals, the receipt preimage, and the failure classes live in one machine-readable source, every public surface is generated from it, and the build fails when prose and machine form drift apart.

Participants on Ethereum Magicians tend to ask whether existing standards could do the same job more cheaply. Documenting intent next to an existing forcedTransfer does not turn that intent into something a proof can check, and as long as the call does not state its action, no proof can separate seizure from confiscation. What ERC-TRUST adds is a typed action identity, a canonical receipt, and an evidence boundary that states what was proved and what is assumed, and none of that shows up in a count of functions.

A security token implementer asks what they gain and what they take on. The gain is an ERC-7943 surface without standing authority, a single owner for state, dependencies that fail closed, and profile declarations backed by evidence. The price is a new deployment and a new manifest for every kernel change, dependency binding and epoch management, the cost of read-only calls, and, on ERC-3643, a choice between Partial and Full.

The design went on to three ways to adopt the standard: a native token, an adapter over an existing ERC-3643 token, and a hook in a new ERC-3643 deployment. What this layer has not given is a proof that builds the runtime correspondence for every accepted execution in a declared profile, from source and compiler or bytecode semantics, external-call assumptions, and trace observations. A handful of selected proofs cannot stand in for that theorem, and we do not let them.

Questions This Layer Left Open

  • Does a standard designed for proof lean toward what today's tools can express? Giving up upgrades was a proof-cost decision for the reference implementation; in a real deployment that needs upgrades, who pays for re-verification?
  • How should an ERC state profile claims so that interface detection is never read as conformance? An ERC-165 identifier cannot tell Partial from Full.
  • Which should be normative, the machine-readable source or the prose people read? Generating the prose from the source removes drift, but editors still review prose.

Conclusion

The hardest lessons of this stage were about order more than technique. Once verification is an input to the design, the design pays first. We gave up upgrades in the reference implementation, put a ticket in front of sensitive calls, wrote a check the tools did not have, and agreed to claim compatibility only as far as the evidence goes. In return, each normative sentence became something that can be checked, and can fail.

The FREEZE pilot showed that this structure can be proved on compiled code, and the run that stopped at 2 of 13 rules is still in the repository next to the one that passed. This much is proved; a proof for every compiled execution is the next piece of work.

For a standard to say that its code can be checked against its own text, what should it be prepared to give up at design time?

References

Subscribe

New research, by email.

  • One email when a new article is published
  • Confirm once, leave with one click
  • Privacy