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