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) }