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

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}