store.gno
3.79 Kb · 133 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/r/demo/defi/grc20reg"
10)
11
12var (
13 denoms *bptree.BPTree // ibcDenom:Denom
14 totalEscrow *bptree.BPTree // denom:chain.Coin
15 voucherTokens *bptree.BPTree // ibcDenom:*voucher
16)
17
18// voucher holds a voucher token and its private ledger for mint/burn.
19type voucher struct {
20 token *grc20.Token
21 ledger *grc20.PrivateLedger
22}
23
24func init() {
25 denoms = bptree.NewBPTree32()
26 totalEscrow = bptree.NewBPTree32()
27 voucherTokens = bptree.NewBPTree32()
28}
29
30func hasDenom(voucherDenom string) bool {
31 return denoms.Has(voucherDenom)
32}
33
34func setDenom(denom Denom) {
35 denoms.Set(denom.IBCDenom(), denom)
36}
37
38func getDenom(ibcDenom string) (Denom, bool) {
39 x, found := denoms.Get(ibcDenom)
40 if !found {
41 return Denom{}, false
42 }
43 return x.(Denom), true
44}
45
46func addEscrowForDenom(c chain.Coin) {
47 x, found := totalEscrow.Get(c.Denom)
48 if found {
49 c = x.(chain.Coin).Add(c)
50 }
51 totalEscrow.Set(c.Denom, c)
52}
53
54func subEscrowForDenom(c chain.Coin) {
55 x, found := totalEscrow.Get(c.Denom)
56 if !found {
57 x = chain.Coin{Denom: c.Denom}
58 }
59 c = x.(chain.Coin).Sub(c)
60 totalEscrow.Set(c.Denom, c)
61}
62
63// getOrCreateVoucher returns the existing voucher instance for the given IBC
64// denom, or creates a new one and registers it in grc20reg for DeFi
65// discoverability. Non-crossing helper; rlm is needed to issue the cross
66// call into grc20reg.Register from this realm's frame.
67//
68// The GRC20 symbol is derived from a short prefix of the IBC hash. We can't
69// use the full voucherDenom ("ibc/<64-hex-hash>") because v2 grc20 enforces
70// MaxSymbolLen=11 and [A-Za-z0-9_-] only — the "/" alone disqualifies it. The
71// grc20reg slug stays the full hash (collision-safe across vouchers); the
72// symbol is purely the display token symbol and an 8-char hex prefix is
73// collision-resistant enough for that purpose. Name remains the base denom.
74func getOrCreateVoucher(_ int, rlm realm, baseDenom, voucherDenom string) *voucher {
75 if v, ok := voucherTokens.Get(voucherDenom); ok {
76 return v.(*voucher)
77 }
78 hash := voucherDenom[len("ibc/"):]
79 symbol := hash
80 if len(symbol) > 11 {
81 symbol = symbol[:11]
82 }
83 token, ledger := grc20.NewToken(0, rlm, baseDenom, symbol, 0)
84 inst := &voucher{token: token, ledger: ledger}
85 voucherTokens.Set(voucherDenom, inst)
86 // Slug = full hash, unique per voucher.
87 grc20reg.Register(cross(rlm), token, hash)
88 return inst
89}
90
91// getVoucher returns the voucher instance for the given IBC denom, or nil if not
92// found.
93func getVoucher(ibcDenom string) *voucher {
94 v, ok := voucherTokens.Get(ibcDenom)
95 if !ok {
96 return nil
97 }
98 return v.(*voucher)
99}
100
101// VoucherBalanceOf returns the balance of a voucher token for a given address.
102// Returns 0 if the token does not exist.
103func VoucherBalanceOf(ibcDenom string, addr address) int64 {
104 inst := getVoucher(ibcDenom)
105 if inst == nil {
106 return 0
107 }
108 return inst.token.BalanceOf(addr)
109}
110
111// GRC20Alias returns a slash-free alias for a GRC20 denom (grc20reg key)
112// by replacing "/" with ":". This is safe because grc20reg slugs are
113// alphanumeric (enforced by grc20reg.Register), so colons never appear in
114// grc20reg keys, making the replacement reversible.
115// Examples:
116//
117// "gno.land/r/demo/foo" → "gno.land:r:demo:foo"
118// "gno.land/r/demo/foo.FOO" → "gno.land:r:demo:foo.FOO"
119func GRC20Alias(grc20regKey string) string {
120 return strings.ReplaceAll(grc20regKey, "/", ":")
121}
122
123// resolveGRC20Alias converts a GRC20 alias back to the grc20reg key
124// by replacing ":" with "/".
125func resolveGRC20Alias(alias string) string {
126 return strings.ReplaceAll(alias, ":", "/")
127}
128
129// isGRC20Alias returns true if the denom is a GRC20 alias.
130// GRC20 aliases always start with "gno.land:" (the colon form of "gno.land/").
131func isGRC20Alias(denom string) bool {
132 return strings.HasPrefix(denom, "gno.land:")
133}