coins.gno
3.08 Kb · 138 lines
1package ucs03_zkgm
2
3import (
4 "chain"
5 "strconv"
6 "strings"
7
8 u256 "gno.land/p/onbloc/math/uint256"
9 "gno.land/r/demo/defi/grc20reg"
10)
11
12// ibcDenomPrefix is the prefix of wrapped IBC voucher denoms (mirrors
13// z.PredictWrappedToken*). Used to detect and trim wrapped denoms.
14const ibcDenomPrefix = "ibc/"
15
16const (
17 // IBC_TOKEN is a zkgm-issued ibc/ voucher. Supply is minted on recv and
18 // burned on send-side UNESCROW or maker burn-address ack; custody moves via
19 // transferVoucher between holders and the proxy escrow.
20 IBC_TOKEN = iota
21 // GRC20_TOKEN is a locally registered grc20 (not a zkgm voucher). Custody
22 // moves via Teller TransferFrom/Transfer; this realm cannot burn its supply.
23 GRC20_TOKEN
24 // NATIVE_COIN is a banker coin attached with tx -send. Escrow consumes the
25 // attached funds budget; unescrow releases from the proxy banker balance.
26 NATIVE_COIN
27)
28
29func isIBCTokenDenom(denom string) bool {
30 return strings.HasPrefix(denom, ibcDenomPrefix)
31}
32
33func getTokenType(denom string) int {
34 if isIBCTokenDenom(denom) {
35 return IBC_TOKEN
36 }
37
38 if token := grc20reg.Get(denom); token != nil {
39 return GRC20_TOKEN
40 }
41
42 return NATIVE_COIN
43}
44
45// parseCoins parses a comma-separated <amount><denom> string into Coins.
46func parseCoins(s string) chain.Coins {
47 if s == "" {
48 return nil
49 }
50
51 parts := strings.Split(s, ",")
52 coins := make([]chain.Coin, 0, len(parts))
53
54 for _, part := range parts {
55 if part == "" {
56 continue
57 }
58
59 i := 0
60 for i < len(part) && part[i] >= '0' && part[i] <= '9' {
61 i++
62 }
63
64 if i == 0 || i == len(part) {
65 panic("zkgm/coins: invalid coin " + part)
66 }
67
68 amount, err := strconv.ParseInt(part[:i], 10, 64)
69 if err != nil {
70 panic(err)
71 }
72
73 coins = append(coins, chain.NewCoin(part[i:], amount))
74 }
75
76 return chain.NewCoins(coins...)
77}
78
79// funds is the gno analog of the reference contract's threaded `funds: &mut Coins`
80// (the message's attached coins). It is decremented as each token order consumes
81// its BaseAmount during verification.
82type funds struct {
83 coins map[string]chain.Coin
84}
85
86// newFunds snapshots the attached coins into a mutable spend budget.
87func newFunds(sent chain.Coins) *funds {
88 f := &funds{coins: make(map[string]chain.Coin)}
89
90 for _, coin := range sent {
91 f.coins[coin.Denom] = coin
92 }
93
94 return f
95}
96
97// sub deducts amount of denom from the attached funds, mirroring funds.sub(coin)
98// in the reference contract. It errors when the funds do not cover the amount.
99func (f *funds) sub(denom string, amount *u256.Uint) error {
100 want, err := amountInt64(amount)
101 if err != nil {
102 return err
103 }
104
105 coin, ok := f.coins[denom]
106 if !ok {
107 return makeError(errCoinMismatch)
108 }
109
110 if coin.Amount < want {
111 return makeError(errCoinMismatch)
112 }
113
114 coin.Amount -= want
115 f.coins[denom] = coin
116
117 return nil
118}
119
120// isEmpty reports whether every attached coin has been fully consumed.
121func (f *funds) isEmpty() bool {
122 for _, coin := range f.coins {
123 if coin.Amount != 0 {
124 return false
125 }
126 }
127
128 return true
129}
130
131// assertEmpty errors when any attached coin is left unconsumed.
132func (f *funds) assertEmpty() error {
133 if !f.isEmpty() {
134 return makeError(errCoinMismatch)
135 }
136
137 return nil
138}