package ucs03_zkgm import ( "gno.land/p/demo/tokens/grc20" z "gno.land/p/onbloc/ibc/union/zkgm" u256 "gno.land/p/onbloc/math/uint256" ) // Voucher bundles a wrapped grc20 token, its private mint/burn ledger, // and the origin token's decimal precision. // It is proxy-owned state held in the Store, // so its mint/burn rights survive impl upgrades. // // Packet amounts are in the origin token's decimals; // the grc20 ledger holds them downscaled to the (possibly capped) ledger decimals, // so they fit int64. // MintScaled / BurnScaled / TransferScaled apply that downscale, // so callers never combine the scale and the ledger by hand; // the raw int64 ops stay reachable via Ledger(). // // SECURITY: those methods and Ledger() all hand out the mint/burn capability, // so a *Voucher must stay impl-gated. // The Store is injected and never publicly returned, // so untrusted realms can only read balances via VoucherBalanceOf. type Voucher struct { token *grc20.Token ledger *grc20.PrivateLedger originDecimals uint8 } // NewVoucher is a crossing-safe constructor, // so the impl realm can build one without tripping the foreign-composite-literal rule. // originDecimals is the origin token's precision; // the ledger token is created at min(originDecimals, ledger cap). func NewVoucher(token *grc20.Token, ledger *grc20.PrivateLedger, originDecimals uint8) *Voucher { return &Voucher{token: token, ledger: ledger, originDecimals: originDecimals} } // VoucherInfo is a read-only snapshot of a wrapped voucher's grc20 identity // for the voucher-list getter. It carries only scalar fields copied out of // Voucher/Token — no token/ledger handles — so listing vouchers never hands // out the mint/burn capability that *Voucher.Ledger() exposes. type VoucherInfo struct { Denom string Name string Symbol string OriginDecimals uint8 LedgerDecimals uint8 TotalSupply int64 } // NewVoucherInfo is a crossing-safe constructor, // so the impl realm (and test doubles) can build one without tripping the // foreign-composite-literal rule. func NewVoucherInfo(denom, name, symbol string, originDecimals, ledgerDecimals uint8, totalSupply int64) VoucherInfo { return VoucherInfo{ Denom: denom, Name: name, Symbol: symbol, OriginDecimals: originDecimals, LedgerDecimals: ledgerDecimals, TotalSupply: totalSupply, } } // Token returns the read-only token handle (safe: balances, metadata). func (v *Voucher) Token() *grc20.Token { return v.token } // Ledger returns the mint/burn capability. Callers MUST be impl-gated. func (v *Voucher) Ledger() *grc20.PrivateLedger { return v.ledger } // OriginDecimals returns the origin token's precision. // The scalar is copied across the realm boundary, so impl reads are safe. func (v *Voucher) OriginDecimals() uint8 { return v.originDecimals } // ScaleExp is the origin->ledger downscale exponent: // the low-order digits dropped between origin precision and the ledger token's precision. // Derived from both stored decimals, so it needs no global constant, // and clamps to 0 for native/low-decimal tokens. func (v *Voucher) ScaleExp() int { exp := int(v.originDecimals) - v.token.GetDecimals() if exp < 0 { return 0 } return exp } // ToLedgerAmount narrows an origin-precision amount to this voucher's int64 ledger amount, // rejecting sub-precision dust and int64 overflow. // MintScaled, BurnScaled, TransferScaled and the impl's pre-flight checks all route through it. func (v *Voucher) ToLedgerAmount(amount *u256.Uint) (int64, error) { return z.ScaleDownToInt64(amount, v.ScaleExp()) } // MintScaled credits an origin-precision amount, downscaled to the ledger. // Carries the mint capability; callers MUST be impl-gated. func (v *Voucher) MintScaled(receiver address, amount *u256.Uint) error { n, err := v.ToLedgerAmount(amount) if err != nil { return err } return v.ledger.Mint(receiver, n) } // BurnScaled debits an origin-precision amount, downscaled to the ledger. // Carries the burn capability; callers MUST be impl-gated. func (v *Voucher) BurnScaled(holder address, amount *u256.Uint) error { n, err := v.ToLedgerAmount(amount) if err != nil { return err } return v.ledger.Burn(holder, n) } // TransferScaled moves an origin-precision amount between holders, downscaled to the ledger. // Carries the ledger capability; callers MUST be impl-gated. func (v *Voucher) TransferScaled(from, to address, amount *u256.Uint) error { n, err := v.ToLedgerAmount(amount) if err != nil { return err } return v.ledger.Transfer(from, to, n) } // Approve grants spender an allowance on holder's ledger-precision balance. // Unlike MintScaled/BurnScaled/TransferScaled, the amount is not scaled from // origin precision: it is a self-service action on the holder's own balance, // matching the ledger units VoucherBalanceOf already reads. // Carries the ledger's Approve capability; callers MUST be impl-gated. func (v *Voucher) Approve(_ int, rlm realm, spender address, amount int64) error { teller := v.ledger.ImpersonateTeller(rlm.Previous().Address()) return teller.Approve(0, rlm, spender, amount) } // Transfer moves amount of holder's ledger-precision balance to another // address. Same scale as Approve: no origin-precision scaling. // Carries the ledger's Transfer capability; callers MUST be impl-gated. func (v *Voucher) Transfer(_ int, rlm realm, to address, amount int64) error { teller := v.ledger.ImpersonateTeller(rlm.Previous().Address()) return teller.Transfer(0, rlm, to, amount) } // VoucherApprove lets the caller grant spender an allowance on their own // wrapped-voucher balance for ibcDenom, so the voucher can be used through // GRC20-composable flows (e.g. Gnoswap) that rely on TransferFrom. func VoucherApprove(cur realm, ibcDenom string, spender address, amount int64) { assertIsNotPaused() if err := mustGetImpl().VoucherApprove(0, cur, ibcDenom, spender, amount); err != nil { panic(err) } } // VoucherTransfer lets the caller move their own wrapped-voucher balance for // ibcDenom directly to another address. func VoucherTransfer(cur realm, ibcDenom string, to address, amount int64) { assertIsNotPaused() if err := mustGetImpl().VoucherTransfer(0, cur, ibcDenom, to, amount); err != nil { panic(err) } } // VoucherBalanceOf returns addr's wrapped-voucher balance for ibcDenom, or 0 if none. // It reads the proxy-owned Store, // so the balance survives impl upgrades and does not depend on grc20reg's key. func VoucherBalanceOf(ibcDenom string, addr address) int64 { vou, ok := store.GetVoucher(ibcDenom) if !ok { return 0 } return vou.Token().BalanceOf(addr) }