store.gno
11.63 Kb · 354 lines
1package ucs03_zkgm
2
3import (
4 types "gno.land/p/onbloc/ibc/union/types"
5
6 "gno.land/p/nt/bptree/v0"
7 z "gno.land/p/onbloc/ibc/union/zkgm"
8 tb "gno.land/p/onbloc/ibc/union/zkgm/tokenbucket"
9 u256 "gno.land/p/onbloc/math/uint256"
10)
11
12var (
13 // store is the proxy-owned singleton holding all persistent domain state. It is
14 // the dependency injected into the implementation realm: the impl receives this
15 // pointer and mutates state through the SetXXX(_ int, rlm realm, ...) methods,
16 // whose trailing realm token carries the caller frame so the write satisfies the
17 // interrealm borrow rule. Reads use plain getters (no mutation, no realm token).
18 //
19 // Mapping to the reference CosmWasm contract (state.rs):
20 //
21 // tokenOrigin <- TOKEN_ORIGIN Map<denom, U256>
22 // channelBalanceV2 <- CHANNEL_BALANCE_V2 Map<(channel,path,base,quote), U256>
23 // metadataImageOf <- METADATA_IMAGE_OF Map<denom, H256>
24 // hashToForeignToken <- HASH_TO_FOREIGN_TOKEN Map<denom, Bytes>
25 // inFlightPacket <- IN_FLIGHT_PACKET Map<packetHash, Packet>
26 // tokenBucket <- TOKEN_BUCKET Map<denom, TokenBucket>
27 // rateLimitDisabled <- CONFIG.rate_limit_disabled
28 //
29 // CONFIG.{admin,ibc_host} are not duplicated here: they are sourced from core.
30 // The CosmWasm code_id fields (token_minter/dummy/cw_account) and the
31 // transaction-scratch items (EXECUTING_PACKET, EXECUTION_ACK, MARKET_MAKER, ...)
32 // are intentionally omitted: gno calls are synchronous, so that data is carried
33 // as parameters/locals instead of persisted across submessage hops.
34 store *Store
35
36 // paused is a proxy-level gate checked by the App receivers before delegating to
37 // the impl. It is governance state, not impl-visible state, so it lives on the
38 // proxy package rather than inside the injected Store.
39 paused bool
40
41 // upgrade.gno is the single admin-gated swap point for the zkgm app logic. The
42 // proxy keeps the Store, identity, and escrow funds stable across upgrades; only
43 // the installed impl changes.
44 //
45 // The swap is two-phase, mirroring core's host upgrade model: an impl realm
46 // registers its constructor under its own package path via RegisterImpl (from its
47 // init), and the admin later activates a registered path via UpdateImpl. The
48 // proxy injects its own Store into the constructor, so the store needs no public
49 // getter — the only way to obtain it is through this gated install.
50 proxyPkgPath string = ""
51 proxyAddress address = ""
52 currentImplPath string
53 currentImpl IApp
54 implConstructors map[string]func(store IStore) IApp
55)
56
57func init(cur realm) {
58 store = NewStore()
59 paused = false
60
61 proxyPkgPath = cur.PkgPath()
62 proxyAddress = cur.Address()
63 currentImpl = nil
64 currentImplPath = ""
65 implConstructors = make(map[string]func(store IStore) IApp)
66}
67
68type Store struct {
69 tokenOrigin *bptree.BPTree // denom -> *u256.Uint (mint path)
70 channelBalanceV2 *bptree.BPTree // key -> *u256.Uint (escrow balance)
71 metadataImageOf *bptree.BPTree // denom -> [32]byte
72 hashToForeignToken *bptree.BPTree // denom -> []byte
73 inFlightPacket *bptree.BPTree // packetHash -> types.Packet (parent)
74 tokenBucket *bptree.BPTree // denom -> *tb.TokenBucket
75 vouchers *bptree.BPTree // ibcDenom -> *Voucher
76 // lockedTokenAmounts is managed alongside vouchers: both track non-escrow
77 // custody of a base token (native or grc20) that zkgm cannot supply-burn,
78 // as opposed to vouchers, which zkgm mints/burns directly.
79 lockedTokenAmounts *bptree.BPTree // denom -> *u256.Uint (permanently locked base token)
80 receivers *bptree.BPTree // pkgPath -> z.Zkgmable
81 rateLimitDisabled bool
82}
83
84func NewStore() *Store {
85 return &Store{
86 tokenOrigin: bptree.NewBPTree32(),
87 channelBalanceV2: bptree.NewBPTree32(),
88 metadataImageOf: bptree.NewBPTree32(),
89 hashToForeignToken: bptree.NewBPTree32(),
90 inFlightPacket: bptree.NewBPTree32(),
91 tokenBucket: bptree.NewBPTree32(),
92 vouchers: bptree.NewBPTree32(),
93 lockedTokenAmounts: bptree.NewBPTree32(),
94 receivers: bptree.NewBPTree32(),
95 // Rate limiting is opt-in: enabled per-denom by an admin who first
96 // configures a token bucket (see SetBucketConfig). With it enabled, a denom
97 // without a bucket is rejected (union TokenBucketIsAbsent), so it must stay
98 // disabled by default to avoid bricking every unconfigured denom.
99 rateLimitDisabled: true,
100 }
101}
102
103// SetTokenOrigin records the mint path for a wrapped denom.
104func (s *Store) SetTokenOrigin(_ int, rlm realm, denom string, path *u256.Uint) {
105 assertIsRlmCurrent(0, rlm)
106
107 pathClone := u256.Zero().Set(path).Clone()
108
109 s.tokenOrigin.Set(denom, pathClone)
110}
111
112// GetTokenOrigin returns the recorded mint path for a denom.
113func (s *Store) GetTokenOrigin(denom string) (*u256.Uint, bool) {
114 v := s.tokenOrigin.Get(denom)
115 if v == nil {
116 return nil, false
117 }
118
119 return v.(*u256.Uint), true
120}
121
122// SetChannelBalanceV2 stores the escrow balance for a channel-balance key.
123func (s *Store) SetChannelBalanceV2(_ int, rlm realm, key string, amount *u256.Uint) {
124 assertIsRlmCurrent(0, rlm)
125
126 calculatedAmount := u256.Zero().Set(amount).Clone()
127
128 s.channelBalanceV2.Set(key, calculatedAmount)
129}
130
131// RemoveChannelBalanceV2 clears a drained channel-balance entry.
132func (s *Store) RemoveChannelBalanceV2(_ int, rlm realm, key string) {
133 assertIsRlmCurrent(0, rlm)
134
135 s.channelBalanceV2.Remove(key)
136}
137
138// GetChannelBalanceV2 returns the escrow balance for a key.
139func (s *Store) GetChannelBalanceV2(key string) (*u256.Uint, bool) {
140 v := s.channelBalanceV2.Get(key)
141 if v == nil {
142 return nil, false
143 }
144
145 return v.(*u256.Uint), true
146}
147
148// SetMetadataImageOf records a wrapped denom's metadata image.
149func (s *Store) SetMetadataImageOf(_ int, rlm realm, denom string, image [32]byte) {
150 assertIsRlmCurrent(0, rlm)
151
152 s.metadataImageOf.Set(denom, image)
153}
154
155// GetMetadataImageOf returns a wrapped denom's metadata image.
156func (s *Store) GetMetadataImageOf(denom string) ([32]byte, bool) {
157 v := s.metadataImageOf.Get(denom)
158 if v == nil {
159 return [32]byte{}, false
160 }
161
162 return v.([32]byte), true
163}
164
165// SetForeignToken records the origin token bytes for a wrapped denom.
166func (s *Store) SetForeignToken(_ int, rlm realm, denom string, token []byte) {
167 assertIsRlmCurrent(0, rlm)
168
169 s.hashToForeignToken.Set(denom, token)
170}
171
172// GetForeignToken returns the origin token bytes for a wrapped denom.
173func (s *Store) GetForeignToken(denom string) ([]byte, bool) {
174 v := s.hashToForeignToken.Get(denom)
175 if v == nil {
176 return nil, false
177 }
178
179 return v.([]byte), true
180}
181
182// SetInFlightPacket records the parent packet awaiting a forwarded child.
183func (s *Store) SetInFlightPacket(_ int, rlm realm, packetHash string, parent types.Packet) {
184 assertIsRlmCurrent(0, rlm)
185
186 s.inFlightPacket.Set(packetHash, parent)
187}
188
189// GetInFlightPacket returns the parent packet for a forwarded-child hash.
190func (s *Store) GetInFlightPacket(packetHash string) (types.Packet, bool) {
191 v := s.inFlightPacket.Get(packetHash)
192 if v == nil {
193 return types.Packet{}, false
194 }
195
196 return v.(types.Packet), true
197}
198
199// RemoveInFlightPacket clears a resolved forwarded-child entry.
200func (s *Store) RemoveInFlightPacket(_ int, rlm realm, packetHash string) {
201 assertIsRlmCurrent(0, rlm)
202
203 s.inFlightPacket.Remove(packetHash)
204}
205
206// SetTokenBucket stores the rate-limit bucket for a denom.
207func (s *Store) SetTokenBucket(_ int, rlm realm, denom string, bucket *tb.TokenBucket) {
208 assertIsRlmCurrent(0, rlm)
209
210 s.tokenBucket.Set(denom, bucket)
211}
212
213// GetTokenBucket returns the rate-limit bucket for a denom.
214func (s *Store) GetTokenBucket(denom string) (*tb.TokenBucket, bool) {
215 v := s.tokenBucket.Get(denom)
216 if v == nil {
217 return nil, false
218 }
219
220 return v.(*tb.TokenBucket), true
221}
222
223// RemoveTokenBucket clears a denom's rate-limit bucket.
224func (s *Store) RemoveTokenBucket(_ int, rlm realm, denom string) {
225 assertIsRlmCurrent(0, rlm)
226
227 s.tokenBucket.Remove(denom)
228}
229
230// SetVoucher stores the wrapped voucher for an ibc denom.
231func (s *Store) SetVoucher(_ int, rlm realm, ibcDenom string, v *Voucher) {
232 assertIsRlmCurrent(0, rlm)
233
234 s.vouchers.Set(ibcDenom, v)
235}
236
237// GetVoucher returns the wrapped voucher for an ibc denom.
238func (s *Store) GetVoucher(ibcDenom string) (*Voucher, bool) {
239 v := s.vouchers.Get(ibcDenom)
240 if v == nil {
241 return nil, false
242 }
243
244 return v.(*Voucher), true
245}
246
247// GetVoucherList returns a page of up to count vouchers starting at offset,
248// ordered by denom. The vouchers tree only grows (there is no voucher
249// deletion), so pagination keeps a single call's gas/response size bounded
250// regardless of how many wrapped tokens have been registered over time.
251func (s *Store) GetVoucherList(offset, count int) []VoucherInfo {
252 voucherList := []VoucherInfo{}
253
254 // The callback returns false to keep iterating (true stops early).
255 s.vouchers.IterateByOffset(offset, count, func(key string, value any) bool {
256 voucher, ok := value.(*Voucher)
257 if !ok {
258 panic(ErrInterfaceConversion)
259 }
260
261 voucherList = append(voucherList, NewVoucherInfo(
262 key,
263 voucher.token.GetName(),
264 voucher.token.GetSymbol(),
265 voucher.originDecimals,
266 uint8(voucher.token.GetDecimals()),
267 voucher.token.TotalSupply(),
268 ))
269
270 return false // continue iteration
271 })
272
273 return voucherList
274}
275
276// GetVoucherSize returns the total number of registered vouchers, i.e. the
277// key count callers need to drive GetVoucherList's offset/count pagination.
278func (s *Store) GetVoucherSize() int {
279 return s.vouchers.Size()
280}
281
282// HasVoucher reports whether a voucher exists for an ibc denom.
283func (s *Store) HasVoucher(ibcDenom string) bool {
284 return s.vouchers.Has(ibcDenom)
285}
286
287// AddLockedTokenAmount tracks base-token escrow locked after burn-address settlement
288// (native or a registered grc20; a voucher is supply-burned instead via burnVoucher).
289// Locked base remains in proxy escrow but is excluded from channel balances.
290// Invariant: proxy balance == sum(channelBalanceV2) + sum(lockedTokenAmounts).
291func (s *Store) AddLockedTokenAmount(_ int, rlm realm, denom string, amount *u256.Uint) {
292 assertIsRlmCurrent(0, rlm)
293
294 totalLockedAmount := u256.Zero()
295
296 lockedTokenAmount, exists := s.GetLockedTokenAmount(denom)
297 if exists && lockedTokenAmount != nil {
298 totalLockedAmount = u256.Zero().Set(lockedTokenAmount)
299 }
300
301 // Reject a wraparound past 2^256-1 rather than silently storing a smaller
302 // totalLockedAmount, which would break the proxy balance invariant.
303 // Astronomically unreachable for real coin supplies, but guarded for safety.
304 totalLockedAmount, overflow := u256.Zero().AddOverflow(totalLockedAmount, amount)
305 if overflow {
306 panic(ErrLockedTokenAmountOverflow)
307 }
308
309 s.lockedTokenAmounts.Set(denom, totalLockedAmount.Clone())
310}
311
312// GetLockedTokenAmount returns the total base-token amount permanently locked for a denom.
313func (s *Store) GetLockedTokenAmount(denom string) (*u256.Uint, bool) {
314 v := s.lockedTokenAmounts.Get(denom)
315 if v == nil {
316 return nil, false
317 }
318
319 lockedTokenAmount, ok := v.(*u256.Uint)
320 if !ok {
321 panic(ErrInterfaceConversion)
322 }
323
324 return lockedTokenAmount, true
325}
326
327// SetReceiver registers a Zkgmable callback target at a package path.
328func (s *Store) SetReceiver(_ int, rlm realm, path string, receiver z.Zkgmable) {
329 assertIsRlmCurrent(0, rlm)
330
331 s.receivers.Set(path, receiver)
332}
333
334// GetReceiver returns the callback target registered at a package path.
335func (s *Store) GetReceiver(path string) (z.Zkgmable, bool) {
336 r := s.receivers.Get(path)
337 if r == nil {
338 return nil, false
339 }
340
341 return r.(z.Zkgmable), true
342}
343
344// SetRateLimitDisabled toggles the global rate-limit kill switch.
345func (s *Store) SetRateLimitDisabled(_ int, rlm realm, disabled bool) {
346 assertIsRlmCurrent(0, rlm)
347
348 s.rateLimitDisabled = disabled
349}
350
351// RateLimitDisabled reports whether rate limiting is globally disabled.
352func (s *Store) RateLimitDisabled() bool {
353 return s.rateLimitDisabled
354}