mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
quoteExactOutput and quoteExactOutputSingle discarded their direction into `_` and returned the exact-input projection, so a caller asking what 1000 token1 costs got what 1000 token0 buys -- wrong by the square of the price, with the fee taken off the wrong side. At price 4 it answered 4000 where the answer is 250. The two questions are inverses of one price-and-fee map: the exact-output side divides where the other multiplies and grosses the fee onto the input rather than deducting it. Every step rounds up, so the quoted input covers the output asked for and no smaller input does -- swept over five fees, five prices, both directions and six amounts, with the one-unit-lower input asserted short. The exact-input answer is unchanged, which is asserted directly. A token move refuses an address carrying no contract code. The EVM answers a CALL into such an address with success and no return data, and that is also how a non-compliant ERC-20 reports success, so the return alone cannot separate them; SafeERC20 separates them by requiring code, and now so does this. It matters on the push leg: withdraw debits the claim before it moves the token, so reading that empty return as delivery retires a claim against a transfer that did not happen. The pull leg was already covered by its delivered-amount check. One moveToken now issues both, so the requirement and the return reading cannot drift apart. Mutation: dropping the code check fails two tests. A token that HAS code and returns nothing still settles, which is asserted separately -- the refusal is bought by the code check alone. getOpenOrders charges for the record loop before running it. The order count comes from the index alone, so the bill is knowable first; the sibling enumeration on 0x9996 already charged at that point. A length read out of a storage word is bounded before it sizes an allocation. int(uint64) is negative above 2^63 and make takes that as a panic, which from a precompile is a halt rather than a refusal. slotLen is the one bound and the three readers -- record, order index, staging -- answer absent above it. This replaces a characterisation test that asserted the panic and asked to be rewritten once the bound existed. reclaim no longer stages a remove it cannot deliver. A remove names a key in the inbound space, where objects a peer exported to this chain live; the intent object went the other way, so the staged remove could never name it, and against a missing key it writes a tombstone into a namespace that has nothing to do with it. What reclaim guarantees on this chain -- the principal exits once, because the zeroed remaining caps any later settlement -- is unchanged and now stated where the comment claimed more.
148 lines
5.8 KiB
Go
148 lines
5.8 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package dex
|
|
|
|
import (
|
|
"math/big"
|
|
"testing"
|
|
|
|
"github.com/luxfi/geth/common"
|
|
)
|
|
|
|
// quote_direction_test.go pins the direction of the 0x9998 projection. The eight
|
|
// quote selectors ask one of two questions — given an input, what output does it
|
|
// buy; given a desired output, what input does it cost — and the two answers are
|
|
// inverses of the same price-and-fee map, not the same number.
|
|
//
|
|
// The exact-output side rounds UP at every step. A quote of what a taker must
|
|
// supply may overstate by a unit; understating it is a quote the taker cannot
|
|
// fill.
|
|
|
|
// qdMarket is a market at price = sqrtP^2 / 2^192 with the given fee in pips.
|
|
func qdMarket(priceSqrtMultiple int64, fee uint24) MarketRecord {
|
|
return vpMarket(new(big.Int).Lsh(big.NewInt(priceSqrtMultiple), 96), fee)
|
|
}
|
|
|
|
// TestExactOutputAsksTheOtherQuestion is the defect: at any price other than 1 the
|
|
// input needed to buy N is not the output that N buys, so a projection that ignores
|
|
// the direction answers the wrong question with a confident number.
|
|
func TestExactOutputAsksTheOtherQuestion(t *testing.T) {
|
|
// sqrtP = 2*2^96 => price 4: one token0 buys four token1.
|
|
rec := qdMarket(2, 0)
|
|
want := big.NewInt(1_000)
|
|
|
|
in := projectSingleTick(rec, want, true /*zeroForOne*/, false /*exactOut*/)
|
|
if in.Int64() != 250 {
|
|
t.Fatalf("input to buy 1000 token1 at price 4 = %s, want 250", in)
|
|
}
|
|
out := projectSingleTick(rec, want, true, true /*exactIn*/)
|
|
if out.Int64() != 4_000 {
|
|
t.Fatalf("output bought by 1000 token0 at price 4 = %s, want 4000", out)
|
|
}
|
|
if in.Cmp(out) == 0 {
|
|
t.Fatal("the exact-output projection returned the exact-input answer")
|
|
}
|
|
|
|
// The other direction inverts: one token1 buys a quarter of a token0.
|
|
in = projectSingleTick(rec, want, false /*oneForZero*/, false)
|
|
if in.Int64() != 4_000 {
|
|
t.Fatalf("input to buy 1000 token0 at price 4 = %s, want 4000", in)
|
|
}
|
|
}
|
|
|
|
// TestExactOutputCoversTheOutputItQuotes is the property that matters: feeding the
|
|
// quoted input back through the exact-input projection must buy at least the output
|
|
// that was asked for. A round trip that comes up short is a quote that cannot fill.
|
|
func TestExactOutputCoversTheOutputItQuotes(t *testing.T) {
|
|
for _, fee := range []uint24{0, 500, 3_000, 10_000, FeeMax} {
|
|
for _, mult := range []int64{1, 2, 3, 7, 1_000} {
|
|
rec := qdMarket(mult, fee)
|
|
for _, zeroForOne := range []bool{true, false} {
|
|
for _, want := range []int64{1, 2, 7, 999, 1_000, 1_000_001} {
|
|
wantOut := big.NewInt(want)
|
|
in := projectSingleTick(rec, wantOut, zeroForOne, false)
|
|
got := projectSingleTick(rec, in, zeroForOne, true)
|
|
if got.Cmp(wantOut) < 0 {
|
|
t.Fatalf("fee=%d price=%d^2 zeroForOne=%v: quoted input %s buys %s, short of the %s asked for",
|
|
fee, mult, zeroForOne, in, got, wantOut)
|
|
}
|
|
// And it must not be wasteful: one unit less must come up short,
|
|
// which pins the rounding to the smallest covering input.
|
|
if in.Sign() > 0 {
|
|
less := new(big.Int).Sub(in, big.NewInt(1))
|
|
if projectSingleTick(rec, less, zeroForOne, true).Cmp(wantOut) >= 0 {
|
|
t.Fatalf("fee=%d price=%d^2 zeroForOne=%v: quoted input %s for %s is one above the smallest that covers it",
|
|
fee, mult, zeroForOne, in, wantOut)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestExactInputProjectionIsUnchanged pins that adding the inverse did not move the
|
|
// exact-input answer. Every existing caller reads that number.
|
|
func TestExactInputProjectionIsUnchanged(t *testing.T) {
|
|
for _, fee := range []uint24{0, 3_000, FeeMax} {
|
|
for _, mult := range []int64{1, 2, 5} {
|
|
rec := qdMarket(mult, fee)
|
|
for _, zeroForOne := range []bool{true, false} {
|
|
amount := big.NewInt(1_000_000)
|
|
if got, want := projectSingleTick(rec, amount, zeroForOne, true), spotOutputFeeAdjusted(rec, amount, zeroForOne); got.Cmp(want) != 0 {
|
|
t.Fatalf("fee=%d price=%d^2 zeroForOne=%v: exact-input projection = %s, want the unchanged %s",
|
|
fee, mult, zeroForOne, got, want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestPricelessMarketQuotesZeroInBothDirections pins the degenerate market: a record
|
|
// with no registered price cannot answer either question, and must answer 0 rather
|
|
// than divide by it.
|
|
func TestPricelessMarketQuotesZeroInBothDirections(t *testing.T) {
|
|
for _, rec := range []MarketRecord{
|
|
{SqrtPriceX96: nil, Fee: 3_000, Status: MarketStatusActive},
|
|
{SqrtPriceX96: big.NewInt(0), Fee: 3_000, Status: MarketStatusActive},
|
|
} {
|
|
for _, zeroForOne := range []bool{true, false} {
|
|
for _, exactIn := range []bool{true, false} {
|
|
if got := projectSingleTick(rec, big.NewInt(1_000), zeroForOne, exactIn); got.Sign() != 0 {
|
|
t.Fatalf("priceless market quoted %s (zeroForOne=%v exactIn=%v), want 0", got, zeroForOne, exactIn)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestQuoterDispatchesTheDirection pins the wiring: the exact-output selectors must
|
|
// reach the exact-output projection through the real Run, not just in isolation.
|
|
func TestQuoterDispatchesTheDirection(t *testing.T) {
|
|
h := newSettleHarness(t)
|
|
h.registerMarket(t)
|
|
q := &QuoterContract{}
|
|
addr := common.HexToAddress(DEXQuoterAddress)
|
|
amount := big.NewInt(1_000_000)
|
|
|
|
read := func(sel uint32) *big.Int {
|
|
t.Helper()
|
|
out, _, err := q.Run(h.state, h.caller, addr, quoteCalldata(sel, h.key, amount, true), 5_000_000, true)
|
|
if err != nil {
|
|
t.Fatalf("selector %#x: %v", sel, err)
|
|
}
|
|
return new(big.Int).SetBytes(out)
|
|
}
|
|
|
|
rec := loadMarket(readOnlyView{state: h.state}, h.key.ID())
|
|
if got, want := read(SelQExactInput), projectSingleTick(rec, amount, true, true); got.Cmp(want) != 0 {
|
|
t.Fatalf("exactInput selector = %s, want %s", got, want)
|
|
}
|
|
for _, sel := range []uint32{SelQExactOutput, SelQExactOutputSingle} {
|
|
if got, want := read(sel), projectSingleTick(rec, amount, true, false); got.Cmp(want) != 0 {
|
|
t.Fatalf("selector %#x = %s, want the exact-output projection %s", sel, got, want)
|
|
}
|
|
}
|
|
}
|