gn-contract 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.openzeppelin/bsc-testnet.json +1847 -0
- package/.prettierrc +15 -0
- package/README.md +14 -0
- package/constants/getter.ts +4 -0
- package/constants/rootAddress.ts +22 -0
- package/contracts/CROWD.sol +32 -0
- package/contracts/CUSDT.sol +32 -0
- package/contracts/NFT.sol +499 -0
- package/contracts/NFTFounder.sol +254 -0
- package/contracts/NFTGenesis.sol +337 -0
- package/contracts/Network.sol +503 -0
- package/contracts/Swap.sol +86 -0
- package/contracts/USDT.sol +32 -0
- package/contracts/lib/AccessControl.sol +35 -0
- package/contracts/lib/NFTGetter.sol +60 -0
- package/contracts/lib/NFTHelpers.sol +111 -0
- package/contracts/lib/ValhallaBlackList.sol +18 -0
- package/contracts/lib/ValhallaPool.sol +44 -0
- package/hardhat.config.ts +44 -0
- package/package.json +50 -0
- package/scripts/DeployNetwork.ts +123 -0
- package/scripts/DeploySwap.ts +57 -0
- package/scripts/DeployUSDT.ts +16 -0
- package/test/Swap.test.ts +182 -0
- package/test/lib/initializer.ts +193 -0
- package/test/nft_genesis.test.ts +399 -0
- package/test/nft_purchase.test.ts +210 -0
- package/test/rank_distribution.test.ts +142 -0
- package/test/registration.test.ts +267 -0
- package/test/registration_reward.test.ts +114 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { fromBn, toBn } from "evm-bn";
|
|
3
|
+
import { initialize, getState, getAccounts } from "./lib/initializer";
|
|
4
|
+
import { BigNumber } from "ethers";
|
|
5
|
+
|
|
6
|
+
const cardsRegisteredProps = [
|
|
7
|
+
{
|
|
8
|
+
price: 5000,
|
|
9
|
+
percentage: 40,
|
|
10
|
+
maxMinted: 2000,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
price: 2000,
|
|
14
|
+
percentage: 20,
|
|
15
|
+
maxMinted: 1500,
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
describe("Valhalla NFT Genesis", function () {
|
|
20
|
+
before(async () => {
|
|
21
|
+
await initialize();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("Register NFT Variants", async () => {
|
|
25
|
+
const { nftGenesis, usdt } = getState();
|
|
26
|
+
|
|
27
|
+
// Use sequential loop to guarantee order (ID 0, ID 1)
|
|
28
|
+
for (const c of cardsRegisteredProps) {
|
|
29
|
+
await (
|
|
30
|
+
await nftGenesis.addNft(c.price, c.percentage, c.maxMinted)
|
|
31
|
+
).wait();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const cardsRegisteredAmount = await nftGenesis.amountNftTypes();
|
|
35
|
+
expect(cardsRegisteredAmount).to.eq(2);
|
|
36
|
+
|
|
37
|
+
const decimals = await usdt.decimals(); // Should be 18
|
|
38
|
+
|
|
39
|
+
// Verify Card 0
|
|
40
|
+
const card0 = await nftGenesis.cardMap(0);
|
|
41
|
+
expect(card0.price).to.eq(
|
|
42
|
+
toBn(String(cardsRegisteredProps[0].price), decimals),
|
|
43
|
+
);
|
|
44
|
+
expect(card0.genesisPercentage).to.eq(cardsRegisteredProps[0].percentage);
|
|
45
|
+
expect(card0.maxMinted).to.eq(cardsRegisteredProps[0].maxMinted);
|
|
46
|
+
|
|
47
|
+
// Verify Card 1
|
|
48
|
+
const card1 = await nftGenesis.cardMap(1);
|
|
49
|
+
expect(card1.price).to.eq(
|
|
50
|
+
toBn(String(cardsRegisteredProps[1].price), decimals),
|
|
51
|
+
);
|
|
52
|
+
expect(card1.genesisPercentage).to.eq(cardsRegisteredProps[1].percentage);
|
|
53
|
+
expect(card1.maxMinted).to.eq(cardsRegisteredProps[1].maxMinted);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should be able to purchase nft Genesis", async () => {
|
|
57
|
+
const { usdt, nftGenesis } = getState();
|
|
58
|
+
const [admin, buyer] = await getAccounts(0, 2);
|
|
59
|
+
const price = toBn("5000", 18);
|
|
60
|
+
|
|
61
|
+
await usdt.connect(admin).transfer(buyer.address, price);
|
|
62
|
+
|
|
63
|
+
const balanceBefore = await usdt.balanceOf(buyer.address);
|
|
64
|
+
const nftBefore = await nftGenesis.balanceOf(buyer.address);
|
|
65
|
+
|
|
66
|
+
const approveTx = await usdt
|
|
67
|
+
.connect(buyer)
|
|
68
|
+
.approve(nftGenesis.address, price);
|
|
69
|
+
await approveTx.wait();
|
|
70
|
+
|
|
71
|
+
const tx = await nftGenesis.connect(buyer).buyMultipleNFT(0, 1);
|
|
72
|
+
await tx.wait();
|
|
73
|
+
|
|
74
|
+
const nftAfter = await nftGenesis.balanceOf(buyer.address);
|
|
75
|
+
const balanceAfter = await usdt.balanceOf(buyer.address);
|
|
76
|
+
const tokenId = await nftGenesis.tokenOfOwnerByIndex(buyer.address, 0);
|
|
77
|
+
const uri = await nftGenesis.tokenURI(tokenId);
|
|
78
|
+
const getMintedNFT = await nftGenesis.nftGenesis(buyer.address, 0);
|
|
79
|
+
|
|
80
|
+
// URI check
|
|
81
|
+
expect(uri).to.eq("https://globalnetwork.finance/api/image/genesis/0");
|
|
82
|
+
|
|
83
|
+
// Balance checks using BigNumber
|
|
84
|
+
expect(getMintedNFT.ownedNfts).to.eq(1);
|
|
85
|
+
expect(nftAfter.sub(nftBefore)).to.eq(1);
|
|
86
|
+
|
|
87
|
+
// Check USDT balance deduction
|
|
88
|
+
expect(balanceBefore.sub(balanceAfter)).to.eq(price);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should be able to purchase multiple nft Genesis", async () => {
|
|
92
|
+
const { usdt, nftGenesis } = getState();
|
|
93
|
+
const [admin, _, buyer] = await getAccounts(0, 3);
|
|
94
|
+
const amount = 3;
|
|
95
|
+
const pricePerUnit = toBn("5000", 18);
|
|
96
|
+
const totalPrice = pricePerUnit.mul(amount);
|
|
97
|
+
|
|
98
|
+
await usdt.connect(admin).transfer(buyer.address, totalPrice);
|
|
99
|
+
|
|
100
|
+
const balanceBefore = await usdt.balanceOf(buyer.address);
|
|
101
|
+
const nftBefore = await nftGenesis.balanceOf(buyer.address);
|
|
102
|
+
|
|
103
|
+
const approveTx = await usdt
|
|
104
|
+
.connect(buyer)
|
|
105
|
+
.approve(nftGenesis.address, totalPrice);
|
|
106
|
+
await approveTx.wait();
|
|
107
|
+
|
|
108
|
+
const tx = await nftGenesis.connect(buyer).buyMultipleNFT(0, amount);
|
|
109
|
+
await tx.wait();
|
|
110
|
+
|
|
111
|
+
const nftAfter = await nftGenesis.balanceOf(buyer.address);
|
|
112
|
+
const balanceAfter = await usdt.balanceOf(buyer.address);
|
|
113
|
+
const getMintedNFT = await nftGenesis.nftGenesis(buyer.address, 0);
|
|
114
|
+
|
|
115
|
+
expect(getMintedNFT.ownedNfts).to.eq(amount);
|
|
116
|
+
expect(nftAfter.sub(nftBefore)).to.eq(amount);
|
|
117
|
+
expect(balanceBefore.sub(balanceAfter)).to.eq(totalPrice);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("make sure able to purchase nft", async () => {
|
|
121
|
+
// This purchases a Regular NFT (to trigger Genesis Pool distribution)
|
|
122
|
+
const { nft, crowd } = getState();
|
|
123
|
+
const [account] = await getAccounts(0);
|
|
124
|
+
const nftPrice = toBn("5000", 18);
|
|
125
|
+
|
|
126
|
+
// Need to approve enough for the purchase
|
|
127
|
+
const approveTx = await crowd
|
|
128
|
+
.connect(account)
|
|
129
|
+
.approve(nft.address, toBn("10000", 18));
|
|
130
|
+
await approveTx.wait();
|
|
131
|
+
|
|
132
|
+
const tx = await nft.connect(account).buy(0);
|
|
133
|
+
const receipt = await tx.wait();
|
|
134
|
+
expect(receipt.status).to.eq(1);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("should be able to check current nft genesis rewards and have correct rewards", async () => {
|
|
138
|
+
const { nftGenesis } = getState();
|
|
139
|
+
|
|
140
|
+
// Logic:
|
|
141
|
+
// Regular NFT Price = 5000
|
|
142
|
+
// Genesis Pool Allocation = 5% of 5000 = 250
|
|
143
|
+
// Type 0 (Price 5000) Allocation = 40% of 250 = 100
|
|
144
|
+
// Reward Per Unit = 100 / MaxMinted(2000) = 0.05
|
|
145
|
+
|
|
146
|
+
const ONE_UNIT = toBn("1", 18);
|
|
147
|
+
const nftPrice = toBn("5000", 18);
|
|
148
|
+
|
|
149
|
+
const genesisAllocation = nftPrice.mul(5).div(100); // 250
|
|
150
|
+
const type0Allocation = genesisAllocation.mul(40).div(100); // 100
|
|
151
|
+
const rewardPerUnit = type0Allocation.div(2000); // 0.05
|
|
152
|
+
|
|
153
|
+
const [buyer, buyer2] = await getAccounts(1, 2);
|
|
154
|
+
|
|
155
|
+
// buyer has 1 NFT (bought in first test)
|
|
156
|
+
const rewards = await nftGenesis
|
|
157
|
+
.connect(buyer)
|
|
158
|
+
.myNftRewards(0, buyer.address);
|
|
159
|
+
|
|
160
|
+
// buyer2 has 3 NFTs (bought in second test)
|
|
161
|
+
const rewards2 = await nftGenesis
|
|
162
|
+
.connect(buyer2)
|
|
163
|
+
.myNftRewards(0, buyer2.address);
|
|
164
|
+
|
|
165
|
+
// Debug logs if needed
|
|
166
|
+
// console.log("Reward Per Unit:", fromBn(rewardPerUnit, 18));
|
|
167
|
+
// console.log("Buyer 1 Reward:", fromBn(rewards, 18));
|
|
168
|
+
|
|
169
|
+
expect(rewards).to.eq(rewardPerUnit);
|
|
170
|
+
expect(rewards2).to.eq(rewardPerUnit.mul(3));
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("should have correct value sended to receiverAddress", async () => {
|
|
174
|
+
const { crowd, nftGenesis } = getState();
|
|
175
|
+
|
|
176
|
+
// Logic:
|
|
177
|
+
// Total Type 0 allocation per Regular NFT sale = 100 tokens.
|
|
178
|
+
// Distributed = RewardPerUnit * TotalMinted
|
|
179
|
+
// Total Minted so far: 1 (buyer) + 3 (buyer2) = 4 units.
|
|
180
|
+
// Distributed = 0.05 * 4 = 0.2 tokens.
|
|
181
|
+
// Sent to Receiver (Undistributed) = 100 - 0.2 = 99.8 tokens.
|
|
182
|
+
|
|
183
|
+
const nftPrice = toBn("5000", 18);
|
|
184
|
+
const genesisAllocation = nftPrice.mul(5).div(100); // 250
|
|
185
|
+
const type0Allocation = genesisAllocation.mul(40).div(100); // 100
|
|
186
|
+
const rewardPerUnit = type0Allocation.div(2000); // 0.05
|
|
187
|
+
|
|
188
|
+
const totalMinted = 4;
|
|
189
|
+
const distributed = rewardPerUnit.mul(totalMinted);
|
|
190
|
+
// Correct logic: Receiver gets the rest of the GLOBAL genesis allocation
|
|
191
|
+
const expectedReceiverAndount = genesisAllocation.sub(distributed);
|
|
192
|
+
|
|
193
|
+
const receiverAddress = await nftGenesis.receiverAddress();
|
|
194
|
+
const balanceReceiver = await crowd.balanceOf(receiverAddress);
|
|
195
|
+
|
|
196
|
+
expect(balanceReceiver).to.eq(expectedReceiverAndount);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("should have correct value genesisPool", async () => {
|
|
200
|
+
const { nftGenesis, crowd } = getState();
|
|
201
|
+
// Genesis Pool (contract balance) should hold the DISTRIBUTED amount that hasn't been claimed yet.
|
|
202
|
+
// i.e., 0.2 tokens.
|
|
203
|
+
|
|
204
|
+
const nftPrice = toBn("5000", 18);
|
|
205
|
+
const genesisAllocation = nftPrice.mul(5).div(100); // 250
|
|
206
|
+
const type0Allocation = genesisAllocation.mul(40).div(100); // 100
|
|
207
|
+
const rewardPerUnit = type0Allocation.div(2000); // 0.05
|
|
208
|
+
const totalMinted = 4;
|
|
209
|
+
const expectedPoolBalance = rewardPerUnit.mul(totalMinted);
|
|
210
|
+
|
|
211
|
+
const balance = await crowd.balanceOf(nftGenesis.address);
|
|
212
|
+
expect(balance).to.eq(expectedPoolBalance);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("should be able to claim genesis rewards and have correct amount", async () => {
|
|
216
|
+
const { nftGenesis, crowd } = getState();
|
|
217
|
+
const [_, buyer, buyer2] = await getAccounts(0, 3); // buyer=idx 1, buyer2=idx 2
|
|
218
|
+
|
|
219
|
+
// buyer2 claims first (owns 3 NFTs, reward 0.15)
|
|
220
|
+
const balanceBefore2 = await crowd.balanceOf(buyer2.address);
|
|
221
|
+
await (await nftGenesis.connect(buyer2).claimRewards(0)).wait();
|
|
222
|
+
const balanceAfter2 = await crowd.balanceOf(buyer2.address);
|
|
223
|
+
|
|
224
|
+
// Expected reward for 3 units = 0.05 * 3 = 0.15
|
|
225
|
+
const expectedReward2 = toBn("0.15", 18); // 0.05 * 3
|
|
226
|
+
expect(balanceAfter2.sub(balanceBefore2)).to.eq(expectedReward2);
|
|
227
|
+
|
|
228
|
+
// buyer claims next (owns 1 NFT, reward 0.05)
|
|
229
|
+
const balanceBefore1 = await crowd.balanceOf(buyer.address);
|
|
230
|
+
await (await nftGenesis.connect(buyer).claimRewards(0)).wait();
|
|
231
|
+
const balanceAfter1 = await crowd.balanceOf(buyer.address);
|
|
232
|
+
|
|
233
|
+
const expectedReward1 = toBn("0.05", 18);
|
|
234
|
+
expect(balanceAfter1.sub(balanceBefore1)).to.eq(expectedReward1);
|
|
235
|
+
|
|
236
|
+
// Pool should be empty now for this distribution
|
|
237
|
+
const genesisPoolAfter = await nftGenesis.nftGenesisPool();
|
|
238
|
+
// Note: genesisPool tracks total value, but contract balance tracks unclaimed.
|
|
239
|
+
// nftGenesisPool variable in contract tracks *available* rewards?
|
|
240
|
+
// Let's check logic: claimRewards reduces nftGenesisPool.
|
|
241
|
+
// So it should be close to 0 (or exactly 0 if only this type exist).
|
|
242
|
+
// Actually there might be dust or other types if any.
|
|
243
|
+
// But since we only distributed to Type 0 and claimed all, it should be 0.
|
|
244
|
+
|
|
245
|
+
expect(genesisPoolAfter).to.eq(0);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("prepare calculation for next test cases", async () => {
|
|
249
|
+
const { nft, crowd } = getState();
|
|
250
|
+
const accounts = await getAccounts(0, 6);
|
|
251
|
+
// Accounts 3, 4, 5
|
|
252
|
+
|
|
253
|
+
const transferAmount = toBn("25000", 18); // 25k (enough for Card #1)
|
|
254
|
+
const smallAmount = toBn("5000", 18); // 5k (Card #0)
|
|
255
|
+
|
|
256
|
+
// Setup balances
|
|
257
|
+
await (
|
|
258
|
+
await crowd
|
|
259
|
+
.connect(accounts[0])
|
|
260
|
+
.transfer(accounts[3].address, transferAmount)
|
|
261
|
+
).wait();
|
|
262
|
+
await (
|
|
263
|
+
await crowd
|
|
264
|
+
.connect(accounts[0])
|
|
265
|
+
.transfer(accounts[4].address, smallAmount)
|
|
266
|
+
).wait();
|
|
267
|
+
await (
|
|
268
|
+
await crowd
|
|
269
|
+
.connect(accounts[0])
|
|
270
|
+
.transfer(accounts[5].address, transferAmount)
|
|
271
|
+
).wait();
|
|
272
|
+
|
|
273
|
+
// Approvals
|
|
274
|
+
await (
|
|
275
|
+
await crowd.connect(accounts[3]).approve(nft.address, transferAmount)
|
|
276
|
+
).wait();
|
|
277
|
+
await (
|
|
278
|
+
await crowd.connect(accounts[4]).approve(nft.address, smallAmount)
|
|
279
|
+
).wait();
|
|
280
|
+
await (
|
|
281
|
+
await crowd.connect(accounts[5]).approve(nft.address, transferAmount)
|
|
282
|
+
).wait();
|
|
283
|
+
|
|
284
|
+
// Buy NFTs to generate more rewards
|
|
285
|
+
// Card #1 sales generate 5% to Genesis Pool too.
|
|
286
|
+
// Price 25000 -> 5% = 1250.
|
|
287
|
+
// Type 0 allocation (40%) = 500.
|
|
288
|
+
// Reward per unit = 500 / 2000 = 0.25.
|
|
289
|
+
|
|
290
|
+
await (await nft.connect(accounts[3]).buy(1)).wait(); // +0.25 reward per unit
|
|
291
|
+
await (await nft.connect(accounts[4]).buy(0)).wait(); // +0.05 reward per unit
|
|
292
|
+
await (await nft.connect(accounts[5]).buy(1)).wait(); // +0.25 reward per unit
|
|
293
|
+
|
|
294
|
+
// Total new reward per unit = 0.25 + 0.05 + 0.25 = 0.55
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("should be able to claim rewards automatically when already have a genesis nft and then buy another", async () => {
|
|
298
|
+
const { crowd, nftGenesis, usdt } = getState();
|
|
299
|
+
const [admin, _, buyer] = await getAccounts(0, 3); // buyer2 (idx 2)
|
|
300
|
+
|
|
301
|
+
// buyer2 currently owns 3 NFTs.
|
|
302
|
+
// Previous rewards were claimed.
|
|
303
|
+
// New rewards generated in "prepare calculation" step: 0.55 per unit.
|
|
304
|
+
// Total pending reward = 0.55 * 3 = 1.65.
|
|
305
|
+
|
|
306
|
+
const pendingRewards = await nftGenesis
|
|
307
|
+
.connect(buyer)
|
|
308
|
+
.myNftRewards(0, buyer.address);
|
|
309
|
+
// Verify pending expectation
|
|
310
|
+
const expectedPending = toBn("0.55", 18).mul(3);
|
|
311
|
+
|
|
312
|
+
// Allow small rounding diff if any, but with integer math usually exact
|
|
313
|
+
expect(pendingRewards).to.eq(expectedPending);
|
|
314
|
+
|
|
315
|
+
const balanceCROWDBefore = await crowd.balanceOf(buyer.address);
|
|
316
|
+
|
|
317
|
+
// Buy 3 more Genesis NFTs
|
|
318
|
+
const pricePerUnit = toBn("5000", 18);
|
|
319
|
+
const cost = pricePerUnit.mul(3);
|
|
320
|
+
|
|
321
|
+
await usdt.connect(admin).transfer(buyer.address, cost);
|
|
322
|
+
await (await usdt.connect(buyer).approve(nftGenesis.address, cost)).wait();
|
|
323
|
+
|
|
324
|
+
// This buy action should trigger auto-claim of the 1.65 CROWD rewards
|
|
325
|
+
await (await nftGenesis.connect(buyer).buyMultipleNFT(0, 3)).wait();
|
|
326
|
+
|
|
327
|
+
const balanceCROWDAfter = await crowd.balanceOf(buyer.address);
|
|
328
|
+
|
|
329
|
+
// Check if rewards were received
|
|
330
|
+
expect(balanceCROWDAfter.sub(balanceCROWDBefore)).to.eq(expectedPending);
|
|
331
|
+
|
|
332
|
+
// New rewards should be 0 immediately after buy (for the old execution point)
|
|
333
|
+
// But wait, the marker updates only when distributed.
|
|
334
|
+
// The claimed amount is reset.
|
|
335
|
+
const newPending = await nftGenesis
|
|
336
|
+
.connect(buyer)
|
|
337
|
+
.myNftRewards(0, buyer.address);
|
|
338
|
+
expect(newPending).to.eq(0);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it("should be able to buy different variant nft", async () => {
|
|
342
|
+
const { usdt, nftGenesis } = getState();
|
|
343
|
+
const [admin, buyer] = await getAccounts(0, 2); // reuse buyer (idx 1)
|
|
344
|
+
|
|
345
|
+
// Buy Type 1 (Price 2000)
|
|
346
|
+
const price = toBn("2000", 18);
|
|
347
|
+
|
|
348
|
+
await usdt.connect(admin).transfer(buyer.address, price);
|
|
349
|
+
await (await usdt.connect(buyer).approve(nftGenesis.address, price)).wait();
|
|
350
|
+
|
|
351
|
+
const nftBefore = await nftGenesis.balanceOf(buyer.address);
|
|
352
|
+
|
|
353
|
+
await (await nftGenesis.connect(buyer).buyMultipleNFT(1, 1)).wait();
|
|
354
|
+
|
|
355
|
+
const nftAfter = await nftGenesis.balanceOf(buyer.address);
|
|
356
|
+
expect(nftAfter.sub(nftBefore)).to.eq(1);
|
|
357
|
+
|
|
358
|
+
// Check ownership of Type 1
|
|
359
|
+
const cardInfo = await nftGenesis.nftGenesis(buyer.address, 1);
|
|
360
|
+
expect(cardInfo.ownedNfts).to.eq(1);
|
|
361
|
+
|
|
362
|
+
// URI Check for Type 1
|
|
363
|
+
// buyer had 1 type 0. bought 1 type 1. Total 2. Index 1?
|
|
364
|
+
// tokenOfOwnerByIndex might return in order.
|
|
365
|
+
// Let's find the token with cardId 1.
|
|
366
|
+
// This part is tricky without knowing exact token ID logic, but usually append.
|
|
367
|
+
// We can just trust cardInfo verification above.
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("should be able to transfer NFT", async () => {
|
|
371
|
+
const { nftGenesis, crowd } = getState();
|
|
372
|
+
const [owner, receiver] = await getAccounts(2, 2); // owner=buyer2(idx2), receiver=idx3
|
|
373
|
+
|
|
374
|
+
// Owner (buyer2) has 3 (initial) + 3 (new) = 6 NFTs of Type 0.
|
|
375
|
+
// Receiver (idx3) has 0 NFTs.
|
|
376
|
+
|
|
377
|
+
const ownerCardBefore = await nftGenesis.nftGenesis(owner.address, 0);
|
|
378
|
+
const receiverCardBefore = await nftGenesis.nftGenesis(receiver.address, 0);
|
|
379
|
+
|
|
380
|
+
// console.log("Owner NFTs:", ownerCardBefore.ownedNfts.toString());
|
|
381
|
+
|
|
382
|
+
// Transfer 1 NFT from Owner to Receiver via token ID
|
|
383
|
+
const tokenId = await nftGenesis.tokenOfOwnerByIndex(owner.address, 0); // Get first token
|
|
384
|
+
|
|
385
|
+
await (
|
|
386
|
+
await nftGenesis
|
|
387
|
+
.connect(owner)
|
|
388
|
+
.transferFrom(owner.address, receiver.address, tokenId)
|
|
389
|
+
).wait();
|
|
390
|
+
|
|
391
|
+
const ownerCardAfter = await nftGenesis.nftGenesis(owner.address, 0);
|
|
392
|
+
const receiverCardAfter = await nftGenesis.nftGenesis(receiver.address, 0);
|
|
393
|
+
|
|
394
|
+
expect(ownerCardAfter.ownedNfts).to.eq(ownerCardBefore.ownedNfts.sub(1));
|
|
395
|
+
expect(receiverCardAfter.ownedNfts).to.eq(
|
|
396
|
+
receiverCardBefore.ownedNfts.add(1),
|
|
397
|
+
);
|
|
398
|
+
});
|
|
399
|
+
});
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { fromBn, toBn } from "evm-bn";
|
|
3
|
+
import { initialize, getState, getAccounts } from "./lib/initializer";
|
|
4
|
+
|
|
5
|
+
describe("Valhalla NFT Purchase", function () {
|
|
6
|
+
before(async () => {
|
|
7
|
+
await initialize();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("make sure able to purchase nft", async () => {
|
|
11
|
+
const { nft, crowd } = getState();
|
|
12
|
+
const [account] = await getAccounts(0);
|
|
13
|
+
// Approve full NFT price - contract handles all transfers including genesis pool
|
|
14
|
+
const approveTx = await crowd
|
|
15
|
+
.connect(account)
|
|
16
|
+
.approve(nft.address, toBn("10000", 18));
|
|
17
|
+
await approveTx.wait();
|
|
18
|
+
|
|
19
|
+
const tx = await nft.connect(account).buy(0);
|
|
20
|
+
const receipt = await tx.wait();
|
|
21
|
+
expect(receipt.status).to.eq(1);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should fail to purchase nft if not registered", async () => {
|
|
25
|
+
const { nft, crowd } = getState();
|
|
26
|
+
const [unregisteredUser] = await getAccounts(50, 1); // Use new account
|
|
27
|
+
|
|
28
|
+
// Transfer token to unregistered user
|
|
29
|
+
const [admin] = await getAccounts(0);
|
|
30
|
+
await crowd
|
|
31
|
+
.connect(admin)
|
|
32
|
+
.transfer(unregisteredUser.address, toBn("10000", 18));
|
|
33
|
+
|
|
34
|
+
// Approve
|
|
35
|
+
await crowd
|
|
36
|
+
.connect(unregisteredUser)
|
|
37
|
+
.approve(nft.address, toBn("10000", 18));
|
|
38
|
+
|
|
39
|
+
// Expect revert
|
|
40
|
+
await expect(nft.connect(unregisteredUser).buy(0)).to.be.revertedWith(
|
|
41
|
+
"Registration required",
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("make sure total supply is correct", async () => {
|
|
46
|
+
const { nft } = getState();
|
|
47
|
+
const totalSupply = await nft.totalSupply();
|
|
48
|
+
expect(totalSupply.toNumber()).to.eq(1);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("make sure global pool is correct (17%)", async () => {
|
|
52
|
+
const { nft } = getState();
|
|
53
|
+
const globalPool = await nft.getGlobalPool();
|
|
54
|
+
const calculation = toBn(String((5000 * 17) / 100), 18);
|
|
55
|
+
const isAccurate = calculation.eq(globalPool.claimable);
|
|
56
|
+
expect(isAccurate).to.eq(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("make sure fee receiver reward is correct (20%)", async () => {
|
|
60
|
+
const { valhalla, crowd } = getState();
|
|
61
|
+
const feeReceiverAddress = await valhalla.feeReceiverAddress();
|
|
62
|
+
const feeReceiverAddress2 = await valhalla.feeReceiverAddress2();
|
|
63
|
+
const balanceFee = await crowd.balanceOf(feeReceiverAddress);
|
|
64
|
+
const balanceFee2 = await crowd.balanceOf(feeReceiverAddress2);
|
|
65
|
+
|
|
66
|
+
// Fee receiver should get 20% total (10% each)
|
|
67
|
+
const rewardCalc = toBn(String((5000 * 20) / 100), 18);
|
|
68
|
+
const halfFeeReward = rewardCalc.div(2);
|
|
69
|
+
|
|
70
|
+
expect(halfFeeReward).to.eq(balanceFee);
|
|
71
|
+
expect(halfFeeReward).to.eq(balanceFee2);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("make sure 50% is burned", async () => {
|
|
75
|
+
const { crowd } = getState();
|
|
76
|
+
const totalSupply = await crowd.totalSupply();
|
|
77
|
+
|
|
78
|
+
// Initial supply is 1,000,000,000 CROWD
|
|
79
|
+
// After buying NFT for 5000 CROWD, 50% (2500 CROWD) should be burned
|
|
80
|
+
const initialSupply = toBn("1000000000", 18);
|
|
81
|
+
const burnedAmount = toBn(String((5000 * 50) / 100), 18);
|
|
82
|
+
const expectedSupply = initialSupply.sub(burnedAmount);
|
|
83
|
+
|
|
84
|
+
expect(totalSupply).to.eq(expectedSupply);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("make sure direct referral gets 8% reward", async () => {
|
|
88
|
+
const { nft, crowd, valhalla, usdt, nftGenesis } = getState();
|
|
89
|
+
const [admin] = await getAccounts(0, 1);
|
|
90
|
+
const [referrer, buyer] = await getAccounts(15, 2);
|
|
91
|
+
|
|
92
|
+
// Register referrer first (referrer needs to be registered)
|
|
93
|
+
const transferUsdtToReferrerTx = await usdt
|
|
94
|
+
.connect(admin)
|
|
95
|
+
.transfer(referrer.address, toBn("100", 18));
|
|
96
|
+
await transferUsdtToReferrerTx.wait();
|
|
97
|
+
|
|
98
|
+
const approveUsdtReferrerTx = await usdt
|
|
99
|
+
.connect(referrer)
|
|
100
|
+
.approve(valhalla.address, toBn("100", 18));
|
|
101
|
+
await approveUsdtReferrerTx.wait();
|
|
102
|
+
|
|
103
|
+
const registerReferrerTx = await valhalla
|
|
104
|
+
.connect(referrer)
|
|
105
|
+
.register(admin.address);
|
|
106
|
+
await registerReferrerTx.wait();
|
|
107
|
+
|
|
108
|
+
// Transfer USDT for registration fee
|
|
109
|
+
const transferUsdtTx = await usdt
|
|
110
|
+
.connect(admin)
|
|
111
|
+
.transfer(buyer.address, toBn("100", 18));
|
|
112
|
+
await transferUsdtTx.wait();
|
|
113
|
+
|
|
114
|
+
// Approve USDT for registration
|
|
115
|
+
const approveUsdtTx = await usdt
|
|
116
|
+
.connect(buyer)
|
|
117
|
+
.approve(valhalla.address, toBn("100", 18));
|
|
118
|
+
await approveUsdtTx.wait();
|
|
119
|
+
|
|
120
|
+
// Transfer CROWD to buyer for NFT purchase
|
|
121
|
+
const transferTx = await crowd
|
|
122
|
+
.connect(admin)
|
|
123
|
+
.transfer(buyer.address, toBn("10000", 18));
|
|
124
|
+
await transferTx.wait();
|
|
125
|
+
|
|
126
|
+
// Register buyer with referrer as direct referral
|
|
127
|
+
const registerTx = await valhalla.connect(buyer).register(referrer.address);
|
|
128
|
+
await registerTx.wait();
|
|
129
|
+
|
|
130
|
+
// Get referrer's reward before purchase
|
|
131
|
+
const rewardBefore = await nft.rewardMap(referrer.address);
|
|
132
|
+
|
|
133
|
+
// Buyer approves and purchases NFT (full amount for genesis pool transfer)
|
|
134
|
+
const approveTx = await crowd
|
|
135
|
+
.connect(buyer)
|
|
136
|
+
.approve(nft.address, toBn("10000", 18));
|
|
137
|
+
await approveTx.wait();
|
|
138
|
+
|
|
139
|
+
const approveGenesisTx = await crowd
|
|
140
|
+
.connect(buyer)
|
|
141
|
+
.approve(nftGenesis.address, toBn("10000", 18));
|
|
142
|
+
await approveGenesisTx.wait();
|
|
143
|
+
|
|
144
|
+
const buyTx = await nft.connect(buyer).buy(0);
|
|
145
|
+
await buyTx.wait();
|
|
146
|
+
|
|
147
|
+
// Get referrer's reward after purchase
|
|
148
|
+
const rewardAfter = await nft.rewardMap(referrer.address);
|
|
149
|
+
const rewardReceived = rewardAfter.sub(rewardBefore);
|
|
150
|
+
|
|
151
|
+
// Direct referral should get 8% of NFT price
|
|
152
|
+
const expectedReward = toBn(String((5000 * 8) / 100), 18);
|
|
153
|
+
expect(rewardReceived).to.eq(expectedReward);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("make sure genesis pool receives 5%", async () => {
|
|
157
|
+
const { nft, crowd, nftGenesis, valhalla, usdt } = getState();
|
|
158
|
+
const [admin] = await getAccounts(0, 1);
|
|
159
|
+
const [referrer] = await getAccounts(14, 1);
|
|
160
|
+
const [buyer] = await getAccounts(20, 1);
|
|
161
|
+
|
|
162
|
+
// Transfer USDT for registration fee
|
|
163
|
+
const transferUsdtTx = await usdt
|
|
164
|
+
.connect(admin)
|
|
165
|
+
.transfer(buyer.address, toBn("100", 18));
|
|
166
|
+
await transferUsdtTx.wait();
|
|
167
|
+
|
|
168
|
+
// Approve USDT for registration
|
|
169
|
+
const approveUsdtTx = await usdt
|
|
170
|
+
.connect(buyer)
|
|
171
|
+
.approve(valhalla.address, toBn("100", 18));
|
|
172
|
+
await approveUsdtTx.wait();
|
|
173
|
+
|
|
174
|
+
// Register buyer first
|
|
175
|
+
const registerTx = await valhalla.connect(buyer).register(referrer.address);
|
|
176
|
+
await registerTx.wait();
|
|
177
|
+
|
|
178
|
+
// Transfer CROWD to buyer
|
|
179
|
+
const transferTx = await crowd
|
|
180
|
+
.connect(admin)
|
|
181
|
+
.transfer(buyer.address, toBn("10000", 18));
|
|
182
|
+
await transferTx.wait();
|
|
183
|
+
|
|
184
|
+
// Get genesis pool balance before purchase
|
|
185
|
+
const genesisAddress = await nftGenesis.receiverAddress();
|
|
186
|
+
const genesisBalanceBefore = await crowd.balanceOf(genesisAddress);
|
|
187
|
+
|
|
188
|
+
// Buyer approves and purchases NFT (full amount for genesis pool transfer)
|
|
189
|
+
const approveTx = await crowd
|
|
190
|
+
.connect(buyer)
|
|
191
|
+
.approve(nft.address, toBn("10000", 18));
|
|
192
|
+
await approveTx.wait();
|
|
193
|
+
|
|
194
|
+
const approveGenesisTx = await crowd
|
|
195
|
+
.connect(buyer)
|
|
196
|
+
.approve(nftGenesis.address, toBn("10000", 18));
|
|
197
|
+
await approveGenesisTx.wait();
|
|
198
|
+
|
|
199
|
+
const buyTx = await nft.connect(buyer).buy(0);
|
|
200
|
+
await buyTx.wait();
|
|
201
|
+
|
|
202
|
+
// Get genesis pool balance after purchase
|
|
203
|
+
const genesisBalanceAfter = await crowd.balanceOf(genesisAddress);
|
|
204
|
+
const genesisReceived = genesisBalanceAfter.sub(genesisBalanceBefore);
|
|
205
|
+
|
|
206
|
+
// Genesis pool should receive 5% of NFT price
|
|
207
|
+
const expectedGenesisAmount = toBn(String((5000 * 5) / 100), 18);
|
|
208
|
+
expect(genesisReceived).to.eq(expectedGenesisAmount);
|
|
209
|
+
});
|
|
210
|
+
});
|