mirror of
https://github.com/luxfi/maker.git
synced 2026-10-30 16:20:04 +00:00
The maker quoted LUX/* at a static config mid (12.5) — fictional; the real WLUX/LUSD V3 spot is ~0.00061 LUSD. provider_v3.go adds V3ProviderSource: reads the MARGINAL spot from factory.getPool + pool.slot0 (sqrtPriceX96), NOT quoteExactInputSingle (a fixed-size quote slips a thin pool). Decomplected: PriceFeed (what to track) is orthogonal to the DEX venue (where to quote) — seeder composes an optional MidProvider (*Aggregator) via UseFeed; currentMid consults feed -> DEX book -> static. LUX lists only on native V2/V3/V4, so the AMM IS its reference. Opt-in via MAKER_V3_RPC/FACTORY/WLUX; absent = prior behavior. Selectors computed at init (getPool 1698ee82, slot0 3850c7bd), pinned in tests; spot orientation + decimals verified; live mainnet read confirms 0.00061. Verified: go test ./... green; raw eth_call to live QuoterV2/pool returns the price.
102 lines
3.7 KiB
Go
102 lines
3.7 KiB
Go
package maker
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"math/big"
|
|
"testing"
|
|
|
|
"github.com/luxfi/geth/common"
|
|
)
|
|
|
|
// TestV3Selectors pins the factory.getPool and pool.slot0 selectors to their
|
|
// canonical Uniswap-V3 values. They are computed at init from the signature
|
|
// strings, so this guards both the spelling and the keccak primitive — a wrong
|
|
// selector silently fails every on-chain read.
|
|
func TestV3Selectors(t *testing.T) {
|
|
if got := hex.EncodeToString(sigGetPool); got != "1698ee82" {
|
|
t.Fatalf("getPool selector = %s, want 1698ee82", got)
|
|
}
|
|
if got := hex.EncodeToString(sigSlot0); got != "3850c7bd" {
|
|
t.Fatalf("slot0 selector = %s, want 3850c7bd", got)
|
|
}
|
|
}
|
|
|
|
// sqrtX96For returns sqrtPriceX96 encoding a raw token1/token0 wei ratio: it sets
|
|
// sqrtPriceX96 = sqrt(raw)·2^96 (the V3 fixed-point spot encoding).
|
|
func sqrtX96For(raw float64) *big.Int {
|
|
q96 := new(big.Float).SetInt(new(big.Int).Lsh(big.NewInt(1), 96))
|
|
s := new(big.Float).Mul(new(big.Float).Sqrt(big.NewFloat(raw)), q96)
|
|
out, _ := s.Int(nil)
|
|
return out
|
|
}
|
|
|
|
// TestV3SpotOrientation verifies marginal-spot decoding from sqrtPriceX96 with
|
|
// correct token0/token1 orientation: when base is token0 the price is the raw
|
|
// token1/token0; when base is token1 it is inverted. Equal decimals.
|
|
func TestV3SpotOrientation(t *testing.T) {
|
|
low := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
|
high := common.HexToAddress("0x2222222222222222222222222222222222222222")
|
|
sqrt := sqrtX96For(4.0) // token1/token0 = 4
|
|
|
|
// base = low (token0), quote = high (token1): quote per base = 4.
|
|
base0 := V3Pair{Base: low, Quote: high, BaseDecimals: 18, QuoteDecimals: 18}
|
|
if got := v3SpotFromSqrt(sqrt, base0); !approx(got, 4.0) {
|
|
t.Fatalf("base=token0 spot = %v, want 4", got)
|
|
}
|
|
// base = high (token1), quote = low (token0): quote per base = 1/4 = 0.25.
|
|
base1 := V3Pair{Base: high, Quote: low, BaseDecimals: 18, QuoteDecimals: 18}
|
|
if got := v3SpotFromSqrt(sqrt, base1); !approx(got, 0.25) {
|
|
t.Fatalf("base=token1 spot = %v, want 0.25", got)
|
|
}
|
|
}
|
|
|
|
// TestV3SpotDecimals verifies the decimal adjustment: token0 has 6 decimals,
|
|
// token1 has 18, raw wei ratio 4 → human token1/token0 = 4·10^6/10^18 = 4e-12.
|
|
func TestV3SpotDecimals(t *testing.T) {
|
|
low := common.HexToAddress("0x1111111111111111111111111111111111111111") // token0, 6 dec
|
|
high := common.HexToAddress("0x2222222222222222222222222222222222222222") // token1, 18 dec
|
|
p := V3Pair{Base: low, Quote: high, BaseDecimals: 6, QuoteDecimals: 18}
|
|
if got := v3SpotFromSqrt(sqrtX96For(4.0), p); !approx(got, 4e-12) {
|
|
t.Fatalf("decimal-adjusted spot = %v, want 4e-12", got)
|
|
}
|
|
}
|
|
|
|
// TestBytesLess pins the V3 token0/token1 (lower-address) ordering.
|
|
func TestBytesLess(t *testing.T) {
|
|
a := common.HexToAddress("0x4888e4a2ee0f03051c72d2bd3acf755ed3498b3e") // WLUX
|
|
b := common.HexToAddress("0x848Cff46eb323f323b6Bbe1Df274E40793d7f2c2") // LUSD
|
|
if !bytesLess(a, b) {
|
|
t.Fatal("WLUX should sort before LUSD (token0)")
|
|
}
|
|
if bytesLess(b, a) {
|
|
t.Fatal("LUSD should not sort before WLUX")
|
|
}
|
|
}
|
|
|
|
// TestNewV3FeedNilWithoutEnv confirms the feed is nil (opt-in) when any required
|
|
// address is absent, and that a real quote token id round-trips to its address.
|
|
func TestNewV3FeedNilWithoutEnv(t *testing.T) {
|
|
pairs := []Pair{{Symbol: "LUX/LUSD", Quote: AssetAddress("0x848Cff46eb323f323b6Bbe1Df274E40793d7f2c2")}}
|
|
if NewV3Feed(pairs, "", "0xfactory", "0xwlux", 18, nil) != nil {
|
|
t.Fatal("expected nil feed when rpcURL is empty")
|
|
}
|
|
if got := common.BytesToAddress(pairs[0].Quote[12:]); got != common.HexToAddress("0x848Cff46eb323f323b6Bbe1Df274E40793d7f2c2") {
|
|
t.Fatalf("quote address extraction = %s", got.Hex())
|
|
}
|
|
}
|
|
|
|
func approx(a, b float64) bool {
|
|
d := a - b
|
|
if d < 0 {
|
|
d = -d
|
|
}
|
|
tol := b * 1e-9
|
|
if tol < 0 {
|
|
tol = -tol
|
|
}
|
|
if tol < 1e-18 {
|
|
tol = 1e-18
|
|
}
|
|
return d <= tol
|
|
}
|