Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}