Search Apps Documentation Source Content File Folder Download Copy Actions Download

limitspike.gno

3.96 Kb · 113 lines
  1// Package limitspike is a throwaway spike proving the make-or-break leg of a
  2// GnoPulse limit-order product: that a realm can (a) read GnoSwap's live pool
  3// tick on-chain to GATE execution, and (b) execute a swap on GnoSwap *on a
  4// user's behalf* — the realm holds the tokens (pulled from the user via GRC20
  5// allowance), is the swap payer, receives the output, and forwards it back.
  6//
  7// Confirmed against GnoSwap on test-13:
  8//   - router debits the immediate calling realm (PreviousRealm), not the EOA,
  9//     so delegated execution works;
 10//   - native ugnot is rejected on the swap tx — the GNOT side must be wugnot;
 11//   - the GRC20 allowance spender for the swap is the router role address.
 12package limitspike
 13
 14import (
 15	"strconv"
 16	"time"
 17
 18	"chain"
 19
 20	"gno.land/r/gnoland/wugnot"
 21	"gno.land/r/gnoswap/gns"
 22	"gno.land/r/gnoswap/pool"
 23	"gno.land/r/gnoswap/router"
 24)
 25
 26const (
 27	inTok    = "gno.land/r/gnoland/wugnot"
 28	outTok   = "gno.land/r/gnoswap/gns"
 29	poolPath = "gno.land/r/gnoland/wugnot:gno.land/r/gnoswap/gns:3000" // 0.3% tier, deepest GNOT/GNS pool
 30	routeArr = poolPath
 31)
 32
 33// routerAddr is the GnoSwap router role address — the spender this realm must
 34// Approve on its wugnot before the swap (the router does the TransferFrom).
 35var routerAddr = address("g1vc883gshu5z7ytk5cdynhc8c2dh67pdp4cszkp")
 36
 37// Tick returns the live GnoSwap pool tick — the trigger signal. Read-only.
 38func Tick() int32 {
 39	return pool.GetSlot0Tick(poolPath)
 40}
 41
 42// Execute runs one delegated limit swap of `amountIn` wugnot -> GNS for the caller.
 43//
 44// Preconditions (set by the caller in prior txs):
 45//   - caller holds >= amountIn wugnot (wrap ugnot via wugnot.Deposit first);
 46//   - caller has Approve'd THIS realm as spender for >= amountIn wugnot.
 47//
 48// The limit gate: execution is refused unless the live pool tick >= minTick.
 49// (minTick is the on-chain, MEV-safe trigger — the realm re-checks it itself,
 50// so a keeper poking this function can never force an off-limit fill.)
 51//
 52// minOut is the slippage floor (amountOutMin) passed to GnoSwap.
 53// Returns (tickAtExecution, gnsForwardedToCaller).
 54func Execute(cur realm, amountIn int64, minTick int32, minOut int64) (int32, int64) {
 55	if !cur.IsCurrent() {
 56		panic("spoofed realm")
 57	}
 58	if amountIn <= 0 {
 59		panic("amountIn must be > 0")
 60	}
 61	user := cur.Previous().Address()
 62	self := cur.Address()
 63
 64	// 1) LIMIT GATE — the whole point. Read the live pool tick and refuse if
 65	//    the trigger price hasn't been reached. Cheap int32 compare, no oracle.
 66	tick := pool.GetSlot0Tick(poolPath)
 67	if tick < minTick {
 68		panic("limit not reached: tick " + i32(tick) + " < minTick " + i32(minTick))
 69	}
 70
 71	// 2) Pull the user's wugnot into this realm (needs prior Approve to us).
 72	wugnot.TransferFrom(cross(cur), user, self, amountIn)
 73
 74	// 3) Approve the GnoSwap router to spend this realm's wugnot.
 75	wugnot.Approve(cross(cur), routerAddr, amountIn)
 76
 77	// 4) Swap wugnot -> GNS. GnoSwap pulls wugnot FROM this realm and sends GNS
 78	//    TO this realm (payer/recipient = PreviousRealm = us). Measure by delta
 79	//    rather than trusting the returned string.
 80	before := gns.BalanceOf(self)
 81	router.ExactInSingleSwapRoute(
 82		cross(cur),
 83		inTok, outTok,
 84		i64(amountIn),   // amountIn
 85		routeArr,        // single-hop route
 86		i64(minOut),     // amountOutMin (slippage floor)
 87		"0",             // sqrtPriceLimitX96 = no price limit
 88		time.Now().Unix()+300, // deadline: block-time unix + 5 min
 89		"",              // referrer
 90	)
 91	out := gns.BalanceOf(self) - before
 92
 93	// 5) Forward the received GNS back to the user.
 94	if out > 0 {
 95		gns.Transfer(cross(cur), user, out)
 96	}
 97
 98	chain.Emit("LimitSwapExecuted",
 99		"user", user.String(),
100		"tick", i32(tick),
101		"amountIn", i64(amountIn),
102		"gnsOut", i64(out),
103	)
104	return tick, out
105}
106
107func Render(_ string) string {
108	return "# limitspike\n\nSpike: realm-delegated GnoSwap limit swap (wugnot->GNS). Live tick: " +
109		i32(Tick()) + "\n"
110}
111
112func i64(n int64) string { return strconv.FormatInt(n, 10) }
113func i32(n int32) string { return strconv.FormatInt(int64(n), 10) }