mirror of
https://github.com/luxfi/maker.git
synced 2026-10-30 16:20:04 +00:00
priceToGrid/gridToPrice were two of the five copies grid.go replaced. Co-authored-by: Hanzo Dev <[email protected]>
204 lines
6.7 KiB
Go
204 lines
6.7 KiB
Go
// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package maker
|
|
|
|
import (
|
|
"context"
|
|
"math/big"
|
|
"testing"
|
|
|
|
"github.com/luxfi/geth/common"
|
|
)
|
|
|
|
func TestPriceGridRoundTrip(t *testing.T) {
|
|
cases := []struct {
|
|
price float64
|
|
grid uint64
|
|
}{
|
|
{1.0, 100_000_000},
|
|
{3500.25, 350_025_000_000},
|
|
{0.004, 400_000},
|
|
{59507.75, 5_950_775_000_000},
|
|
}
|
|
for _, c := range cases {
|
|
if g := GridOf(c.price); g != c.grid {
|
|
t.Errorf("GridOf(%v) = %d, want %d", c.price, g, c.grid)
|
|
}
|
|
if p := PriceOfBig(new(big.Int).SetUint64(c.grid)); !nearly(p, c.price, 1e-6) {
|
|
t.Errorf("PriceOfBig(%d) = %v, want %v", c.grid, p, c.price)
|
|
}
|
|
}
|
|
if GridOf(0) != 0 || GridOf(-5) != 0 {
|
|
t.Error("non-positive price must grid to 0")
|
|
}
|
|
if PriceOfBig(big.NewInt(0)) != 0 {
|
|
t.Error("zero grid must price to 0")
|
|
}
|
|
}
|
|
|
|
func TestParseCoherenceMarkets(t *testing.T) {
|
|
eth := "0x0000000000000000000000000000000000000011"
|
|
btc := "0x0000000000000000000000000000000000000012"
|
|
lux := "0x0000000000000000000000000000000000000013"
|
|
usd := "0x00000000000000000000000000000000000000ff"
|
|
wlux := "0x00000000000000000000000000000000000000ee"
|
|
spec := "ETH/USD=ETHUSD:" + eth + ":" + usd +
|
|
",BTC/USD=XBTUSD:" + btc + ":" + usd + ":50000" +
|
|
",LUX/USD=fixed:12.5:" + lux + ":" + usd +
|
|
",LETH/LUX=ratio:ETHUSD/12.5:" + eth + ":" + wlux
|
|
|
|
markets, kraken, fixed, ratio, err := ParseCoherenceMarkets(spec)
|
|
if err != nil {
|
|
t.Fatalf("parse: %v", err)
|
|
}
|
|
if len(markets) != 4 {
|
|
t.Fatalf("got %d markets, want 4", len(markets))
|
|
}
|
|
if markets[0].Symbol != "ETH/USD" || markets[0].KrakenPair != "ETHUSD" ||
|
|
markets[0].Base != common.HexToAddress(eth) || markets[0].Quote != common.HexToAddress(usd) {
|
|
t.Errorf("ETH market mis-parsed: %+v", markets[0])
|
|
}
|
|
if markets[1].Size != 50000 {
|
|
t.Errorf("BTC size = %d, want 50000", markets[1].Size)
|
|
}
|
|
if markets[2].KrakenPair != "" || markets[2].Base != common.HexToAddress(lux) {
|
|
t.Errorf("LUX market mis-parsed: %+v", markets[2])
|
|
}
|
|
if markets[3].Symbol != "LETH/LUX" || markets[3].KrakenPair != "" ||
|
|
markets[3].Base != common.HexToAddress(eth) || markets[3].Quote != common.HexToAddress(wlux) {
|
|
t.Errorf("LETH/LUX market mis-parsed: %+v", markets[3])
|
|
}
|
|
if kraken["ETH/USD"] != "ETHUSD" || kraken["BTC/USD"] != "XBTUSD" {
|
|
t.Errorf("kraken pairs wrong: %v", kraken)
|
|
}
|
|
if fixed["LUX/USD"] != 12.5 {
|
|
t.Errorf("fixed LUX = %v, want 12.5", fixed["LUX/USD"])
|
|
}
|
|
if _, ok := fixed["ETH/USD"]; ok {
|
|
t.Error("ETH/USD must not be a fixed market")
|
|
}
|
|
r, ok := ratio["LETH/LUX"]
|
|
if !ok || r.Pair != "ETHUSD" || r.Denom != 12.5 {
|
|
t.Errorf("ratio LETH/LUX = %+v (ok=%v), want {ETHUSD 12.5}", r, ok)
|
|
}
|
|
if _, ok := ratio["ETH/USD"]; ok {
|
|
t.Error("ETH/USD must not be a ratio market")
|
|
}
|
|
}
|
|
|
|
func TestParseCoherenceMarketsErrors(t *testing.T) {
|
|
bad := []string{
|
|
"",
|
|
"ETH/USD", // no '='
|
|
"ETH/USD=ETHUSD", // no base/quote
|
|
"ETH/USD=ETHUSD:0x0000000000000000000000000000000000000000:0x00000000000000000000000000000000000000ff", // zero base
|
|
"LETH/LUX=ratio:ETHUSD:0x00000000000000000000000000000000000000aa:0x00000000000000000000000000000000000000ee", // ratio missing /denom
|
|
"LETH/LUX=ratio:ETHUSD/0:0x00000000000000000000000000000000000000aa:0x00000000000000000000000000000000000000ee", // ratio non-positive denom
|
|
"LETH/LUX=ratio:ETHUSD/x:0x00000000000000000000000000000000000000aa:0x00000000000000000000000000000000000000ee", // ratio bad denom
|
|
}
|
|
for _, spec := range bad {
|
|
if _, _, _, _, err := ParseCoherenceMarkets(spec); err == nil {
|
|
t.Errorf("spec %q: want error, got nil", spec)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCoherenceOracle(t *testing.T) {
|
|
o := NewCoherenceOracle(nil, map[string]float64{"LUX/USD": 12.5}, nil, nil)
|
|
if p, err := o.GetMidPrice(context.Background(), "LUX/USD"); err != nil || p != 12.5 {
|
|
t.Fatalf("fixed LUX/USD = %v, %v; want 12.5, nil", p, err)
|
|
}
|
|
if _, err := o.GetMidPrice(context.Background(), "UNKNOWN"); err == nil {
|
|
t.Error("unknown symbol must error (never a fabricated price)")
|
|
}
|
|
}
|
|
|
|
// TestCoherenceOracleRatio verifies the cross-rate source divides the Kraken leg by
|
|
// the denom, using a stub feed so the test needs no network.
|
|
func TestCoherenceOracleRatio(t *testing.T) {
|
|
o := &compositeOracle{
|
|
kraken: fixedFeed{"LETH/LUX": 3800}, // stub: Kraken(ETHUSD) leg = 3800
|
|
fixed: nil,
|
|
ratio: map[string]RatioRef{"LETH/LUX": {Pair: "ETHUSD", Denom: 12.5}},
|
|
}
|
|
p, err := o.GetMidPrice(context.Background(), "LETH/LUX")
|
|
if err != nil || !nearly(p, 3800.0/12.5, 1e-9) {
|
|
t.Fatalf("ratio LETH/LUX = %v, %v; want %v, nil", p, err, 3800.0/12.5)
|
|
}
|
|
// A ratio symbol with no feed must error, never fabricate.
|
|
o2 := &compositeOracle{ratio: map[string]RatioRef{"LETH/LUX": {Pair: "ETHUSD", Denom: 12.5}}}
|
|
if _, err := o2.GetMidPrice(context.Background(), "LETH/LUX"); err == nil {
|
|
t.Error("ratio with no kraken feed must error")
|
|
}
|
|
}
|
|
|
|
// fixedFeed is a stub MidProvider returning a constant mid per symbol.
|
|
type fixedFeed map[string]float64
|
|
|
|
func (f fixedFeed) GetMidPrice(_ context.Context, symbol string) (float64, error) {
|
|
if p, ok := f[symbol]; ok {
|
|
return p, nil
|
|
}
|
|
return 0, context.Canceled
|
|
}
|
|
|
|
func TestFixedMid(t *testing.T) {
|
|
var f FixedMid = 42.5
|
|
if p, _ := f.GetMidPrice(context.Background(), "anything"); p != 42.5 {
|
|
t.Errorf("FixedMid = %v, want 42.5", p)
|
|
}
|
|
}
|
|
|
|
func TestPoolKeyForOrientation(t *testing.T) {
|
|
a := common.HexToAddress("0x00000000000000000000000000000000000000aa")
|
|
b := common.HexToAddress("0x00000000000000000000000000000000000000bb")
|
|
k1 := PoolKeyFor(a, b)
|
|
k2 := PoolKeyFor(b, a) // reversed input must yield the same key (currency0 < currency1)
|
|
if k1.Currency0.Address != a || k1.Currency1.Address != b {
|
|
t.Errorf("orientation wrong: %v", k1)
|
|
}
|
|
if k1.ID() != k2.ID() {
|
|
t.Error("PoolKeyFor must be order-independent")
|
|
}
|
|
}
|
|
|
|
func TestDecodeBytes32ArrayU64(t *testing.T) {
|
|
// ABI bytes32[] of [3, 7]: offset(0x20), len(2), word(3), word(7).
|
|
enc := make([]byte, 32*4)
|
|
new(big.Int).SetUint64(0x20).FillBytes(enc[0:32])
|
|
new(big.Int).SetUint64(2).FillBytes(enc[32:64])
|
|
new(big.Int).SetUint64(3).FillBytes(enc[64:96])
|
|
new(big.Int).SetUint64(7).FillBytes(enc[96:128])
|
|
ids, err := decodeBytes32ArrayU64(enc)
|
|
if err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
if len(ids) != 2 || ids[0] != 3 || ids[1] != 7 {
|
|
t.Errorf("decoded %v, want [3 7]", ids)
|
|
}
|
|
if got, _ := decodeBytes32ArrayU64(make([]byte, 32)); got != nil {
|
|
t.Errorf("short input -> nil, got %v", got)
|
|
}
|
|
}
|
|
|
|
func TestDiffNew(t *testing.T) {
|
|
before := toSet([]uint64{1, 2, 3})
|
|
got := diffNew(before, []uint64{1, 2, 3, 9, 10})
|
|
if len(got) != 2 || got[0] != 9 || got[1] != 10 {
|
|
t.Errorf("diffNew = %v, want [9 10]", got)
|
|
}
|
|
}
|
|
|
|
func nearly(a, b, tol float64) bool {
|
|
d := a - b
|
|
if d < 0 {
|
|
d = -d
|
|
}
|
|
bb := b
|
|
if bb < 0 {
|
|
bb = -bb
|
|
}
|
|
return d <= tol*(1+bb)
|
|
}
|