Why specify something already this well understood
A specification is not documentation of code that exists. It is the artefact that sits between a design intention and an implementation, and it either makes the system's properties checkable or it does not. Specifying a protocol nobody disputes is the cleanest way to show what the document is supposed to do, because no argument about the mechanism gets in the way of the argument about the method.
The structure below is the one I use on systems that do not yet exist: decompose the state, map the mechanisms over it, then derive each state transition and state its invariants explicitly.
State
Four state layers, kept separate on purpose
The separation is the load-bearing decision. Collapse these and you lose the ability to say which agent knows what.
Global state
The information required to maintain an active pool: quantities and of each asset, and the supply of tokens representing rights to pool liquidity — all over .
Stateful parameters and metrics
Parameters carry units, which is where most informal specifications go wrong: gas amount in , gas price in , and swap fee as a rate. Metrics are derived, not stored:
is the market price of asset 2 in the numeraire; is the invariant the pool quantities always adhere to.
Local state — and the inside/outside distinction
Local state is what an address holds: , , . It also carries each agent's private price beliefs , explicitly tagged as inside versus outside information.
Exogenous state
Not part of the system, but intrinsic to any dynamic model of it: assumed global prices in ETH and in USD. Naming these as exogenous is what stops a model from quietly assuming its own price process.
Mechanisms
Three transitions, each with its invariant
Every mechanism declares what it preserves. That declaration is the specification's actual content.
Add liquidity — preserves price
Takes and returns the that must accompany it so the price is unchanged:
The invariant moves, and that is the point:
Shares are issued on the linear proportion of the contribution, which is what keeps ownership claims consistent across deposits:
Resolving the global state:
Locally and , subject to and — preconditions belong in the specification, not in the implementation's error handling.
Withdraw liquidity — also preserves price
The symmetric operation. Burning returns a pro-rata share of both reserves:
Price is again held constant and falls. Add and withdraw are the two mechanisms that move the invariant without moving the price; swap is the one that does the opposite.
Swap — preserves the invariant, moves the price
The constant product is what is held fixed here:
Which fixes the output for a given input:
And the price resolves to a new value:
Actors
The action space
Roles drive mechanisms
A state machine with no actors is not a model of anything. The specification closes by assigning mechanisms to roles — an operational trader swapping in and out, an arbitrage trader closing the gap between and the exogenous , and liquidity providers adding and withdrawing.
Once roles are attached, the specification is executable: each role has a feasible action set over the state, which is exactly what a simulation needs to be written against.
What this method buys
- Properties become checkable. "Price is preserved" is a claim you can test, not a sentence in a blog post.
- Preconditions surface early. The balance constraints appear in the specification rather than as a revert message.
- Simulation is a translation, not a redesign. The state decomposition maps directly onto a cadCAD model.
- Disagreement gets localised. Arguments become arguments about a named mechanism's invariant, which is a much shorter conversation.