Skip to content

Commit cc83333

Browse files
committed
zkgm: move local asset class records unto the proxy realm
this will make grc20 escrow classification survives implementation swaps
1 parent cae0743 commit cc83333

8 files changed

Lines changed: 124 additions & 27 deletions

File tree

gno.land/r/core/ibc/v1/apps/zkgm/proxy.gno

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ var (
2424
gRealmSet bool
2525
receivers = bptree.NewBPTree32()
2626
escrowedGRC20 = bptree.NewBPTree32()
27+
assetClassOf = bptree.NewBPTree32()
2728
)
2829

2930
const proxyPkgPath = "gno.land/r/onbloc/unionibc/v1/apps/zkgm"
@@ -162,7 +163,7 @@ func BatchSend(cur realm, msg core.MsgBatchSend) core.H256 {
162163
//
163164
// ReleaseNative is used by implementation-side sendNative calls so refunds and
164165
// releases are sourced from the escrow where the user's -send funds land.
165-
func ReleaseNative(cur realm, denom string, recipient address, amount int64) {
166+
func ReleaseNative(cur realm, denom string, recipient address, amount int64) error {
166167
cacheRealm(cur)
167168
requireImplRealm(cur, "release native")
168169

@@ -172,9 +173,14 @@ func ReleaseNative(cur realm, denom string, recipient address, amount int64) {
172173
if denom == "" {
173174
panic("zkgm: ReleaseNative denom must not be empty")
174175
}
176+
rb := banker.NewReadonlyBanker()
177+
if rb.GetCoins(cur.Address()).AmountOf(denom) < amount {
178+
return errors.New("zkgm: insufficient native escrow balance")
179+
}
175180
b := banker.NewBanker(banker.BankerTypeRealmSend, cur)
176181
b.SendCoins(cur.Address(), recipient, chain.NewCoins(chain.NewCoin(denom, amount)))
177182
emitZkgmNativeReleasedEvent(cur.Previous().PkgPath(), denom, recipient, amount)
183+
return nil
178184
}
179185

180186
func EscrowGRC20(cur realm, denomKey string, from address, amount int64) error {
@@ -231,6 +237,22 @@ func escrowedGRC20Token(denomKey string) (*grc20.Token, error) {
231237
return tok, nil
232238
}
233239

240+
func RecordAssetClass(cur realm, denom string, class uint8) {
241+
requireImplRealm(cur, "record asset class")
242+
if _, ok := assetClassOf.Get(denom); ok {
243+
return
244+
}
245+
assetClassOf.Set(denom, class)
246+
}
247+
248+
func GetAssetClass(denom string) (uint8, bool) {
249+
value, ok := assetClassOf.Get(denom)
250+
if !ok {
251+
return 0, false
252+
}
253+
return value.(uint8), true
254+
}
255+
234256
// requireImplRealm panics unless the caller is the registered implementation
235257
// realm or an allowed implementation realm.
236258
func requireImplRealm(cur realm, action string) {

gno.land/r/core/ibc/v1/apps/zkgm/v0/impl/asset.gno

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"strings"
66

77
u256 "gno.land/p/gnoswap/uint256"
8-
"gno.land/p/nt/bptree/v0"
98
"gno.land/r/demo/defi/grc20reg"
109
zkgm "gno.land/r/onbloc/unionibc/v1/apps/zkgm"
1110
)
@@ -18,8 +17,6 @@ const (
1817
classLocalGRC20
1918
)
2019

21-
var assetClassOf = bptree.NewBPTree32()
22-
2320
func classify(denom string) assetClass {
2421
// Voucher wire denoms are `ibc/<hash>`, while their grc20reg keys use the
2522
// voucher realm path and hash slug. Check the prefix before registry lookup.
@@ -32,16 +29,13 @@ func classify(denom string) assetClass {
3229
return classNative
3330
}
3431

35-
func recordAssetClass(denom string, c assetClass) {
36-
if _, ok := assetClassOf.Get(denom); ok {
37-
return
38-
}
39-
assetClassOf.Set(denom, c)
32+
func recordAssetClass(cur realm, denom string, c assetClass) {
33+
zkgm.RecordAssetClass(cross(cur), denom, uint8(c))
4034
}
4135

4236
func assetClassFor(denom string) assetClass {
43-
if v, ok := assetClassOf.Get(denom); ok {
44-
return v.(assetClass)
37+
if v, ok := zkgm.GetAssetClass(denom); ok {
38+
return assetClass(v)
4539
}
4640
// Compatibility fallback for pre-upgrade in-flight native/voucher orders.
4741
// New local-GRC20 escrow records its class before channel balance is credited,
@@ -55,8 +49,8 @@ func assetClassFor(denom string) assetClass {
5549
func escrowAsset(cur realm, denom string, sender string, amount *u256.Uint, budget *sentCoinBudget) error {
5650
liveClass := classify(denom)
5751
c := liveClass
58-
if value, ok := assetClassOf.Get(denom); ok {
59-
c = value.(assetClass)
52+
if value, ok := zkgm.GetAssetClass(denom); ok {
53+
c = assetClass(value)
6054
if c != liveClass {
6155
return errors.New("zkgm/v1: asset class changed for " + denom)
6256
}
@@ -79,7 +73,7 @@ func escrowAsset(cur realm, denom string, sender string, amount *u256.Uint, budg
7973
return err
8074
}
8175
}
82-
recordAssetClass(denom, c)
76+
recordAssetClass(cur, denom, c)
8377
return nil
8478
}
8579

gno.land/r/core/ibc/v1/apps/zkgm/v0/impl/channel_balance.gno

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import (
55
"errors"
66

77
u256 "gno.land/p/gnoswap/uint256"
8-
core "gno.land/r/onbloc/unionibc/v1/core"
98
zkgm "gno.land/r/onbloc/unionibc/v1/apps/zkgm"
9+
core "gno.land/r/onbloc/unionibc/v1/core"
1010
)
1111

1212
// ChannelBalanceKeyV2 returns the storage key for a v2 channel balance entry.
@@ -31,10 +31,10 @@ func decreaseChannelBalanceV2(cur realm, channelId core.ChannelId, path *u256.Ui
3131
return nil
3232
}
3333
key := ChannelBalanceKeyV2(channelId, path, baseToken, quoteToken)
34-
curAmount := zkgm.GetChannelBalanceV2(key)
35-
if curAmount == nil || curAmount.Cmp(amount) < 0 {
36-
return errors.New("zkgm/channel_balance: underflow")
34+
if err := ensureChannelBalanceV2(key, amount); err != nil {
35+
return err
3736
}
37+
curAmount := zkgm.GetChannelBalanceV2(key)
3838
next := new(u256.Uint).Sub(curAmount, amount)
3939
if next.IsZero() {
4040
zkgm.RemoveChannelBalanceV2(cross(cur), key)
@@ -43,3 +43,11 @@ func decreaseChannelBalanceV2(cur realm, channelId core.ChannelId, path *u256.Ui
4343
}
4444
return nil
4545
}
46+
47+
func ensureChannelBalanceV2(key string, amount *u256.Uint) error {
48+
curAmount := zkgm.GetChannelBalanceV2(key)
49+
if curAmount == nil || curAmount.Cmp(amount) < 0 {
50+
return errors.New("zkgm/channel_balance: underflow")
51+
}
52+
return nil
53+
}

gno.land/r/core/ibc/v1/apps/zkgm/v0/impl/dispatch_test.gno

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ func TestRecvTokenOrderGRC20ReleaseFailureAborts(cur realm, t *testing.T) {
145145
resetTokenOrderTest(cur)
146146

147147
denom, _, _ := registerImplTestGRC20(cur, "recv_release_fail")
148-
recordAssetClass(denom, classLocalGRC20)
148+
recordAssetClass(cur, denom, classLocalGRC20)
149149
path := u256.Zero()
150150
reversed, err := z.ReverseChannelPath(path)
151151
uassert.NoError(t, err)
@@ -168,6 +168,34 @@ func TestRecvTokenOrderGRC20ReleaseFailureAborts(cur realm, t *testing.T) {
168168
})
169169
}
170170

171+
func TestRecvTokenOrderNativeReleaseFailureAbortsAndKeepsChannelBalance(cur realm, t *testing.T) {
172+
resetTokenOrderTest(cur)
173+
174+
denom := "unfunded-native-release"
175+
path := u256.Zero()
176+
reversed, err := z.ReverseChannelPath(path)
177+
uassert.NoError(t, err)
178+
order := z.TokenOrderV2{
179+
Kind: z.TOKEN_ORDER_KIND_UNESCROW,
180+
Sender: []byte(testAddress("native-relfail-send")),
181+
Receiver: []byte(testAddress("native-relfail-rx")),
182+
BaseToken: []byte("remote"),
183+
BaseAmount: u256.NewUint(20),
184+
QuoteToken: []byte(denom),
185+
QuoteAmount: u256.NewUint(17),
186+
}
187+
destChan := core.ChannelId(17)
188+
increaseChannelBalanceV2(cross(cur), destChan, reversed, order.QuoteToken, order.BaseToken, order.BaseAmount)
189+
data := mustZkgmPacketBytes(batchMustOrderInstruction(order))
190+
packet := core.NewPacket(cross(cur), core.ChannelId(1), destChan, data, 0, 0)
191+
192+
uassert.AbortsContains(t, cur, "insufficient native escrow balance", func(cur realm) {
193+
(&ZkgmV1{}).Recv(cur, packet, address(testAddress("native-relfail-relay")), nil)
194+
})
195+
balance := zkgm.GetChannelBalanceV2(ChannelBalanceKeyV2(destChan, reversed, order.QuoteToken, order.BaseToken))
196+
uassert.Equal(t, "20", balance.ToString())
197+
}
198+
171199
func mustZkgmPacketBytes(instr z.Instruction) []byte {
172200
out, err := z.EncodeZkgmPacket(z.ZkgmPacket{
173201
Salt: [32]byte{},

gno.land/r/core/ibc/v1/apps/zkgm/v0/impl/token_order.gno

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,8 @@ func (v *ZkgmV1) executeTokenOrderV2(cur realm, packet core.Packet, relayer addr
201201
if _, err := amountInt64(fee); err != nil {
202202
return core.NewRecvPacketResult(cross(cur), core.PacketStatusUnknown, nil), err
203203
}
204-
if err := decreaseChannelBalanceV2(cross(cur), packet.DestinationChannelId, reversedPath, order.QuoteToken, order.BaseToken, order.BaseAmount); err != nil {
204+
balanceKey := ChannelBalanceKeyV2(packet.DestinationChannelId, reversedPath, order.QuoteToken, order.BaseToken)
205+
if err := ensureChannelBalanceV2(balanceKey, order.BaseAmount); err != nil {
205206
return core.NewRecvPacketResult(cross(cur), core.PacketStatusUnknown, nil), err
206207
}
207208
if !order.QuoteAmount.IsZero() {
@@ -214,6 +215,9 @@ func (v *ZkgmV1) executeTokenOrderV2(cur realm, packet core.Packet, relayer addr
214215
panic(execFatal{err.Error()})
215216
}
216217
}
218+
if err := decreaseChannelBalanceV2(cross(cur), packet.DestinationChannelId, reversedPath, order.QuoteToken, order.BaseToken, order.BaseAmount); err != nil {
219+
panic(execFatal{err.Error()})
220+
}
217221
case z.TOKEN_ORDER_KIND_SOLVE:
218222
return core.NewRecvPacketResult(cross(cur), core.PacketStatusUnknown, nil), errors.New("zkgm/v1: solve recv not implemented")
219223
default:
@@ -332,6 +336,5 @@ func sendNative(cur realm, denom string, receiver string, amount *u256.Uint) err
332336
return err
333337
}
334338

335-
zkgm.ReleaseNative(cross(cur), denom, address(receiver), n)
336-
return nil
339+
return zkgm.ReleaseNative(cross(cur), denom, address(receiver), n)
337340
}

gno.land/r/core/ibc/v1/apps/zkgm/v0/impl/token_order_test.gno

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99

1010
"gno.land/p/demo/tokens/grc20"
1111
u256 "gno.land/p/gnoswap/uint256"
12-
"gno.land/p/nt/bptree/v0"
1312
"gno.land/p/nt/testutils/v0"
1413
"gno.land/p/nt/uassert/v0"
1514
tb "gno.land/p/onbloc/tokenbucket"
@@ -105,6 +104,35 @@ func TestTokenOrderAckMarketMakerEscrowWithLocalGRC20RewardsMarketMaker(cur real
105104
uassert.True(t, balance == nil || balance.IsZero())
106105
}
107106

107+
func TestTokenOrderAckMarketMakerGRC20AfterImplResetUsesRecordedClass(cur realm, t *testing.T) {
108+
resetTokenOrderTest(cur)
109+
110+
sender := testAddress("mm-g20-up-send")
111+
marketMaker := testAddress("mm-g20-up-maker")
112+
channelId := core.ChannelId(26)
113+
denom, tok, ledger := registerImplTestGRC20(cur, "mm_grc20_upgrade")
114+
uassert.NoError(t, ledger.Mint(address(sender), 50))
115+
uassert.NoError(t, ledger.Approve(address(sender), zkgm.ProxyAddress(), 13))
116+
order := z.TokenOrderV2{
117+
Sender: []byte(sender),
118+
BaseToken: []byte(denom),
119+
BaseAmount: u256.NewUint(13),
120+
QuoteToken: []byte("quote-upgrade"),
121+
QuoteAmount: u256.NewUint(13),
122+
Kind: z.TOKEN_ORDER_KIND_ESCROW,
123+
}
124+
uassert.NoError(t, (&ZkgmV1{}).verifyTokenOrderV2(cur, channelId, sender, newSentCoinBudget(nil), u256.Zero(), order))
125+
126+
resetTokenOrderTest(cur)
127+
err := (&ZkgmV1{}).acknowledgeTokenOrderV2(cur, channelId, u256.Zero(), order, marketMakerAck(marketMaker))
128+
129+
uassert.NoError(t, err)
130+
uassert.Equal(t, int64(13), tok.BalanceOf(address(marketMaker)))
131+
uassert.Equal(t, int64(0), tok.BalanceOf(zkgm.ProxyAddress()))
132+
balance := zkgm.GetChannelBalanceV2(ChannelBalanceKeyV2(channelId, u256.Zero(), order.BaseToken, order.QuoteToken))
133+
uassert.True(t, balance == nil || balance.IsZero())
134+
}
135+
108136
func TestTokenOrderVerifyNativeRequiresSentCoin(cur realm, t *testing.T) {
109137
order := z.TokenOrderV2{
110138
Sender: []byte(testAddress("v1-native-sender")),
@@ -274,7 +302,7 @@ func TestTokenOrderExecuteUnescrowReleasesLocalGRC20(cur realm, t *testing.T) {
274302
relayer := testAddress("g20-unescrow-relay")
275303
denom, tok, ledger := registerImplTestGRC20(cur, "unescrow_release")
276304
uassert.NoError(t, ledger.Mint(zkgm.ProxyAddress(), 20))
277-
recordAssetClass(denom, classLocalGRC20)
305+
recordAssetClass(cur, denom, classLocalGRC20)
278306
path := u256.NewUint(7)
279307
reversed, err := z.ReverseChannelPath(path)
280308
uassert.NoError(t, err)
@@ -304,7 +332,7 @@ func TestTokenOrderAckFailureRefundsLocalGRC20(cur realm, t *testing.T) {
304332
sender := testAddress("v1-grc20-ack-refund")
305333
denom, tok, ledger := registerImplTestGRC20(cur, "ack_refund")
306334
uassert.NoError(t, ledger.Mint(zkgm.ProxyAddress(), 13))
307-
recordAssetClass(denom, classLocalGRC20)
335+
recordAssetClass(cur, denom, classLocalGRC20)
308336
order := z.TokenOrderV2{
309337
Sender: []byte(sender),
310338
BaseToken: []byte(denom),
@@ -354,7 +382,7 @@ func TestTokenOrderTimeoutRefundsLocalGRC20(cur realm, t *testing.T) {
354382
sender := testAddress("g20-timeout-refund")
355383
denom, tok, ledger := registerImplTestGRC20(cur, "timeout_refund")
356384
uassert.NoError(t, ledger.Mint(zkgm.ProxyAddress(), 13))
357-
recordAssetClass(denom, classLocalGRC20)
385+
recordAssetClass(cur, denom, classLocalGRC20)
358386
order := z.TokenOrderV2{
359387
Sender: []byte(sender),
360388
BaseToken: []byte(denom),
@@ -1035,7 +1063,6 @@ func TestAckConstantConstructorsReturnIndependentInstances(cur realm, t *testing
10351063
}
10361064

10371065
func resetTokenOrderTest(cur realm) {
1038-
assetClassOf = bptree.NewBPTree32()
10391066
zkgm.UpdateImpl(cross(cur), zkgm.NewUpdateRequest(cross(cur), &ZkgmV1{}, nil))
10401067
}
10411068

gno.land/r/core/ibc/v1/apps/zkgm/z1_app_test.gno

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ func resetZkgmForTest() {
9696
bootstrapped = false
9797
paused = false
9898
escrowedGRC20 = bptree.NewBPTree32()
99+
assetClassOf = bptree.NewBPTree32()
99100
}
100101

101102
func TestAppChannelOpenInitVersion(cur realm, t *testing.T) {

gno.land/r/core/ibc/v1/apps/zkgm/z1_proxy_test.gno

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,20 @@ func TestReleaseNativeRejectsUnlistedCaller(cur realm, t *testing.T) {
235235
uassert.Equal(t, int64(7), bk.GetCoins(ProxyAddress()).AmountOf(denom))
236236
}
237237

238+
func TestReleaseNativeReturnsErrorForMissingEscrow(cur realm, t *testing.T) {
239+
resetZkgmForTest()
240+
cacheRealm(cur)
241+
implPath = "gno.land/r/not-this-realm"
242+
allowedImpls = []string{ProxyPkgPath()}
243+
244+
recipient := address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm")
245+
testing.SetRealm(testing.NewCodeRealm(ProxyPkgPath()))
246+
uassert.NotAborts(t, cur, func(cur realm) {
247+
err := ReleaseNative(cross(cur), "unfunded-native", recipient, 7)
248+
uassert.ErrorContains(t, err, "insufficient native escrow balance")
249+
})
250+
}
251+
238252
func TestEscrowGRC20AllowsAuthorizedImplCaller(cur realm, t *testing.T) {
239253
resetZkgmForTest()
240254
cacheRealm(cur)

0 commit comments

Comments
 (0)