fomo3d_test.gno

9.68 Kb ยท 294 lines
  1package fomo3d
  2
  3import (
  4	"std"
  5	"testing"
  6
  7	"gno.land/p/demo/avl"
  8	"gno.land/p/demo/grc/grc721"
  9	"gno.land/p/demo/ownable"
 10	"gno.land/p/demo/testutils"
 11	"gno.land/p/demo/urequire"
 12)
 13
 14// Reset game state
 15func setupTestGame(t *testing.T) {
 16	gameState = GameState{
 17		StartBlock:   0,
 18		EndBlock:     0,
 19		LastKeyBlock: 0,
 20		LastBuyer:    "",
 21		Jackpot:      0,
 22		KeyPrice:     MIN_KEY_PRICE,
 23		TotalKeys:    0,
 24		Ended:        true,
 25		CurrentRound: 0,
 26		NextPot:      0,
 27		OwnerFee:     0,
 28	}
 29	players = avl.NewTree()
 30	Ownable = ownable.New()
 31}
 32
 33// Test ownership functionality
 34func TestOwnership(t *testing.T) {
 35	owner := testutils.TestAddress("owner")
 36	nonOwner := testutils.TestAddress("nonOwner")
 37
 38	// Set up initial owner
 39	std.TestSetOriginCaller(owner)
 40	std.TestSetOriginPkgAddress(owner)
 41	setupTestGame(t)
 42
 43	// Transfer ownership to nonOwner first to test ownership functions
 44	std.TestSetOriginCaller(owner)
 45	urequire.NotPanics(t, func() {
 46		Ownable.TransferOwnership(nonOwner)
 47	})
 48
 49	// Test fee accumulation
 50	StartGame()
 51	payment := MIN_KEY_PRICE * 10
 52	std.TestSetOriginCaller(owner)
 53	std.TestSetOriginSend(std.Coins{{"ugnot", payment}}, nil)
 54	std.TestIssueCoins(owner, std.Coins{{"ugnot", payment}})
 55	std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", payment}})
 56	BuyKeys()
 57
 58	// Verify fee accumulation
 59	_, fees := GetOwnerInfo()
 60	expectedFees := payment * OWNER_FEE_PERCENT / 100
 61	urequire.Equal(t, expectedFees, fees)
 62
 63	// Test unauthorized fee claim (using old owner)
 64	std.TestSetOriginCaller(owner)
 65	urequire.PanicsWithMessage(t, "ownable: caller is not owner", ClaimOwnerFee)
 66
 67	// Test authorized fee claim (using new owner)
 68	std.TestSetOriginCaller(nonOwner)
 69	initialBalance := std.NewBanker(std.BankerTypeRealmSend).GetCoins(nonOwner)
 70	std.TestIssueCoins(std.CurrentRealm().Address(), std.Coins{{"ugnot", expectedFees}})
 71	urequire.NotPanics(t, ClaimOwnerFee)
 72
 73	// Verify fees were claimed
 74	_, feesAfter := GetOwnerInfo()
 75	urequire.Equal(t, int64(0), feesAfter)
 76
 77	finalBalance := std.NewBanker(std.BankerTypeRealmSend).GetCoins(nonOwner)
 78	urequire.Equal(t, initialBalance.AmountOf("ugnot")+expectedFees, finalBalance.AmountOf("ugnot"))
 79}
 80
 81// Test full game flow
 82func TestFullGameFlow(t *testing.T) {
 83	setupTestGame(t)
 84
 85	player1 := testutils.TestAddress("player1")
 86	player2 := testutils.TestAddress("player2")
 87	player3 := testutils.TestAddress("player3")
 88
 89	// Test initial state
 90	urequire.Equal(t, int64(0), gameState.CurrentRound)
 91	urequire.Equal(t, MIN_KEY_PRICE, gameState.KeyPrice)
 92	urequire.Equal(t, true, gameState.Ended)
 93
 94	// Start game
 95	urequire.NotPanics(t, StartGame)
 96	urequire.Equal(t, false, gameState.Ended)
 97	urequire.Equal(t, std.ChainHeight(), gameState.StartBlock)
 98	urequire.Equal(t, int64(1), gameState.CurrentRound)
 99
100	t.Run("buying keys", func(t *testing.T) {
101		// Test insufficient payment
102		std.TestSetOriginCaller(player1)
103		std.TestIssueCoins(player1, std.Coins{{"ugnot", MIN_KEY_PRICE - 1}})
104		std.TestSetOriginSend(std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}, nil)
105		std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", MIN_KEY_PRICE - 1}})
106		urequire.PanicsWithMessage(t, ErrInsufficientPayment.Error(), BuyKeys)
107
108		// Test successful key purchase
109		payment := MIN_KEY_PRICE * 3
110		std.TestSetOriginSend(std.Coins{{"ugnot", payment}}, nil)
111		std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", payment}})
112
113		currentBlock := std.ChainHeight()
114		urequire.NotPanics(t, BuyKeys)
115
116		// Verify time extension
117		_, endBlock, _, _, _, _, _, _, _, _ := GetGameState()
118		urequire.Equal(t, currentBlock+TIME_EXTENSION, endBlock)
119
120		// Verify player state
121		keys, dividends := GetPlayerInfo(player1.String())
122
123		urequire.Equal(t, int64(3), keys)
124		urequire.Equal(t, int64(0), dividends)
125		urequire.Equal(t, player1, gameState.LastBuyer)
126
127		// Verify game state
128		_, endBlock, _, buyer, pot, price, keys, isEnded, nextPot, round := GetGameState()
129		urequire.Equal(t, player1, buyer)
130		urequire.Equal(t, int64(3), keys)
131		urequire.Equal(t, false, isEnded)
132
133		urequire.Equal(t, payment*JACKPOT_PERCENT/100, pot)
134
135		// Verify owner fee
136		_, ownerFees := GetOwnerInfo()
137		urequire.Equal(t, payment*OWNER_FEE_PERCENT/100, ownerFees)
138	})
139
140	t.Run("dividend distribution and claiming", func(t *testing.T) {
141		// Player 2 buys keys
142		std.TestSetOriginCaller(player2)
143		payment := gameState.KeyPrice * 2 // Buy 2 keys using current keyPrice
144		std.TestSetOriginSend(std.Coins{{"ugnot", payment}}, nil)
145		std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", payment}})
146		urequire.NotPanics(t, BuyKeys)
147
148		// Check player1 received dividends
149		keys1, dividends1 := GetPlayerInfo(player1.String())
150
151		urequire.Equal(t, int64(3), keys1)
152		expectedDividends := payment * DIVIDENDS_PERCENT / 100 * 3 / gameState.TotalKeys
153		urequire.Equal(t, expectedDividends, dividends1)
154
155		// Test claiming dividends
156		{
157			// Player1 claims dividends
158			std.TestSetOriginCaller(player1)
159			initialBalance := std.NewBanker(std.BankerTypeRealmSend).GetCoins(player1)
160			urequire.NotPanics(t, ClaimDividends)
161
162			// Verify dividends were claimed
163			_, dividendsAfter := GetPlayerInfo(player1.String())
164			urequire.Equal(t, int64(0), dividendsAfter)
165
166			lastBuyerBalance := std.NewBanker(std.BankerTypeRealmSend).GetCoins(player1)
167			urequire.Equal(t, initialBalance.AmountOf("ugnot")+expectedDividends, lastBuyerBalance.AmountOf("ugnot"))
168		}
169	})
170
171	t.Run("game ending", func(t *testing.T) {
172		// Try ending too early
173		urequire.PanicsWithMessage(t, ErrGameNotInProgress.Error(), EndGame)
174
175		// Skip to end of current time window
176		currentEndBlock := gameState.EndBlock
177		std.TestSkipHeights(currentEndBlock - std.ChainHeight() + 1)
178
179		// End game successfully
180		urequire.NotPanics(t, EndGame)
181		urequire.Equal(t, true, gameState.Ended)
182		urequire.Equal(t, int64(1), gameState.CurrentRound)
183
184		// Verify winner received jackpot
185		lastBuyerBalance := std.NewBanker(std.BankerTypeRealmSend).GetCoins(gameState.LastBuyer)
186		urequire.Equal(t, gameState.Jackpot, lastBuyerBalance.AmountOf("ugnot"))
187
188		// Verify NFT was minted to winner
189		balance, err := BalanceOf(gameState.LastBuyer)
190		urequire.NoError(t, err)
191		urequire.Equal(t, uint64(1), balance)
192
193		// Check NFT metadata
194		tokenID := grc721.TokenID("1")
195		metadata, err := TokenMetadata(tokenID)
196
197		urequire.NoError(t, err)
198		urequire.Equal(t, "Fomo3D Winner - Round #1", metadata.Name)
199	})
200
201	// Test new round
202	t.Run("new round", func(t *testing.T) {
203		// Calculate expected next pot from previous round
204		payment1 := MIN_KEY_PRICE * 3
205		// After buying 3 keys, price increased by 3% (1% per key)
206		secondKeyPrice := MIN_KEY_PRICE + (MIN_KEY_PRICE * 3 / 100)
207		payment2 := secondKeyPrice * 2
208		expectedNextPot := (payment1 * NEXT_ROUND_POT / 100) + (payment2 * NEXT_ROUND_POT / 100)
209
210		// Start new round
211		urequire.NotPanics(t, StartGame)
212		urequire.Equal(t, false, gameState.Ended)
213		urequire.Equal(t, int64(2), gameState.CurrentRound)
214
215		start, end, last, buyer, pot, price, keys, isEnded, nextPot, round := GetGameState()
216		urequire.Equal(t, int64(2), round)
217		urequire.Equal(t, expectedNextPot, pot)
218		urequire.Equal(t, int64(0), nextPot)
219	})
220}
221
222// Test individual components
223func TestStartGame(t *testing.T) {
224	setupTestGame(t)
225
226	// Test starting first game
227	urequire.NotPanics(t, StartGame)
228	urequire.Equal(t, false, gameState.Ended)
229	urequire.Equal(t, std.ChainHeight(), gameState.StartBlock)
230
231	// Test cannot start while game in progress
232	urequire.PanicsWithMessage(t, ErrGameInProgress.Error(), StartGame)
233}
234
235func TestBuyKeys(t *testing.T) {
236	setupTestGame(t)
237	StartGame()
238
239	player := testutils.TestAddress("player")
240	std.TestSetOriginCaller(player)
241
242	// Test invalid coin denomination
243	std.TestIssueCoins(player, std.Coins{{"invalid", MIN_KEY_PRICE}})
244	std.TestSetOriginSend(std.Coins{{"invalid", MIN_KEY_PRICE}}, nil)
245	std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"invalid", MIN_KEY_PRICE}})
246	urequire.PanicsWithMessage(t, ErrInvalidPayment.Error(), BuyKeys)
247
248	// Test multiple coin types
249	std.TestIssueCoins(player, std.Coins{{"ugnot", MIN_KEY_PRICE}, {"other", 100}})
250	std.TestSetOriginSend(std.Coins{{"ugnot", MIN_KEY_PRICE}, {"other", 100}}, nil)
251	std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", MIN_KEY_PRICE}, {"other", 100}})
252	urequire.PanicsWithMessage(t, ErrInvalidPayment.Error(), BuyKeys)
253
254	// Test insufficient payment
255	std.TestIssueCoins(player, std.Coins{{"ugnot", MIN_KEY_PRICE - 1}})
256	std.TestSetOriginSend(std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}, nil)
257	std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", MIN_KEY_PRICE - 1}})
258	urequire.PanicsWithMessage(t, ErrInsufficientPayment.Error(), BuyKeys)
259
260	// Test successful purchase
261	std.TestIssueCoins(player, std.Coins{{"ugnot", MIN_KEY_PRICE * 2}})
262	std.TestSetOriginSend(std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}, nil)
263	std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", MIN_KEY_PRICE * 2}})
264	urequire.NotPanics(t, BuyKeys)
265}
266
267func TestClaimDividends(t *testing.T) {
268	setupTestGame(t)
269	StartGame()
270
271	player := testutils.TestAddress("player")
272	std.TestSetOriginCaller(player)
273
274	// Test claiming with no dividends
275	urequire.PanicsWithMessage(t, ErrNoDividendsToClaim.Error(), ClaimDividends)
276
277	// Setup player with dividends
278	std.TestIssueCoins(player, std.Coins{{"ugnot", MIN_KEY_PRICE}})
279	std.TestSetOriginSend(std.Coins{{"ugnot", MIN_KEY_PRICE}}, nil)
280	std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", MIN_KEY_PRICE}})
281	BuyKeys()
282
283	// Have another player buy to generate dividends
284	player2 := testutils.TestAddress("player2")
285	std.TestSetOriginCaller(player2)
286	std.TestIssueCoins(player2, std.Coins{{"ugnot", MIN_KEY_PRICE * 2}})
287	std.TestSetOriginSend(std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}, nil)
288	std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", MIN_KEY_PRICE * 2}})
289	BuyKeys()
290
291	// Test successful claim
292	std.TestSetOriginCaller(player)
293	urequire.NotPanics(t, ClaimDividends)
294}