voucher.gno
7.23 Kb · 194 lines
1package ucs03_zkgm
2
3import (
4 "strings"
5
6 "gno.land/p/demo/tokens/grc20"
7 "gno.land/p/nt/fqname/v0"
8 z "gno.land/p/onbloc/ibc/union/zkgm"
9 u256 "gno.land/p/onbloc/math/uint256"
10 "gno.land/r/demo/defi/grc20reg"
11 zkgm "gno.land/r/onbloc/ibc/union/apps/ucs03_zkgm"
12)
13
14// canonicalDecimals is the ledger precision a wrapped voucher is capped to.
15// A source token with more decimals overflows the grc20 int64 ledger,
16// so its amounts are downscaled to this precision on the way in,
17// and upscaled back on the way out.
18const canonicalDecimals = 6
19
20// mintVoucher mints amount of the wrapped voucher for ibcDenom to receiver,
21// creating the token on first use.
22// The voucher is created before the mint,
23// so a first-use mint already has the origin decimals bundled in;
24// MintScaled then downscales the origin-precision amount to the ledger.
25func (v *ucs03ZkgmV1) mintVoucher(_ int, rlm realm, ibcDenom string, image [32]byte, name, symbol string, decimals uint8, receiver address, amount *u256.Uint) error {
26 vou, err := v.getOrCreateVoucher(0, rlm, ibcDenom, image, name, symbol, decimals)
27 if err != nil {
28 return err
29 }
30
31 return vou.MintScaled(receiver, amount)
32}
33
34// burnVoucher burns amount of the ibc/ wrapped voucher held by sender.
35// Used on send-side UNESCROW (destroy wrapped supply before remote unwrap) and
36// maker burn-address ack (burnEscrowedV2). GRC20 and native coins are never
37// passed here — they cannot be supply-burned by this realm.
38// BurnScaled downscales the origin-precision amount to the ledger.
39func (v *ucs03ZkgmV1) burnVoucher(_ int, rlm realm, ibcDenom string, sender address, amount *u256.Uint) error {
40 vou, ok := v.store.GetVoucher(ibcDenom)
41 if !ok {
42 return makeError(errVoucherNotFound, ibcDenom)
43 }
44
45 return vou.BurnScaled(sender, amount)
46}
47
48// transferVoucher moves amount of the wrapped voucher between holders (market-maker fill).
49// TransferScaled downscales the origin-precision amount to the ledger.
50func (v *ucs03ZkgmV1) transferVoucher(_ int, rlm realm, ibcDenom string, from, to address, amount *u256.Uint) error {
51 vou, ok := v.store.GetVoucher(ibcDenom)
52 if !ok {
53 return makeError(errVoucherNotFound, ibcDenom)
54 }
55
56 return vou.TransferScaled(from, to, amount)
57}
58
59// VoucherApprove lets the caller (rlm.Previous(), the address that called the
60// proxy) grant spender an allowance on their own wrapped-voucher balance.
61// Delegates to Voucher.Approve so mint/burn stay reachable exclusively via
62// mintVoucher/burnVoucher above; this never touches Ledger() directly.
63func (v *ucs03ZkgmV1) VoucherApprove(_ int, rlm realm, ibcDenom string, spender address, amount int64) error {
64 assertIsRlmCurrent(0, rlm)
65
66 vou, ok := v.store.GetVoucher(ibcDenom)
67 if !ok {
68 return makeError(errVoucherNotFound, ibcDenom)
69 }
70
71 return vou.Approve(0, rlm, spender, amount)
72}
73
74// VoucherTransfer lets the caller (rlm.Previous(), the address that called the
75// proxy) move their own wrapped-voucher balance directly to another address.
76// Same capability scope as VoucherApprove: Transfer only, no mint/burn.
77func (v *ucs03ZkgmV1) VoucherTransfer(_ int, rlm realm, ibcDenom string, to address, amount int64) error {
78 assertIsRlmCurrent(0, rlm)
79
80 vou, ok := v.store.GetVoucher(ibcDenom)
81 if !ok {
82 return makeError(errVoucherNotFound, ibcDenom)
83 }
84
85 return vou.Transfer(0, rlm, to, amount)
86}
87
88// getOrCreateVoucher returns the wrapped grc20 voucher for ibcDenom, creating and registering it on first use.
89func (v *ucs03ZkgmV1) getOrCreateVoucher(_ int, rlm realm, ibcDenom string, image [32]byte, name, symbol string, decimals uint8) (*zkgm.Voucher, error) {
90 if vou, ok := v.store.GetVoucher(ibcDenom); ok {
91 return vou, nil
92 }
93
94 if name == "" {
95 name = ibcDenom
96 }
97
98 symbol = sanitizeVoucherSymbol(symbol, ibcDenom)
99
100 // grc20reg keys its registry by (registering realm, token symbol) and
101 // panics on a repeat key. Every voucher this realm registers shares the
102 // same realm path, so a symbol collision here always means a different
103 // ibcDenom already claimed this symbol. Check the prospective key
104 // ourselves so that case fails with a controlled error instead of a panic.
105 key := fqname.Construct(rlm.PkgPath(), symbol)
106 if grc20reg.Get(key) != nil {
107 return nil, makeError(errVoucherSymbolAlreadyRegistered, symbol)
108 }
109
110 // The grc20 ledger is int64,
111 // so a source token above canonicalDecimals is created at canonicalDecimals.
112 // The origin decimals are bundled into the voucher (see Voucher.ScaleExp),
113 // so only the local ledger is downscaled; the packet keeps origin precision.
114 ledgerDecimals := decimals
115 if scaleExpForDecimals(decimals) > 0 {
116 ledgerDecimals = canonicalDecimals
117 }
118
119 token, ledger := grc20.NewToken(name, symbol, int(ledgerDecimals), 0, rlm)
120 grc20reg.Register(cross(rlm), token, strings.TrimPrefix(ibcDenom, ibcDenomPrefix))
121
122 // originDecimals travels with the token,
123 // so every mint/burn/transfer derives the same scale from it and the ledger token,
124 // with no parallel map to keep in sync.
125 voucher := zkgm.NewVoucher(token, ledger, decimals)
126 v.store.SetVoucher(0, rlm, ibcDenom, voucher)
127
128 return voucher, nil
129}
130
131// ledgerAmountOf downscales an origin-precision amount to the ledger amount for a denom.
132// It delegates to Voucher.ToLedgerAmount when a voucher exists,
133// otherwise it is an identity narrow for native/low-decimal denoms.
134// Used by the token-order pre-flight checks, which hold only a denom string.
135func (v *ucs03ZkgmV1) ledgerAmountOf(ibcDenom string, amount *u256.Uint) (int64, error) {
136 if vou, ok := v.store.GetVoucher(ibcDenom); ok {
137 return vou.ToLedgerAmount(amount)
138 }
139
140 return amountInt64(amount)
141}
142
143// scaleExpOf returns the origin->ledger downscale exponent registered for a
144// denom, or 0 when no voucher exists for it (native and low-decimal tokens).
145func (v *ucs03ZkgmV1) scaleExpOf(ibcDenom string) int {
146 if vou, ok := v.store.GetVoucher(ibcDenom); ok {
147 return vou.ScaleExp()
148 }
149
150 return 0
151}
152
153// sanitizeVoucherSymbol derives a grc20-legal symbol (<=MaxSymbolLen, [A-Za-z0-9_-])
154// from the candidate, falling back to a prefix of the IBC hash when empty.
155func sanitizeVoucherSymbol(candidate, ibcDenom string) string {
156 out := make([]byte, 0, grc20.MaxSymbolLen)
157 for i := 0; i < len(candidate) && len(out) < grc20.MaxSymbolLen; i++ {
158 c := candidate[i]
159 if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-' {
160 out = append(out, c)
161 }
162 }
163
164 if len(out) == 0 {
165 fallback := strings.TrimPrefix(ibcDenom, ibcDenomPrefix)
166 if len(fallback) > grc20.MaxSymbolLen {
167 fallback = fallback[:grc20.MaxSymbolLen]
168 }
169
170 return fallback
171 }
172
173 return string(out)
174}
175
176// scaleExpForDecimals returns the origin->ledger downscale exponent for `decimals`:
177// the low-order digits dropped so the ledger holds canonicalDecimals.
178// Tokens at or below canonicalDecimals return 0.
179// Used at creation to cap the ledger,
180// and by the INITIALIZE pre-flight check before a voucher exists.
181func scaleExpForDecimals(decimals uint8) int {
182 if int(decimals) > canonicalDecimals {
183 return int(decimals) - canonicalDecimals
184 }
185
186 return 0
187}
188
189// amountInt64 narrows a uint256 to int64 for the native banker/grc20 paths (no scaling).
190// It is the scaleExp-0 case of the shared downscaler,
191// kept as a named helper for the native call sites (funds.sub, sendNative).
192func amountInt64(amount *u256.Uint) (int64, error) {
193 return z.ScaleDownToInt64(amount, 0)
194}