Move once, trade many. Value crosses the C<->D boundary twice in its life; in
between, D owns it and every order, match and trade is a local balance movement.
So the crossing has exactly one job — move ownership exactly once — and the
object shrinks to what that job needs:
beneficiary(20) | asset(32) | amount(8) = 60 bytes
Out of the wire: the 49-byte market/side/limit/size tail, the `spent` execution
witness, and the lane byte selecting a trading pot. Out of the precompile: the
per-order escrow with its principal cap and deadline, the taker-authenticated
price floor, the deadline reclaim and its selector, the shared-memory discovery
trait, and the .v2 domain rotation that existed only because the tail had
changed the object's width.
ONE CUSTODY POT. seamReserve and committedPositions were the swap lane and the
LP lane of one quantity — "value C holds because D owns it" — and the lane byte
was the only thing keeping them apart. Splitting them is what created the
cross-lane drain the byte then had to defend against. One number per asset, one
credit path, nothing to cross.
WHAT REPLACES THE PER-ORDER CAP AND THE PRICE FLOOR: D refuses an over-debit on
export, C refuses a credit custody cannot back, a claim is consumable exactly
once, and Block.Verify proves the carried bytes are the recorded object. Those
are properties of the value. The cap and the floor were properties of an order,
which is why the order had to ride the object for them to be checkable at all.
The claim id is one derivation for both directions and both repos:
SHA-256(domain | source | dest | sourceTx | index). It replaces DeriveOrderID,
DerivePositionCommitID and deriveSeamOutputID, which were three spellings of the
same fact.
Co-authored-by: Hanzo Dev <[email protected]>
Order -> Match -> Execution -> Trade is the vocabulary; "intent" was the old
name for what an Order is. Renames the identifiers across the 0x9999 settle
precompile so the code reads in the settled terms.
Values that live on a chain keep their bytes, because renaming one of those is
a migration and not a rename:
- "lux.dex.native.intent.v1" and "lux.dex.native.intent.pending.v2" are hash
INPUTS; a byte changed there moves every derived order id and the pending
trait both repos must agree on.
- settleStateNamespace + "intent" is an on-disk key prefix; renaming it
orphans the submitted-order replay set.
- reclaimIntent(bytes32), IntentSubmitted(...) and IntentReclaimed(...) are
the deployed ABI; the signature is what hashes, so a rename would move a
selector and two topics.
Each of those now carries one line saying why the literal does not track the
identifier that names it, so the inconsistency is not "fixed" later.
TestOrderID_CrossRepoGolden still reads 5b0e8d38... and
TestSeamPendingTrait_MatchesDChainGolden still reads 5f10f9ad..., which is the
evidence no derivation input moved.
Co-authored-by: Hanzo Dev <[email protected]>
A settled swap could never be synced past. Both C-side consume paths
(ImportSettlement, ImportPositionCollect) read the D->C object from shared
memory DURING EVM execution, while the consuming Remove landed at block
Accept. A node re-executing that block — bootstrapping, state-syncing,
re-tracing — found the object gone, computed a reverted receipt where the
network had computed a successful one, and died on the receipt root. The
seam settled live and wedged every node that tried to follow.
Move the object's 69 bytes into the transaction (Phase-B hookData, and the
collectPosition calldata) so execution is a pure function of the block and
replays byte-identically forever. Both rails now go through ONE binding
primitive, bindImportObject, which also DECLARES the consumption as a log
committing to the object's hash. The host authenticates that declaration
against shared memory at Block.Verify, so a forged object costs the
producer a block instead of forking a receipt root.
The ship rule did not weaken, it moved: a C balance is still credited only
by consuming a real D->C object. What changed is where that is proven.
Also:
- amount is no longer restated on the wire; it IS the object's amount, so
there is one declaration of the fact instead of two to cross-check.
- the retired DS01 tag is refused BY NAME. The fallback for an unknown tag
is Phase A, so a stale settle would otherwise have been silently re-read
as an intent and LOCKED the sender's input — the one mixed-fleet outcome
worse than a revert.
- the zero-amount refusal moved into the single binding point, so an absent
object zero-padded to canonical width reports the same clear error on
both rails rather than surfacing as a cross-rail violation.
336 tests pass, 0 fail.
Co-authored-by: Hanzo Dev <[email protected]>