Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

pair_test.gno

3.27 Kb · 102 lines
  1package pair
  2
  3import (
  4	"testing"
  5
  6	"gno.land/p/nt/uassert/v0"
  7)
  8
  9// The pricing function, on its own, is the part worth pinning numerically: it
 10// is pure, it is where the 128-bit arithmetic lives, and every instance in the
 11// ecosystem runs this exact code.
 12func TestAmountOut(t *testing.T) {
 13	cases := []struct {
 14		name                    string
 15		in, resIn, resOut, want int64
 16	}{
 17		{"balanced pool, small trade", 1_000, 1_000_000, 1_000_000, 996},
 18		{"balanced pool, 1%", 10_000, 1_000_000, 1_000_000, 9_871},
 19		{"balanced pool, 10%", 100_000, 1_000_000, 1_000_000, 90_661},
 20		{"balanced pool, 50%", 500_000, 1_000_000, 1_000_000, 332_665},
 21		{"skewed pool", 100_000, 1_000_000, 4_000_000, 362_644},
 22		{"near drain", 1_000, 1, 1_000_000, 998_997},
 23		{"dust rounds to zero", 1, 1_000_000, 1_000_000, 0},
 24		{"reserves at the cap", MaxReserve / 2, MaxReserve / 2, MaxReserve, 4_604_758_097_518_382},
 25	}
 26	for _, tc := range cases {
 27		got := AmountOut(tc.in, tc.resIn, tc.resOut)
 28		uassert.Equal(t, tc.want, got, tc.name)
 29	}
 30}
 31
 32// The fee is what stays behind: k must never decrease across a swap.
 33func TestConstantProductNeverRegresses(t *testing.T) {
 34	resIn, resOut := int64(1_000_000), int64(4_000_000)
 35	for _, in := range []int64{1, 1_000, 100_000, 999_999} {
 36		out := AmountOut(in, resIn, resOut)
 37		uassert.True(t, cmpProd(resIn+in, resOut-out, resIn, resOut) >= 0,
 38			"k must not regress")
 39	}
 40}
 41
 42func TestAmountOutGuards(t *testing.T) {
 43	mustPanic(t, "pair: amountIn must be > 0", func() {
 44		AmountOut(0, 1_000, 1_000)
 45	})
 46	mustPanic(t, "pair: pair has an empty reserve", func() {
 47		AmountOut(1, 0, 1_000)
 48	})
 49	mustPanic(t, "pair: reserve above cap", func() {
 50		AmountOut(1, MaxReserve+1, 1_000)
 51	})
 52	mustPanic(t, "pair: reserve cap exceeded", func() {
 53		AmountOut(2, MaxReserve-1, 1_000)
 54	})
 55}
 56
 57// mulDiv is the only place a 128-bit intermediate is required, and the only
 58// place bits.Div64 could panic. Both ends are asserted rather than argued.
 59func TestMulDiv(t *testing.T) {
 60	uassert.Equal(t, int64(2), mulDiv(4, 3, 6))
 61	uassert.Equal(t, int64(0), mulDiv(1, 1, 3), "floors")
 62	uassert.Equal(t, int64(4_611_686_018_427_387_903), mulDiv(9_223_372_036_854_775_807, 1, 2),
 63		"operands near MaxInt64 stay exact through the 128-bit path")
 64
 65	mustPanic(t, "pair: division by a non-positive value", func() {
 66		mulDiv(1, 1, 0)
 67	})
 68	mustPanic(t, "pair: quotient overflows int64", func() {
 69		mulDiv(9_223_372_036_854_775_807, 9_223_372_036_854_775_807, 1)
 70	})
 71}
 72
 73func TestCmpProd(t *testing.T) {
 74	uassert.Equal(t, 0, cmpProd(6, 7, 42, 1))
 75	uassert.Equal(t, -1, cmpProd(6, 6, 42, 1))
 76	uassert.Equal(t, 1, cmpProd(7, 7, 42, 1))
 77	// Both products exceed int64 and differ only in the low word.
 78	uassert.Equal(t, -1, cmpProd(4_000_000_000, 4_000_000_000, 4_000_000_000, 4_000_000_001))
 79}
 80
 81// mustPanic is uassert.PanicsWithMessage without the realm argument: a pure
 82// package cannot produce a `cur` to hand it, since it can never declare a
 83// crossing function in the first place.
 84func mustPanic(t *testing.T, want string, f func()) {
 85	t.Helper()
 86	defer func() {
 87		r := recover()
 88		if r == nil {
 89			t.Errorf("expected panic %q, got none", want)
 90			return
 91		}
 92		got, ok := r.(string)
 93		if !ok {
 94			t.Errorf("expected a string panic %q, got %v", want, r)
 95			return
 96		}
 97		if got != want {
 98			t.Errorf("expected panic %q, got %q", want, got)
 99		}
100	}()
101	f()
102}