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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

exchange_route.gno

4.75 Kb · 143 lines
  1package game29
  2
  3// ── P2P exchange: router (book first while it beats the pool, rest via pools)
  4
  5import (
  6	"strconv"
  7	"time"
  8
  9	"chain"
 10)
 11
 12// poolPathOut quotes amountIn of coinIn → coinOut through the grin pools
 13// (one hop if either side is grin, else coinIn→grin→coinOut). ok=false if a
 14// needed pool is missing or not open. Read-only.
 15func poolPathOut(coinIn, coinOut string, amountIn uint64) (uint64, bool) {
 16	if amountIn == 0 || coinIn == coinOut {
 17		return 0, false
 18	}
 19	if coinIn == hubCoin || coinOut == hubCoin {
 20		c, inIsHub := coinIn, false
 21		if coinIn == hubCoin {
 22			c, inIsHub = coinOut, true
 23		}
 24		if !pools.Has(c) {
 25			return 0, false
 26		}
 27		p := getPool(c)
 28		if !poolOpenForSwaps(p) {
 29			return 0, false
 30		}
 31		out, _ := poolQuote(p, inIsHub, amountIn)
 32		return out, out > 0
 33	}
 34	mid, ok := poolPathOut(coinIn, hubCoin, amountIn)
 35	if !ok {
 36		return 0, false
 37	}
 38	return poolPathOut(hubCoin, coinOut, mid)
 39}
 40
 41// routePlan splits amountIn between book fills (direct pair) and the pool
 42// path. A book fill is taken while its rate is at least the pool path's
 43// average rate for the whole remainder; everything else goes to the pools.
 44func routePlan(caller, coinIn, coinOut string, amountIn uint64, maxFills int, now int64) ([]fillStep, []*order, uint64, uint64, uint64) {
 45	steps, expired, _, _ := planFills(caller, coinIn, coinOut, amountIn, maxFills, now)
 46	var take []fillStep
 47	remaining := amountIn
 48	bookOut := uint64(0)
 49	for _, st := range steps {
 50		cost := addChk(st.x, mulDivUp(st.x, takerFeeBps, 10000))
 51		if cost > remaining {
 52			break
 53		}
 54		if pOut, ok := poolPathOut(coinIn, coinOut, remaining); ok {
 55			// book rate g/cost vs pool average rate pOut/remaining (128-bit compare)
 56			if mulLess(st.g, remaining, pOut, cost) {
 57				break
 58			}
 59		}
 60		take = append(take, st)
 61		remaining = subChk(remaining, cost)
 62		bookOut = addChk(bookOut, st.g)
 63	}
 64	poolOut := uint64(0)
 65	if remaining > 0 {
 66		if out, ok := poolPathOut(coinIn, coinOut, remaining); ok {
 67			poolOut = out
 68		} else {
 69			remaining = 0 // no pool path: only the book part executes
 70		}
 71	}
 72	return take, expired, remaining, bookOut, poolOut
 73}
 74
 75// SwapRoute swaps up to amountIn of coinIn for at least minOut of coinOut,
 76// using the order book (direct pair) while it beats the pools and the grin
 77// pools for the rest. Coins not needed are never debited.
 78func SwapRoute(cur realm, coinIn string, coinOut string, amountIn uint64, minOut uint64, maxFills int, deadline int64) uint64 {
 79	caller := exchangeCaller(cur)
 80	requireMarketOpen(caller)
 81	requireDeadline(deadline)
 82	requireAmount("amountIn", amountIn)
 83	requireCoin(coinIn)
 84	requireCoin(coinOut)
 85	if coinIn == coinOut {
 86		panic("coinIn and coinOut must differ")
 87	}
 88	take, expired, poolIn, bookOut, poolOut := routePlan(caller, coinIn, coinOut, amountIn, maxFills, time.Now().Unix())
 89	planned := addChk(bookOut, poolOut)
 90	if planned == 0 || planned < minOut {
 91		panic("slippage: output below minimum")
 92	}
 93	need := poolIn
 94	for _, st := range take {
 95		need = addChk(need, addChk(st.x, mulDivUp(st.x, takerFeeBps, 10000)))
 96	}
 97	requireBalance(caller, coinIn, need)
 98	// ── all checks passed; writes start here ──
 99	for _, o := range expired {
100		closeOrder(o, "expired")
101	}
102	out := executeFills(caller, coinIn, take)
103	if poolIn > 0 {
104		debitBal(caller, coinIn, poolIn)
105		if coinIn == hubCoin {
106			out = addChk(out, poolSwap(getPool(coinOut), true, poolIn))
107		} else if coinOut == hubCoin {
108			out = addChk(out, poolSwap(getPool(coinIn), false, poolIn))
109		} else {
110			mid := poolSwap(getPool(coinIn), false, poolIn)
111			out = addChk(out, poolSwap(getPool(coinOut), true, mid))
112		}
113	}
114	if out != planned {
115		panic("invariant: route plan mismatch")
116	}
117	creditBal(caller, coinOut, out)
118	chain.Emit("SwapRouted", "who", caller, "in", coinIn, "out", coinOut, "amountIn", u(amountIn),
119		"bookFills", strconv.Itoa(len(take)), "poolIn", u(poolIn), "amountOut", u(out))
120	return out
121}
122
123// QuoteRoute previews SwapRoute: how much goes to the book vs the pools and
124// the expected output (fees included).
125func QuoteRoute(caller string, coinIn string, coinOut string, amountIn uint64, maxFills int) string {
126	if amountIn == 0 || amountIn > maxAmount || coinIn == coinOut {
127		return `{"out":"0"}`
128	}
129	if _, ok := coinRarity[coinIn]; !ok {
130		return `{"out":"0"}`
131	}
132	if _, ok := coinRarity[coinOut]; !ok {
133		return `{"out":"0"}`
134	}
135	take, _, poolIn, bookOut, poolOut := routePlan(caller, coinIn, coinOut, amountIn, maxFills, time.Now().Unix())
136	bookIn := uint64(0)
137	for _, st := range take {
138		bookIn = addChk(bookIn, addChk(st.x, mulDivUp(st.x, takerFeeBps, 10000)))
139	}
140	return `{"bookIn":"` + u(bookIn) + `","bookOut":"` + u(bookOut) + `","bookFills":` + strconv.Itoa(len(take)) +
141		`,"poolIn":"` + u(poolIn) + `","poolOut":"` + u(poolOut) + `","out":"` + u(addChk(bookOut, poolOut)) +
142		`","unused":"` + u(amountIn-bookIn-poolIn) + `"}`
143}