voucher.gno
6.56 Kb · 178 lines
1package ucs03_zkgm
2
3import (
4 "gno.land/p/demo/tokens/grc20"
5 z "gno.land/p/onbloc/ibc/union/zkgm"
6 u256 "gno.land/p/onbloc/math/uint256"
7)
8
9// Voucher bundles a wrapped grc20 token, its private mint/burn ledger,
10// and the origin token's decimal precision.
11// It is proxy-owned state held in the Store,
12// so its mint/burn rights survive impl upgrades.
13//
14// Packet amounts are in the origin token's decimals;
15// the grc20 ledger holds them downscaled to the (possibly capped) ledger decimals,
16// so they fit int64.
17// MintScaled / BurnScaled / TransferScaled apply that downscale,
18// so callers never combine the scale and the ledger by hand;
19// the raw int64 ops stay reachable via Ledger().
20//
21// SECURITY: those methods and Ledger() all hand out the mint/burn capability,
22// so a *Voucher must stay impl-gated.
23// The Store is injected and never publicly returned,
24// so untrusted realms can only read balances via VoucherBalanceOf.
25type Voucher struct {
26 token *grc20.Token
27 ledger *grc20.PrivateLedger
28 originDecimals uint8
29}
30
31// NewVoucher is a crossing-safe constructor,
32// so the impl realm can build one without tripping the foreign-composite-literal rule.
33// originDecimals is the origin token's precision;
34// the ledger token is created at min(originDecimals, ledger cap).
35func NewVoucher(token *grc20.Token, ledger *grc20.PrivateLedger, originDecimals uint8) *Voucher {
36 return &Voucher{token: token, ledger: ledger, originDecimals: originDecimals}
37}
38
39// VoucherInfo is a read-only snapshot of a wrapped voucher's grc20 identity
40// for the voucher-list getter. It carries only scalar fields copied out of
41// Voucher/Token — no token/ledger handles — so listing vouchers never hands
42// out the mint/burn capability that *Voucher.Ledger() exposes.
43type VoucherInfo struct {
44 Denom string
45 Name string
46 Symbol string
47 OriginDecimals uint8
48 LedgerDecimals uint8
49 TotalSupply int64
50}
51
52// NewVoucherInfo is a crossing-safe constructor,
53// so the impl realm (and test doubles) can build one without tripping the
54// foreign-composite-literal rule.
55func NewVoucherInfo(denom, name, symbol string, originDecimals, ledgerDecimals uint8, totalSupply int64) VoucherInfo {
56 return VoucherInfo{
57 Denom: denom,
58 Name: name,
59 Symbol: symbol,
60 OriginDecimals: originDecimals,
61 LedgerDecimals: ledgerDecimals,
62 TotalSupply: totalSupply,
63 }
64}
65
66// Token returns the read-only token handle (safe: balances, metadata).
67func (v *Voucher) Token() *grc20.Token { return v.token }
68
69// Ledger returns the mint/burn capability. Callers MUST be impl-gated.
70func (v *Voucher) Ledger() *grc20.PrivateLedger { return v.ledger }
71
72// OriginDecimals returns the origin token's precision.
73// The scalar is copied across the realm boundary, so impl reads are safe.
74func (v *Voucher) OriginDecimals() uint8 { return v.originDecimals }
75
76// ScaleExp is the origin->ledger downscale exponent:
77// the low-order digits dropped between origin precision and the ledger token's precision.
78// Derived from both stored decimals, so it needs no global constant,
79// and clamps to 0 for native/low-decimal tokens.
80func (v *Voucher) ScaleExp() int {
81 exp := int(v.originDecimals) - v.token.GetDecimals()
82 if exp < 0 {
83 return 0
84 }
85
86 return exp
87}
88
89// ToLedgerAmount narrows an origin-precision amount to this voucher's int64 ledger amount,
90// rejecting sub-precision dust and int64 overflow.
91// MintScaled, BurnScaled, TransferScaled and the impl's pre-flight checks all route through it.
92func (v *Voucher) ToLedgerAmount(amount *u256.Uint) (int64, error) {
93 return z.ScaleDownToInt64(amount, v.ScaleExp())
94}
95
96// MintScaled credits an origin-precision amount, downscaled to the ledger.
97// Carries the mint capability; callers MUST be impl-gated.
98func (v *Voucher) MintScaled(receiver address, amount *u256.Uint) error {
99 n, err := v.ToLedgerAmount(amount)
100 if err != nil {
101 return err
102 }
103
104 return v.ledger.Mint(receiver, n)
105}
106
107// BurnScaled debits an origin-precision amount, downscaled to the ledger.
108// Carries the burn capability; callers MUST be impl-gated.
109func (v *Voucher) BurnScaled(holder address, amount *u256.Uint) error {
110 n, err := v.ToLedgerAmount(amount)
111 if err != nil {
112 return err
113 }
114
115 return v.ledger.Burn(holder, n)
116}
117
118// TransferScaled moves an origin-precision amount between holders, downscaled to the ledger.
119// Carries the ledger capability; callers MUST be impl-gated.
120func (v *Voucher) TransferScaled(from, to address, amount *u256.Uint) error {
121 n, err := v.ToLedgerAmount(amount)
122 if err != nil {
123 return err
124 }
125
126 return v.ledger.Transfer(from, to, n)
127}
128
129// Approve grants spender an allowance on holder's ledger-precision balance.
130// Unlike MintScaled/BurnScaled/TransferScaled, the amount is not scaled from
131// origin precision: it is a self-service action on the holder's own balance,
132// matching the ledger units VoucherBalanceOf already reads.
133// Carries the ledger's Approve capability; callers MUST be impl-gated.
134func (v *Voucher) Approve(_ int, rlm realm, spender address, amount int64) error {
135 teller := v.ledger.ImpersonateTeller(rlm.Previous().Address())
136 return teller.Approve(0, rlm, spender, amount)
137}
138
139// Transfer moves amount of holder's ledger-precision balance to another
140// address. Same scale as Approve: no origin-precision scaling.
141// Carries the ledger's Transfer capability; callers MUST be impl-gated.
142func (v *Voucher) Transfer(_ int, rlm realm, to address, amount int64) error {
143 teller := v.ledger.ImpersonateTeller(rlm.Previous().Address())
144 return teller.Transfer(0, rlm, to, amount)
145}
146
147// VoucherApprove lets the caller grant spender an allowance on their own
148// wrapped-voucher balance for ibcDenom, so the voucher can be used through
149// GRC20-composable flows (e.g. Gnoswap) that rely on TransferFrom.
150func VoucherApprove(cur realm, ibcDenom string, spender address, amount int64) {
151 assertIsNotPaused()
152
153 if err := mustGetImpl().VoucherApprove(0, cur, ibcDenom, spender, amount); err != nil {
154 panic(err)
155 }
156}
157
158// VoucherTransfer lets the caller move their own wrapped-voucher balance for
159// ibcDenom directly to another address.
160func VoucherTransfer(cur realm, ibcDenom string, to address, amount int64) {
161 assertIsNotPaused()
162
163 if err := mustGetImpl().VoucherTransfer(0, cur, ibcDenom, to, amount); err != nil {
164 panic(err)
165 }
166}
167
168// VoucherBalanceOf returns addr's wrapped-voucher balance for ibcDenom, or 0 if none.
169// It reads the proxy-owned Store,
170// so the balance survives impl upgrades and does not depend on grc20reg's key.
171func VoucherBalanceOf(ibcDenom string, addr address) int64 {
172 vou, ok := store.GetVoucher(ibcDenom)
173 if !ok {
174 return 0
175 }
176
177 return vou.Token().BalanceOf(addr)
178}