Kind Realm (renderable)
Name v0
Namespace moul / x / amm
Exported functions 15
Module gno.land/r/moul/x/amm/v0
gno 0.9 amm.gno gno
⧉
1 // Package amm is a minimal constant-product automated market maker for GRC20 2 // token pairs, in a single file. 3 // 4 // It holds many pools in one realm. A pool is created implicitly by its first 5 // liquidity deposit and is addressed by the two tokens' grc20reg keys, in 6 // either order. Swaps follow x*y=k with a 30 bps fee that stays in the pool 7 AddLiquidity (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, keyA string, keyB string, maxA int64, maxB int64) int64
AllowanceLP (keyA string, keyB string, owner string, spender string) int64
AmountOut (amountIn int64, reserveIn int64, reserveOut int64) int64
ApproveLP (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, keyA string, keyB string, spender string, amount int64)
LPToken (keyA string, keyB string) string
PoolCount () int
Quote (keyIn string, keyOut string, amountIn int64) int64
RemoveLiquidity (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, keyA string, keyB string, shares int64) (int64, int64)
Render (path string) string
Reserves (keyA string, keyB string) (int64, int64)
SharesOf (keyA string, keyB string, owner string) int64
Swap (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, keyIn string, keyOut string, amountIn int64, minOut int64) int64
TotalShares (keyA string, keyB string) int64
TransferFromLP (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, keyA string, keyB string, from string, to string, amount int64)
TransferLP (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, keyA string, keyB string, to string, amount int64)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.
// and therefore accrues to the liquidity providers.
8 //
9 // # What it is not
10 //
11 // Not an oracle: the reserve ratio is a spot price that any trader can move
12 // within a single transaction. Nothing should price off this realm.
13 // Not audited. Not a router: one hop, one pool, no path finding. Not a
14 // place to park value.
15 //
16 // # Design notes worth knowing before calling it
17 //
18 // Reserves are stored fields and are NEVER derived from BalanceOf. That is
19 // the single decision that removes Uniswap V2's MINIMUM_LIQUIDITY burn, its
20 // skim/sync pair, and the first-depositor share-inflation attack in one go:
21 // sending tokens straight to this realm's address changes no reserve, no
22 // price and no share value. The price of that is blunt and worth stating:
23 // tokens sent directly to the realm address are PERMANENTLY STUCK.
24 //
25 // The first deposit mints shares equal to the token-A amount rather than
26 // sqrt(A*B). The geometric mean is cosmetic, every later operation uses only
27 // ratios, and dropping it removes an integer square root over a 128-bit
28 // product. Later deposits mint min(A-side, B-side) with floor division on
29 // both, so an off-ratio deposit always rounds against the depositor.
30 //
31 // # v1 vs v0: LP shares are a real GRC20 token
32 //
33 // v0 tracks LP positions in a private avl ledger: smallest possible, but a
34 // position can only be held by the address that opened it and is invisible to
35 // every other contract. v1 mints one GRC20 token per pool instead, holds its
36 // PrivateLedger, and registers it with grc20reg. A position is then an
37 // ordinary fungible token: transferable, approvable, usable as collateral,
38 // and readable by any realm that knows the registry key.
39 //
40 // The cost is honest and measurable. Pool creation now also mints a token and
41 // writes a registry entry. Positions gain an allowance surface, and with it
42 // the classic GRC20 approve race. And because MsgCall cannot pass a realm
43 // argument, this realm has to re-export Transfer/Approve/TransferFrom/
44 // Allowance wrappers over the LP token for signing users, four functions that
45 // v0 does not need at all. Another REALM can skip them and move its own LP
46 // balance through grc20reg directly.
47 //
48 // All amounts are int64, as GRC20 mandates, and there is no 256-bit type in
49 // reach. Pricing therefore runs through a 128-bit mulDiv (math/bits), and
50 // every reserve is capped at maxReserve so that the plain int64 parts of the
51 // formula cannot overflow. See the comment on maxReserve for the arithmetic,
52 // and note the consequence: this AMM is unusable with 18-decimal tokens.
53 // Six to nine decimals is the practical band.
54 //
55 // Design study and full analysis: https://github.com/moul/gno-contracts/issues/135
56 package amm
57
58 import (
59 "chain"
60 "math"
61 "math/bits"
62 "strconv"
63
64 "gno.land/p/nt/avl/v0"
65 "gno.land/p/nt/grc20/v0"
66 "gno.land/p/nt/seqid/v0"
67 "gno.land/p/nt/ufmt/v0"
68 "gno.land/r/nt/grc20reg/v0"
69 )
70
71 const (
72 // feeNum/feeDen is the swap fee kept by the pool: 997/1000, i.e. 30 bps.
73 feeNum = 997
74 feeDen = 1000
75
76 // maxReserve is the largest reserve a pool will hold, in base units.
77 //
78 // Pricing computes den = reserveIn*feeDen + amountIn*feeNum in plain
79 // int64 before handing off to the 128-bit mulDiv. Capping every reserve,
80 // and every post-swap reserve, at MaxInt64/feeDen makes that sum provably
81 // safe:
82 //
83 // den < (reserveIn + amountIn) * feeDen <= maxReserve * feeDen
84 // = 9223372036854775000 <= MaxInt64 = 9223372036854775807
85 //
86 // AddLiquidity enforces the cap on the reserves themselves and Swap
87 // enforces it on reserveIn+amountIn, so a pool can never even enter the
88 // region where the formula would be unsafe.
89 maxReserve = math.MaxInt64 / feeDen // 9223372036854775
90 )
91
92 // pool is one token pair. keyA < keyB always holds: the pair is canonicalised
93 // on the way in, so (X,Y) and (Y,X) address the same pool.
94 type pool struct {
95 keyA, keyB string
96 tokA, tokB *grc20.Token
97 resA, resB int64
98
99 // lp is this pool's LP token: total supply is the share supply and a
100 // holder's balance is their position. lpLedger is the minting authority
101 // and MUST NOT leak out of this file. lpKey is lp's grc20reg key.
102 lp *grc20.Token
103 lpLedger *grc20.PrivateLedger
104 lpKey string
105 }
106
107 // pools maps the pool id "keyA~keyB" -> *pool. "~" cannot occur in a
108 // grc20reg key, and unlike "|" it does not break a markdown table cell.
109 var pools avl.Tree
110
111 // lpSeq numbers the LP tokens. It is never reset, so a symbol is never reused
112 // and grc20reg's one-token-per-realm-and-symbol rule is never tripped.
113 var lpSeq seqid.ID
114
115 //
116 // Writes.
117 //
118
119 // AddLiquidity deposits up to maxA of keyA and maxB of keyB and mints LP
120 // shares to the caller. It creates the pool if this is its first deposit, in
121 // which case the caller's amounts set the starting price and the whole of
122 // maxA and maxB is taken.
123 //
124 // On an existing pool the deposit is trimmed to the current reserve ratio:
125 // only one side is consumed in full and the remainder of the richer side is
126 // left untouched, so pass the amounts you are willing to spend, not the
127 // amounts you insist on spending.
128 //
129 // The caller must first Approve this realm's address on BOTH tokens for at
130 // least the amounts that will be taken. Returns the shares minted.
131 func AddLiquidity(cur realm, keyA, keyB string , maxA, maxB int64 ) int64 {
132 a, b, flipped := canon(keyA, keyB)
133 amtA, amtB := maxA, maxB
134 if flipped {
135 amtA, amtB = maxB, maxA
136 }
137 if amtA <= 0 || amtB <= 0 {
138 panic( "amm: both deposit amounts must be > 0" )
139 }
140
141 p := getPool(a, b)
142 if p == nil {
143 p = newPool( 0 , cur, a, b)
144 pools.Set(poolID(a, b), p)
145 }
146
147 var minted int64
148 if p.lp.TotalSupply() == 0 {
149 // First position: the depositor sets the price and the share unit is
150 // token A at seed time. No sqrt, no MINIMUM_LIQUIDITY burn.
151 minted = amtA
152 } else {
153 // Trim to the current ratio, then mint from whichever side is scarcer.
154 // Both divisions floor, and taking the minimum means an off-ratio
155 // deposit is rounded against the depositor, never against the pool.
156 if want := mulDiv(amtA, p.resB, p.resA); want <= amtB {
157 amtB = want
158 } else {
159 amtA = mulDiv(amtB, p.resA, p.resB)
160 }
161 if amtA <= 0 || amtB <= 0 {
162 panic( "amm: deposit too small for the current ratio" )
163 }
164 supply := p.lp.TotalSupply()
165 minted = min64(
166 mulDiv(amtA, supply, p.resA),
167 mulDiv(amtB, supply, p.resB),
168 )
169 }
170 if minted <= 0 {
171 panic( "amm: deposit mints zero shares" )
172 }
173 if p.resA+amtA > maxReserve || p.resB+amtB > maxReserve {
174 panic( "amm: reserve cap exceeded" )
175 }
176
177 provider := cur.Previous().Address()
178 self := cur.Address()
179 pull( 0 , cur, p.tokA, provider, self, amtA)
180 pull( 0 , cur, p.tokB, provider, self, amtB)
181
182 p.resA += amtA
183 p.resB += amtB
184 if err := p.lpLedger.Mint(provider, minted); err != nil {
185 panic( "amm: cannot mint LP shares: " + err.Error())
186 }
187
188 chain.Emit( "AddLiquidity" ,
189 "pool" , poolID(p.keyA, p.keyB),
190 "provider" , provider.String(),
191 "amountA" , itoa(amtA),
192 "amountB" , itoa(amtB),
193 "shares" , itoa(minted),
194 )
195 return minted
196 }
197
198 // RemoveLiquidity burns shares held by the caller and returns the proportional
199 // amounts of both tokens, in the caller's (keyA, keyB) argument order.
200 //
201 // Burning the pool's entire share supply pays out the whole reserve, so the
202 // last provider out leaves nothing unclaimable behind and the pool can be
203 // reseeded at a fresh price.
204 func RemoveLiquidity(cur realm, keyA, keyB string , shares int64 ) ( int64 , int64 ) {
205 if shares <= 0 {
206 panic( "amm: shares must be > 0" )
207 }
208 a, b, flipped := canon(keyA, keyB)
209 p := mustPool(a, b)
210
211 owner := cur.Previous().Address()
212 if p.lp.BalanceOf(owner) < shares {
213 panic( "amm: insufficient shares" )
214 }
215
216 supply := p.lp.TotalSupply()
217 var amtA, amtB int64
218 if shares == supply {
219 amtA, amtB = p.resA, p.resB
220 } else {
221 amtA = mulDiv(shares, p.resA, supply)
222 amtB = mulDiv(shares, p.resB, supply)
223 }
224 if amtA <= 0 || amtB <= 0 {
225 panic( "amm: burn would return nothing on one side" )
226 }
227
228 p.resA -= amtA
229 p.resB -= amtB
230 if err := p.lpLedger.Burn(owner, shares); err != nil {
231 panic( "amm: cannot burn LP shares: " + err.Error())
232 }
233
234 push( 0 , cur, p.tokA, owner, amtA)
235 push( 0 , cur, p.tokB, owner, amtB)
236
237 chain.Emit( "RemoveLiquidity" ,
238 "pool" , poolID(p.keyA, p.keyB),
239 "provider" , owner.String(),
240 "amountA" , itoa(amtA),
241 "amountB" , itoa(amtB),
242 "shares" , itoa(shares),
243 )
244 if flipped {
245 return amtB, amtA
246 }
247 return amtA, amtB
248 }
249
250 // Swap sells amountIn of keyIn for keyOut and aborts unless at least minOut
251 // comes back. The caller must first Approve this realm's address on keyIn.
252 //
253 // minOut is the only protection against being sandwiched or against the pool
254 // moving between quoting and execution. Pass a real bound; passing 0 means
255 // accepting any price at all.
256 func Swap(cur realm, keyIn, keyOut string , amountIn, minOut int64 ) int64 {
257 if amountIn <= 0 {
258 panic( "amm: amountIn must be > 0" )
259 }
260 if minOut < 0 {
261 panic( "amm: minOut must be >= 0" )
262 }
263 a, b, flipped := canon(keyIn, keyOut)
264 p := mustPool(a, b)
265
266 resIn, resOut := p.resA, p.resB
267 tokIn, tokOut := p.tokA, p.tokB
268 if flipped {
269 resIn, resOut = p.resB, p.resA
270 tokIn, tokOut = p.tokB, p.tokA
271 }
272
273 out := AmountOut(amountIn, resIn, resOut)
274 if out <= 0 {
275 panic( "amm: output rounds to zero" )
276 }
277 if out < minOut {
278 panic( "amm: slippage, output below minOut" )
279 }
280
281 trader := cur.Previous().Address()
282 pull( 0 , cur, tokIn, trader, cur.Address(), amountIn)
283
284 newIn, newOut := resIn+amountIn, resOut-out
285 // The invariant holds by construction (out is floored), so this can only
286 // fire if the pricing above is ever edited into being wrong. It is exact:
287 // both products are compared in 128 bits.
288 if cmpProd(newIn, newOut, resIn, resOut) < 0 {
289 panic( "amm: constant product regression" )
290 }
291 if flipped {
292 p.resB, p.resA = newIn, newOut
293 } else {
294 p.resA, p.resB = newIn, newOut
295 }
296
297 push( 0 , cur, tokOut, trader, out)
298
299 chain.Emit( "Swap" ,
300 "pool" , poolID(p.keyA, p.keyB),
301 "trader" , trader.String(),
302 "tokenIn" , tokIn.GetSymbol(),
303 "amountIn" , itoa(amountIn),
304 "amountOut" , itoa(out),
305 )
306 return out
307 }
308
309 // TransferLP moves amount of the caller's LP position to `to`.
310 //
311 // The LP token lives in this realm, so a signing user cannot reach it through
312 // the token's own entry points the way they would for any other GRC20: there
313 // are none. These four wrappers are that entry point. A REALM holding LP does
314 // not need them and can go through grc20reg instead:
315 //
316 // grc20reg.Transfer(0, cur, amm.LPToken(keyA, keyB), to, n)
317 func TransferLP(cur realm, keyA, keyB string , to address, amount int64 ) {
318 a, b, _ := canon(keyA, keyB)
319 lpCheck(mustPool(a, b).lpLedger.CallerTeller().Transfer( 0 , cur, to, amount))
320 }
321
322 // ApproveLP lets spender move up to amount of the caller's LP position.
323 //
324 // Same approve race as any GRC20: an allowance changed from a non-zero value
325 // can be spent at both the old and the new one if the holder is unlucky with
326 // ordering. Set it to 0 first when lowering it.
327 func ApproveLP(cur realm, keyA, keyB string , spender address, amount int64 ) {
328 a, b, _ := canon(keyA, keyB)
329 lpCheck(mustPool(a, b).lpLedger.CallerTeller().Approve( 0 , cur, spender, amount))
330 }
331
332 // TransferFromLP spends an allowance the owner granted to the caller.
333 func TransferFromLP(cur realm, keyA, keyB string , from, to address, amount int64 ) {
334 a, b, _ := canon(keyA, keyB)
335 lpCheck(mustPool(a, b).lpLedger.CallerTeller().TransferFrom( 0 , cur, from, to, amount))
336 }
337
338 func lpCheck(err error ) {
339 if err != nil {
340 panic( "amm: LP token: " + err.Error())
341 }
342 }
343
344 //
345 // Reads.
346 //
347
348 // AmountOut is the pricing function, fee included, as a pure function of the
349 // two reserves. Exported so a caller can quote off-chain against reserves it
350 // already holds, and so the arithmetic is testable on its own.
351 func AmountOut(amountIn, reserveIn, reserveOut int64 ) int64 {
352 if amountIn <= 0 {
353 panic( "amm: amountIn must be > 0" )
354 }
355 if reserveIn <= 0 || reserveOut <= 0 {
356 panic( "amm: pool has an empty reserve" )
357 }
358 if reserveIn > maxReserve || reserveOut > maxReserve {
359 panic( "amm: reserve above cap" )
360 }
361 if amountIn > maxReserve-reserveIn {
362 panic( "amm: reserve cap exceeded" )
363 }
364 inFee := amountIn * feeNum
365 den := reserveIn*feeDen + inFee
366 return mulDiv(inFee, reserveOut, den)
367 }
368
369 // Quote prices amountIn of keyIn against the pool's live reserves. It is the
370 // number Swap would return right now, which is not a promise about the next
371 // block.
372 func Quote(keyIn, keyOut string , amountIn int64 ) int64 {
373 a, b, flipped := canon(keyIn, keyOut)
374 p := mustPool(a, b)
375 if flipped {
376 return AmountOut(amountIn, p.resB, p.resA)
377 }
378 return AmountOut(amountIn, p.resA, p.resB)
379 }
380
381 // Reserves returns the two reserves in the caller's argument order.
382 func Reserves(keyA, keyB string ) ( int64 , int64 ) {
383 a, b, flipped := canon(keyA, keyB)
384 p := mustPool(a, b)
385 if flipped {
386 return p.resB, p.resA
387 }
388 return p.resA, p.resB
389 }
390
391 // SharesOf returns owner's LP shares, i.e. their LP token balance.
392 func SharesOf(keyA, keyB string , owner address) int64 {
393 a, b, _ := canon(keyA, keyB)
394 return mustPool(a, b).lp.BalanceOf(owner)
395 }
396
397 // TotalShares returns the pool's LP token total supply.
398 func TotalShares(keyA, keyB string ) int64 {
399 a, b, _ := canon(keyA, keyB)
400 return mustPool(a, b).lp.TotalSupply()
401 }
402
403 // LPToken returns the grc20reg key of the pool's LP token. Hand it to any
404 // realm that should read or move these positions without importing this one.
405 func LPToken(keyA, keyB string ) string {
406 a, b, _ := canon(keyA, keyB)
407 return mustPool(a, b).lpKey
408 }
409
410 // AllowanceLP reports how much of owner's LP position spender may move.
411 func AllowanceLP(keyA, keyB string , owner, spender address) int64 {
412 a, b, _ := canon(keyA, keyB)
413 return mustPool(a, b).lp.Allowance(owner, spender)
414 }
415
416 // PoolCount returns how many pools exist.
417 func PoolCount() int { return pools.Size() }
418
419 // Render lists every pool, or one pool's detail when path is a "keyA~keyB"
420 // pool id.
421 func Render(path string ) string {
422 if path != "" {
423 v := pools.Get(path)
424 if v == nil {
425 return "# 404\n\nNo pool `" + path + "`.\n"
426 }
427 p := v.(*pool)
428 out := "# " + p.tokA.GetSymbol() + " / " + p.tokB.GetSymbol() + "\n\n"
429 out += "- **" + p.tokA.GetSymbol() + "** reserve: " + itoa(p.resA) + " (`" + p.keyA + "`)\n"
430 out += "- **" + p.tokB.GetSymbol() + "** reserve: " + itoa(p.resB) + " (`" + p.keyB + "`)\n"
431 out += "- **LP token**: `" + p.lpKey + "` (" + p.lp.GetSymbol() + ")\n"
432 out += "- **LP shares**: " + itoa(p.lp.TotalSupply()) + " across " + strconv.Itoa(p.lp.KnownAccounts()) + " holder(s)\n"
433 return out
434 }
435
436 out := "# Minimal AMM\n\n"
437 out += "Constant product (`x*y=k`) with a 30 bps fee kept by the pool. Every position is a transferable GRC20 LP token. Reserves are stored, never read from balances, so sending tokens here directly does nothing and they cannot be recovered.\n\n"
438 if pools.Size() == 0 {
439 out += "_No pools yet. Call `AddLiquidity` with two `grc20reg` keys to open one._\n"
440 return out
441 }
442 out += "| pool | reserves | LP token | supply | holders |\n"
443 out += "|---|---|---|---|---|\n"
444 pools.Iterate( "" , "" , func (key string , value any ) bool {
445 p := value.(*pool)
446 out += ufmt.Sprintf( "| [%s/%s](/r/moul/x/amm/v0:%s) | %d %s / %d %s | %s | %d | %d |\n" ,
447 p.tokA.GetSymbol(), p.tokB.GetSymbol(), key,
448 p.resA, p.tokA.GetSymbol(), p.resB, p.tokB.GetSymbol(),
449 p.lp.GetSymbol(), p.lp.TotalSupply(), p.lp.KnownAccounts())
450 return false
451 })
452 return out
453 }
454
455 //
456 // Internals.
457 //
458
459 // canon sorts a caller's token pair into the pool's canonical order and
460 // reports whether the caller's order was reversed.
461 func canon(first, second string ) (a, b string , flipped bool ) {
462 if first == "" || second == "" {
463 panic( "amm: empty token key" )
464 }
465 if first == second {
466 panic( "amm: a pool needs two different tokens" )
467 }
468 if first > second {
469 return second, first, true
470 }
471 return first, second, false
472 }
473
474 // newPool resolves both tokens, mints this pool's LP token and registers it.
475 //
476 // Non-crossing (`_ int, rlm realm`): grc20.NewToken binds the token's
477 // origRealm to rlm.PkgPath() under an IsCurrent assertion, and grc20reg
478 // keys off the registering caller, so rlm has to stay AddLiquidity's own
479 // live frame rather than a fresh one.
480 //
481 // The LP unit is token A at seed time, so the LP token mirrors token A's
482 // decimals. The symbol is LP<n> because grc20 caps a symbol at 11 chars,
483 // which "LP-" plus two 11-char symbols would blow past; the human-readable
484 // pair lives in the name instead.
485 func newPool(_ int , rlm realm, a, b string ) *pool {
486 tokA, tokB := grc20reg.MustGet(a), grc20reg.MustGet(b)
487 id := lpSeq.Next()
488 lp, ledger := grc20.NewToken(
489 "AMM LP " +tokA.GetSymbol()+ "/" +tokB.GetSymbol(),
490 "LP" +strconv.FormatUint( uint64 (id), 10 ),
491 tokA.GetDecimals(),
492 id,
493 rlm,
494 )
495 p := &pool{keyA: a, keyB: b, tokA: tokA, tokB: tokB, lp: lp, lpLedger: ledger}
496 p.lpKey = grc20reg.Register(cross(rlm), lp, "" )
497 return p
498 }
499
500 // poolID is the storage and render key for a canonicalised pair.
501 func poolID(a, b string ) string { return a + "~" + b }
502
503 func getPool(a, b string ) *pool {
504 v := pools.Get(poolID(a, b))
505 if v == nil {
506 return nil
507 }
508 return v.(*pool)
509 }
510
511 func mustPool(a, b string ) *pool {
512 p := getPool(a, b)
513 if p == nil {
514 panic( "amm: no such pool: " + poolID(a, b))
515 }
516 return p
517 }
518
519 // pull moves amount of tok from `from` into this realm, spending the
520 // allowance `from` granted to this realm's address.
521 //
522 // Non-crossing on purpose: `_ int, rlm realm` is the only shape that keeps
523 // rlm the caller's own live frame. RealmTeller binds the spender eagerly to
524 // rlm.Address(), which is this realm.
525 func pull(_ int , rlm realm, tok *grc20.Token, from, to address, amount int64 ) {
526 err := tok.RealmTeller( 0 , rlm).TransferFrom( 0 , rlm, from, to, amount)
527 if err != nil {
528 panic( "amm: cannot take " + tok.GetSymbol() + ": " + err.Error())
529 }
530 }
531
532 // push sends amount of tok from this realm to `to`.
533 func push(_ int , rlm realm, tok *grc20.Token, to address, amount int64 ) {
534 err := tok.RealmTeller( 0 , rlm).Transfer( 0 , rlm, to, amount)
535 if err != nil {
536 panic( "amm: cannot send " + tok.GetSymbol() + ": " + err.Error())
537 }
538 }
539
540 // mulDiv returns floor(a*b/c) through a 128-bit intermediate, which plain
541 // int64 arithmetic cannot do: a*b is routinely wider than 63 bits here even
542 // when the quotient is small.
543 func mulDiv(a, b, c int64 ) int64 {
544 if a < 0 || b < 0 {
545 panic( "amm: negative operand" )
546 }
547 if c <= 0 {
548 panic( "amm: division by a non-positive value" )
549 }
550 hi, lo := bits.Mul64( uint64 (a), uint64 (b))
551 // bits.Div64 panics for y <= hi; refusing here turns that into a named
552 // abort and also covers every quotient that would not fit in 64 bits.
553 if hi >= uint64 (c) {
554 panic( "amm: quotient overflows int64" )
555 }
556 q, _ := bits.Div64(hi, lo, uint64 (c))
557 if q > uint64 (math.MaxInt64) {
558 panic( "amm: quotient overflows int64" )
559 }
560 return int64 (q)
561 }
562
563 // cmpProd compares a*b with c*d exactly, in 128 bits, and returns -1, 0 or 1.
564 func cmpProd(a, b, c, d int64 ) int {
565 h1, l1 := bits.Mul64( uint64 (a), uint64 (b))
566 h2, l2 := bits.Mul64( uint64 (c), uint64 (d))
567 if h1 != h2 {
568 if h1 < h2 {
569 return - 1
570 }
571 return 1
572 }
573 if l1 != l2 {
574 if l1 < l2 {
575 return - 1
576 }
577 return 1
578 }
579 return 0
580 }
581
582 func min64(a, b int64 ) int64 {
583 if a < b {
584 return a
585 }
586 return b
587 }
588
589 func itoa(v int64 ) string { return strconv.FormatInt(v, 10 ) }
590