@@ -407,29 +407,131 @@ ref is a serial number that simply must not have been seen before, which reveals
407407output is being consumed. One TMS runs one token driver, so the mode is chosen at deployment and never
408408changes.
409409
410- #### The contracts
410+ #### The six files, and what each one is
411+
412+ ` contracts/src ` holds six files, but only four of them become contracts at an address. The other two are
413+ libraries whose functions are all ` internal ` , which the Solidity compiler inlines into whoever calls them,
414+ so they never get deployed on their own.
411415
412416``` mermaid
413417flowchart TB
414- F["TokenStateFactory"] -->|"creates and initializes<br/>in one transaction"| C
415- C["TokenState clone<br/>one per TMS"] -.->|"delegatecall, all logic"| I["TokenState implementation<br/>deployed once, locked"]
416- C -->|"verify"| V["EndorsementVerifier<br/>endorser set and threshold"]
418+ subgraph dep ["Deployed to an address"]
419+ direction TB
420+ TSI["TokenState.sol<br/>implementation, deployed once"]
421+ TSC["TokenState clone<br/>one per TMS, holds all storage"]
422+ EV["EndorsementVerifier.sol<br/>endorser set and threshold"]
423+ FAC["TokenStateFactory.sol<br/>deploy time only"]
424+ end
425+
426+ subgraph inl ["Libraries, inlined into the caller"]
427+ direction TB
428+ E712["EIP712.sol<br/>compiled into TokenState"]
429+ CL["Clones.sol<br/>compiled into TokenStateFactory"]
430+ end
431+
432+ subgraph typ ["Types only, no code"]
433+ SD["StateDelta.sol<br/>two structs, field order frozen"]
434+ end
435+
436+ style dep fill:#ddf4ff,stroke:#54aeff,color:#1f2328
437+ style inl fill:#fff8c5,stroke:#d4a72c,color:#1f2328
438+ style typ fill:#f6f8fa,stroke:#d1d9e0,color:#1f2328
439+ ```
440+
441+ ` StateDelta.sol ` is compiled into both ` TokenState ` and ` EIP712 ` . Its field order is frozen because the
442+ EIP-712 hash is computed from it and every endorser signs that hash, so reordering a field would silently
443+ invalidate every signature.
444+
445+ The two libraries are worth being explicit about, because their names suggest more than they are.
446+ ` Clones.sol ` is production code, not test scaffolding: ` TokenStateFactory.create ` calls
447+ ` Clones.clone(implementation) ` to create each per-TMS clone. It also appears in the contract tests, which
448+ clone directly so they exercise the same shape production uses.
449+
450+ #### Phase 1, deployment
451+
452+ Run once per network, from
453+ [ ` Deploy.s.sol ` ] ( ../../x/token/services/network/evm/contracts/script/Deploy.s.sol ) . Three contracts are
454+ created, and then the factory produces the clone that a TMS will actually use.
455+
456+ ``` mermaid
457+ sequenceDiagram
458+ autonumber
459+ participant S as Deploy script
460+ participant EV as EndorsementVerifier
461+ participant I as TokenState implementation
462+ participant F as TokenStateFactory
463+ participant C as TokenState clone
464+
465+ S->>EV: new EndorsementVerifier(endorsers, threshold)
466+ S->>I: new TokenState()
467+ Note over I: locks itself in its constructor,<br/>so only a clone can be initialized
468+ S->>F: new TokenStateFactory(implementation)
469+
470+ S->>F: create(verifier, deployer, pp0, graphHiding)
471+ F->>C: Clones.clone deploys an EIP-1167 proxy
472+ F->>C: initialize(verifier, deployer, pp0, graphHiding)
473+ Note over F,C: both in one transaction, so an<br/>uninitialized clone is never reachable
474+ F-->>S: clone address
475+
476+ S->>C: read back verifier, params hash, version, mode, deployer
477+ ```
478+
479+ Cloning and initializing in one call is deliberate. ` initialize ` is deliberately unguarded, because the
480+ implementation is locked and only a fresh clone can ever be initialized. Doing it in two transactions
481+ would leave a window in which anyone could seize the clone by initializing it first with their own
482+ verifier, and the honest deployer's call would then revert with ` AlreadyInitialized ` .
483+
484+ Each TMS gets its own [ EIP-1167] ( https://eips.ethereum.org/EIPS/eip-1167 ) minimal proxy, so a new TMS
485+ costs a proxy rather than a full deployment while sharing one copy of the logic. Every clone has its own
486+ storage.
487+
488+ #### Phase 2, processing a transaction
489+
490+ Three addresses take part, but only one of them holds any state.
491+
492+ ``` mermaid
493+ flowchart LR
494+ D["Driver"] -->|"applyStateDelta<br/>delta + signatures"| C
417495
418- style C fill:#ddf4ff,stroke:#54aeff,color:#1f2328
496+ subgraph clone ["TokenState clone: the address, and all the storage"]
497+ C["EIP-1167 proxy,<br/>almost no code of its own"]
498+ end
499+
500+ subgraph impl ["TokenState implementation: the code, no storage of its own"]
501+ direction TB
502+ E["applyStateDelta,<br/>running on the clone's storage"] --> H["EIP712, inlined here:<br/>recompute the digest"]
503+ H --> A["apply the writes"]
504+ end
505+
506+ C -->|"delegatecall"| E
507+ H -->|"verify(digest, signatures)"| V["EndorsementVerifier"]
508+ V -->|"accepted, or revert"| H
509+ A -->|"emit StateCommitted"| L["Logs, at the clone's address"]
510+
511+ style clone fill:#ddf4ff,stroke:#54aeff,color:#1f2328
512+ style impl fill:#f6f8fa,stroke:#d1d9e0,color:#1f2328
419513 style V fill:#dafbe1,stroke:#4ac26b,color:#1f2328
420514```
421515
422- ` TokenState ` holds all token storage. ` EndorsementVerifier ` holds the endorser set and the threshold and
423- has no token storage at all: it is a pure checker. Keeping them apart means the endorsement policy can be
424- reviewed, and replaced, without touching the code that owns tokens.
516+ Two things there are easy to get wrong when reading the sources.
517+
518+ ** The code does not live where the storage lives.** The clone owns the address, the token map, the spent
519+ markers and the public parameters, but it is a minimal proxy and carries almost no logic. Every call to it
520+ ` delegatecall ` s the shared implementation, which then executes against the clone's storage. So
521+ ` applyStateDelta ` is the implementation's code running as though it were the clone.
522+
523+ ** ` EIP712 ` is not a contract.** Its functions are all ` internal ` , so the compiler copies them into
524+ ` TokenState ` at build time. Recomputing the digest costs no external call, and there is no ` EIP712 `
525+ address to look up on a block explorer.
526+
527+ That leaves exactly one call crossing an address boundary while a transaction is processed: ` TokenState `
528+ asking ` EndorsementVerifier ` to check the quorum. ` TokenStateFactory ` and ` Clones ` play no part at all
529+ once deployment is over.
425530
426- Each TMS gets its own ` TokenState ` , created as an [ EIP-1167] ( https://eips.ethereum.org/EIPS/eip-1167 )
427- minimal proxy so a new TMS costs a proxy rather than a full deployment. The shared implementation locks
428- itself in its constructor, so only a clone can ever be initialized. The factory clones ** and** initializes
429- in a single transaction, which closes the window where an uninitialized clone would be sitting on chain
430- for anyone to seize.
531+ Because the writes and the event happen in the clone's context, ` StateCommitted ` is emitted at the
532+ clone's address, and that is the address the finality layer filters ` eth_getLogs ` on.
431533
432- #### One transaction, end to end
534+ #### The same transaction, step by step
433535
434536``` mermaid
435537sequenceDiagram
0 commit comments