Files
precompile/dex/zzblueE_view_test.go
zeekay 5ab320e270 dex, fhe: finish the refactor the merge half-applied
collectPosition's calldata is a fixed 156 bytes now — claimID, asset,
positionID, and the 60-byte claim — so the two tests that built the old
128-byte layout were refusing on length before reaching what they meant to
exercise. The reentrancy test carries a real claim; the amount-domain test
becomes a width sweep, because there is no longer an amount word to put out of
range: the amount is the uint64 inside the claim, which a caller cannot widen.
ErrLPCollectBadAmount goes with it, having become a sentinel nothing returns.

The swap arm's write witness moves 13 -> 10. One custody pot and no per-order
escrow is less state, and the witness exists to make that a decision rather
than a drift.

fhe's derived gas comments still stated the products of the old wall-clock
figures — 180M and 936M against constants that now compute 300M and 2.88G.
2026-08-28 20:19:27 -07:00

999 lines
44 KiB
Go

// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package dex
import (
"bytes"
"context"
"errors"
"math/big"
"testing"
"github.com/luxfi/geth/common"
"github.com/luxfi/ids"
"github.com/luxfi/precompile/contract"
"github.com/luxfi/precompile/precompileconfig"
)
// zzblueE_view_test.go covers the three READ modules a chain exposes — 0x9997
// StateView, 0x9998 Quoter, 0x9996 PositionManager — plus the 0x9999 LP-commit
// kernel they route into (position_commit.go).
//
// The property that holds all of this together: a precompile is handed calldata it
// does not own. geth's CALL path passes `scope.Memory.GetPtr(off, size)`, a two-index
// slice of EVM memory, so cap(input) is the rest of memory and every byte past len is
// attacker-chosen. A missing length check therefore does NOT panic in production — it
// reads bytes the caller planted and answers over input that was never declared. So
// every bound below is asserted on len, and every refusal is re-asserted against an
// input whose spare capacity is poisoned: the verdict has to be identical.
// blueEPoisoned returns b with `spare` bytes of attacker-chosen 0xA5 past its length,
// modelling EVM memory the caller wrote before the CALL. len is unchanged; cap is not.
func blueEPoisoned(b []byte, spare int) []byte {
full := make([]byte, len(b)+spare)
for i := range full {
full[i] = 0xA5
}
copy(full, b)
return full[:len(b)]
}
// blueENoBlockCtx is a harness state that reports NO block context — the shape a host
// hands a precompile outside a block. Every module must refuse rather than proceed
// with a nil context it would later dereference.
type blueENoBlockCtx struct{ *nativeAtomicState }
func (blueENoBlockCtx) GetBlockContext() contract.BlockContext { return nil }
// blueECountingDB counts GetState calls so a handler's real storage-read work can be
// compared against the gas it charged for that work.
type blueECountingDB struct {
contract.StateDB
reads *int
}
func (d blueECountingDB) GetState(a common.Address, k common.Hash) common.Hash {
*d.reads++
return d.StateDB.GetState(a, k)
}
type blueECountingState struct {
*nativeAtomicState
reads *int
}
func (s blueECountingState) GetStateDB() contract.StateDB {
return blueECountingDB{StateDB: s.nativeAtomicState.GetStateDB(), reads: s.reads}
}
func blueEStateView() *StateViewContract { return &StateViewContract{} }
func blueEQuoter() *QuoterContract { return &QuoterContract{} }
func blueEPositions() *PositionManagerContract {
return &PositionManagerContract{}
}
// ---------------------------------------------------------------------------
// Dispatch bounds: shorter than a selector, and no block context.
// ---------------------------------------------------------------------------
// TestBlueEViewModulesRefuseTruncatedSelector sweeps every input shorter than the
// 4-byte selector on all three read modules, and then re-runs each one with the SAME
// declared length over poisoned spare capacity. A handler that reads past len would
// find a real selector in the poison and dispatch; the verdict must not move.
func TestBlueEViewModulesRefuseTruncatedSelector(t *testing.T) {
h := newSettleHarness(t)
sv, q, pm := blueEStateView(), blueEQuoter(), blueEPositions()
mods := []struct {
name string
run func(input []byte, gas uint64) ([]byte, uint64, error)
}{
{"stateview", func(in []byte, gas uint64) ([]byte, uint64, error) {
return sv.Run(h.state, h.caller, stateViewAddr, in, gas, true)
}},
{"quoter", func(in []byte, gas uint64) ([]byte, uint64, error) {
return q.Run(h.state, h.caller, common.HexToAddress(DEXQuoterAddress), in, gas, true)
}},
{"positionmanager", func(in []byte, gas uint64) ([]byte, uint64, error) {
return pm.Run(h.state, h.caller, positionManagerAddr, in, gas, false)
}},
}
// A real selector planted in the poison. If any module reads past len it will
// dispatch this instead of refusing.
bait := []byte{byte(SelectorGetPoolId >> 24), byte(SelectorGetPoolId >> 16), byte(SelectorGetPoolId >> 8), byte(SelectorGetPoolId)}
for _, m := range mods {
for n := 0; n < 4; n++ {
clean := bytes.Repeat([]byte{0x11}, n)
_, gasLeft, err := m.run(clean, 5_000_000)
if err == nil {
t.Fatalf("%s accepted a %d-byte input — shorter than its own selector", m.name, n)
}
// Refusals here precede any work, so the module hands the gas back; geth
// then zeroes it because the error is not ErrExecutionReverted. Pin the
// contract the module actually has: it charges nothing for a refusal it
// makes before reading state.
if gasLeft != 5_000_000 {
t.Fatalf("%s charged %d gas for a pre-dispatch refusal", m.name, 5_000_000-gasLeft)
}
poisoned := blueEPoisoned(clean, 64)
copy(poisoned[:cap(poisoned)][n:], bait) // plant a valid selector past len
_, _, perr := m.run(poisoned, 5_000_000)
if perr == nil {
t.Fatalf("%s accepted a %d-byte input once its spare capacity held a real "+
"selector — it is dispatching on bytes the caller never declared", m.name, n)
}
}
}
}
// TestBlueEViewModulesRefuseWithoutBlockContext: a host that supplies no block context
// must get a refusal, not a nil dereference. All three read modules check it before
// they decode anything.
func TestBlueEViewModulesRefuseWithoutBlockContext(t *testing.T) {
h := newSettleHarness(t)
st := blueENoBlockCtx{h.state}
if st.GetBlockContext() != nil {
t.Fatal("the fixture still reports a block context")
}
svIn := append(selectorBytes(SelectorGetPoolId), EncodePoolKeyABI(h.key)...)
if _, gasLeft, err := blueEStateView().Run(st, h.caller, stateViewAddr, svIn, 5_000_000, true); err == nil {
t.Fatal("stateview ran with no block context")
} else if gasLeft != 5_000_000 {
t.Fatalf("stateview charged %d gas before the block-context check", 5_000_000-gasLeft)
}
qIn := append(selectorBytes(SelQExactInput), quoteBody(h.key, big.NewInt(1000), true)...)
if _, gasLeft, err := blueEQuoter().Run(st, h.caller, common.HexToAddress(DEXQuoterAddress), qIn, 5_000_000, true); err == nil {
t.Fatal("quoter ran with no block context")
} else if gasLeft != 5_000_000 {
t.Fatalf("quoter charged %d gas before the block-context check", 5_000_000-gasLeft)
}
pmIn := append(selectorBytes(SelectorPMPositionsOf), make([]byte, 32)...)
if _, gasLeft, err := blueEPositions().Run(st, h.caller, positionManagerAddr, pmIn, 5_000_000, false); err == nil {
t.Fatal("position manager ran with no block context")
} else if gasLeft != 5_000_000 {
t.Fatalf("position manager charged %d gas before the block-context check", 5_000_000-gasLeft)
}
}
// ---------------------------------------------------------------------------
// 0x9997 StateView — receipt, halt, open orders, position encoding.
// ---------------------------------------------------------------------------
// TestBlueEStateViewReceiptStatusTracksTheConsumedSet: getReceiptStatus is the public
// read of the one-time settlement guard. It must report false for an unseen object and
// true once that exact object is consumed — and it must not report true for a NEIGHBOUR
// id, which would let a keeper believe a settlement already landed and drop it.
func TestBlueEStateViewReceiptStatusTracksTheConsumedSet(t *testing.T) {
h := newSettleHarness(t)
sv := blueEStateView()
db := newPoolStateAdapter(h.state)
consumed := ids.ID{0x11, 0x22}
neighbour := ids.ID{0x11, 0x23}
ask := func(id ids.ID) bool {
in := append(selectorBytes(SelectorGetReceiptStatus), id[:]...)
out, _, err := sv.Run(h.state, h.caller, stateViewAddr, in, 5_000_000, true)
if err != nil {
t.Fatalf("getReceiptStatus: %v", err)
}
if len(out) != 32 {
t.Fatalf("getReceiptStatus returned %d bytes, want one word", len(out))
}
return out[31] == 1
}
if ask(consumed) || ask(neighbour) {
t.Fatal("an unconsumed settlement object already reports consumed")
}
markClaimConsumed(db, consumed, 7)
if !ask(consumed) {
t.Fatal("a consumed settlement object reports NOT consumed — the replay guard is invisible to the view")
}
if ask(neighbour) {
t.Fatal("consuming one object marked a different id consumed")
}
// Short input is refused rather than read out of the poison.
for n := 0; n < 32; n++ {
in := append(selectorBytes(SelectorGetReceiptStatus), bytes.Repeat([]byte{0x11}, n)...)
if _, _, err := sv.Run(h.state, h.caller, stateViewAddr, in, 5_000_000, true); !errors.Is(err, ErrViewBadInput) {
t.Fatalf("getReceiptStatus with %d argument bytes = %v, want ErrViewBadInput", n, err)
}
if _, _, err := sv.Run(h.state, h.caller, stateViewAddr, blueEPoisoned(in, 64), 5_000_000, true); !errors.Is(err, ErrViewBadInput) {
t.Fatalf("getReceiptStatus with %d argument bytes over poisoned capacity = %v, want ErrViewBadInput", n, err)
}
}
}
// TestBlueEStateViewHaltStatusReportsBothScopes: getHaltStatus returns two independent
// words — the GLOBAL halt and whether the named id is halted as a market OR an asset.
// The two must not bleed: halting one market must not make the global word read halted,
// or an operator reading this view cannot tell a single frozen market from a frozen chain.
func TestBlueEStateViewHaltStatusReportsBothScopes(t *testing.T) {
h := newSettleHarness(t)
sv := blueEStateView()
db := newPoolStateAdapter(h.state)
market := [32]byte{0xA1}
other := [32]byte{0xB2}
ask := func(id [32]byte) (global, scoped bool) {
in := append(selectorBytes(SelectorGetHaltStatus), id[:]...)
out, _, err := sv.Run(h.state, h.caller, stateViewAddr, in, 5_000_000, true)
if err != nil {
t.Fatalf("getHaltStatus: %v", err)
}
if len(out) != 64 {
t.Fatalf("getHaltStatus returned %d bytes, want two words", len(out))
}
return out[31] == 1, out[63] == 1
}
if g, s := ask(market); g || s {
t.Fatalf("a fresh chain already reports halted (global=%v scoped=%v)", g, s)
}
// Market halt: scoped only.
SetHaltMarket(db, market, true)
if g, s := ask(market); g || !s {
t.Fatalf("after a MARKET halt: global=%v scoped=%v, want false/true", g, s)
}
if _, s := ask(other); s {
t.Fatal("halting one market reported a different id halted")
}
SetHaltMarket(db, market, false)
// Asset halt on the same id: also scoped only, and reached through the second
// arm of the OR — so both arms are load-bearing.
SetHaltAsset(db, market, true)
if g, s := ask(market); g || !s {
t.Fatalf("after an ASSET halt: global=%v scoped=%v, want false/true", g, s)
}
SetHaltAsset(db, market, false)
// Global halt: reported globally for EVERY id, including ids that were never
// individually halted.
SetHaltGlobal(db, true)
for _, id := range [][32]byte{market, other, {}} {
if g, _ := ask(id); !g {
t.Fatalf("the global halt is invisible for id %x", id[:4])
}
}
SetHaltGlobal(db, false)
if g, _ := ask(market); g {
t.Fatal("clearing the global halt left it reading halted")
}
for n := 0; n < 32; n++ {
in := append(selectorBytes(SelectorGetHaltStatus), bytes.Repeat([]byte{0x11}, n)...)
if _, _, err := sv.Run(h.state, h.caller, stateViewAddr, in, 5_000_000, true); !errors.Is(err, ErrViewBadInput) {
t.Fatalf("getHaltStatus with %d argument bytes = %v, want ErrViewBadInput", n, err)
}
if _, _, err := sv.Run(h.state, h.caller, stateViewAddr, blueEPoisoned(in, 64), 5_000_000, true); !errors.Is(err, ErrViewBadInput) {
t.Fatalf("getHaltStatus over poisoned capacity (%d bytes) = %v, want ErrViewBadInput", n, err)
}
}
}
// blueESeedOrder writes a resting order and indexes it under its owner, the shape
// runSettleModifyLiquidity leaves behind.
func blueESeedOrder(db stateKV, owner common.Address, id [32]byte, status OrderStatus, amt *big.Int) {
storeRestingOrder(db, id, RestingOrder{
Owner: owner,
PoolID: [32]byte{0xB0},
Side: MakerSideBid,
LockedAsset: [32]byte{0x01},
LockedAmt: amt,
TickLower: -60,
TickUpper: 60,
Status: status,
})
appendOwnerOrder(db, owner, id)
}
// TestBlueEStateViewOpenOrdersReportsOnlyOpen: getOpenOrders enumerates the owner's
// index and returns only the OPEN ones. A closed or cancelled order that still showed
// up would tell a keeper to act on a position that no longer exists.
func TestBlueEStateViewOpenOrdersReportsOnlyOpen(t *testing.T) {
h := newSettleHarness(t)
sv := blueEStateView()
db := newPoolStateAdapter(h.state)
owner := common.HexToAddress("0x00000000000000000000000000000000000000E1")
open1 := [32]byte{0x01}
closing := [32]byte{0x02}
cancelled := [32]byte{0x03}
open2 := [32]byte{0x04}
blueESeedOrder(db, owner, open1, OrderStatusOpen, big.NewInt(1000))
blueESeedOrder(db, owner, closing, OrderStatusClosing, big.NewInt(2000))
blueESeedOrder(db, owner, cancelled, OrderStatusCancelled, big.NewInt(3000))
blueESeedOrder(db, owner, open2, OrderStatusOpen, big.NewInt(4000))
in := append(selectorBytes(SelectorGetOpenOrders), common.LeftPadBytes(owner.Bytes(), 32)...)
out, _, err := sv.Run(h.state, h.caller, stateViewAddr, in, 5_000_000, true)
if err != nil {
t.Fatalf("getOpenOrders: %v", err)
}
// ABI dynamic array: offset word, length word, then the elements.
if len(out) < 64 {
t.Fatalf("getOpenOrders returned %d bytes", len(out))
}
n := new(big.Int).SetBytes(out[32:64]).Uint64()
if n != 2 {
t.Fatalf("getOpenOrders returned %d ids, want the 2 OPEN ones out of 4", n)
}
var got [][32]byte
for i := uint64(0); i < n; i++ {
var id [32]byte
copy(id[:], out[64+32*i:64+32*(i+1)])
got = append(got, id)
}
if got[0] != open1 || got[1] != open2 {
t.Fatalf("getOpenOrders returned %x, want the two OPEN ids in insertion order", got)
}
// Enumeration order is the index's insertion order, not a map's. Repeat it: a
// map-ordered enumeration would eventually disagree with itself, and two
// validators would return different bytes for the same state.
for i := 0; i < 32; i++ {
again, _, err := sv.Run(h.state, h.caller, stateViewAddr, in, 5_000_000, true)
if err != nil {
t.Fatalf("getOpenOrders repeat %d: %v", i, err)
}
if !bytes.Equal(again, out) {
t.Fatalf("getOpenOrders is not deterministic — repeat %d returned different bytes", i)
}
}
// An owner with nothing gets an empty array, not an error.
empty := append(selectorBytes(SelectorGetOpenOrders), common.LeftPadBytes(common.HexToAddress("0xdead").Bytes(), 32)...)
eout, _, err := sv.Run(h.state, h.caller, stateViewAddr, empty, 5_000_000, true)
if err != nil {
t.Fatalf("getOpenOrders for an unknown owner: %v", err)
}
if new(big.Int).SetBytes(eout[32:64]).Sign() != 0 {
t.Fatal("an owner with no orders got a non-empty array")
}
for n := 0; n < 32; n++ {
short := append(selectorBytes(SelectorGetOpenOrders), bytes.Repeat([]byte{0x11}, n)...)
if _, _, err := sv.Run(h.state, h.caller, stateViewAddr, short, 5_000_000, true); !errors.Is(err, ErrViewBadInput) {
t.Fatalf("getOpenOrders with %d argument bytes = %v, want ErrViewBadInput", n, err)
}
if _, _, err := sv.Run(h.state, h.caller, stateViewAddr, blueEPoisoned(short, 64), 5_000_000, true); !errors.Is(err, ErrViewBadInput) {
t.Fatalf("getOpenOrders over poisoned capacity (%d bytes) = %v, want ErrViewBadInput", n, err)
}
}
}
// TestBlueEOpenOrdersDoesTheWorkBeforeItChargesForIt is a DEFECT WITNESS, not a
// property test.
//
// getOpenOrders charges GasStateView per indexed order — but it enumerates the index
// and loads every order FIRST, and only then compares the total against suppliedGas.
// So a caller who supplies the base fee alone still makes the node perform a storage
// read for every order the owner holds. The refusal costs the caller its whole
// (small) budget while the node's work scales with a number the caller does not pay
// for. settle_maker.go already carries a comment about closing exactly this class on
// the WRITE path ("a compute-griefing vector"); the read path still has it.
//
// This test pins the current behaviour — reads happen, then the refusal — so the fix
// (hoist the count read and the gas check above the loop; the returned error and the
// zeroed gas are unchanged) is a visible, deliberate change rather than a silent one.
func TestBlueEOpenOrdersDoesTheWorkBeforeItChargesForIt(t *testing.T) {
h := newSettleHarness(t)
sv := blueEStateView()
db := newPoolStateAdapter(h.state)
owner := common.HexToAddress("0x00000000000000000000000000000000000000E2")
const orders = 24
for i := 0; i < orders; i++ {
var id [32]byte
id[0], id[1] = 0xE2, byte(i)
blueESeedOrder(db, owner, id, OrderStatusOpen, big.NewInt(int64(i+1)))
}
in := append(selectorBytes(SelectorGetOpenOrders), common.LeftPadBytes(owner.Bytes(), 32)...)
reads := 0
counting := blueECountingState{nativeAtomicState: h.state, reads: &reads}
// Supply exactly the base fee: strictly less than GasStateView*(1+orders), so the
// call MUST refuse.
_, gasLeft, err := sv.Run(counting, h.caller, stateViewAddr, in, GasStateView, true)
if err == nil {
t.Fatalf("getOpenOrders for %d orders succeeded on the base fee alone", orders)
}
if gasLeft != 0 {
t.Fatalf("the out-of-gas refusal returned %d gas; it must consume the budget", gasLeft)
}
if reads <= orders {
t.Fatalf("the refusal performed %d storage reads for %d orders; expected the "+
"enumeration to have already run (this test is the witness for that defect "+
"— if the charge now precedes the work, reads should be ~1 and this "+
"assertion is the thing to update)", reads, orders)
}
// And the same call WITH enough gas succeeds and charges per order, so the fee
// schedule itself is right — only its position relative to the work is wrong.
want := GasStateView * (1 + orders)
_, gasLeft, err = sv.Run(h.state, h.caller, stateViewAddr, in, want, true)
if err != nil {
t.Fatalf("getOpenOrders at exactly the per-order fee: %v", err)
}
if gasLeft != 0 {
t.Fatalf("gasLeft = %d at exactly the computed fee, want 0", gasLeft)
}
// One gas short of the schedule is refused — the boundary is inclusive on the
// paying side and exclusive one wei of gas below it.
if _, _, err := sv.Run(h.state, h.caller, stateViewAddr, in, want-1, true); err == nil {
t.Fatal("one gas below the per-order schedule was accepted")
}
}
// TestBlueEStateViewPositionEncodesTheLockedAmount: getPosition packs the resting
// order into six words. The locked amount lands in word 3; an order with no amount
// leaves it zero rather than reading a neighbouring field.
func TestBlueEStateViewPositionEncodesTheLockedAmount(t *testing.T) {
h := newSettleHarness(t)
sv := blueEStateView()
db := newPoolStateAdapter(h.state)
owner := common.HexToAddress("0x00000000000000000000000000000000000000E3")
funded := [32]byte{0xF1}
blueESeedOrder(db, owner, funded, OrderStatusOpen, big.NewInt(0x1234567890))
read := func(id [32]byte) []byte {
in := append(selectorBytes(SelectorGetPosition), id[:]...)
out, _, err := sv.Run(h.state, h.caller, stateViewAddr, in, 5_000_000, true)
if err != nil {
t.Fatalf("getPosition: %v", err)
}
return out
}
out := read(funded)
if !bytes.Equal(out[0:32], funded[:]) {
t.Fatalf("word 0 is not the order id: %x", out[0:32])
}
if common.BytesToAddress(out[44:64]) != owner {
t.Fatalf("word 1 is not the owner: %x", out[32:64])
}
if got := new(big.Int).SetBytes(out[96:128]); got.Cmp(big.NewInt(0x1234567890)) != 0 {
t.Fatalf("word 3 (locked amount) = %s, want 0x1234567890", got)
}
// An unknown order id reads back as the zero record, and in particular the amount
// word stays zero — a nil LockedAmt must not leave the previous caller's bytes or
// spill an adjacent field into the amount slot.
unknown := read([32]byte{0xFE})
if !bytes.Equal(unknown[96:128], make([]byte, 32)) {
t.Fatalf("an unknown order reported a locked amount: %x", unknown[96:128])
}
if unknown[63] != 0 {
t.Fatalf("an unknown order reported an owner: %x", unknown[32:64])
}
}
// ---------------------------------------------------------------------------
// 0x9998 Quoter — the degenerate-price arm.
// ---------------------------------------------------------------------------
// TestBlueEQuoterZeroPriceQuotesZeroInBothDirections: the token1->token0 leg divides
// by sqrtP^2, and a division by zero from inside a precompile is a validator halt. The
// single guard at the top of spotOutput is what stands between an unpriced market and
// that halt, so it has to hold for BOTH directions — a guard that only covered the
// multiply direction would leave the divide direction live.
//
// (The second `sq.Sign() == 0` check further down that same function is unreachable:
// sq is SqrtPriceX96 squared, and the only value whose square is zero is zero, which
// the first guard already returned on.)
func TestBlueEQuoterZeroPriceQuotesZeroInBothDirections(t *testing.T) {
for _, rec := range []MarketRecord{{SqrtPriceX96: big.NewInt(0)}, {SqrtPriceX96: nil}} {
for _, zeroForOne := range []bool{true, false} {
got := spotOutput(rec, big.NewInt(1_000_000), zeroForOne)
if got == nil || got.Sign() != 0 {
t.Fatalf("an unpriced market quoted %v (zeroForOne=%v), want zero", got, zeroForOne)
}
}
}
// A real price quotes a real amount in both directions, so the zero arms are not
// masking a dead happy path.
priced := MarketRecord{SqrtPriceX96: new(big.Int).Lsh(big.NewInt(1), 96)}
for _, zeroForOne := range []bool{true, false} {
if got := spotOutput(priced, big.NewInt(1_000_000), zeroForOne); got.Sign() <= 0 {
t.Fatalf("a unit sqrt price quoted %s (zeroForOne=%v), want a positive amount", got, zeroForOne)
}
}
}
// ---------------------------------------------------------------------------
// 0x9996 PositionManager — argument bounds on the read arms and the zero delta.
// ---------------------------------------------------------------------------
// TestBlueEPositionManagerReadArmsRefuseShortArgs sweeps every argument length below
// one word on positionsOf and positionInfo, clean and poisoned. positionInfo also has
// a gas floor it must charge before it decodes.
func TestBlueEPositionManagerReadArmsRefuseShortArgs(t *testing.T) {
h := newSettleHarness(t)
pm := blueEPositions()
for _, arm := range []struct {
name string
sel uint32
}{{"positionsOf", SelectorPMPositionsOf}, {"positionInfo", SelectorPMPositionInfo}} {
for n := 0; n < 32; n++ {
in := append(selectorBytes(arm.sel), bytes.Repeat([]byte{0x11}, n)...)
if _, _, err := pm.Run(h.state, h.caller, positionManagerAddr, in, 5_000_000, false); !errors.Is(err, ErrPMBadInput) {
t.Fatalf("%s with %d argument bytes = %v, want ErrPMBadInput", arm.name, n, err)
}
if _, _, err := pm.Run(h.state, h.caller, positionManagerAddr, blueEPoisoned(in, 64), 5_000_000, false); !errors.Is(err, ErrPMBadInput) {
t.Fatalf("%s with %d argument bytes over poisoned capacity = %v, want ErrPMBadInput", arm.name, n, err)
}
}
// One full word is accepted — the sweep above is bounding a real edge, not
// refusing everything.
in := append(selectorBytes(arm.sel), make([]byte, 32)...)
if _, _, err := pm.Run(h.state, h.caller, positionManagerAddr, in, 5_000_000, false); err != nil {
t.Fatalf("%s with a full word: %v", arm.name, err)
}
}
// positionInfo refuses below its own read fee, and consumes the budget when it does.
in := append(selectorBytes(SelectorPMPositionInfo), make([]byte, 32)...)
if _, gasLeft, err := pm.Run(h.state, h.caller, positionManagerAddr, in, GasPositionRead-1, false); err == nil {
t.Fatal("positionInfo ran below its read fee")
} else if gasLeft != 0 {
t.Fatalf("the out-of-gas refusal returned %d gas", gasLeft)
}
}
// blueEPMLifecycle builds the 320-byte 0x9996 lifecycle calldata.
func blueEPMLifecycle(key PoolKey, lower, upper int32, delta *big.Int, salt byte, side MakerSide) []byte {
out := make([]byte, 0, 320)
out = append(out, EncodePoolKeyABI(key)...)
out = append(out, encodeInt24Word(int24(lower))...)
out = append(out, encodeInt24Word(int24(upper))...)
out = append(out, encodeSigned256(delta)...)
s := make([]byte, 32)
s[31] = salt
out = append(out, s...)
sideWord := make([]byte, 32)
sideWord[31] = byte(side)
return append(out, sideWord...)
}
// TestBlueEPositionManagerRefusesZeroDelta: every lifecycle op coerces the delta's
// SIGN but never its magnitude, so a zero delta would route a no-op into the 0x9999
// kernel and consume the custody guard for nothing. It is refused at the adapter.
func TestBlueEPositionManagerRefusesZeroDelta(t *testing.T) {
h := newSettleHarness(t)
pm := blueEPositions()
body := blueEPMLifecycle(h.key, -60, 60, big.NewInt(0), 0x01, MakerSideBid)
for _, sel := range []uint32{
SelectorPMMint, SelectorPMOpenPosition, SelectorPMIncreaseLiquidity, SelectorPMPlaceLimit,
SelectorPMDecreaseLiquidity, SelectorPMBurn, SelectorPMClosePosition, SelectorPMCancelLimit,
SelectorPMModifyPosition,
} {
in := append(selectorBytes(sel), body...)
_, _, err := pm.Run(h.state, h.caller, positionManagerAddr, in, 5_000_000, false)
if !errors.Is(err, ErrPMZeroDelta) {
t.Fatalf("selector %08x with a zero delta = %v, want ErrPMZeroDelta", sel, err)
}
if _, _, perr := pm.Run(h.state, h.caller, positionManagerAddr, blueEPoisoned(in, 96), 5_000_000, false); !errors.Is(perr, ErrPMZeroDelta) {
t.Fatalf("selector %08x with a zero delta over poisoned capacity = %v, want ErrPMZeroDelta", sel, perr)
}
}
// Short lifecycle calldata is refused across the whole span below 320 bytes.
for _, n := range []int{0, 1, 159, 160, 287, 288, 319} {
in := append(selectorBytes(SelectorPMMint), bytes.Repeat([]byte{0x22}, n)...)
if _, _, err := pm.Run(h.state, h.caller, positionManagerAddr, in, 5_000_000, false); !errors.Is(err, ErrPMBadInput) {
t.Fatalf("lifecycle with %d argument bytes = %v, want ErrPMBadInput", n, err)
}
if _, _, err := pm.Run(h.state, h.caller, positionManagerAddr, blueEPoisoned(in, 512), 5_000_000, false); !errors.Is(err, ErrPMBadInput) {
t.Fatalf("lifecycle with %d argument bytes over poisoned capacity = %v, want ErrPMBadInput", n, err)
}
}
}
// ---------------------------------------------------------------------------
// 0x9999 LP-commit kernel (position_commit.go).
// ---------------------------------------------------------------------------
// blueEModifyLiquidityCalldata builds the V4 modifyLiquidity argument buffer.
func blueEModifyLiquidityCalldata(key PoolKey, lower, upper int32, delta *big.Int, salt byte) []byte {
out := make([]byte, 0, 288)
out = append(out, EncodePoolKeyABI(key)...)
out = append(out, encodeInt24Word(int24(lower))...)
out = append(out, encodeInt24Word(int24(upper))...)
out = append(out, encodeSigned256(delta)...)
s := make([]byte, 32)
s[31] = salt
return append(out, s...)
}
// TestBlueEModifyLiquidityArgumentGate walks every refusal the LP-commit kernel makes
// before it touches the custody guard: short calldata, a zero delta, a delta wider than
// a word, and an inverted or out-of-domain tick range. Each must refuse with its OWN
// error — a caller distinguishes "you sent nothing" from "your range is upside down",
// and collapsing them would hide a decode bug behind a range complaint.
func TestBlueEModifyLiquidityArgumentGate(t *testing.T) {
h := newSettleHarness(t)
sel := SelectorModifyLiquidity
run := func(body []byte) error {
_, _, err := h.c.Run(h.state, h.caller, poolManagerAddr9999, prependSelector(sel, body), 5_000_000, false)
return err
}
// Short: the decoder's own refusal, surfaced through the kernel.
for _, n := range []int{0, 1, 159, 160, 287} {
if err := run(bytes.Repeat([]byte{0x33}, n)); err == nil {
t.Fatalf("modifyLiquidity accepted %d argument bytes", n)
}
if err := run(blueEPoisoned(bytes.Repeat([]byte{0x33}, n), 512)); err == nil {
t.Fatalf("modifyLiquidity accepted %d argument bytes over poisoned capacity — it is "+
"decoding bytes past the declared input", n)
}
}
// Zero delta.
if err := run(blueEModifyLiquidityCalldata(h.key, -60, 60, big.NewInt(0), 1)); !errors.Is(err, ErrMakerZeroDelta) {
t.Fatalf("zero delta = %v, want ErrMakerZeroDelta", err)
}
// The amount bound the kernel leans on: |delta| must fit one word. A 256-bit word
// is the largest thing calldata can carry, so the bound is only reachable through
// isWord itself — assert it there, at both sides of the edge, since the kernel's
// ErrMakerAmountRange arm is exactly this predicate negated.
maxWord := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1))
if !isWord(maxWord) {
t.Fatal("isWord refused the largest 256-bit magnitude; the kernel would reject every maximal delta")
}
if isWord(new(big.Int).Add(maxWord, big.NewInt(1))) {
t.Fatal("isWord accepted a 257-bit magnitude; the kernel's amount bound is not a bound")
}
// Tick range: inverted, equal, and both domain edges. Sweep rather than
// point-check — an off-by-one here admits a degenerate range that the D book
// cannot represent.
for _, tc := range []struct {
name string
lower, upper int32
}{
{"inverted", 60, -60},
{"equal", 60, 60},
{"lower below MinTick", MinTick - 1, 60},
{"upper above MaxTick", -60, MaxTick + 1},
{"both outside", MinTick - 1, MaxTick + 1},
} {
err := run(blueEModifyLiquidityCalldata(h.key, tc.lower, tc.upper, big.NewInt(1000), 1))
if !errors.Is(err, ErrMakerBadTickRange) {
t.Fatalf("%s tick range (%d,%d) = %v, want ErrMakerBadTickRange", tc.name, tc.lower, tc.upper, err)
}
}
// The inclusive edges themselves are ACCEPTED — the refusals above are bounding a
// real domain, not swallowing the whole of it. (It gets past the range gate; what
// it fails on next is a different, later check.)
if err := run(blueEModifyLiquidityCalldata(h.key, MinTick, MaxTick, big.NewInt(1000), 1)); errors.Is(err, ErrMakerBadTickRange) {
t.Fatal("the inclusive tick bounds MinTick/MaxTick were refused as out of range")
}
}
// TestBlueEModifyLiquidityRequiresTheAtomicSeam: the LP rail moves value ONLY as
// cross-chain atomic objects. A host with no atomic capability must be refused — a
// C-side-only lock would take the LP's funds with no D-side claim to return them.
func TestBlueEModifyLiquidityRequiresTheAtomicSeam(t *testing.T) {
h := newSettleHarness(t)
plain := &blueEPlainState{inner: h.state}
if _, ok := interface{}(plain).(contract.AtomicState); ok {
t.Fatal("the fixture still advertises the atomic capability")
}
body := blueEModifyLiquidityCalldata(h.key, -60, 60, big.NewInt(1000), 1)
_, _, err := h.c.Run(plain, h.caller, poolManagerAddr9999, prependSelector(SelectorModifyLiquidity, body), 5_000_000, false)
if !errors.Is(err, ErrLPNoAtomicState) {
t.Fatalf("modifyLiquidity without the atomic seam = %v, want ErrLPNoAtomicState", err)
}
// collectPosition is the other half of the rail and must refuse identically —
// otherwise a chain could credit an LP out of the pot with no object to consume.
claim := make([]byte, 128)
_, _, err = h.c.Run(plain, h.caller, poolManagerAddr9999, prependSelector(SelectorCollectPosition, claim), 5_000_000, false)
if !errors.Is(err, ErrLPNoAtomicState) {
t.Fatalf("collectPosition without the atomic seam = %v, want ErrLPNoAtomicState", err)
}
}
// blueEPlainState is an AccessibleState with NO cross-chain atomic capability — a
// single-chain dev host. It must be refused by both LP rail legs.
type blueEPlainState struct{ inner *nativeAtomicState }
func (p *blueEPlainState) GetStateDB() contract.StateDB { return p.inner.GetStateDB() }
func (p *blueEPlainState) GetBlockContext() contract.BlockContext {
return p.inner.GetBlockContext()
}
func (p *blueEPlainState) GetConsensusContext() context.Context { return p.inner.GetConsensusContext() }
func (p *blueEPlainState) GetChainConfig() precompileconfig.ChainConfig {
return p.inner.GetChainConfig()
}
func (p *blueEPlainState) GetPrecompileEnv() contract.PrecompileEnvironment {
return p.inner.GetPrecompileEnv()
}
var _ contract.AccessibleState = (*blueEPlainState)(nil)
// TestBlueECollectPositionRefusesAReentrantClaim: the collect path takes the ONE
// global custody guard before it credits, because the credit's ERC-20 transfer can
// hand control to a malicious token. With the guard already set — the state a
// re-entrant call sees — the claim must be refused outright rather than proceeding to
// a second read-modify-write of the same pot.
func TestBlueECollectPositionRefusesAReentrantClaim(t *testing.T) {
h := newSettleHarness(t)
db := newPoolStateAdapter(h.state)
if !enterCustodyKV(db) {
t.Fatal("the custody guard was already held on a fresh harness")
}
defer exitCustodyKV(db)
input := EncodeCollectPositionInput(ids.ID{0x01}, [32]byte{}, [32]byte{0x02},
encodeClaim(h.caller, [32]byte{}, 500))
_, _, err := h.c.Run(h.state, h.caller, poolManagerAddr9999, prependSelector(SelectorCollectPosition, input), 5_000_000, false)
if !errors.Is(err, ErrCustodyReentrant) {
t.Fatalf("a collect made while the custody guard is held = %v, want ErrCustodyReentrant", err)
}
}
// TestBlueECollectPositionWidthIsExact sweeps the collectPosition calldata width. The
// layout is FIXED — claimID(32) | asset(32) | positionID(32) | claim(60) — so every
// other length is refused on len.
//
// The amount is no longer among the refusals because it is no longer in the calldata:
// it is the uint64 inside the claim object, which the caller cannot widen. A word that
// does not fit a pot is unrepresentable rather than rejected, which is the stronger
// property; that the object must authenticate is settle_import_test.go's subject.
//
// Each length is driven twice, the second time over poisoned spare capacity: calldata
// is a two-index slice of EVM memory the caller filled with MSTORE, so a bound checked
// against cap rather than len reads bytes the caller chose. A fixture built by append
// leaves that region zeroed and passes either way.
func TestBlueECollectPositionWidthIsExact(t *testing.T) {
h := newSettleHarness(t)
for _, n := range []int{0, 1, 31, 32, 95, 96, 127, 128, collectPositionInputLen - 1, collectPositionInputLen + 1} {
body := bytes.Repeat([]byte{0x44}, n)
_, _, err := h.c.Run(h.state, h.caller, poolManagerAddr9999,
prependSelector(SelectorCollectPosition, body), 5_000_000, false)
if !errors.Is(err, ErrLPBadCollectInput) {
t.Fatalf("collect with %d argument bytes = %v, want ErrLPBadCollectInput", n, err)
}
_, _, err = h.c.Run(h.state, h.caller, poolManagerAddr9999,
prependSelector(SelectorCollectPosition, blueEPoisoned(body, 256)), 5_000_000, false)
if !errors.Is(err, ErrLPBadCollectInput) {
t.Fatalf("collect with %d argument bytes over poisoned capacity = %v, want ErrLPBadCollectInput", n, err)
}
}
// The exact width is INSIDE the accepted set: the refusals above are a real
// boundary, not a blanket. It fails later (no such claim), never on length.
exact := EncodeCollectPositionInput(ids.ID{0x01}, [32]byte{}, [32]byte{0x02},
encodeClaim(h.caller, [32]byte{}, 1))
if _, _, err := h.c.Run(h.state, h.caller, poolManagerAddr9999,
prependSelector(SelectorCollectPosition, exact), 5_000_000, false); errors.Is(err, ErrLPBadCollectInput) {
t.Fatal("the canonical width was refused as malformed")
}
}
// blueEActivateMarket registers the harness pool so the LP kernel gets past its
// market gate and reaches the commit/withdraw legs under test.
func blueEActivateMarket(h *settleHarness) [32]byte {
poolID := h.key.ID()
storeMarket(newPoolStateAdapter(h.state), poolID, MarketRecord{
Status: MarketStatusActive,
SqrtPriceX96: new(big.Int).Lsh(big.NewInt(1), 96),
Tick: 0,
Currency0: h.key.Currency0.Address,
Currency1: h.key.Currency1.Address,
Fee: h.key.Fee,
TickSpacing: int24(h.key.TickSpacing),
Creator: h.caller,
})
return poolID
}
// blueECommitCalldata is the V4 modifyLiquidity argument buffer with the maker
// envelope the kernel reads the side from.
func blueECommitCalldata(h *settleHarness, lower, upper int24, delta *big.Int, salt [32]byte, side MakerSide) []byte {
hookData := []byte{makerEnvelopeTag[0], makerEnvelopeTag[1], makerEnvelopeTag[2], makerEnvelopeTag[3], byte(side)}
return buildModifyLiquidityArgs(h.key, lower, upper, delta, salt, hookData)
}
// TestBlueECommitRefusesAHaltedAsset: halting an asset must stop NEW commitments while
// leaving withdrawals open — funds already committed have to stay releasable, or a halt
// becomes a confiscation. Both halves are asserted, and the halt is asserted to be
// scoped: halting the OTHER side of the same pool must not block this side.
func TestBlueECommitRefusesAHaltedAsset(t *testing.T) {
h := newSettleHarness(t)
blueEActivateMarket(h)
db := newPoolStateAdapter(h.state)
h.fundCallerNative(10_000)
bidAsset := lockedAssetForSide(h.key, MakerSideBid)
askAsset := lockedAssetForSide(h.key, MakerSideAsk)
if bidAsset == askAsset {
t.Fatal("the harness pool's two sides share one asset id; the scoping assertion below is vacuous")
}
salt := [32]byte{0x51}
commit := func() error {
_, _, err := h.c.Run(h.state, h.caller, poolManagerAddr9999,
prependSelector(SelectorModifyLiquidity, blueECommitCalldata(h, -60, 60, big.NewInt(1000), salt, MakerSideBid)),
5_000_000, false)
return err
}
// Halting the OTHER side leaves this commit alone.
SetHaltAsset(db, askAsset, true)
if err := commit(); errors.Is(err, ErrAssetHalted) {
t.Fatal("halting the ask asset blocked a bid-side commit — the halt is not scoped to the asset")
}
SetHaltAsset(db, askAsset, false)
// Halting THIS side refuses the commit.
SetHaltAsset(db, bidAsset, true)
if err := commit(); !errors.Is(err, ErrAssetHalted) {
t.Fatalf("commit against a halted asset = %v, want ErrAssetHalted", err)
}
// The custody guard is not left held by the refusal: a second call reaches the
// same halt refusal rather than ErrCustodyReentrant. A refusal that leaked the
// guard would wedge the WHOLE 0x9999 custody surface for the rest of the block.
if err := commit(); !errors.Is(err, ErrAssetHalted) {
t.Fatalf("the second commit after a halted refusal = %v — the first refusal leaked the custody guard", err)
}
// And a WITHDRAW of an existing position is still permitted while the asset is
// halted: the funds stay releasable.
orderID := MakerOrderID(h.caller, h.key.ID(), salt, -60, 60)
storeRestingOrder(db, orderID, RestingOrder{
Owner: h.caller, PoolID: h.key.ID(), Side: MakerSideBid, LockedAsset: bidAsset,
LockedAmt: big.NewInt(1000), TickLower: -60, TickUpper: 60, Status: OrderStatusOpen,
})
_, _, err := h.c.Run(h.state, h.caller, poolManagerAddr9999,
prependSelector(SelectorModifyLiquidity, blueECommitCalldata(h, -60, 60, big.NewInt(-1000), salt, MakerSideBid)),
5_000_000, false)
if err != nil {
t.Fatalf("withdraw while the asset is halted = %v, want it permitted so funds stay releasable", err)
}
if loadRestingOrder(db, orderID).Status != OrderStatusClosing {
t.Fatal("the withdraw did not mark the position closing")
}
}
// TestBlueECommitRefusesADeltaWiderThanThePot: the committedPositions pot is uint64.
// A signed-256 delta that survives the word bound but does not fit uint64 must be
// refused, never truncated — a truncation would commit the low 64 bits of an
// arbitrarily large number and leave the record disagreeing with the money.
func TestBlueECommitRefusesADeltaWiderThanThePot(t *testing.T) {
h := newSettleHarness(t)
blueEActivateMarket(h)
h.fundCallerNative(1_000_000)
for _, tc := range []struct {
name string
delta *big.Int
}{
{"one past uint64", new(big.Int).Lsh(big.NewInt(1), 64)},
{"2^128", new(big.Int).Lsh(big.NewInt(1), 128)},
{"the largest positive signed word", new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 255), big.NewInt(1))},
} {
_, _, err := h.c.Run(h.state, h.caller, poolManagerAddr9999,
prependSelector(SelectorModifyLiquidity, blueECommitCalldata(h, -60, 60, tc.delta, [32]byte{0x52}, MakerSideBid)),
5_000_000, false)
if !errors.Is(err, ErrMakerAmountRange) {
t.Fatalf("commit of %s (%s) = %v, want ErrMakerAmountRange", tc.name, tc.delta, err)
}
}
// The top of the uint64 domain is inside the accepted set — the refusals above are
// a real edge, not a blanket refusal of large numbers. It fails on funding, which
// is a different, later check.
max64 := new(big.Int).SetUint64(^uint64(0))
_, _, err := h.c.Run(h.state, h.caller, poolManagerAddr9999,
prependSelector(SelectorModifyLiquidity, blueECommitCalldata(h, -60, 60, max64, [32]byte{0x53}, MakerSideBid)),
5_000_000, false)
if errors.Is(err, ErrMakerAmountRange) {
t.Fatal("the largest representable uint64 delta was refused as out of range")
}
}
// TestBlueEWithdrawChecksTheRecordedOwnerAndTheClosedState covers the two guards the
// withdraw leg applies AFTER it has found a record.
//
// The order id is derived from the caller, so in normal operation the recorded owner
// always equals the caller and the `order.Owner != caller` check is defence in depth.
// It is exactly the kind of check that rots unnoticed, so it is exercised the only way
// it can be: by seeding a record whose owner field disagrees with the derivation. If a
// future change ever makes the id derivable without the owner, this guard is the thing
// standing between one LP and another LP's position — it has to work.
func TestBlueEWithdrawChecksTheRecordedOwnerAndTheClosedState(t *testing.T) {
h := newSettleHarness(t)
blueEActivateMarket(h)
db := newPoolStateAdapter(h.state)
poolID := h.key.ID()
other := common.HexToAddress("0x00000000000000000000000000000000000000FF")
withdraw := func(salt [32]byte) error {
_, _, err := h.c.Run(h.state, h.caller, poolManagerAddr9999,
prependSelector(SelectorModifyLiquidity, blueECommitCalldata(h, -60, 60, big.NewInt(-1000), salt, MakerSideBid)),
5_000_000, false)
return err
}
// No record at all: refused as not-owner (there is nothing of the caller's here).
if err := withdraw([32]byte{0x61}); !errors.Is(err, ErrMakerNotOwner) {
t.Fatalf("withdraw with no record = %v, want ErrMakerNotOwner", err)
}
// A record at the caller's derived id whose OWNER is somebody else.
foreign := [32]byte{0x62}
fid := MakerOrderID(h.caller, poolID, foreign, -60, 60)
storeRestingOrder(db, fid, RestingOrder{
Owner: other, PoolID: poolID, Side: MakerSideBid, LockedAsset: lockedAssetForSide(h.key, MakerSideBid),
LockedAmt: big.NewInt(1000), TickLower: -60, TickUpper: 60, Status: OrderStatusOpen,
})
if err := withdraw(foreign); !errors.Is(err, ErrMakerNotOwner) {
t.Fatalf("withdraw of a record owned by another address = %v, want ErrMakerNotOwner", err)
}
if got := loadRestingOrder(db, fid); got.Status != OrderStatusOpen || got.Owner != other {
t.Fatalf("the refused withdraw mutated the record: %+v", got)
}
// A record that is already CANCELLED is closed for good — withdrawing again must
// not re-arm it, or the same position could be released twice.
dead := [32]byte{0x63}
did := MakerOrderID(h.caller, poolID, dead, -60, 60)
storeRestingOrder(db, did, RestingOrder{
Owner: h.caller, PoolID: poolID, Side: MakerSideBid, LockedAsset: lockedAssetForSide(h.key, MakerSideBid),
LockedAmt: big.NewInt(1000), TickLower: -60, TickUpper: 60, Status: OrderStatusCancelled,
})
if err := withdraw(dead); !errors.Is(err, ErrMakerAlreadyClosed) {
t.Fatalf("withdraw of a cancelled position = %v, want ErrMakerAlreadyClosed", err)
}
if loadRestingOrder(db, did).Status != OrderStatusCancelled {
t.Fatal("the refused withdraw moved a cancelled position out of the cancelled state")
}
// An OPEN record the caller really owns goes to CLOSING, and a second withdraw is
// idempotent rather than a second release: Closing is not Cancelled, so it passes
// the guards and re-marks the same state.
live := [32]byte{0x64}
lid := MakerOrderID(h.caller, poolID, live, -60, 60)
storeRestingOrder(db, lid, RestingOrder{
Owner: h.caller, PoolID: poolID, Side: MakerSideBid, LockedAsset: lockedAssetForSide(h.key, MakerSideBid),
LockedAmt: big.NewInt(1000), TickLower: -60, TickUpper: 60, Status: OrderStatusOpen,
})
if err := withdraw(live); err != nil {
t.Fatalf("withdraw of the caller's own open position: %v", err)
}
if loadRestingOrder(db, lid).Status != OrderStatusClosing {
t.Fatal("the withdraw did not mark the position closing")
}
if err := withdraw(live); err != nil {
t.Fatalf("a repeated withdraw of a closing position = %v, want it idempotent", err)
}
if got := loadRestingOrder(db, lid); got.Status != OrderStatusClosing || got.LockedAmt.Cmp(big.NewInt(1000)) != 0 {
t.Fatalf("the repeated withdraw changed the record: %+v", got)
}
}