diff --git a/contrib/core-contract-tests/tests/pox-4/pox-4.stateful-prop.test.ts b/contrib/core-contract-tests/tests/pox-4/pox-4.stateful-prop.test.ts index 6935d35cf..eb34174c1 100644 --- a/contrib/core-contract-tests/tests/pox-4/pox-4.stateful-prop.test.ts +++ b/contrib/core-contract-tests/tests/pox-4/pox-4.stateful-prop.test.ts @@ -119,24 +119,27 @@ it("statefully interacts with PoX-4", async () => { simnet.setEpoch("3.0"); - fc.assert( - fc.property( - PoxCommands(model.wallets, sut.network), - (cmds) => { - const initialState = () => ({ model: model, real: sut }); - fc.modelRun(initialState, cmds); + // The testing suite will run for 5000 times. + for (let i = 0; i < 5000; i++) { + fc.assert( + fc.property( + PoxCommands(model.wallets, sut.network), + (cmds) => { + const initialState = () => ({ model: model, real: sut }); + fc.modelRun(initialState, cmds); + }, + ), + { + // Defines the number of test iterations to run; default is 100. + numRuns: 10, + // Adjusts the level of detail in test reports. Default is 0 (minimal). + // At level 2, reports include extensive details, helpful for deep + // debugging. This includes not just the failing case and its seed, but + // also a comprehensive log of all executed steps and their outcomes. + verbose: 2, }, - ), - { - // Defines the number of test iterations to run; default is 100. - numRuns: 10, - // Adjusts the level of detail in test reports. Default is 0 (minimal). - // At level 2, reports include extensive details, helpful for deep - // debugging. This includes not just the failing case and its seed, but - // also a comprehensive log of all executed steps and their outcomes. - verbose: 2, - }, - ); + ); + } model.reportCommandRuns(); }); diff --git a/contrib/core-contract-tests/tests/pox-4/pox_AllowContractCallerCommand.ts b/contrib/core-contract-tests/tests/pox-4/pox_AllowContractCallerCommand.ts index 13283af8a..9682d286b 100644 --- a/contrib/core-contract-tests/tests/pox-4/pox_AllowContractCallerCommand.ts +++ b/contrib/core-contract-tests/tests/pox-4/pox_AllowContractCallerCommand.ts @@ -85,6 +85,9 @@ export class AllowContractCallerCommand implements PoxCommand { "until", optionalCVToString(this.allowUntilBurnHt), ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.stateRefresh(real); } toString() { diff --git a/contrib/core-contract-tests/tests/pox-4/pox_CommandModel.ts b/contrib/core-contract-tests/tests/pox-4/pox_CommandModel.ts index 91f68e68b..e20bb4980 100644 --- a/contrib/core-contract-tests/tests/pox-4/pox_CommandModel.ts +++ b/contrib/core-contract-tests/tests/pox-4/pox_CommandModel.ts @@ -1,7 +1,11 @@ import fc from "fast-check"; import { Simnet } from "@hirosystems/clarinet-sdk"; -import { StacksPrivateKey } from "@stacks/transactions"; +import { + ClarityValue, + cvToValue, + StacksPrivateKey, +} from "@stacks/transactions"; import { StackingClient } from "@stacks/stacking"; export type StxAddress = string; @@ -12,7 +16,8 @@ export class Stub { readonly wallets: Map; readonly statistics: Map; stackingMinimum: number; - nextRewardSetIndex: number + nextRewardSetIndex: number; + lastRefreshedCycle: number; constructor( wallets: Map, @@ -22,6 +27,7 @@ export class Stub { this.statistics = statistics; this.stackingMinimum = 0; this.nextRewardSetIndex = 0; + this.lastRefreshedCycle = 0; } trackCommandRun(commandName: string) { @@ -35,6 +41,53 @@ export class Stub { console.log(`${commandName}: ${count}`); }); } + + stateRefresh(real: Real) { + const burnBlockHeightResult = real.network.runSnippet("burn-block-height"); + const burnBlockHeight = cvToValue(burnBlockHeightResult as ClarityValue); + const lastRefreshedCycle = this.lastRefreshedCycle; + const currentRewCycle = Math.floor((Number(burnBlockHeight) - 0) / 1050); + + if (lastRefreshedCycle < currentRewCycle) { + this.nextRewardSetIndex = 0; + + this.wallets.forEach((wallet) => { + const expiredDelegators = wallet.poolMembers.filter((stackerAddress) => + this.wallets.get(stackerAddress)!.delegatedUntilBurnHt + 1 < + burnBlockHeight + ); + const expiredStackers = wallet.lockedAddresses.filter( + (stackerAddress) => + this.wallets.get(stackerAddress)!.unlockHeight + 1 <= + burnBlockHeight, + ); + + expiredDelegators.forEach((expDelegator) => { + const expDelegatorIndex = wallet.poolMembers.indexOf(expDelegator); + wallet.poolMembers.splice(expDelegatorIndex, 1); + }); + + expiredStackers.forEach((expStacker) => { + const expStackerWallet = this.wallets.get(expStacker)!; + const expStackerIndex = wallet.lockedAddresses.indexOf(expStacker); + wallet.lockedAddresses.splice(expStackerIndex, 1); + wallet.amountToCommit -= expStackerWallet.amountLocked; + }); + + if ( + wallet.unlockHeight > 0 && wallet.unlockHeight + 1 <= burnBlockHeight + ) { + wallet.isStacking = false; + wallet.amountUnlocked += wallet.amountLocked; + wallet.amountLocked = 0; + wallet.unlockHeight = 0; + wallet.firstLockedRewardCycle = 0; + } + wallet.committedRewCycleIndexes = []; + }); + } + this.lastRefreshedCycle = currentRewCycle; + } } export type Real = { diff --git a/contrib/core-contract-tests/tests/pox-4/pox_Commands.ts b/contrib/core-contract-tests/tests/pox-4/pox_Commands.ts index 0faa84c32..bb1fc38cd 100644 --- a/contrib/core-contract-tests/tests/pox-4/pox_Commands.ts +++ b/contrib/core-contract-tests/tests/pox-4/pox_Commands.ts @@ -357,7 +357,7 @@ export function PoxCommands( // More on size: https://github.com/dubzzz/fast-check/discussions/2978 // More on cmds: https://github.com/dubzzz/fast-check/discussions/3026 - return fc.commands(cmds, { size: "large" }); + return fc.commands(cmds, { size: "xsmall" }); } export const REWARD_CYCLE_LENGTH = 1050; diff --git a/contrib/core-contract-tests/tests/pox-4/pox_DelegateStackExtendCommand.ts b/contrib/core-contract-tests/tests/pox-4/pox_DelegateStackExtendCommand.ts index f66b42ddb..0ec241a27 100644 --- a/contrib/core-contract-tests/tests/pox-4/pox_DelegateStackExtendCommand.ts +++ b/contrib/core-contract-tests/tests/pox-4/pox_DelegateStackExtendCommand.ts @@ -149,6 +149,9 @@ export class DelegateStackExtendCommand implements PoxCommand { "new unlock height", this.stacker.unlockHeight.toString(), ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.stateRefresh(real); } toString() { diff --git a/contrib/core-contract-tests/tests/pox-4/pox_DelegateStackIncreaseCommand.ts b/contrib/core-contract-tests/tests/pox-4/pox_DelegateStackIncreaseCommand.ts index eb1ee6bfe..75f504b75 100644 --- a/contrib/core-contract-tests/tests/pox-4/pox_DelegateStackIncreaseCommand.ts +++ b/contrib/core-contract-tests/tests/pox-4/pox_DelegateStackIncreaseCommand.ts @@ -123,6 +123,9 @@ export class DelegateStackIncreaseCommand implements PoxCommand { "total locked", stackerWallet.amountLocked.toString(), ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.stateRefresh(real); } toString() { diff --git a/contrib/core-contract-tests/tests/pox-4/pox_DelegateStackStxCommand.ts b/contrib/core-contract-tests/tests/pox-4/pox_DelegateStackStxCommand.ts index 5b46c5443..baaaad23c 100644 --- a/contrib/core-contract-tests/tests/pox-4/pox_DelegateStackStxCommand.ts +++ b/contrib/core-contract-tests/tests/pox-4/pox_DelegateStackStxCommand.ts @@ -169,6 +169,9 @@ export class DelegateStackStxCommand implements PoxCommand { "until", this.stacker.unlockHeight.toString() ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.stateRefresh(real); } toString() { diff --git a/contrib/core-contract-tests/tests/pox-4/pox_DelegateStxCommand.ts b/contrib/core-contract-tests/tests/pox-4/pox_DelegateStxCommand.ts index 6de496d37..f94259d0b 100644 --- a/contrib/core-contract-tests/tests/pox-4/pox_DelegateStxCommand.ts +++ b/contrib/core-contract-tests/tests/pox-4/pox_DelegateStxCommand.ts @@ -106,6 +106,9 @@ export class DelegateStxCommand implements PoxCommand { "until", this.untilBurnHt.toString() ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.stateRefresh(real); } toString() { diff --git a/contrib/core-contract-tests/tests/pox-4/pox_DisallowContractCallerCommand.ts b/contrib/core-contract-tests/tests/pox-4/pox_DisallowContractCallerCommand.ts index 9216f1eb2..1a90bd59f 100644 --- a/contrib/core-contract-tests/tests/pox-4/pox_DisallowContractCallerCommand.ts +++ b/contrib/core-contract-tests/tests/pox-4/pox_DisallowContractCallerCommand.ts @@ -87,6 +87,9 @@ export class DisallowContractCallerCommand implements PoxCommand { "disallow-contract-caller", this.callerToDisallow.label, ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.stateRefresh(real); } toString() { diff --git a/contrib/core-contract-tests/tests/pox-4/pox_GetStackingMinimumCommand.ts b/contrib/core-contract-tests/tests/pox-4/pox_GetStackingMinimumCommand.ts index c5a84e6a3..173a72242 100644 --- a/contrib/core-contract-tests/tests/pox-4/pox_GetStackingMinimumCommand.ts +++ b/contrib/core-contract-tests/tests/pox-4/pox_GetStackingMinimumCommand.ts @@ -55,6 +55,9 @@ export class GetStackingMinimumCommand implements PoxCommand { "pox-4", stackingMinimum.value.toString(), ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.stateRefresh(real); } toString() { diff --git a/contrib/core-contract-tests/tests/pox-4/pox_GetStxAccountCommand.ts b/contrib/core-contract-tests/tests/pox-4/pox_GetStxAccountCommand.ts index 71cf99207..aee09dd5b 100644 --- a/contrib/core-contract-tests/tests/pox-4/pox_GetStxAccountCommand.ts +++ b/contrib/core-contract-tests/tests/pox-4/pox_GetStxAccountCommand.ts @@ -54,6 +54,9 @@ export class GetStxAccountCommand implements PoxCommand { "unlocked-height", actual.unlockHeight.toString(), ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.stateRefresh(real); } toString() { diff --git a/contrib/core-contract-tests/tests/pox-4/pox_RevokeDelegateStxCommand.ts b/contrib/core-contract-tests/tests/pox-4/pox_RevokeDelegateStxCommand.ts index 5ada55932..68528fb49 100644 --- a/contrib/core-contract-tests/tests/pox-4/pox_RevokeDelegateStxCommand.ts +++ b/contrib/core-contract-tests/tests/pox-4/pox_RevokeDelegateStxCommand.ts @@ -85,6 +85,9 @@ export class RevokeDelegateStxCommand implements PoxCommand { // Log to console for debugging purposes. This is not necessary for the // test to pass but it is useful for debugging and eyeballing the test. logCommand(`✓ ${this.wallet.label}`, "revoke-delegate-stx"); + + // Refresh the model's state if the network gets to the next reward cycle. + model.stateRefresh(real); } toString() { diff --git a/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand.ts b/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand.ts index 22544274c..698e36c74 100644 --- a/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand.ts +++ b/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand.ts @@ -124,12 +124,17 @@ export class StackAggregationCommitAuthCommand implements PoxCommand { committedAmount.toString(), "authorization", ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.stateRefresh(real); } toString() { // fast-check will call toString() in case of errors, e.g. property failed. // It will then make a minimal counterexample, a process called 'shrinking' // https://github.com/dubzzz/fast-check/issues/2864#issuecomment-1098002642 - return `${this.operator.label} stack-aggregation-commit auth-id ${this.authId} for reward cycle ${this.currentCycle}`; + return `${this.operator.label} stack-aggregation-commit auth-id ${this.authId} for reward cycle ${ + this.currentCycle + 1 + }`; } } diff --git a/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand.ts b/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand.ts index 54e4639d7..d84da451e 100644 --- a/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand.ts +++ b/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand.ts @@ -128,12 +128,17 @@ export class StackAggregationCommitIndexedAuthCommand implements PoxCommand { committedAmount.toString(), "authorization", ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.stateRefresh(real); } toString() { // fast-check will call toString() in case of errors, e.g. property failed. // It will then make a minimal counterexample, a process called 'shrinking' // https://github.com/dubzzz/fast-check/issues/2864#issuecomment-1098002642 - return `${this.operator.label} stack-aggregation-commit-indexed auth-id ${this.authId} for reward cycle ${this.currentCycle}`; + return `${this.operator.label} stack-aggregation-commit-indexed auth-id ${this.authId} for reward cycle ${ + this.currentCycle + 1 + }`; } } diff --git a/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand.ts b/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand.ts index 3ee837852..0fb6f3675 100644 --- a/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand.ts +++ b/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand.ts @@ -128,12 +128,17 @@ export class StackAggregationCommitIndexedSigCommand implements PoxCommand { committedAmount.toString(), "signature", ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.stateRefresh(real); } toString() { // fast-check will call toString() in case of errors, e.g. property failed. // It will then make a minimal counterexample, a process called 'shrinking' // https://github.com/dubzzz/fast-check/issues/2864#issuecomment-1098002642 - return `${this.operator.label} stack-aggregation-commit-indexed auth-id ${this.authId} for reward cycle ${this.currentCycle}`; + return `${this.operator.label} stack-aggregation-commit-indexed auth-id ${this.authId} for reward cycle ${ + this.currentCycle + 1 + }`; } } diff --git a/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationCommitSigCommand.ts b/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationCommitSigCommand.ts index e1bf23364..8e0f2d8f8 100644 --- a/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationCommitSigCommand.ts +++ b/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationCommitSigCommand.ts @@ -124,12 +124,17 @@ export class StackAggregationCommitSigCommand implements PoxCommand { committedAmount.toString(), "signature", ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.stateRefresh(real); } toString() { // fast-check will call toString() in case of errors, e.g. property failed. // It will then make a minimal counterexample, a process called 'shrinking' // https://github.com/dubzzz/fast-check/issues/2864#issuecomment-1098002642 - return `${this.operator.label} stack-aggregation-commit auth-id ${this.authId} for reward cycle ${this.currentCycle}`; + return `${this.operator.label} stack-aggregation-commit auth-id ${this.authId} for reward cycle ${ + this.currentCycle + 1 + }`; } } diff --git a/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationIncreaseCommand.ts b/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationIncreaseCommand.ts index e15ac26c5..e56c733f5 100644 --- a/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationIncreaseCommand.ts +++ b/contrib/core-contract-tests/tests/pox-4/pox_StackAggregationIncreaseCommand.ts @@ -96,6 +96,9 @@ export class StackAggregationIncreaseCommand implements PoxCommand { "cycle index", this.rewardCycleIndex.toString(), ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.stateRefresh(real); } toString() { diff --git a/contrib/core-contract-tests/tests/pox-4/pox_StackStxCommand.ts b/contrib/core-contract-tests/tests/pox-4/pox_StackStxCommand.ts index 205704f97..4bd04b4ee 100644 --- a/contrib/core-contract-tests/tests/pox-4/pox_StackStxCommand.ts +++ b/contrib/core-contract-tests/tests/pox-4/pox_StackStxCommand.ts @@ -172,6 +172,9 @@ export class StackStxCommand implements PoxCommand { "lock-amount", amountUstx.toString(), ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.stateRefresh(real); } toString() {