An adversarial pass over the calldata found three more ways a crafted quote
became a signable transaction.
A hop address was taken as given. An address is twenty bytes and the path packs
it against a uint24 with nothing marking the boundary, so a short one shifted
every byte after it — and the path still parsed, as a route through pools
nobody named. Width is the only thing separating the two, so both hop addresses
are now checked the way the swapper already was.
A quote says which chain priced it, and that was ignored. Building this chain's
router call from another chain's quote addresses pools by the same numbers on a
book that never quoted them, and these chains share deployment addresses, so it
is a real trade at a price nobody was shown.
Slippage is a percentage of an amount and lives between none of it and all of
it. Below zero the factor inverted and the bound grew a minus sign; above a
hundred it ran past a word. Clamping the percentage bounds both directions at
one place, where the alternative was clamping every product it reaches.
IsAddress is exported: whether a chain has an address to address a transaction
to is one question, and the process that mounts these handlers has to ask it.
Co-authored-by: Hanzo Dev <[email protected]>
Every argument in a call is one word wide and position is the only thing that
says which argument is which. An amount above 2^256-1 rendered 65 hex
characters instead of 64, shifting every argument after it — the call stayed
well-formed and meant something else: a different recipient, a different bound.
The bound belongs at parseWei, where an amount enters, so the quote, the swap
and the approval all get it from one place rather than three.
Inflating an exact-output bound could carry a value that fits past what fits,
so it stops at the width instead. A bound wider than the type can express is
not a wider bound.
Co-authored-by: Hanzo Dev <[email protected]>
The swap form's four questions were split across two services: the indexer
answered what can be traded and what it pays, while a TypeScript service
answered what to approve and what to sign. The last two need the route the
first two produced, so the split meant a quote crossed a process boundary to
be turned into calldata.
HandleSwap encodes the router call from a quote and bounds it by the caller's
slippage. It reads no state — the quote already carries the route, the amounts
and each hop's fee, and re-reading here would be a second opinion about a price
the caller has already accepted, a block later than the one they saw.
HandleCheckApproval reads the allowance the router holds and returns the grant
when it falls short. Silence from a token is not an allowance of zero, and an
unreachable node is not either; both would skip an approval and leave the swap
to revert.
The conversation with a chain moves to one type. It was a method on the quoter,
which left the approval endpoint either reaching through a quoter to ask an
unrelated question or growing a second copy of the batching, timeout and decode.
Calldata is asserted against bytes recorded from the service being retired,
across exact-input, exact-output, single-hop and multi-hop. A wallet signs
these, so a byte is the unit of correctness.
Co-authored-by: Hanzo Dev <[email protected]>
They were written into cmd/graphd, which is package main. Production does not
run graphd: lux/explorer imports graph as a library and mounts the routes
itself, so a handler in main was reachable by nothing that serves traffic.
Same code, now in engine beside the GraphQL engine that reads the same store.
Co-authored-by: Hanzo Dev <[email protected]>