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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

merkledrop_test.gno

6.81 Kb · 225 lines
  1package merkledrop
  2
  3import (
  4	"chain"
  5	"chain/banker"
  6	"chain/runtime"
  7	"testing"
  8
  9	"gno.land/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/x/merkle/v0"
 10	"gno.land/p/nt/urequire/v0"
 11)
 12
 13const (
 14	alice   = address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // demo index 0, 100
 15	bob     = address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") // demo index 1, 250
 16	mallory = address("g1sqmuwtsrgd6t8y2n8f6z5xkltmpmcd64sx3vgg")
 17)
 18
 19func fund(n int64) {
 20	testing.IssueCoins(Address(), chain.NewCoins(chain.NewCoin(Denom, n)))
 21}
 22
 23func balanceOf(a address) int64 {
 24	return banker.NewReadonlyBanker().GetCoins(a).AmountOf(Denom)
 25}
 26
 27// Every seeded allocation must verify against the seeded root, and no other
 28// address or amount may.
 29func TestDemoDropVerifies(t *testing.T) {
 30	seed()
 31	if Total() != 4 || Root() == "" {
 32		t.Fatalf("seed left Total=%d Root=%q", Total(), Root())
 33	}
 34	for i, a := range demoAllocations {
 35		proof := DemoProof(i)
 36		if !Verify(i, a.Address, a.Amount, proof) {
 37			t.Errorf("allocation %d does not verify", i)
 38		}
 39		if Verify(i, mallory, a.Amount, proof) {
 40			t.Errorf("allocation %d verified for the wrong address", i)
 41		}
 42		if Verify(i, a.Address, a.Amount+1, proof) {
 43			t.Errorf("allocation %d verified for the wrong amount", i)
 44		}
 45		if i > 0 && Verify(i-1, a.Address, a.Amount, proof) {
 46			t.Errorf("allocation %d verified at the wrong index", i)
 47		}
 48	}
 49}
 50
 51// The leaf is the whole interface to an off-chain generator, so pin it.
 52func TestLeafEncoding(t *testing.T) {
 53	got := Leaf(3, alice, 1234)
 54	want := "gno.land/r/moul/x/daily/merkledrop/v1|3|g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5|1234"
 55	if got != want {
 56		t.Errorf("Leaf = %q, want %q", got, want)
 57	}
 58}
 59
 60func TestClaimPays(cur realm, t *testing.T) {
 61	seed()
 62	fund(1000)
 63	before := balanceOf(alice)
 64
 65	testing.SetRealm(testing.NewUserRealm(alice))
 66	Claim(cross(cur), 0, 100, DemoProof(0))
 67
 68	if got := balanceOf(alice) - before; got != 100 {
 69		t.Errorf("alice received %d ugnot, want 100", got)
 70	}
 71	if !HasClaimed(0) {
 72		t.Error("index 0 not marked claimed")
 73	}
 74	if ClaimedBy(0) != alice {
 75		t.Errorf("ClaimedBy(0) = %s, want alice", ClaimedBy(0))
 76	}
 77	if Paid() != 100 || Claims() != 1 {
 78		t.Errorf("Paid=%d Claims=%d, want 100 and 1", Paid(), Claims())
 79	}
 80	if Balance() != 900 {
 81		t.Errorf("drop balance %d, want 900", Balance())
 82	}
 83}
 84
 85func TestClaimRejects(cur realm, t *testing.T) {
 86	seed()
 87	fund(1000)
 88
 89	testing.SetRealm(testing.NewUserRealm(alice))
 90	urequire.AbortsContains(t, cur, "invalid proof", func() {
 91		Claim(cross(cur), 0, 999, DemoProof(0)) // wrong amount
 92	})
 93	urequire.AbortsContains(t, cur, "invalid proof", func() {
 94		Claim(cross(cur), 1, 250, DemoProof(1)) // bob's allocation, alice calling
 95	})
 96	urequire.AbortsContains(t, cur, "invalid proof", func() {
 97		Claim(cross(cur), 0, 100, "deadbeef") // malformed proof
 98	})
 99	urequire.AbortsContains(t, cur, "amount must be positive", func() {
100		Claim(cross(cur), 0, 0, DemoProof(0))
101	})
102
103	testing.SetRealm(testing.NewUserRealm(mallory))
104	urequire.AbortsContains(t, cur, "invalid proof", func() {
105		Claim(cross(cur), 0, 100, DemoProof(0)) // not in the tree at all
106	})
107
108	// Nothing above may have consumed an allocation.
109	if Claims() != 0 || Paid() != 0 {
110		t.Errorf("a rejected claim left state behind: Claims=%d Paid=%d", Claims(), Paid())
111	}
112}
113
114func TestDoubleClaimRejected(cur realm, t *testing.T) {
115	seed()
116	fund(1000)
117	testing.SetRealm(testing.NewUserRealm(bob))
118	Claim(cross(cur), 1, 250, DemoProof(1))
119	urequire.AbortsContains(t, cur, "already claimed", func() {
120		Claim(cross(cur), 1, 250, DemoProof(1))
121	})
122	if Claims() != 1 {
123		t.Errorf("Claims = %d, want 1", Claims())
124	}
125}
126
127// An underfunded drop must refuse rather than mark the allocation claimed and
128// leave the recipient with nothing to re-claim.
129func TestUnderfundedDropDoesNotBurnAnAllocation(cur realm, t *testing.T) {
130	seed()
131	fund(10) // less than alice's 100
132	testing.SetRealm(testing.NewUserRealm(alice))
133	urequire.AbortsContains(t, cur, "cannot cover", func() {
134		Claim(cross(cur), 0, 100, DemoProof(0))
135	})
136	if HasClaimed(0) {
137		t.Error("a refused claim still consumed the allocation")
138	}
139	fund(1000)
140	Claim(cross(cur), 0, 100, DemoProof(0))
141	if !HasClaimed(0) {
142		t.Error("the claim did not go through once funded")
143	}
144}
145
146func TestSetDropOwnerOnly(cur realm, t *testing.T) {
147	seed()
148	newRoot := merkle.New([][]byte{[]byte("only-leaf")}).RootHex()
149
150	testing.SetRealm(testing.NewUserRealm(mallory))
151	urequire.AbortsContains(t, cur, "not owner", func() {
152		SetDrop(cross(cur), newRoot, 1, 0)
153	})
154
155	testing.SetRealm(testing.NewUserRealm(Ownable.Owner()))
156	SetDrop(cross(cur), newRoot, 1, 0)
157	if Root() != newRoot || Total() != 1 {
158		t.Errorf("SetDrop left Root=%s Total=%d", Root(), Total())
159	}
160	if len(demo) != 0 {
161		t.Error("a real drop must not keep the seeded allocation list")
162	}
163	if Claims() != 0 || Paid() != 0 {
164		t.Error("SetDrop did not reset the claim ledger")
165	}
166}
167
168func TestSetDropRejectsBadInput(cur realm, t *testing.T) {
169	seed()
170	testing.SetRealm(testing.NewUserRealm(Ownable.Owner()))
171	good := merkle.New([][]byte{[]byte("x")}).RootHex()
172	urequire.AbortsContains(t, cur, "32 bytes of hex", func() { SetDrop(cross(cur), "zz", 1, 0) })
173	urequire.AbortsContains(t, cur, "32 bytes of hex", func() { SetDrop(cross(cur), "abcd", 1, 0) })
174	urequire.AbortsContains(t, cur, "total must be positive", func() { SetDrop(cross(cur), good, 0, 0) })
175}
176
177func TestClosingAndSweep(cur realm, t *testing.T) {
178	seed()
179	fund(1000)
180	owner := Ownable.Owner()
181	testing.SetRealm(testing.NewUserRealm(owner))
182
183	// A drop that never closes can never be swept: that is what setting a
184	// closing height buys.
185	urequire.AbortsContains(t, cur, "not closed yet", func() { Sweep(cross(cur)) })
186
187	closeAt := runtime.ChainHeight() + 10
188	root := merkle.New([][]byte{[]byte(Leaf(0, alice, 100))}).RootHex()
189	SetDrop(cross(cur), root, 1, closeAt)
190	if IsClosed() {
191		t.Fatal("drop closed before its height")
192	}
193	urequire.AbortsContains(t, cur, "not closed yet", func() { Sweep(cross(cur)) })
194
195	testing.SkipHeights(11)
196	if !IsClosed() {
197		t.Fatal("drop still open past its closing height")
198	}
199	testing.SetRealm(testing.NewUserRealm(alice))
200	urequire.AbortsContains(t, cur, "has closed", func() {
201		Claim(cross(cur), 0, 100, "")
202	})
203
204	ownerBefore := balanceOf(owner)
205	testing.SetRealm(testing.NewUserRealm(owner))
206	Sweep(cross(cur))
207	if Balance() != 0 {
208		t.Errorf("drop balance after sweep = %d, want 0", Balance())
209	}
210	if balanceOf(owner)-ownerBefore != 1000 {
211		t.Errorf("owner received %d, want 1000", balanceOf(owner)-ownerBefore)
212	}
213	seed()
214}
215
216func TestIndexKeyPadsForOrdering(t *testing.T) {
217	// ufmt has no width flags, so the padding is by hand. Unpadded keys sort
218	// "0","1","10","11","2" and Render loses the order past nine entries.
219	if indexKey(7) != "000007" || indexKey(123456) != "123456" {
220		t.Errorf("indexKey(7)=%q indexKey(123456)=%q", indexKey(7), indexKey(123456))
221	}
222	if !(indexKey(2) < indexKey(10)) {
223		t.Error("padded keys do not sort numerically")
224	}
225}