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

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}