store.gno
4.62 Kb · 147 lines
1package transfer
2
3import (
4 "chain"
5 "strings"
6
7 "gno.land/p/demo/tokens/grc20"
8 "gno.land/p/nt/bptree/v0"
9 "gno.land/p/nt/seqid/v0"
10 "gno.land/r/demo/defi/grc20reg"
11)
12
13var (
14 denoms *bptree.BPTree // ibcDenom:Denom
15 totalEscrow *bptree.BPTree // denom:chain.Coin
16 voucherTokens *bptree.BPTree // ibcDenom:*voucher
17
18 // nextVoucherID allocates a unique grc20 token id for each voucher. Voucher
19 // symbols are truncated IBC-hash prefixes that can collide, so a shared
20 // monotonic id keeps every token's grc20 identifier distinct.
21 nextVoucherID seqid.ID
22)
23
24// voucher holds a voucher token and its private ledger for mint/burn.
25type voucher struct {
26 token *grc20.Token
27 ledger *grc20.PrivateLedger
28}
29
30func init() {
31 denoms = bptree.NewBPTree32()
32 totalEscrow = bptree.NewBPTree32()
33 voucherTokens = bptree.NewBPTree32()
34}
35
36func hasDenom(voucherDenom string) bool {
37 return denoms.Has(voucherDenom)
38}
39
40func setDenom(denom Denom) {
41 denoms.Set(denom.IBCDenom(), denom)
42}
43
44func getDenom(ibcDenom string) (Denom, bool) {
45 d := denoms.Get(ibcDenom)
46 if d == nil {
47 return Denom{}, false
48 }
49 return d.(Denom), true
50}
51
52func addEscrowForDenom(c chain.Coin) {
53 d := totalEscrow.Get(c.Denom)
54 if d != nil {
55 c = d.(chain.Coin).Add(c)
56 }
57 totalEscrow.Set(c.Denom, c)
58}
59
60func subEscrowForDenom(c chain.Coin) {
61 x := totalEscrow.Get(c.Denom)
62 if x == nil {
63 x = chain.Coin{Denom: c.Denom}
64 }
65 c = x.(chain.Coin).Sub(c)
66 totalEscrow.Set(c.Denom, c)
67}
68
69// VoucherSymbol returns the grc20 token symbol for an IBC voucher denom: the IBC
70// hash truncated to grc20's MaxSymbolLen. The full voucherDenom ("ibc/<64-hex-
71// hash>") can't be a symbol because v2 grc20 enforces MaxSymbolLen and
72// [A-Za-z0-9_-] only. Since grc20reg keys tokens by rlmpath.symbol, this is also
73// the symbol component of a voucher's registry key, so callers locate a voucher
74// via grc20reg.Get(<this realm's pkgpath> + "." + VoucherSymbol(ibcDenom)).
75func VoucherSymbol(ibcDenom string) string {
76 symbol := strings.TrimPrefix(ibcDenom, "ibc/")
77 if len(symbol) > grc20.MaxSymbolLen {
78 symbol = symbol[:grc20.MaxSymbolLen]
79 }
80 return symbol
81}
82
83// getOrCreateVoucher returns the existing voucher instance for the given IBC
84// denom, or creates a new one and registers it in grc20reg for DeFi
85// discoverability. Non-crossing helper; rlm is needed to issue the cross
86// call into grc20reg.Register from this realm's frame.
87//
88// The token symbol is a truncated IBC hash (see VoucherSymbol); its name is the
89// base denom, and nextVoucherID keeps each token's grc20 id distinct. grc20reg
90// keys the token by rlmpath.symbol (not the slug), so the full-hash slug is
91// retained only as event metadata. Because the key is the truncated symbol,
92// two vouchers whose hashes share an 11-char prefix would collide on the
93// registry key (astronomically unlikely, but no longer guarded by the slug).
94func getOrCreateVoucher(_ int, rlm realm, baseDenom, voucherDenom string) *voucher {
95 if v := voucherTokens.Get(voucherDenom); v != nil {
96 return v.(*voucher)
97 }
98 token, ledger := grc20.NewToken(baseDenom, VoucherSymbol(voucherDenom), 0, nextVoucherID.Next(), rlm)
99 inst := &voucher{token: token, ledger: ledger}
100 voucherTokens.Set(voucherDenom, inst)
101 grc20reg.Register(cross(rlm), token, voucherDenom[len("ibc/"):])
102 return inst
103}
104
105// getVoucher returns the voucher instance for the given IBC denom, or nil if not
106// found.
107func getVoucher(ibcDenom string) *voucher {
108 v := voucherTokens.Get(ibcDenom)
109 if v == nil {
110 return nil
111 }
112 return v.(*voucher)
113}
114
115// VoucherBalanceOf returns the balance of a voucher token for a given address.
116// Returns 0 if the token does not exist.
117func VoucherBalanceOf(ibcDenom string, addr address) int64 {
118 inst := getVoucher(ibcDenom)
119 if inst == nil {
120 return 0
121 }
122 return inst.token.BalanceOf(addr)
123}
124
125// GRC20Alias returns a slash-free alias for a GRC20 denom (grc20reg key)
126// by replacing "/" with ":". This is safe because grc20reg slugs are
127// alphanumeric (enforced by grc20reg.Register), so colons never appear in
128// grc20reg keys, making the replacement reversible.
129// Examples:
130//
131// "gno.land/r/demo/foo" → "gno.land:r:demo:foo"
132// "gno.land/r/demo/foo.FOO" → "gno.land:r:demo:foo.FOO"
133func GRC20Alias(grc20regKey string) string {
134 return strings.ReplaceAll(grc20regKey, "/", ":")
135}
136
137// resolveGRC20Alias converts a GRC20 alias back to the grc20reg key
138// by replacing ":" with "/".
139func resolveGRC20Alias(alias string) string {
140 return strings.ReplaceAll(alias, ":", "/")
141}
142
143// isGRC20Alias returns true if the denom is a GRC20 alias.
144// GRC20 aliases always start with "gno.land:" (the colon form of "gno.land/").
145func isGRC20Alias(denom string) bool {
146 return strings.HasPrefix(denom, "gno.land:")
147}