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)
}