Skip to content

Commit 1d91318

Browse files
committed
test: add sip-031 tests with randomness
1 parent 4549c36 commit 1d91318

File tree

1 file changed

+254
-0
lines changed

1 file changed

+254
-0
lines changed

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

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,40 @@ test('vesting calculation one block before boundary', () => {
562562
}
563563
});
564564

565+
test('vesting calculation one block after boundary', () => {
566+
mintInitial();
567+
const deployBlockHeight = rov(contract.getDeployBlockHeight());
568+
569+
// Test one block after each iteration boundary
570+
for (let i = 1n; i < constants.INITIAL_MINT_VESTING_ITERATIONS; i++) {
571+
const oneBlockAfter =
572+
deployBlockHeight +
573+
i * constants.INITIAL_MINT_VESTING_ITERATION_BLOCKS +
574+
1n;
575+
const claimable = rov(contract.calcClaimableAmount(oneBlockAfter));
576+
577+
// Should still be the current iteration's amount
578+
const expectedVested =
579+
i *
580+
(constants.INITIAL_MINT_VESTING_AMOUNT /
581+
constants.INITIAL_MINT_VESTING_ITERATIONS);
582+
const expected = constants.INITIAL_MINT_IMMEDIATE_AMOUNT + expectedVested;
583+
expect(claimable).toBe(expected);
584+
}
585+
586+
// Special case for the last iteration
587+
const oneBlockAfter =
588+
deployBlockHeight +
589+
constants.INITIAL_MINT_VESTING_ITERATION_BLOCKS *
590+
constants.INITIAL_MINT_VESTING_ITERATIONS +
591+
1n;
592+
const claimable = rov(contract.calcClaimableAmount(oneBlockAfter));
593+
594+
// Should still be the current iteration's amount
595+
const expectedVested = constants.INITIAL_MINT_AMOUNT;
596+
expect(claimable).toBe(expectedVested);
597+
});
598+
565599
test('contract balance exactly equals vested amount (no extra funds)', () => {
566600
// Only mint the exact initial amount, no extra
567601
mintInitial();
@@ -636,3 +670,223 @@ test('recipient change during vesting period preserves vested amounts', () => {
636670
const secondClaim = txOk(contract.claim(), accounts.wallet_1.address);
637671
expect(secondClaim.value).toBe(expectedVested);
638672
});
673+
674+
// -----------------------------------------------------------------------------
675+
// Fuzz Testing - Randomized Tests
676+
// -----------------------------------------------------------------------------
677+
678+
test('fuzz: random extra deposits at random times', () => {
679+
mintInitial();
680+
681+
let totalExtraDeposited = 0n;
682+
let totalMonthsElapsed = 0;
683+
684+
// Generate 10 random deposit/time combinations
685+
for (let i = 0; i < 10; i++) {
686+
// Random deposit between 1 and 1000 STX
687+
const randomDeposit =
688+
BigInt(Math.floor(Math.random() * 1000) + 1) * 1000000n;
689+
mint(randomDeposit);
690+
totalExtraDeposited += randomDeposit;
691+
692+
// Random time advancement between 0 and 3 months
693+
const randomMonths = Math.floor(Math.random() * 4);
694+
if (randomMonths > 0) {
695+
simnet.mineEmptyBlocks(months(randomMonths));
696+
totalMonthsElapsed += randomMonths;
697+
}
698+
}
699+
700+
// Cap at 24 months for vesting calculation
701+
const effectiveMonths = Math.min(totalMonthsElapsed, 24);
702+
const expectedVested =
703+
effectiveMonths < 24
704+
? (constants.INITIAL_MINT_VESTING_AMOUNT /
705+
constants.INITIAL_MINT_VESTING_ITERATIONS) *
706+
BigInt(effectiveMonths)
707+
: constants.INITIAL_MINT_VESTING_AMOUNT;
708+
709+
const expectedTotal =
710+
constants.INITIAL_MINT_IMMEDIATE_AMOUNT +
711+
expectedVested +
712+
totalExtraDeposited;
713+
714+
const receipt = txOk(contract.claim(), accounts.deployer.address);
715+
expect(receipt.value).toBe(expectedTotal);
716+
717+
// Verify balance consistency
718+
const remainingBalance = rov(
719+
indirectContract.getBalance(contract.identifier),
720+
);
721+
const expectedRemaining =
722+
effectiveMonths < 24
723+
? constants.INITIAL_MINT_VESTING_AMOUNT - expectedVested
724+
: 0n;
725+
expect(remainingBalance).toBe(expectedRemaining);
726+
});
727+
728+
test('fuzz: random recipient changes during vesting', () => {
729+
mintInitial();
730+
731+
const wallets = [
732+
accounts.deployer.address,
733+
accounts.wallet_1.address,
734+
accounts.wallet_2.address,
735+
accounts.wallet_3.address,
736+
];
737+
738+
let currentRecipient: string = accounts.deployer.address;
739+
let currentRecipientIndex = 0;
740+
let totalMonthsElapsed = 0;
741+
742+
// Perform random recipient changes over time
743+
for (let i = 0; i < 8; i++) {
744+
// Random time advancement
745+
const monthsToAdvance = Math.floor(Math.random() * 4) + 1; // 1-4 months
746+
simnet.mineEmptyBlocks(months(monthsToAdvance));
747+
totalMonthsElapsed += monthsToAdvance;
748+
749+
if (totalMonthsElapsed >= 24) break;
750+
751+
// Random recipient change
752+
const newRecipientIndex = Math.floor(Math.random() * wallets.length);
753+
if (newRecipientIndex !== currentRecipientIndex) {
754+
txOk(
755+
contract.updateRecipient(wallets[newRecipientIndex]),
756+
currentRecipient,
757+
);
758+
currentRecipient = wallets[newRecipientIndex];
759+
currentRecipientIndex = newRecipientIndex;
760+
}
761+
}
762+
763+
// Final recipient should be able to claim all vested funds
764+
const expectedVested =
765+
totalMonthsElapsed < 24
766+
? (constants.INITIAL_MINT_VESTING_AMOUNT /
767+
constants.INITIAL_MINT_VESTING_ITERATIONS) *
768+
BigInt(totalMonthsElapsed)
769+
: constants.INITIAL_MINT_VESTING_AMOUNT;
770+
771+
// If balance is sufficient, we can claim the vested amount
772+
const receipt = txOk(contract.claim(), currentRecipient);
773+
expect(receipt.value).toBe(
774+
expectedVested + constants.INITIAL_MINT_IMMEDIATE_AMOUNT,
775+
);
776+
});
777+
778+
test('fuzz: random tiny deposits', () => {
779+
mintInitial();
780+
781+
let totalTinyDeposits = 0n;
782+
783+
// Add many tiny deposits (1-10 micro-STX each)
784+
for (let i = 0; i < 100; i++) {
785+
const tinyAmount = BigInt(Math.floor(Math.random() * 10) + 1); // 1-10 micro-STX
786+
mint(tinyAmount);
787+
totalTinyDeposits += tinyAmount;
788+
}
789+
790+
// Advance some time
791+
simnet.mineEmptyBlocks(months(6));
792+
793+
const expectedVested =
794+
(constants.INITIAL_MINT_VESTING_AMOUNT /
795+
constants.INITIAL_MINT_VESTING_ITERATIONS) *
796+
6n;
797+
const expected =
798+
constants.INITIAL_MINT_IMMEDIATE_AMOUNT +
799+
expectedVested +
800+
totalTinyDeposits;
801+
802+
const receipt = txOk(contract.claim(), accounts.deployer.address);
803+
expect(receipt.value).toBe(expected);
804+
805+
// Verify the expected remaining balance
806+
const remainingBalance = rov(
807+
indirectContract.getBalance(contract.identifier),
808+
);
809+
expect(remainingBalance).toBe(
810+
constants.INITIAL_MINT_AMOUNT -
811+
constants.INITIAL_MINT_IMMEDIATE_AMOUNT -
812+
expectedVested,
813+
);
814+
});
815+
816+
test('fuzz: stress test claim at random intervals', () => {
817+
mintInitial();
818+
819+
let totalClaimed = 0n;
820+
let currentBlock = 0n;
821+
let currentMonth = 0n;
822+
let initialBalance = rov(
823+
indirectContract.getBalance(accounts.deployer.address),
824+
);
825+
let totalExtraDeposited = 0n;
826+
827+
// Perform claims at random intervals with random extra deposits
828+
while (currentMonth < 24) {
829+
const balance = rov(indirectContract.getBalance(contract.identifier));
830+
console.log(`Current balance: ${balance}`);
831+
832+
// Random extra deposit
833+
const extraDeposit = BigInt(Math.floor(Math.random() * 10000000000));
834+
if (extraDeposit > 0n) {
835+
mint(extraDeposit);
836+
totalExtraDeposited += extraDeposit;
837+
}
838+
839+
// Random time advancement, up to 5 months
840+
const blocksToAdvance =
841+
Math.floor(
842+
Math.random() *
843+
Number(constants.INITIAL_MINT_VESTING_ITERATION_BLOCKS) *
844+
5,
845+
) + 1;
846+
simnet.mineEmptyBlocks(blocksToAdvance);
847+
currentBlock += BigInt(blocksToAdvance);
848+
currentMonth =
849+
currentBlock / constants.INITIAL_MINT_VESTING_ITERATION_BLOCKS;
850+
851+
const totalClaimable =
852+
currentMonth < 24
853+
? (constants.INITIAL_MINT_VESTING_AMOUNT /
854+
constants.INITIAL_MINT_VESTING_ITERATIONS) *
855+
currentMonth +
856+
constants.INITIAL_MINT_IMMEDIATE_AMOUNT
857+
: constants.INITIAL_MINT_AMOUNT;
858+
const expectedClaimable =
859+
totalClaimable + totalExtraDeposited - totalClaimed;
860+
861+
console.log(
862+
`Claiming at month ${currentMonth} (block ${currentBlock})\n extra deposit: ${extraDeposit}\n expected claimable: ${expectedClaimable}`,
863+
);
864+
865+
// Claim whatever is available
866+
const receipt = txOk(contract.claim(), accounts.deployer.address);
867+
totalClaimed += receipt.value;
868+
869+
// Verify claim amount is correct
870+
expect(receipt.value).toBe(expectedClaimable);
871+
872+
// Verify the account balance after claim
873+
const newBalance = rov(
874+
indirectContract.getBalance(accounts.deployer.address),
875+
);
876+
expect(newBalance).toBe(initialBalance + totalClaimed);
877+
}
878+
879+
// After all claims, verify the final balance
880+
const finalBalance = rov(indirectContract.getBalance(contract.identifier));
881+
const expectedFinalBalance =
882+
currentMonth >= 24
883+
? 0n
884+
: constants.INITIAL_MINT_AMOUNT -
885+
constants.INITIAL_MINT_IMMEDIATE_AMOUNT -
886+
currentMonth *
887+
(constants.INITIAL_MINT_VESTING_AMOUNT /
888+
constants.INITIAL_MINT_VESTING_ITERATIONS);
889+
890+
// Total claimed + final balance should account for all deposited funds
891+
expect(finalBalance).toBe(expectedFinalBalance);
892+
});

0 commit comments

Comments
 (0)