Skip to content

Commit 4549c36

Browse files
committed
test: add more basic tests for the sip-031 contract
1 parent bcb3bca commit 4549c36

File tree

1 file changed

+212
-7
lines changed

1 file changed

+212
-7
lines changed

contrib/core-contract-tests/tests/sip-031/sip-031.test.ts

Lines changed: 212 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,9 @@ test('calculating vested amounts at a block height', () => {
148148
function expectedAmount(burnHeight: bigint) {
149149
const diff = burnHeight - deployBlockHeight;
150150
const iterations = diff / 4383n;
151-
const stxPerIteration = (initialMintAmount - immediateAmount) / 24n;
151+
const stxPerIteration =
152+
(initialMintAmount - immediateAmount) /
153+
constants.INITIAL_MINT_VESTING_ITERATIONS;
152154
const vestingAmount = stxPerIteration * iterations;
153155
return immediateAmount + vestingAmount;
154156
}
@@ -164,12 +166,16 @@ test('calculating vested amounts at a block height', () => {
164166
);
165167
}
166168

167-
for (let i = 1n; i < 24n; i++) {
169+
for (let i = 1n; i < constants.INITIAL_MINT_VESTING_ITERATIONS; i++) {
168170
expectAmount(i);
169171
}
170172
// At 24+ months, the entire vesting bucket should be unlocked
171173
expect(
172-
rov(contract.calcClaimableAmount(deployBlockHeight + 24n * 4383n)),
174+
rov(
175+
contract.calcClaimableAmount(
176+
deployBlockHeight + constants.INITIAL_MINT_VESTING_ITERATIONS * 4383n,
177+
),
178+
),
173179
).toBe(initialMintAmount);
174180
expect(
175181
rov(contract.calcClaimableAmount(deployBlockHeight + 25n * 4383n)),
@@ -189,7 +195,8 @@ test('claim scenario 1', () => {
189195
const receipt = txOk(contract.claim(), accounts.deployer.address);
190196
const expected =
191197
constants.INITIAL_MINT_IMMEDIATE_AMOUNT +
192-
constants.INITIAL_MINT_VESTING_AMOUNT / 24n +
198+
constants.INITIAL_MINT_VESTING_AMOUNT /
199+
constants.INITIAL_MINT_VESTING_ITERATIONS +
193200
100n * 1000000n;
194201
expect(receipt.value).toBe(expected);
195202

@@ -206,7 +213,10 @@ test('claim scenario 1', () => {
206213
simnet.mineEmptyBlocks(months(4));
207214
const receipt2 = txOk(contract.claim(), accounts.deployer.address);
208215
const expected2 =
209-
(constants.INITIAL_MINT_VESTING_AMOUNT / 24n) * 4n + 500n * 1000000n;
216+
(constants.INITIAL_MINT_VESTING_AMOUNT /
217+
constants.INITIAL_MINT_VESTING_ITERATIONS) *
218+
4n +
219+
500n * 1000000n;
210220
expect(receipt2.value).toBe(expected2);
211221

212222
const [event2] = filterEvents(
@@ -218,7 +228,10 @@ test('claim scenario 1', () => {
218228

219229
// wait until end of vesting (20 more months), with an extra 1500 STX
220230
// calc remainder of unvested, to deal with integer division
221-
const vestedAlready = (constants.INITIAL_MINT_VESTING_AMOUNT / 24n) * 5n;
231+
const vestedAlready =
232+
(constants.INITIAL_MINT_VESTING_AMOUNT /
233+
constants.INITIAL_MINT_VESTING_ITERATIONS) *
234+
5n;
222235
const unvested = constants.INITIAL_MINT_VESTING_AMOUNT - vestedAlready;
223236
const expected3 = unvested + 1500n * 1000000n;
224237
mint(1500n * 1000000n);
@@ -406,7 +419,8 @@ test('claiming after waiting more than 1 month', () => {
406419
const receipt = txOk(contract.claim(), accounts.deployer.address);
407420
expect(receipt.value).toBe(
408421
constants.INITIAL_MINT_IMMEDIATE_AMOUNT +
409-
constants.INITIAL_MINT_VESTING_AMOUNT / 24n,
422+
constants.INITIAL_MINT_VESTING_AMOUNT /
423+
constants.INITIAL_MINT_VESTING_ITERATIONS,
410424
);
411425
});
412426

@@ -431,3 +445,194 @@ test('recipient can be set to a contract', () => {
431445
txOk(contract.updateRecipient(contractAddr), accounts.deployer.address);
432446
expect(rov(contract.getRecipient())).toBe(contractAddr);
433447
});
448+
449+
test('multiple recipient changes in same block work correctly', () => {
450+
// Initial recipient is deployer
451+
expect(rov(contract.getRecipient())).toBe(accounts.deployer.address);
452+
453+
// Change to wallet_1
454+
txOk(
455+
contract.updateRecipient(accounts.wallet_1.address),
456+
accounts.deployer.address,
457+
);
458+
expect(rov(contract.getRecipient())).toBe(accounts.wallet_1.address);
459+
460+
// Change back to deployer in same block
461+
txOk(
462+
contract.updateRecipient(accounts.deployer.address),
463+
accounts.wallet_1.address,
464+
);
465+
expect(rov(contract.getRecipient())).toBe(accounts.deployer.address);
466+
});
467+
468+
test('large extra deposit does not break vesting calculations', () => {
469+
mintInitial();
470+
471+
// Add a large amount of extra STX (100M STX)
472+
const largeAmount = 100000000n * 1000000n; // 100 million STX
473+
mint(largeAmount);
474+
475+
// Move forward 12 months
476+
simnet.mineEmptyBlocks(months(12));
477+
478+
// Calculate expected: immediate + half vesting + large amount
479+
const expected =
480+
constants.INITIAL_MINT_IMMEDIATE_AMOUNT +
481+
(constants.INITIAL_MINT_VESTING_AMOUNT /
482+
constants.INITIAL_MINT_VESTING_ITERATIONS) *
483+
12n +
484+
largeAmount;
485+
486+
const receipt = txOk(contract.claim(), accounts.deployer.address);
487+
expect(receipt.value).toBe(expected);
488+
});
489+
490+
test('previous recipient cannot update the recipient', () => {
491+
txOk(
492+
contract.updateRecipient(accounts.wallet_1.address),
493+
accounts.deployer.address,
494+
);
495+
expect(rov(contract.getRecipient())).toBe(accounts.wallet_1.address);
496+
497+
txErr(
498+
contract.updateRecipient(accounts.wallet_2.address),
499+
accounts.deployer.address,
500+
);
501+
expect(rov(contract.getRecipient())).toBe(accounts.wallet_1.address);
502+
});
503+
504+
test('previous recipient cannot claim', () => {
505+
mintInitial();
506+
507+
txOk(
508+
contract.updateRecipient(accounts.wallet_1.address),
509+
accounts.deployer.address,
510+
);
511+
512+
txErr(contract.claim(), accounts.deployer.address);
513+
514+
// Contract should still have the unvested portion
515+
const remainingBalance = rov(
516+
indirectContract.getBalance(contract.identifier),
517+
);
518+
expect(remainingBalance).toBe(constants.INITIAL_MINT_AMOUNT);
519+
});
520+
521+
test('vesting calculation at exact boundary blocks', () => {
522+
mintInitial();
523+
const deployBlockHeight = rov(contract.getDeployBlockHeight());
524+
525+
// Test at exact iteration boundaries
526+
for (let i = 1n; i <= constants.INITIAL_MINT_VESTING_ITERATIONS; i++) {
527+
const exactBoundary =
528+
deployBlockHeight + i * constants.INITIAL_MINT_VESTING_ITERATION_BLOCKS;
529+
const claimable = rov(contract.calcClaimableAmount(exactBoundary));
530+
531+
const expectedVested =
532+
i < constants.INITIAL_MINT_VESTING_ITERATIONS
533+
? (constants.INITIAL_MINT_VESTING_AMOUNT /
534+
constants.INITIAL_MINT_VESTING_ITERATIONS) *
535+
i
536+
: constants.INITIAL_MINT_VESTING_AMOUNT;
537+
538+
const expected = constants.INITIAL_MINT_IMMEDIATE_AMOUNT + expectedVested;
539+
expect(claimable).toBe(expected);
540+
}
541+
});
542+
543+
test('vesting calculation one block before boundary', () => {
544+
mintInitial();
545+
const deployBlockHeight = rov(contract.getDeployBlockHeight());
546+
547+
// Test one block before each iteration boundary
548+
for (let i = 1n; i <= constants.INITIAL_MINT_VESTING_ITERATIONS; i++) {
549+
const oneBlockBefore =
550+
deployBlockHeight +
551+
i * constants.INITIAL_MINT_VESTING_ITERATION_BLOCKS -
552+
1n;
553+
const claimable = rov(contract.calcClaimableAmount(oneBlockBefore));
554+
555+
// Should still be the previous iteration's amount
556+
const expectedVested =
557+
(i - 1n) *
558+
(constants.INITIAL_MINT_VESTING_AMOUNT /
559+
constants.INITIAL_MINT_VESTING_ITERATIONS);
560+
const expected = constants.INITIAL_MINT_IMMEDIATE_AMOUNT + expectedVested;
561+
expect(claimable).toBe(expected);
562+
}
563+
});
564+
565+
test('contract balance exactly equals vested amount (no extra funds)', () => {
566+
// Only mint the exact initial amount, no extra
567+
mintInitial();
568+
569+
// Move to middle of vesting period (12 months)
570+
simnet.mineEmptyBlocks(months(12));
571+
572+
const vested =
573+
(constants.INITIAL_MINT_VESTING_AMOUNT /
574+
constants.INITIAL_MINT_VESTING_ITERATIONS) *
575+
12n;
576+
// Should be able to claim immediate + half of vesting
577+
const expected = constants.INITIAL_MINT_IMMEDIATE_AMOUNT + vested;
578+
const receipt = txOk(contract.claim(), accounts.deployer.address);
579+
expect(receipt.value).toBe(expected);
580+
581+
// Contract should still have the unvested portion
582+
const remainingBalance = rov(
583+
indirectContract.getBalance(contract.identifier),
584+
);
585+
const expectedRemaining = constants.INITIAL_MINT_VESTING_AMOUNT - vested;
586+
expect(remainingBalance).toBe(expectedRemaining);
587+
});
588+
589+
test('multiple small extra deposits accumulate correctly', () => {
590+
mintInitial();
591+
592+
// Add multiple small deposits over time
593+
mint(100n * 1000000n); // 100 STX
594+
simnet.mineEmptyBlocks(months(1));
595+
596+
mint(200n * 1000000n); // 200 STX
597+
simnet.mineEmptyBlocks(months(1));
598+
599+
mint(300n * 1000000n); // 300 STX
600+
simnet.mineEmptyBlocks(months(1));
601+
602+
// Total extra: 600 STX, Total time: 3 months
603+
const extraAmount = 600n * 1000000n;
604+
const vestedAfter3Months =
605+
(constants.INITIAL_MINT_VESTING_AMOUNT /
606+
constants.INITIAL_MINT_VESTING_ITERATIONS) *
607+
3n;
608+
const expected =
609+
constants.INITIAL_MINT_IMMEDIATE_AMOUNT + vestedAfter3Months + extraAmount;
610+
611+
const receipt = txOk(contract.claim(), accounts.deployer.address);
612+
expect(receipt.value).toBe(expected);
613+
});
614+
615+
test('recipient change during vesting period preserves vested amounts', () => {
616+
mintInitial();
617+
618+
// Original recipient claims immediate amount
619+
const firstClaim = txOk(contract.claim(), accounts.deployer.address);
620+
expect(firstClaim.value).toBe(constants.INITIAL_MINT_IMMEDIATE_AMOUNT);
621+
622+
// Wait 6 months
623+
simnet.mineEmptyBlocks(months(6));
624+
625+
// Change recipient
626+
txOk(
627+
contract.updateRecipient(accounts.wallet_1.address),
628+
accounts.deployer.address,
629+
);
630+
631+
// New recipient should be able to claim 6 months of vesting
632+
const expectedVested =
633+
(constants.INITIAL_MINT_VESTING_AMOUNT /
634+
constants.INITIAL_MINT_VESTING_ITERATIONS) *
635+
6n;
636+
const secondClaim = txOk(contract.claim(), accounts.wallet_1.address);
637+
expect(secondClaim.value).toBe(expectedVested);
638+
});

0 commit comments

Comments
 (0)