Files
dex/pkg/gateway/contract_test.go
zeekayandHanzo Dev 8cc08f3b69 One trading surface, at one name, describing itself truthfully
Three things claimed to be this repo's trading API and no two agreed.

pkg/trading carried its own Venue interface, its own V4/native/V2/V3 and broker
venues, its own constant-product math and its own eth_call client — a copy of
what pkg/gateway already had — and nothing mounted it: grep for the import
outside that directory returns empty. api/openapi.yaml documented it anyway, so
the one published contract in the repo described the one package no binary
served. Meanwhile the binary answered at /v1/* and again at /trading/* in
Uniswap's shapes: two contracts for one API, reachable by both names, free to
drift. Deleted, both of them, rather than picked between and left beside.

Everything answers under /v1/trade now. api.lux.cloud carries the chain at
/v1/chain, the C-chain at the root and other services besides, so /v1/quote was
a generic name claimed on a shared host; one prefix is one namespace with one
owner, and the edge rule that publishes it collapses from a nine-clause
allowlist to a prefix. Server.routes() is the surface written once, and
TestTheDocumentIsTheSurface reads api/openapi.yaml and holds the two to each
other in both directions.

/v1/trade/venues is new. It is the question a screen asks before it asks
anything else — which chains this deployment can price, and from what — grouped
by chain, because a venue is a contract on a chain and the same name means a
different pool on each. Without it a client learns a chain is unquotable by
quoting on it and reading an error.

The allowance is read rather than assumed. approval/check and permit2/check
answered allowance 0 and needsApproval on every request, under a comment saying
the gateway had no way to reach a chain; it has one per chain, one field away —
the same endpoint the pool is quoted from. Measured against 96369: LUSD to the
position manager reads 6000000000000000000000000, which is what eth_call
returns for that pair, and the approve tx now rides along only when it is
needed. Handed back beside "you already approved" it is a transaction somebody
sends, and the user pays gas for a state change that changes nothing before
every swap. Permit2 reads the expiration too, because a grant that has run out
is not a grant.

Nothing here invents a number any more. /v1/history/prices and /v1/history/tvl
returned a random walk seeded from hardcoded base prices — LUX at 2.47, WBTC at
96420, 12.5M of TVL on a chain whose pools were seeded hours ago — and both
were routed to the public internet, where a chart drawn from them is
indistinguishable from a chart of the market. History belongs to the indexer,
which reads the chain. /v1/tokens/search answered 501 on a TODO, a route lying
about the surface in the other direction. Gone, with a test that keeps them
gone.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-09 00:40:44 -07:00

79 lines
2.1 KiB
Go

package gateway
import (
"os"
"regexp"
"sort"
"strings"
"testing"
)
// The document and the binary describe one surface, so they are held to each
// other rather than to a reader's memory.
//
// api/openapi.yaml used to describe /v1/trade/* paths that no binary served,
// while the binary served /v1/* and again /trading/*, and the three drifted
// for as long as nothing compared them. A route added in code and not written
// down is a route no client can be told about; a path written down and not
// served is a promise the binary breaks the first time somebody believes it.
func TestTheDocumentIsTheSurface(t *testing.T) {
doc, err := os.ReadFile("../../api/openapi.yaml")
if err != nil {
t.Fatalf("the published contract: %v", err)
}
// A path key at the top level of `paths:`, which is the only thing indented
// exactly two spaces and starting with a slash.
key := regexp.MustCompile(`(?m)^ (/\S*):$`)
written := map[string]bool{}
for _, m := range key.FindAllStringSubmatch(string(doc), -1) {
path := m[1]
if !strings.HasPrefix(path, tradePrefix) {
t.Errorf("%s is documented outside %s", path, tradePrefix)
continue
}
// /v1/trade/pool/{chainId}/{address} is served as the subtree
// /v1/trade/pool/ — everything after the first placeholder is read
// out of the path by the handler.
route := strings.TrimPrefix(path, tradePrefix)
if i := strings.Index(route, "{"); i >= 0 {
route = route[:i]
}
written[route] = true
}
served := map[string]bool{}
for route := range (&Server{}).routes() {
served[route] = true
}
for route := range served {
if !written[route] {
t.Errorf("%s%s is served and not in api/openapi.yaml", tradePrefix, route)
}
}
for route := range written {
if !served[route] {
t.Errorf("%s%s is in api/openapi.yaml and not served", tradePrefix, route)
}
}
if len(served) == 0 {
t.Fatal("no routes")
}
if t.Failed() {
t.Logf("served: %s", sorted(served))
t.Logf("written: %s", sorted(written))
}
}
func sorted(set map[string]bool) []string {
out := make([]string, 0, len(set))
for k := range set {
out = append(out, k)
}
sort.Strings(out)
return out
}