Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}