store_test.gno
4.91 Kb · 144 lines
1package transfer
2
3import (
4 "chain"
5 "testing"
6
7 "gno.land/p/nt/bptree/v0"
8 "gno.land/p/nt/uassert/v0"
9 "gno.land/p/nt/urequire/v0"
10)
11
12func TestGRC20AliasRoundTrip(t *testing.T) {
13 testCases := []struct {
14 name string
15 key string
16 expAlias string
17 }{
18 {
19 "simple path without slug",
20 "gno.land/r/demo/foo",
21 "gno.land:r:demo:foo",
22 },
23 {
24 "path with slug",
25 "gno.land/r/demo/foo.FOO",
26 "gno.land:r:demo:foo.FOO",
27 },
28 {
29 "path with alphanumeric slug",
30 "gno.land/r/demo/foo.Bar123",
31 "gno.land:r:demo:foo.Bar123",
32 },
33 }
34 for _, tc := range testCases {
35 t.Run(tc.name, func(t *testing.T) {
36 alias := GRC20Alias(tc.key)
37 uassert.Equal(t, tc.expAlias, alias)
38
39 got := resolveGRC20Alias(alias)
40 uassert.Equal(t, tc.key, got)
41 })
42 }
43}
44
45func TestIsGRC20Alias(t *testing.T) {
46 testCases := []struct {
47 name string
48 denom string
49 expect bool
50 }{
51 {"grc20 alias", "gno.land:r:demo:foo.FOO", true},
52 {"native denom", "ugnot", false},
53 {"ibc denom", "ibc/ABC123", false},
54 {"gno path not aliased", "gno.land/r/demo/foo", false},
55 }
56 for _, tc := range testCases {
57 t.Run(tc.name, func(t *testing.T) {
58 uassert.Equal(t, tc.expect, isGRC20Alias(tc.denom))
59 })
60 }
61}
62
63func TestEscrowIsolationPerClient(t *testing.T) {
64 // Reset the escrow tree so the test is independent of ordering.
65 totalEscrow = bptree.NewBPTree32()
66
67 // clientA escrows 100ugnot; clientB escrows nothing. The checks below
68 // prove the accounting was recorded and isolated per client.
69 addEscrowForClient("07-tendermint-1", chain.NewCoin("ugnot", 100))
70
71 // clientA can release its own escrow.
72 coin := chain.NewCoin("ugnot", 30)
73 urequire.NoError(t, checkEscrowForClient("07-tendermint-1", coin))
74 debitEscrowForClient("07-tendermint-1", coin)
75
76 // clientB must NOT be able to release funds clientA locked: this is the
77 // shared-escrow drain defense.
78 err := checkEscrowForClient("07-tendermint-2", chain.NewCoin("ugnot", 10))
79 urequire.ErrorContains(t, err, "insufficient escrow for client 07-tendermint-2")
80
81 // clientA cannot release more than it escrowed (70 remaining after the 30
82 // release above).
83 err = checkEscrowForClient("07-tendermint-1", chain.NewCoin("ugnot", 71))
84 urequire.ErrorContains(t, err, "insufficient escrow for client 07-tendermint-1 denom ugnot: have 70")
85
86 // A denom clientA never escrowed is rejected even though clientA has ugnot.
87 err = checkEscrowForClient("07-tendermint-1", chain.NewCoin("uatom", 1))
88 urequire.ErrorContains(t, err, "insufficient escrow for client 07-tendermint-1 denom uatom")
89}
90
91func TestEscrowAccumulatesPerClientDenom(t *testing.T) {
92 totalEscrow = bptree.NewBPTree32()
93
94 // Two sends over the same client accumulate.
95 addEscrowForClient("07-tendermint-1", chain.NewCoin("ugnot", 40))
96 addEscrowForClient("07-tendermint-1", chain.NewCoin("ugnot", 60))
97
98 // A single release of the full accumulated amount must succeed.
99 coin := chain.NewCoin("ugnot", 100)
100 urequire.NoError(t, checkEscrowForClient("07-tendermint-1", coin))
101 debitEscrowForClient("07-tendermint-1", coin)
102
103 // The pool is now empty for that (client, denom).
104 err := checkEscrowForClient("07-tendermint-1", chain.NewCoin("ugnot", 1))
105 urequire.ErrorContains(t, err, "insufficient escrow for client 07-tendermint-1 denom ugnot: have 0")
106}
107
108// TestCheckEscrowForClientDoesNotMutate verifies the check is read-only. The
109// unescrow path relies on this: OnRecvPacket returning Failure does not roll
110// back store writes, so a failed transfer after a successful check must leave
111// the accounting intact.
112func TestCheckEscrowForClientDoesNotMutate(t *testing.T) {
113 totalEscrow = bptree.NewBPTree32()
114 addEscrowForClient("07-tendermint-1", chain.NewCoin("ugnot", 100))
115
116 // A passing check must not change the balance.
117 urequire.NoError(t, checkEscrowForClient("07-tendermint-1", chain.NewCoin("ugnot", 30)))
118 t1 := escrowForClient("07-tendermint-1")
119 uassert.Equal(t, int64(100), t1.Get("ugnot").(chain.Coin).Amount)
120
121 // A failing check must not change the balance either.
122 err := checkEscrowForClient("07-tendermint-1", chain.NewCoin("ugnot", 101))
123 uassert.ErrorContains(t, err, "have 100")
124 uassert.Equal(t, int64(100), t1.Get("ugnot").(chain.Coin).Amount)
125
126 // And a check against the wrong client must not create an empty entry for it.
127 err = checkEscrowForClient("07-tendermint-2", chain.NewCoin("ugnot", 1))
128 uassert.ErrorContains(t, err, "insufficient escrow for client 07-tendermint-2")
129 uassert.Nil(t, totalEscrow.Get("07-tendermint-2"))
130}
131
132// TestDebitEscrowForClientMatchesCheck verifies debit applies the authorized
133// subtraction without re-checking.
134func TestDebitEscrowForClientMatchesCheck(t *testing.T) {
135 totalEscrow = bptree.NewBPTree32()
136 addEscrowForClient("07-tendermint-1", chain.NewCoin("ugnot", 100))
137 coin := chain.NewCoin("ugnot", 40)
138
139 urequire.NoError(t, checkEscrowForClient("07-tendermint-1", coin))
140 debitEscrowForClient("07-tendermint-1", coin)
141
142 t1 := escrowForClient("07-tendermint-1")
143 uassert.Equal(t, int64(60), t1.Get("ugnot").(chain.Coin).Amount)
144}