treasury_test.gno
9.62 Kb · 341 lines
1package test
2
3import (
4 "chain"
5 "chain/banker"
6 "chain/runtime/unsafe"
7 "sort"
8 "strings"
9 "testing"
10
11 "gno.land/p/demo/tokens/grc20"
12 "gno.land/p/nt/fqname/v0"
13 "gno.land/p/nt/seqid/v0"
14 "gno.land/p/nt/testutils/v0"
15 trs_pkg "gno.land/p/nt/treasury/v0"
16 "gno.land/p/nt/uassert/v0"
17
18 "gno.land/r/demo/defi/grc20reg"
19 "gno.land/r/gov/dao"
20 "gno.land/r/gov/dao/v3/impl"
21 "gno.land/r/gov/dao/v3/treasury"
22)
23
24// cur is a zero-value realm used as a placeholder when forwarding to
25// uassert/urequire dispatch helpers that gained an `rlm realm` param.
26// These tests pass `func()` callbacks (no crossing inside the callback),
27// so rlm is ignored — a nil realm here is safe.
28var cur realm
29var nextTokenID seqid.ID
30var (
31 user1Addr = testutils.TestAddress("g1user1")
32 user2Addr = testutils.TestAddress("g1user2")
33 treasuryAddr = chain.PackageAddress("gno.land/r/gov/dao/v3/treasury")
34 allowedRealm = testing.NewCodeRealm("gno.land/r/test/allowed")
35 notAllowedRealm = testing.NewCodeRealm("gno.land/r/test/notallowed")
36 mintAmount = int64(1000)
37)
38
39// Define a dummy trs_pkg.Payment type for testing purposes.
40type dummyPayment struct {
41 bankerID string
42 str string
43}
44
45var _ trs_pkg.Payment = (*dummyPayment)(nil)
46
47func (dp *dummyPayment) BankerID() string { return dp.bankerID }
48func (dp *dummyPayment) String() string { return dp.str }
49
50func init(cur realm) {
51 // Register allowed Realm path.
52 dao.UpdateImpl(cross(cur), dao.NewUpdateRequest(impl.NewGovDAO(), []string{allowedRealm.PkgPath()}))
53}
54
55func ugnotCoins(t *testing.T, amount int64) chain.Coins {
56 t.Helper()
57
58 // Create a new coin with the ugnot denomination.
59 return chain.NewCoins(chain.NewCoin("ugnot", amount))
60}
61
62func ugnotBalance(t *testing.T, addr address) int64 {
63 t.Helper()
64
65 // Get the balance of ugnot coins for the given address.
66 banker_ := banker.NewReadonlyBanker()
67 coins := banker_.GetCoins(addr)
68
69 return coins.AmountOf("ugnot")
70}
71
72// Define a keyedToken type to hold the token and its key.
73type keyedToken struct {
74 key string
75 token *grc20.Token
76}
77
78func registerGRC20Tokens(_ int, rlm realm, t *testing.T, tokenNames []string, toMint address) []keyedToken {
79 t.Helper()
80
81 var (
82 keyedTokens = make([]keyedToken, 0, len(tokenNames))
83 keys = make([]string, 0, len(tokenNames))
84 )
85
86 for _, name := range tokenNames {
87 // Create the token.
88 symbol := strings.ToUpper(name)
89 token, ledger := grc20.NewToken(name, symbol, 0, nextTokenID.Next(), rlm)
90
91 // Register the token.
92 grc20reg.Register(cross(rlm), token, symbol)
93
94 // Mint tokens to the specified address.
95 ledger.Mint(toMint, mintAmount)
96
97 // Add the token and key to the lists.
98 key := fqname.Construct(unsafe.CurrentRealm().PkgPath(), symbol)
99 keyedTokens = append(keyedTokens, keyedToken{key: key, token: token})
100 keys = append(keys, key)
101 }
102
103 // Set the token keys in the treasury.
104 treasury.SetTokenKeys(cross(rlm), keys)
105
106 return keyedTokens
107}
108
109func TestAllowedDAOs(cur realm, t *testing.T) {
110 // Set the current Realm to the not allowed one.
111 testing.SetRealm(notAllowedRealm)
112
113 // Define a dummy payment to test sending.
114 dummyP := &dummyPayment{bankerID: "Dummy"}
115
116 // Try to send, it should abort because the Realm is not allowed.
117 uassert.AbortsWithMessage(
118 t, cur,
119 "this Realm is not allowed to send payment: "+notAllowedRealm.PkgPath(),
120 func() { treasury.Send(cross(cur), dummyP) },
121 )
122
123 // Set the current Realm to the allowed one.
124 testing.SetRealm(allowedRealm)
125
126 // Try to send, it should not abort because the Realm is allowed,
127 // but because the dummy banker ID is not registered.
128 uassert.AbortsWithMessage(
129 t, cur,
130 "banker not found: "+dummyP.BankerID(),
131 func() { treasury.Send(cross(cur), dummyP) },
132 )
133}
134
135func TestRegisteredBankers(t *testing.T) {
136 // Set the current Realm to the allowed one.
137 testing.SetRealm(allowedRealm)
138
139 // Define the expected banker IDs.
140 expectedBankerIDs := []string{
141 trs_pkg.CoinsBanker{}.ID(),
142 trs_pkg.GRC20Banker{}.ID(),
143 }
144
145 // Get the registered bankers from the treasury and compare their lengths.
146 registered := treasury.ListBankerIDs()
147 uassert.Equal(t, len(registered), len(expectedBankerIDs))
148
149 // The treasury-returned slice is foreign-readonly; copy locally
150 // before sorting in place.
151 registeredBankerIDs := append([]string(nil), registered...)
152
153 // Sort both slices then compare them.
154 sort.StringSlice(expectedBankerIDs).Sort()
155 sort.StringSlice(registeredBankerIDs).Sort()
156
157 for i := range expectedBankerIDs {
158 uassert.Equal(t, expectedBankerIDs[i], registeredBankerIDs[i])
159 }
160
161 // Test HasBanker method.
162 for _, bankerID := range expectedBankerIDs {
163 uassert.True(t, treasury.HasBanker(bankerID))
164 }
165 uassert.False(t, treasury.HasBanker("UnknownBankerID"))
166
167 // Test Address method.
168 for _, bankerID := range expectedBankerIDs {
169 // The two bankers used for now should have the treasury Realm address.
170 uassert.Equal(t, treasury.Address(bankerID), treasuryAddr.String())
171 }
172}
173
174func TestSendGRC20Payment(cur realm, t *testing.T) {
175 // Set the current Realm to the allowed one.
176 testing.SetRealm(allowedRealm)
177
178 // Try to send a GRC20 payment with a not registered token, it should abort.
179 uassert.AbortsWithMessage(
180 t, cur,
181 "failed to send payment: GRC20 token not found: UNKNOW",
182 func() {
183 treasury.Send(cross(cur), trs_pkg.NewGRC20Payment("UNKNOW", 100, user1Addr))
184 },
185 )
186
187 // Create 3 GRC20 tokens and register them.
188 keyedTokens := registerGRC20Tokens(
189 0, cur,
190 t,
191 []string{"TestToken0", "TestToken1", "TestToken2"},
192 treasuryAddr,
193 )
194
195 const txAmount = 42
196
197 // For each token-user pair.
198 for i, userAddr := range []address{user1Addr, user2Addr} {
199 for _, keyed := range keyedTokens {
200 // Check that the treasury has the expected balance before sending.
201 uassert.Equal(t, keyed.token.BalanceOf(treasuryAddr), mintAmount-int64(txAmount*i))
202
203 // Check that the user has no balance before sending.
204 uassert.Equal(t, keyed.token.BalanceOf(userAddr), int64(0))
205
206 // Try to send a GRC20 payment with a registered token, it should not abort.
207 uassert.NotAborts(t, cur, func() {
208 treasury.Send(
209 cross(cur),
210 trs_pkg.NewGRC20Payment(
211 keyed.key,
212 txAmount,
213 userAddr,
214 ),
215 )
216 })
217
218 // Check that the user has the expected balance after sending.
219 uassert.Equal(t, keyed.token.BalanceOf(userAddr), int64(txAmount))
220
221 // Check that the treasury has the expected balance after sending.
222 uassert.Equal(t, keyed.token.BalanceOf(treasuryAddr), mintAmount-int64(txAmount*(i+1)))
223 }
224 }
225
226 // Get the GRC20Banker ID.
227 grc20BankerID := trs_pkg.GRC20Banker{}.ID()
228
229 // Test Balances method for the GRC20Banker.
230 balances := treasury.Balances(grc20BankerID)
231 uassert.Equal(t, len(balances), len(keyedTokens))
232
233 compared := 0
234 for _, balance := range balances {
235 for _, keyed := range keyedTokens {
236 if balance.Denom == keyed.key {
237 uassert.Equal(t, balance.Amount, keyed.token.BalanceOf(treasuryAddr))
238 compared++
239 }
240 }
241 }
242 uassert.Equal(t, compared, len(keyedTokens))
243
244 // Check the history of the GRC20Banker.
245 history := treasury.History(grc20BankerID, 1, 10)
246 uassert.Equal(t, len(history), 6)
247
248 // Try to send a dummy payment with the GRC20 banker ID, it should abort.
249 uassert.AbortsWithMessage(
250 t, cur,
251 "failed to send payment: invalid payment type",
252 func() {
253 treasury.Send(cross(cur), &dummyPayment{bankerID: grc20BankerID})
254 },
255 )
256
257 // Try to send a GRC20 payment without enough balance, it should abort.
258 uassert.AbortsWithMessage(
259 t, cur,
260 "failed to send payment: insufficient balance",
261 func() {
262 treasury.Send(
263 cross(cur),
264 trs_pkg.NewGRC20Payment(
265 keyedTokens[0].key,
266 mintAmount*42, // Try to send more than the treasury has.
267 user1Addr,
268 ),
269 )
270 },
271 )
272
273 // Check the history of the GRC20Banker.
274 history = treasury.History(grc20BankerID, 1, 10)
275 uassert.Equal(t, len(history), 6)
276}
277
278func TestSendCoinPayment(cur realm, t *testing.T) {
279 // Set the current Realm to the allowed one.
280 testing.SetRealm(allowedRealm)
281
282 // Issue initial ugnot coins to the treasury address.
283 testing.IssueCoins(treasuryAddr, ugnotCoins(t, mintAmount))
284
285 // Get the CoinsBanker ID.
286 bankerID := trs_pkg.CoinsBanker{}.ID()
287
288 // Define helper function to check balances and history.
289 var (
290 expectedTreasuryBalance = mintAmount
291 expectedUser1Balance = int64(0)
292 expectedUser2Balance = int64(0)
293 expectedHistoryLen = 0
294 checkHistoryAndBalances = func() {
295 t.Helper()
296
297 uassert.Equal(t, ugnotBalance(t, treasuryAddr), expectedTreasuryBalance)
298 uassert.Equal(t, ugnotBalance(t, user1Addr), expectedUser1Balance)
299 uassert.Equal(t, ugnotBalance(t, user2Addr), expectedUser2Balance)
300
301 // Check treasury.Balances returned value.
302 balances := treasury.Balances(bankerID)
303 uassert.Equal(t, len(balances), 1)
304 uassert.Equal(t, balances[0].Denom, "ugnot")
305 uassert.Equal(t, balances[0].Amount, expectedTreasuryBalance)
306
307 // Check treasury.History returned value.
308 history := treasury.History(bankerID, 1, expectedHistoryLen+1)
309 uassert.Equal(t, len(history), expectedHistoryLen)
310 }
311 )
312
313 // Check initial balances and history.
314 checkHistoryAndBalances()
315
316 const txAmount = int64(42)
317
318 // Treasury send coins.
319 for i := int64(0); i < 3; i++ {
320 // Send ugnot coins to user1 and user2.
321 uassert.NotAborts(t, cur, func() {
322 treasury.Send(
323 cross(cur),
324 trs_pkg.NewCoinsPayment(ugnotCoins(t, txAmount), user1Addr),
325 )
326 treasury.Send(
327 cross(cur),
328 trs_pkg.NewCoinsPayment(ugnotCoins(t, txAmount), user2Addr),
329 )
330 })
331
332 // Update expected balances and history length.
333 expectedTreasuryBalance = mintAmount - txAmount*2*(i+1)
334 expectedUser1Balance = txAmount * (i + 1)
335 expectedUser2Balance = expectedUser1Balance
336 expectedHistoryLen = int(2 * (i + 1))
337
338 // Check balances and history after sending.
339 checkHistoryAndBalances()
340 }
341}