Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}