From 050418cfa3018c3d842bbec2a99899c4905dc7e3 Mon Sep 17 00:00:00 2001 From: Nikos Baxevanis Date: Wed, 22 May 2024 00:08:29 +0200 Subject: [PATCH 01/72] feat(pox-4-tests): add check function delegate to PoxCommand-derived types - Added import for StackStxSigCommand_Err and StackStxAuthCommand_Err - Added StackStxAuthCommand_Err with a custom check function delegate to PoxCommands - Added StackStxSigCommand_Err with a custom check function delegate to PoxCommands This allows the check function to be parameterized, reducing the need for copy-pasting classes. Note: This is a very work in progress. --- .../tests/pox-4/pox_Commands.ts | 62 +++++++ .../pox-4/pox_StackStxAuthCommand_Err.ts | 172 ++++++++++++++++++ .../tests/pox-4/pox_StackStxSigCommand_Err.ts | 170 +++++++++++++++++ 3 files changed, 404 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts index ba7043d5ec..44ff2c8ea0 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts @@ -3,7 +3,9 @@ import { Real, Stacker, Stub, StxAddress, Wallet } from "./pox_CommandModel"; import { GetStackingMinimumCommand } from "./pox_GetStackingMinimumCommand"; import { GetStxAccountCommand } from "./pox_GetStxAccountCommand"; import { StackStxSigCommand } from "./pox_StackStxSigCommand"; +import { StackStxSigCommand_Err } from "./pox_StackStxSigCommand_Err"; import { StackStxAuthCommand } from "./pox_StackStxAuthCommand"; +import { StackStxAuthCommand_Err } from "./pox_StackStxAuthCommand_Err"; import { DelegateStxCommand } from "./pox_DelegateStxCommand"; import { DelegateStackStxCommand } from "./pox_DelegateStackStxCommand"; import { Simnet } from "@hirosystems/clarinet-sdk"; @@ -83,6 +85,36 @@ export function PoxCommands( r.margin, ) ), + // StackStxAuthCommand_Err + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + period: fc.integer({ min: 1, max: 12 }), + margin: fc.integer({ min: 1, max: 9 }), + }).map(( + r: { + wallet: Wallet; + authId: number; + period: number; + margin: number; + }, + ) => + new StackStxAuthCommand_Err( + r.wallet, + r.authId, + r.period, + r.margin, + function (this: StackStxAuthCommand_Err, model: Readonly): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + console.log("I in StackStxAuthCommand_Err stacker", stacker); + return ( + model.stackingMinimum > 0 && !stacker.isStacking && + !stacker.hasDelegated + ); + }, + 123, + ) + ), // StackExtendAuthCommand fc .record({ @@ -105,6 +137,36 @@ export function PoxCommands( r.currentCycle, ), ), + // StackStxSigCommand_Err + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + period: fc.integer({ min: 1, max: 12 }), + margin: fc.integer({ min: 1, max: 9 }), + }).map(( + r: { + wallet: Wallet; + authId: number; + period: number; + margin: number; + }, + ) => + new StackStxSigCommand_Err( + r.wallet, + r.authId, + r.period, + r.margin, + function (this: StackStxSigCommand_Err, model: Readonly): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + console.log("I in StackStxSigCommand_Err stacker", stacker); + return ( + model.stackingMinimum > 0 && !stacker.isStacking && + !stacker.hasDelegated + ); + }, + 123, + ) + ), // StackExtendSigCommand fc .record({ diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts new file mode 100644 index 0000000000..ad310fef9a --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts @@ -0,0 +1,172 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { poxAddressToTuple } from "@stacks/stacking"; +import { assert, expect } from "vitest"; +import { + Cl, + ClarityType, + ClarityValue, + cvToValue, + isClarityType, +} from "@stacks/transactions"; +import { currentCycle } from "./pox_Commands.ts"; + +type CheckFunc = ( + this: StackStxAuthCommand_Err, + model: Readonly, +) => boolean; + +export class StackStxAuthCommand_Err implements PoxCommand { + readonly wallet: Wallet; + readonly authId: number; + readonly period: number; + readonly margin: number; + + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackStxAuthCommand_Err` to lock uSTX for stacking. + * + * @param wallet - Represents the Stacker's wallet. + * @param authId - Unique auth-id for the authorization. + * @param period - Number of reward cycles to lock uSTX. + * @param margin - Multiplier for minimum required uSTX to stack so that each + * Stacker locks a different amount of uSTX across test runs. + * @param checkFunc - A function to check constraints for running this command. + */ + constructor( + wallet: Wallet, + authId: number, + period: number, + margin: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.wallet = wallet; + this.authId = authId; + this.period = period; + this.margin = margin; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + model.trackCommandRun(this.constructor.name); + const currentRewCycle = currentCycle(real.network); + + // The maximum amount of uSTX that can be used (per tx) with this signer + // key. For our tests, we will use the minimum amount of uSTX to be stacked + // in the given reward cycle multiplied by the margin, which is a randomly + // generated number passed to the constructor of this class. + const maxAmount = model.stackingMinimum * this.margin; + + const { result: setAuthorization } = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "set-signer-key-authorization", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.wallet.btcAddress), + // (period uint) + Cl.uint(this.period), + // (reward-cycle uint) + Cl.uint(currentRewCycle), + // (topic (string-ascii 14)) + Cl.stringAscii("stack-stx"), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (allowed bool) + Cl.bool(true), + // (max-amount uint) + Cl.uint(maxAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ); + + expect(setAuthorization).toBeOk(Cl.bool(true)); + const burnBlockHeightCV = real.network.runSnippet("burn-block-height"); + const burnBlockHeight = Number( + cvToValue(burnBlockHeightCV as ClarityValue), + ); + + // The amount of uSTX to be locked in the reward cycle. For this test, we + // will use the maximum amount of uSTX that can be used (per tx) with this + // signer key. + const amountUstx = maxAmount; + + // Act + const stackStx = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-stx", + [ + // (amount-ustx uint) + Cl.uint(amountUstx), + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.wallet.btcAddress), + // (start-burn-ht uint) + Cl.uint(burnBlockHeight), + // (lock-period uint) + Cl.uint(this.period), + // (signer-sig (optional (buff 65))) + Cl.none(), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (max-amount uint) + Cl.uint(maxAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ); + + const { result: rewardCycle } = real.network.callReadOnlyFn( + "ST000000000000000000002AMW42H.pox-4", + "burn-height-to-reward-cycle", + [Cl.uint(burnBlockHeight)], + this.wallet.stxAddress, + ); + assert(isClarityType(rewardCycle, ClarityType.UInt)); + + const { result: unlockBurnHeight } = real.network.callReadOnlyFn( + "ST000000000000000000002AMW42H.pox-4", + "reward-cycle-to-burn-height", + [Cl.uint(Number(rewardCycle.value) + this.period + 1)], + this.wallet.stxAddress, + ); + assert(isClarityType(unlockBurnHeight, ClarityType.UInt)); + + // Assert + expect(stackStx.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.wallet.label}`, + "stack-stx-auth", + "lock-amount", + amountUstx.toString(), + "period", + this.period.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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.wallet.label} stack-stx auth auth-id ${this.authId} and period ${this.period}`; + } +} diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts new file mode 100644 index 0000000000..4c5f7ce149 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts @@ -0,0 +1,170 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { Pox4SignatureTopic, poxAddressToTuple } from "@stacks/stacking"; +import { assert, expect } from "vitest"; +import { + Cl, + ClarityType, + ClarityValue, + cvToValue, + isClarityType, +} from "@stacks/transactions"; +import { currentCycle } from "./pox_Commands.ts"; + +type CheckFunc = ( + this: StackStxSigCommand_Err, + model: Readonly, +) => boolean; + +export class StackStxSigCommand_Err implements PoxCommand { + readonly wallet: Wallet; + readonly authId: number; + readonly period: number; + readonly margin: number; + + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackStxSigCommand` to lock uSTX for stacking. + * + * @param wallet - Represents the Stacker's wallet. + * @param authId - Unique auth-id for the authorization. + * @param period - Number of reward cycles to lock uSTX. + * @param margin - Multiplier for minimum required uSTX to stack so that each + * Stacker locks a different amount of uSTX across test runs. + * @param checkFunc - A function to check constraints for running this command. + */ + constructor( + wallet: Wallet, + authId: number, + period: number, + margin: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.wallet = wallet; + this.authId = authId; + this.period = period; + this.margin = margin; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + model.trackCommandRun(this.constructor.name); + const burnBlockHeightCV = real.network.runSnippet("burn-block-height"); + const burnBlockHeight = Number( + cvToValue(burnBlockHeightCV as ClarityValue), + ); + const currentRewCycle = currentCycle(real.network); + + // The maximum amount of uSTX that can be used (per tx) with this signer + // key. For our tests, we will use the minimum amount of uSTX to be stacked + // in the given reward cycle multiplied by the margin, which is a randomly + // generated number passed to the constructor of this class. + const maxAmount = model.stackingMinimum * this.margin; + + const signerSig = this.wallet.stackingClient.signPoxSignature({ + // The signer key being authorized. + signerPrivateKey: this.wallet.signerPrvKey, + // The reward cycle for which the authorization is valid. + // For `stack-stx` and `stack-extend`, this refers to the reward cycle + // where the transaction is confirmed. For `stack-aggregation-commit`, + // this refers to the reward cycle argument in that function. + rewardCycle: currentRewCycle, + // For `stack-stx`, this refers to `lock-period`. For `stack-extend`, + // this refers to `extend-count`. For `stack-aggregation-commit`, this is + // `u1`. + period: this.period, + // A string representing the function where this authorization is valid. + // Either `stack-stx`, `stack-extend`, `stack-increase` or `agg-commit`. + topic: Pox4SignatureTopic.StackStx, + // The PoX address that can be used with this signer key. + poxAddress: this.wallet.btcAddress, + // The unique auth-id for this authorization. + authId: this.authId, + // The maximum amount of uSTX that can be used (per tx) with this signer + // key. + maxAmount: maxAmount, + }); + + // The amount of uSTX to be locked in the reward cycle. For this test, we + // will use the maximum amount of uSTX that can be used (per tx) with this + // signer key. + const amountUstx = maxAmount; + + // Act + const stackStx = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-stx", + [ + // (amount-ustx uint) + Cl.uint(amountUstx), + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.wallet.btcAddress), + // (start-burn-ht uint) + Cl.uint(burnBlockHeight), + // (lock-period uint) + Cl.uint(this.period), + // (signer-sig (optional (buff 65))) + Cl.some(Cl.bufferFromHex(signerSig)), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (max-amount uint) + Cl.uint(maxAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ); + + const { result: rewardCycle } = real.network.callReadOnlyFn( + "ST000000000000000000002AMW42H.pox-4", + "burn-height-to-reward-cycle", + [Cl.uint(burnBlockHeight)], + this.wallet.stxAddress, + ); + assert(isClarityType(rewardCycle, ClarityType.UInt)); + + const { result: unlockBurnHeight } = real.network.callReadOnlyFn( + "ST000000000000000000002AMW42H.pox-4", + "reward-cycle-to-burn-height", + [Cl.uint(Number(rewardCycle.value) + this.period + 1)], + this.wallet.stxAddress, + ); + assert(isClarityType(unlockBurnHeight, ClarityType.UInt)); + + // Assert + expect(stackStx.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.wallet.label}`, + "stack-stx-sig", + "lock-amount", + amountUstx.toString(), + "period", + this.period.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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.wallet.label} stack-stx sig auth-id ${this.authId} and period ${this.period}`; + } +} From 6f02aeb64414e057d93a44e00ea6795aaf7940d3 Mon Sep 17 00:00:00 2001 From: Nikos Baxevanis Date: Wed, 22 May 2024 08:27:17 +0200 Subject: [PATCH 02/72] feat(pox-4-tests): add check function delegate to PoxCommand-derived types - Separate success paths from failure paths to keep pox_Commands.ts focused on success cases only. This prevents the file from growing with out-of-scope cases. Note: This is a work in progress. --- .../tests/pox-4/err_Commands.ts | 74 +++++++++++++++++++ .../tests/pox-4/pox-4.stateful-prop.test.ts | 8 +- .../tests/pox-4/pox_Commands.ts | 70 +----------------- .../pox-4/pox_StackStxAuthCommand_Err.ts | 2 +- .../tests/pox-4/pox_StackStxSigCommand_Err.ts | 2 +- 5 files changed, 86 insertions(+), 70 deletions(-) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts new file mode 100644 index 0000000000..2e4259f740 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -0,0 +1,74 @@ +import fc from "fast-check"; +import { PoxCommand, Stacker, Stub, StxAddress, Wallet } from "./pox_CommandModel"; +import { StackStxSigCommand_Err } from "./pox_StackStxSigCommand_Err"; +import { StackStxAuthCommand_Err } from "./pox_StackStxAuthCommand_Err"; +import { Simnet } from "@hirosystems/clarinet-sdk"; + +export function ErrCommands( + wallets: Map, + stackers: Map, + network: Simnet, +): fc.Arbitrary[] { + const cmds = [ + // StackStxAuthCommand_Err + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + period: fc.integer({ min: 1, max: 12 }), + margin: fc.integer({ min: 1, max: 9 }), + }).map(( + r: { + wallet: Wallet; + authId: number; + period: number; + margin: number; + }, + ) => + new StackStxAuthCommand_Err( + r.wallet, + r.authId, + r.period, + r.margin, + function (this: StackStxAuthCommand_Err, model: Readonly): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + return ( + model.stackingMinimum > 0 && !stacker.isStacking && + !stacker.hasDelegated + ); + }, + 123, + ) + ), + // StackStxSigCommand_Err + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + period: fc.integer({ min: 1, max: 12 }), + margin: fc.integer({ min: 1, max: 9 }), + }).map(( + r: { + wallet: Wallet; + authId: number; + period: number; + margin: number; + }, + ) => + new StackStxSigCommand_Err( + r.wallet, + r.authId, + r.period, + r.margin, + function (this: StackStxSigCommand_Err, model: Readonly): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + return ( + model.stackingMinimum > 0 && !stacker.isStacking && + !stacker.hasDelegated + ); + }, + 123, + ) + ), + ]; + + return cmds; +} diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts index 15f4d4ddc0..29c57187b3 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts @@ -16,6 +16,7 @@ import { StackingClient } from "@stacks/stacking"; import fc from "fast-check"; import { PoxCommands } from "./pox_Commands.ts"; +import { ErrCommands } from "./err_Commands.ts"; import fs from "fs"; import path from "path"; @@ -139,9 +140,14 @@ it("statefully interacts with PoX-4", async () => { simnet.setEpoch("3.0"); + const successPath = PoxCommands(model.wallets, model.stackers, sut.network); + const failurePath = ErrCommands(model.wallets, model.stackers, sut.network); + fc.assert( fc.property( - PoxCommands(model.wallets, model.stackers, sut.network), + // More on size: https://github.com/dubzzz/fast-check/discussions/2978 + // More on cmds: https://github.com/dubzzz/fast-check/discussions/3026 + fc.commands(successPath.concat(failurePath), { size: "xsmall" }), (cmds) => { const initialState = () => ({ model: model, real: sut }); fc.modelRun(initialState, cmds); diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts index 44ff2c8ea0..d44ef23b22 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts @@ -1,11 +1,9 @@ import fc from "fast-check"; -import { Real, Stacker, Stub, StxAddress, Wallet } from "./pox_CommandModel"; +import { PoxCommand, Stacker, StxAddress, Wallet } from "./pox_CommandModel"; import { GetStackingMinimumCommand } from "./pox_GetStackingMinimumCommand"; import { GetStxAccountCommand } from "./pox_GetStxAccountCommand"; import { StackStxSigCommand } from "./pox_StackStxSigCommand"; -import { StackStxSigCommand_Err } from "./pox_StackStxSigCommand_Err"; import { StackStxAuthCommand } from "./pox_StackStxAuthCommand"; -import { StackStxAuthCommand_Err } from "./pox_StackStxAuthCommand_Err"; import { DelegateStxCommand } from "./pox_DelegateStxCommand"; import { DelegateStackStxCommand } from "./pox_DelegateStackStxCommand"; import { Simnet } from "@hirosystems/clarinet-sdk"; @@ -29,7 +27,7 @@ export function PoxCommands( wallets: Map, stackers: Map, network: Simnet, -): fc.Arbitrary>> { +): fc.Arbitrary[] { const cmds = [ // GetStackingMinimumCommand fc.record({ @@ -85,36 +83,6 @@ export function PoxCommands( r.margin, ) ), - // StackStxAuthCommand_Err - fc.record({ - wallet: fc.constantFrom(...wallets.values()), - authId: fc.nat(), - period: fc.integer({ min: 1, max: 12 }), - margin: fc.integer({ min: 1, max: 9 }), - }).map(( - r: { - wallet: Wallet; - authId: number; - period: number; - margin: number; - }, - ) => - new StackStxAuthCommand_Err( - r.wallet, - r.authId, - r.period, - r.margin, - function (this: StackStxAuthCommand_Err, model: Readonly): boolean { - const stacker = model.stackers.get(this.wallet.stxAddress)!; - console.log("I in StackStxAuthCommand_Err stacker", stacker); - return ( - model.stackingMinimum > 0 && !stacker.isStacking && - !stacker.hasDelegated - ); - }, - 123, - ) - ), // StackExtendAuthCommand fc .record({ @@ -137,36 +105,6 @@ export function PoxCommands( r.currentCycle, ), ), - // StackStxSigCommand_Err - fc.record({ - wallet: fc.constantFrom(...wallets.values()), - authId: fc.nat(), - period: fc.integer({ min: 1, max: 12 }), - margin: fc.integer({ min: 1, max: 9 }), - }).map(( - r: { - wallet: Wallet; - authId: number; - period: number; - margin: number; - }, - ) => - new StackStxSigCommand_Err( - r.wallet, - r.authId, - r.period, - r.margin, - function (this: StackStxSigCommand_Err, model: Readonly): boolean { - const stacker = model.stackers.get(this.wallet.stxAddress)!; - console.log("I in StackStxSigCommand_Err stacker", stacker); - return ( - model.stackingMinimum > 0 && !stacker.isStacking && - !stacker.hasDelegated - ); - }, - 123, - ) - ), // StackExtendSigCommand fc .record({ @@ -511,9 +449,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: "xsmall" }); + return cmds; } export const REWARD_CYCLE_LENGTH = 1050; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts index ad310fef9a..e1d0a2e113 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts @@ -26,7 +26,6 @@ export class StackStxAuthCommand_Err implements PoxCommand { readonly authId: number; readonly period: number; readonly margin: number; - readonly checkFunc: CheckFunc; readonly errorCode: number; @@ -39,6 +38,7 @@ export class StackStxAuthCommand_Err implements PoxCommand { * @param margin - Multiplier for minimum required uSTX to stack so that each * Stacker locks a different amount of uSTX across test runs. * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. */ constructor( wallet: Wallet, diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts index 4c5f7ce149..db6af5c5ba 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts @@ -26,7 +26,6 @@ export class StackStxSigCommand_Err implements PoxCommand { readonly authId: number; readonly period: number; readonly margin: number; - readonly checkFunc: CheckFunc; readonly errorCode: number; @@ -39,6 +38,7 @@ export class StackStxSigCommand_Err implements PoxCommand { * @param margin - Multiplier for minimum required uSTX to stack so that each * Stacker locks a different amount of uSTX across test runs. * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. */ constructor( wallet: Wallet, From 04e7fe683289f95fa852ad800b3014b9fdd5939b Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Wed, 22 May 2024 18:32:24 +0300 Subject: [PATCH 03/72] Remove command tracking from the command's `run` method The command run tracking will be added to the command's `check` method. --- .../tests/pox-4/pox_StackStxAuthCommand_Err.ts | 1 - .../tests/pox-4/pox_StackStxSigCommand_Err.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts index e1d0a2e113..35212e0320 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts @@ -59,7 +59,6 @@ export class StackStxAuthCommand_Err implements PoxCommand { check = (model: Readonly): boolean => this.checkFunc.call(this, model); run(model: Stub, real: Real): void { - model.trackCommandRun(this.constructor.name); const currentRewCycle = currentCycle(real.network); // The maximum amount of uSTX that can be used (per tx) with this signer diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts index db6af5c5ba..58092109f0 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts @@ -59,7 +59,6 @@ export class StackStxSigCommand_Err implements PoxCommand { check = (model: Readonly): boolean => this.checkFunc.call(this, model); run(model: Stub, real: Real): void { - model.trackCommandRun(this.constructor.name); const burnBlockHeightCV = real.network.runSnippet("burn-block-height"); const burnBlockHeight = Number( cvToValue(burnBlockHeightCV as ClarityValue), From a243d3bdf4c8cd51001edf048c359d6ce0a58ae0 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Wed, 22 May 2024 18:36:52 +0300 Subject: [PATCH 04/72] Pass the incremented burn height when calling `stack-stx` If not passed incremented, the call will result in an `ERR_INVALID_START_BURN_HEIGHT` when being sent at the limit between 2 cycles. --- .../tests/pox-4/pox_StackStxAuthCommand_Err.ts | 2 +- .../tests/pox-4/pox_StackStxSigCommand_Err.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts index 35212e0320..6889e89917 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts @@ -112,7 +112,7 @@ export class StackStxAuthCommand_Err implements PoxCommand { // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) poxAddressToTuple(this.wallet.btcAddress), // (start-burn-ht uint) - Cl.uint(burnBlockHeight), + Cl.uint(burnBlockHeight + 1), // (lock-period uint) Cl.uint(this.period), // (signer-sig (optional (buff 65))) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts index 58092109f0..f9c2cdc8d4 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts @@ -110,7 +110,7 @@ export class StackStxSigCommand_Err implements PoxCommand { // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) poxAddressToTuple(this.wallet.btcAddress), // (start-burn-ht uint) - Cl.uint(burnBlockHeight), + Cl.uint(burnBlockHeight + 1), // (lock-period uint) Cl.uint(this.period), // (signer-sig (optional (buff 65))) From 7a0c1fd96e3f8ba4392720501b252f343ca9c582 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Wed, 22 May 2024 19:01:19 +0300 Subject: [PATCH 05/72] Add the unhappy path cases for `StackStxXCommand_Err` This commit: - adds 6 unhappy path cases for the `stack-stx` PoX-4 method, 3 for each signing method (authorization or signature) - adds a dictionary that contains the PoX-4 error names and the error codes - adds the command run tracking inside the `check` method, resulting in displaying all the paths hit and the number of times. --- .../tests/pox-4/err_Commands.ts | 207 ++++++++++++++++-- 1 file changed, 194 insertions(+), 13 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 2e4259f740..08a911e68a 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -1,16 +1,27 @@ import fc from "fast-check"; -import { PoxCommand, Stacker, Stub, StxAddress, Wallet } from "./pox_CommandModel"; +import { + PoxCommand, + Stacker, + Stub, + StxAddress, + Wallet, +} from "./pox_CommandModel"; import { StackStxSigCommand_Err } from "./pox_StackStxSigCommand_Err"; import { StackStxAuthCommand_Err } from "./pox_StackStxAuthCommand_Err"; import { Simnet } from "@hirosystems/clarinet-sdk"; +const POX_4_ERRORS = { + ERR_STACKING_ALREADY_STACKED: 3, + ERR_STACKING_ALREADY_DELEGATED: 20, +}; + export function ErrCommands( wallets: Map, stackers: Map, network: Simnet, ): fc.Arbitrary[] { const cmds = [ - // StackStxAuthCommand_Err + // StackStxAuthCommand_Err_Stacking_Already_Stacked_1 fc.record({ wallet: fc.constantFrom(...wallets.values()), authId: fc.nat(), @@ -29,17 +40,102 @@ export function ErrCommands( r.authId, r.period, r.margin, - function (this: StackStxAuthCommand_Err, model: Readonly): boolean { + function ( + this: StackStxAuthCommand_Err, + model: Readonly, + ): boolean { const stacker = model.stackers.get(this.wallet.stxAddress)!; - return ( - model.stackingMinimum > 0 && !stacker.isStacking && + if ( + model.stackingMinimum > 0 && + stacker.isStacking && !stacker.hasDelegated - ); + ) { + model.trackCommandRun( + "StackStxAuthCommand_Err_Stacking_Already_Stacked_1", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_ALREADY_STACKED, + ) + ), + // StackStxAuthCommand_Err_Stacking_Already_Stacked_2 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + period: fc.integer({ min: 1, max: 12 }), + margin: fc.integer({ min: 1, max: 9 }), + }).map(( + r: { + wallet: Wallet; + authId: number; + period: number; + margin: number; + }, + ) => + new StackStxAuthCommand_Err( + r.wallet, + r.authId, + r.period, + r.margin, + function ( + this: StackStxAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.hasDelegated + ) { + model.trackCommandRun( + "StackStxAuthCommand_Err_Stacking_Already_Stacked_2", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_ALREADY_STACKED, + ) + ), + // StackStxAuthCommand_Err_Stacking_Already_Delegated + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + period: fc.integer({ min: 1, max: 12 }), + margin: fc.integer({ min: 1, max: 9 }), + }).map(( + r: { + wallet: Wallet; + authId: number; + period: number; + margin: number; + }, + ) => + new StackStxAuthCommand_Err( + r.wallet, + r.authId, + r.period, + r.margin, + function ( + this: StackStxAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + !stacker.isStacking && + stacker.hasDelegated + ) { + model.trackCommandRun( + "StackStxAuthCommand_Err_Stacking_Already_Delegated", + ); + return true; + } else return false; }, - 123, + POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, ) ), - // StackStxSigCommand_Err + // StackStxSigCommand_Err_Stacking_Already_Stacked_1 fc.record({ wallet: fc.constantFrom(...wallets.values()), authId: fc.nat(), @@ -58,14 +154,99 @@ export function ErrCommands( r.authId, r.period, r.margin, - function (this: StackStxSigCommand_Err, model: Readonly): boolean { + function ( + this: StackStxSigCommand_Err, + model: Readonly, + ): boolean { const stacker = model.stackers.get(this.wallet.stxAddress)!; - return ( - model.stackingMinimum > 0 && !stacker.isStacking && + if ( + model.stackingMinimum > 0 && + stacker.isStacking && !stacker.hasDelegated - ); + ) { + model.trackCommandRun( + "StackStxSigCommand_Err_Stacking_Already_Stacked_1", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_ALREADY_STACKED, + ) + ), + // StackStxSigCommand_Err_Stacking_Already_Stacked_2 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + period: fc.integer({ min: 1, max: 12 }), + margin: fc.integer({ min: 1, max: 9 }), + }).map(( + r: { + wallet: Wallet; + authId: number; + period: number; + margin: number; + }, + ) => + new StackStxSigCommand_Err( + r.wallet, + r.authId, + r.period, + r.margin, + function ( + this: StackStxSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.hasDelegated + ) { + model.trackCommandRun( + "StackStxSigCommand_Err_Stacking_Already_Stacked_2", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_ALREADY_STACKED, + ) + ), + // StackStxSigCommand_Err_Stacking_Already_Delegated + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + period: fc.integer({ min: 1, max: 12 }), + margin: fc.integer({ min: 1, max: 9 }), + }).map(( + r: { + wallet: Wallet; + authId: number; + period: number; + margin: number; + }, + ) => + new StackStxSigCommand_Err( + r.wallet, + r.authId, + r.period, + r.margin, + function ( + this: StackStxSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + !stacker.isStacking && + stacker.hasDelegated + ) { + model.trackCommandRun( + "StackStxSigCommand_Err_Stacking_Already_Delegated", + ); + return true; + } else return false; }, - 123, + POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, ) ), ]; From bd159e349616ce9312ba84a81afd657564d4985a Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Wed, 22 May 2024 19:02:27 +0300 Subject: [PATCH 06/72] Remove `StackStxXCommand.ts` from statistics They needed to be excluded as we have removed the command run tracking from the run method. --- .../tests/pox-4/pox-4.stateful-prop.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts index 29c57187b3..d5f7245c25 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts @@ -109,7 +109,9 @@ it("statefully interacts with PoX-4", async () => { // commands are run at least once. const statistics = fs.readdirSync(path.join(__dirname)).filter((file) => file.startsWith("pox_") && file.endsWith(".ts") && - file !== "pox_CommandModel.ts" && file !== "pox_Commands.ts" + file !== "pox_CommandModel.ts" && file !== "pox_Commands.ts" && + file !== "pox_StackStxAuthCommand_Err.ts" && + file !== "pox_StackStxSigCommand_Err.ts" ).map((file) => file.slice(4, -3)); // Remove "pox_" prefix and ".ts" suffix. // This is the initial state of the model. @@ -155,7 +157,7 @@ it("statefully interacts with PoX-4", async () => { ), { // Defines the number of test iterations to run; default is 100. - numRuns: 1000, + numRuns: 20000, // 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 From 7405ab080c49d8abacbc9cb72364c9754c04032a Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 24 May 2024 14:08:26 +0300 Subject: [PATCH 07/72] =?UTF-8?q?Add=20unhappy=20path=20for=C2=A0`revoke-d?= =?UTF-8?q?elegate-stx`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The added unhappy path tries to call revoke-delegate-stx with an address that is not delegating. --- .../tests/pox-4/err_Commands.ts | 30 +++++++++ .../pox-4/pox_RevokeDelegateStxCommand_Err.ts | 66 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 08a911e68a..a7e05dc5c7 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -9,10 +9,12 @@ import { import { StackStxSigCommand_Err } from "./pox_StackStxSigCommand_Err"; import { StackStxAuthCommand_Err } from "./pox_StackStxAuthCommand_Err"; import { Simnet } from "@hirosystems/clarinet-sdk"; +import { RevokeDelegateStxCommand_Err } from "./pox_RevokeDelegateStxCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_ALREADY_STACKED: 3, ERR_STACKING_ALREADY_DELEGATED: 20, + ERR_DELEGATION_ALREADY_REVOKED: 34, }; export function ErrCommands( @@ -249,6 +251,34 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, ) ), + // RevokeDelegateStxCommand_Err_Delegation_Already_Revoked + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + }).map(( + r: { + wallet: Wallet; + }, + ) => + new RevokeDelegateStxCommand_Err( + r.wallet, + function ( + this: RevokeDelegateStxCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + !stacker.hasDelegated + ) { + model.trackCommandRun( + "RevokeDelegateStxCommand_Err_Delegation_Already_Revoked", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_DELEGATION_ALREADY_REVOKED, + ) + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand_Err.ts new file mode 100644 index 0000000000..60b3439e8e --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand_Err.ts @@ -0,0 +1,66 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { expect } from "vitest"; +import { Cl } from "@stacks/transactions"; + +type CheckFunc = ( + this: RevokeDelegateStxCommand_Err, + model: Readonly, +) => boolean; + +export class RevokeDelegateStxCommand_Err implements PoxCommand { + readonly wallet: Wallet; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `RevokeDelegateStxCommand_Err` to revoke delegate uSTX for stacking. + * + * @param wallet - Represents the Stacker's wallet. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor(wallet: Wallet, checkFunc: CheckFunc, errorCode: number) { + this.wallet = wallet; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + // Act + const revokeDelegateStx = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "revoke-delegate-stx", + [], + this.wallet.stxAddress, + ); + + // Assert + expect(revokeDelegateStx.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.wallet.label}`, + "revoke-delegate-stx", + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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.wallet.stxAddress} revoke-delegate-stx`; + } +} From 10d0b9b385b8752db2c66e405dc7e11aded6cd65 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 24 May 2024 14:16:45 +0300 Subject: [PATCH 08/72] Remove `RevokeDelegateStxCommand_Err` from statistics The command run tracking was moved inside the command's check function. No need to report the run using the file name anymore. --- .../tests/pox-4/pox-4.stateful-prop.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts index d5f7245c25..654c500b6b 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts @@ -111,7 +111,8 @@ it("statefully interacts with PoX-4", async () => { file.startsWith("pox_") && file.endsWith(".ts") && file !== "pox_CommandModel.ts" && file !== "pox_Commands.ts" && file !== "pox_StackStxAuthCommand_Err.ts" && - file !== "pox_StackStxSigCommand_Err.ts" + file !== "pox_StackStxSigCommand_Err.ts" && + file !== "pox_RevokeDelegateStxCommand_Err.ts" ).map((file) => file.slice(4, -3)); // Remove "pox_" prefix and ".ts" suffix. // This is the initial state of the model. From 0c83016919b30b81984dcdefaead22d762eac7e8 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 24 May 2024 14:21:58 +0300 Subject: [PATCH 09/72] =?UTF-8?q?Add=20unhappy=20path=20for=C2=A0`delegate?= =?UTF-8?q?-stx`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The added unhappy path tries to call delegate-stx with an address that is already delegating. --- .../tests/pox-4/err_Commands.ts | 39 +++++++ .../tests/pox-4/pox_DelegateStxCommand_Err.ts | 104 ++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStxCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index a7e05dc5c7..6a6c3b7028 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -10,6 +10,7 @@ import { StackStxSigCommand_Err } from "./pox_StackStxSigCommand_Err"; import { StackStxAuthCommand_Err } from "./pox_StackStxAuthCommand_Err"; import { Simnet } from "@hirosystems/clarinet-sdk"; import { RevokeDelegateStxCommand_Err } from "./pox_RevokeDelegateStxCommand_Err"; +import { DelegateStxCommand_Err } from "./pox_DelegateStxCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_ALREADY_STACKED: 3, @@ -279,6 +280,44 @@ export function ErrCommands( POX_4_ERRORS.ERR_DELEGATION_ALREADY_REVOKED, ) ), + // DelegateStxCommand_Err_Stacking_Already_Delegated + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + delegateTo: fc.constantFrom(...wallets.values()), + untilBurnHt: fc.integer({ min: 1 }), + amount: fc.bigInt({ min: 0n, max: 100_000_000_000_000n }), + }) + .map(( + r: { + wallet: Wallet; + delegateTo: Wallet; + untilBurnHt: number; + amount: bigint; + }, + ) => + new DelegateStxCommand_Err( + r.wallet, + r.delegateTo, + r.untilBurnHt, + r.amount, + function ( + this: DelegateStxCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + stacker.hasDelegated + ) { + model.trackCommandRun( + "DelegateStxCommand_Err_Stacking_Already_Delegated", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, + ) + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStxCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStxCommand_Err.ts new file mode 100644 index 0000000000..138d99265f --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStxCommand_Err.ts @@ -0,0 +1,104 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { poxAddressToTuple } from "@stacks/stacking"; +import { expect } from "vitest"; +import { Cl } from "@stacks/transactions"; + +type CheckFunc = ( + this: DelegateStxCommand_Err, + model: Readonly, +) => boolean; + +export class DelegateStxCommand_Err implements PoxCommand { + readonly wallet: Wallet; + readonly delegateTo: Wallet; + readonly untilBurnHt: number; + readonly amount: bigint; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `DelegateStxCommand_Err` to delegate uSTX for stacking. + * + * @param wallet - Represents the Stacker's wallet. + * @param delegateTo - Represents the Delegatee's STX address. + * @param untilBurnHt - The burn block height until the delegation is valid. + * @param amount - The maximum amount the `Stacker` delegates the `Delegatee` + * to stack on his behalf. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + wallet: Wallet, + delegateTo: Wallet, + untilBurnHt: number, + amount: bigint, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.wallet = wallet; + this.delegateTo = delegateTo; + this.untilBurnHt = untilBurnHt; + this.amount = amount; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + // The amount of uSTX delegated by the Stacker to the Delegatee. + // Even if there are no constraints about the delegated amount, + // it will be checked in the future, when calling delegate-stack-stx. + const amountUstx = Number(this.amount); + + // Act + const delegateStx = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "delegate-stx", + [ + // (amount-ustx uint) + Cl.uint(amountUstx), + // (delegate-to principal) + Cl.principal(this.delegateTo.stxAddress), + // (until-burn-ht (optional uint)) + Cl.some(Cl.uint(this.untilBurnHt)), + // (pox-addr (optional { version: (buff 1), hashbytes: (buff 32) })) + Cl.some(poxAddressToTuple(this.delegateTo.btcAddress)), + ], + this.wallet.stxAddress, + ); + + // Assert + expect(delegateStx.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.wallet.label}`, + "delegate-stx", + "amount", + amountUstx.toString(), + "delegated to", + this.delegateTo.label, + "until", + this.untilBurnHt.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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.wallet.label} delegate-stx to ${this.delegateTo.label} until burn ht ${this.untilBurnHt}`; + } +} From 48b9032e6e600d85a9fe8dfe37a0aa447aa1ddff Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 24 May 2024 14:24:20 +0300 Subject: [PATCH 10/72] Remove `DelegateStxCommand_Err` from statistics The command run tracking was moved inside the command's check function. No need to report the run using the file name anymore. --- .../tests/pox-4/pox-4.stateful-prop.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts index 654c500b6b..04a596bbb6 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts @@ -112,7 +112,8 @@ it("statefully interacts with PoX-4", async () => { file !== "pox_CommandModel.ts" && file !== "pox_Commands.ts" && file !== "pox_StackStxAuthCommand_Err.ts" && file !== "pox_StackStxSigCommand_Err.ts" && - file !== "pox_RevokeDelegateStxCommand_Err.ts" + file !== "pox_RevokeDelegateStxCommand_Err.ts" && + file !== "pox_DelegateStxCommand_Err.ts" ).map((file) => file.slice(4, -3)); // Remove "pox_" prefix and ".ts" suffix. // This is the initial state of the model. From b38c224b2e0c99e45a8364287aa3d10e18cc2fed Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 24 May 2024 16:26:27 +0300 Subject: [PATCH 11/72] Use simnet `mineBlock` inside `StackStxAuthCommand_Err` This commit: - includes the authorization and the function call in the same block. It is needed because otherwise, it can result in issuing the authorization for the wrong reward cycle. - updates the passed start-burn-ht param, different from the StackStxSigCommand. If not doing it like this, the test fails when the command is called at the limit between 2 reward cycles. - removes unnecessary operations: retrieving the reward cycle, retrieving the unlockBurnHeight. --- .../pox-4/pox_StackStxAuthCommand_Err.ts | 102 +++++++----------- 1 file changed, 39 insertions(+), 63 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts index 6889e89917..37f32a5458 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts @@ -6,15 +6,10 @@ import { Wallet, } from "./pox_CommandModel.ts"; import { poxAddressToTuple } from "@stacks/stacking"; -import { assert, expect } from "vitest"; -import { - Cl, - ClarityType, - ClarityValue, - cvToValue, - isClarityType, -} from "@stacks/transactions"; +import { expect } from "vitest"; +import { Cl, ClarityValue, cvToValue } from "@stacks/transactions"; import { currentCycle } from "./pox_Commands.ts"; +import { tx } from "@hirosystems/clarinet-sdk"; type CheckFunc = ( this: StackStxAuthCommand_Err, @@ -66,53 +61,50 @@ export class StackStxAuthCommand_Err implements PoxCommand { // in the given reward cycle multiplied by the margin, which is a randomly // generated number passed to the constructor of this class. const maxAmount = model.stackingMinimum * this.margin; + const amountUstx = maxAmount; - const { result: setAuthorization } = real.network.callPublicFn( - "ST000000000000000000002AMW42H.pox-4", - "set-signer-key-authorization", - [ - // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) - poxAddressToTuple(this.wallet.btcAddress), - // (period uint) - Cl.uint(this.period), - // (reward-cycle uint) - Cl.uint(currentRewCycle), - // (topic (string-ascii 14)) - Cl.stringAscii("stack-stx"), - // (signer-key (buff 33)) - Cl.bufferFromHex(this.wallet.signerPubKey), - // (allowed bool) - Cl.bool(true), - // (max-amount uint) - Cl.uint(maxAmount), - // (auth-id uint) - Cl.uint(this.authId), - ], - this.wallet.stxAddress, - ); - - expect(setAuthorization).toBeOk(Cl.bool(true)); const burnBlockHeightCV = real.network.runSnippet("burn-block-height"); const burnBlockHeight = Number( cvToValue(burnBlockHeightCV as ClarityValue), ); - // The amount of uSTX to be locked in the reward cycle. For this test, we - // will use the maximum amount of uSTX that can be used (per tx) with this - // signer key. - const amountUstx = maxAmount; - // Act - const stackStx = real.network.callPublicFn( - "ST000000000000000000002AMW42H.pox-4", - "stack-stx", - [ + + // Include the authorization and the `stack-stx` transactions in a single + // block. This way we ensure both the authorization and the stack-stx + // transactions are called during the same reward cycle, so the authorization + // currentRewCycle param is relevant for the upcoming stack-stx call. + const block = real.network.mineBlock([ + tx.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "set-signer-key-authorization", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.wallet.btcAddress), + // (period uint) + Cl.uint(this.period), + // (reward-cycle uint) + Cl.uint(currentRewCycle), + // (topic (string-ascii 14)) + Cl.stringAscii("stack-stx"), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (allowed bool) + Cl.bool(true), + // (max-amount uint) + Cl.uint(maxAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ), + tx.callPublicFn("ST000000000000000000002AMW42H.pox-4", "stack-stx", [ // (amount-ustx uint) Cl.uint(amountUstx), // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) poxAddressToTuple(this.wallet.btcAddress), // (start-burn-ht uint) - Cl.uint(burnBlockHeight + 1), + Cl.uint(burnBlockHeight), // (lock-period uint) Cl.uint(this.period), // (signer-sig (optional (buff 65))) @@ -123,28 +115,12 @@ export class StackStxAuthCommand_Err implements PoxCommand { Cl.uint(maxAmount), // (auth-id uint) Cl.uint(this.authId), - ], - this.wallet.stxAddress, - ); - - const { result: rewardCycle } = real.network.callReadOnlyFn( - "ST000000000000000000002AMW42H.pox-4", - "burn-height-to-reward-cycle", - [Cl.uint(burnBlockHeight)], - this.wallet.stxAddress, - ); - assert(isClarityType(rewardCycle, ClarityType.UInt)); - - const { result: unlockBurnHeight } = real.network.callReadOnlyFn( - "ST000000000000000000002AMW42H.pox-4", - "reward-cycle-to-burn-height", - [Cl.uint(Number(rewardCycle.value) + this.period + 1)], - this.wallet.stxAddress, - ); - assert(isClarityType(unlockBurnHeight, ClarityType.UInt)); + ], this.wallet.stxAddress), + ]); // Assert - expect(stackStx.result).toBeErr(Cl.int(this.errorCode)); + expect(block[0].result).toBeOk(Cl.bool(true)); + expect(block[1].result).toBeErr(Cl.int(this.errorCode)); // 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. From 93bfa6c8c80d54214283f4350bf2be6340682e6b Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 27 May 2024 12:22:29 +0300 Subject: [PATCH 12/72] Add the unhappy path cases for `StackAggCommitSigCommand_Err` This commit: - adds 3 unhappy path cases for the `stack-aggregation-commit` PoX-4 method, called using a signature. - adds the command run tracking inside the `check` method. - adds the expected `stack-aggregation-commit` PoX-4 errors to the POX_4_ERRORS dictionary. --- .../tests/pox-4/err_Commands.ts | 89 +++++++++++++ ...ox_StackAggregationCommitSigCommand_Err.ts | 121 ++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 6a6c3b7028..8c1bdf774d 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -11,9 +11,12 @@ import { StackStxAuthCommand_Err } from "./pox_StackStxAuthCommand_Err"; import { Simnet } from "@hirosystems/clarinet-sdk"; import { RevokeDelegateStxCommand_Err } from "./pox_RevokeDelegateStxCommand_Err"; import { DelegateStxCommand_Err } from "./pox_DelegateStxCommand_Err"; +import { StackAggregationCommitSigCommand_Err } from "./pox_StackAggregationCommitSigCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_ALREADY_STACKED: 3, + ERR_STACKING_NO_SUCH_PRINCIPAL: 4, + ERR_STACKING_THRESHOLD_NOT_MET: 11, ERR_STACKING_ALREADY_DELEGATED: 20, ERR_DELEGATION_ALREADY_REVOKED: 34, }; @@ -318,6 +321,92 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, ) ), + // StackAggregationCommitSigCommand_Err_Stacking_Threshold_Not_Met + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitSigCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitSigCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + operator.lockedAddresses.length > 0 && + !(operator.amountToCommit >= model.stackingMinimum) && + operator.amountToCommit > 0 + ) { + model.trackCommandRun( + "StackAggregationCommitSigCommand_Err_Stacking_Threshold_Not_Met", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_THRESHOLD_NOT_MET, + ), + ), + // StackAggregationCommitSigCommand_Err_Stacking_No_Such_Principal_1 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitSigCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitSigCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + operator.lockedAddresses.length > 0 && + !(operator.amountToCommit >= model.stackingMinimum) && + operator.amountToCommit == 0 + ) { + model.trackCommandRun( + "StackAggregationCommitSigCommand_Err_Stacking_No_Such_Principal_1", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), + // StackAggregationCommitSigCommand_Err_Stacking_No_Such_Principal_2 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitSigCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitSigCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + !(operator.lockedAddresses.length > 0) && + !(operator.amountToCommit >= model.stackingMinimum) + ) { + model.trackCommandRun( + "StackAggregationCommitSigCommand_Err_Stacking_No_Such_Principal_2", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand_Err.ts new file mode 100644 index 0000000000..ca53b56d1c --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand_Err.ts @@ -0,0 +1,121 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { Pox4SignatureTopic, poxAddressToTuple } from "@stacks/stacking"; +import { expect } from "vitest"; +import { Cl } from "@stacks/transactions"; +import { bufferFromHex } from "@stacks/transactions/dist/cl"; +import { currentCycle } from "./pox_Commands.ts"; + +type CheckFunc = ( + this: StackAggregationCommitSigCommand_Err, + model: Readonly, +) => boolean; + +export class StackAggregationCommitSigCommand_Err implements PoxCommand { + readonly operator: Wallet; + readonly authId: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackAggregationCommitAuthCommand_Err` to lock uSTX for stacking. + * + * @param operator - Represents the `Operator`'s wallet. + * @param authId - Unique `auth-id` for the authorization. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + operator: Wallet, + authId: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.operator = operator; + this.authId = authId; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const currentRewCycle = currentCycle(real.network); + const operatorWallet = model.stackers.get(this.operator.stxAddress)!; + const committedAmount = operatorWallet.amountToCommit; + + const signerSig = this.operator.stackingClient.signPoxSignature({ + // The signer key being authorized. + signerPrivateKey: this.operator.signerPrvKey, + // The reward cycle for which the authorization is valid. + // For stack-stx and stack-extend, this refers to the reward cycle + // where the transaction is confirmed. For stack-aggregation-commit, + // this refers to the reward cycle argument in that function. + rewardCycle: currentRewCycle + 1, + // For stack-stx, this refers to lock-period. For stack-extend, + // this refers to extend-count. For stack-aggregation-commit, this is + // u1. + period: 1, + // A string representing the function where this authorization is valid. + // Either stack-stx, stack-extend, stack-increase or agg-commit. + topic: Pox4SignatureTopic.AggregateCommit, + // The PoX address that can be used with this signer key. + poxAddress: this.operator.btcAddress, + // The unique auth-id for this authorization. + authId: this.authId, + // The maximum amount of uSTX that can be used (per tx) with this signer + // key. + maxAmount: committedAmount, + }); + + // Act + const stackAggregationCommit = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-aggregation-commit", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.operator.btcAddress), + // (reward-cycle uint) + Cl.uint(currentRewCycle + 1), + // (signer-sig (optional (buff 65))) + Cl.some(bufferFromHex(signerSig)), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.operator.signerPubKey), + // (max-amount uint) + Cl.uint(committedAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.operator.stxAddress, + ); + + // Assert + expect(stackAggregationCommit.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.operator.label}`, + "stack-agg-commit", + "amount committed", + committedAmount.toString(), + "signature", + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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}`; + } +} From 16311d89ec1c4b7cbc9c60a9b0e1f4fdcb3d6aca Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 27 May 2024 12:26:54 +0300 Subject: [PATCH 13/72] Remove `StackAggregationCommitSigCommand_Err` from statistics The command run tracking was moved inside the command's check function. No need to report the run using the file name anymore. --- .../tests/pox-4/pox-4.stateful-prop.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts index 04a596bbb6..edf454eafa 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts @@ -113,7 +113,8 @@ it("statefully interacts with PoX-4", async () => { file !== "pox_StackStxAuthCommand_Err.ts" && file !== "pox_StackStxSigCommand_Err.ts" && file !== "pox_RevokeDelegateStxCommand_Err.ts" && - file !== "pox_DelegateStxCommand_Err.ts" + file !== "pox_DelegateStxCommand_Err.ts" && + file !== "pox_StackAggregationCommitSigCommand_Err.ts" ).map((file) => file.slice(4, -3)); // Remove "pox_" prefix and ".ts" suffix. // This is the initial state of the model. From a8e7ea3d0febf0f8a31a02fdbca790241f998974 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 27 May 2024 13:29:41 +0300 Subject: [PATCH 14/72] Add the unhappy path cases for `StackAggCommitAuthCommand_Err` This commit: - adds 3 unhappy path cases for the `stack-aggregation-commit` PoX-4 method, called using an authorization. - adds the command run tracking inside the `check` method. --- .../tests/pox-4/err_Commands.ts | 87 ++++++++++++ ...x_StackAggregationCommitAuthCommand_Err.ts | 127 ++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 8c1bdf774d..8276d02f12 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -12,6 +12,7 @@ import { Simnet } from "@hirosystems/clarinet-sdk"; import { RevokeDelegateStxCommand_Err } from "./pox_RevokeDelegateStxCommand_Err"; import { DelegateStxCommand_Err } from "./pox_DelegateStxCommand_Err"; import { StackAggregationCommitSigCommand_Err } from "./pox_StackAggregationCommitSigCommand_Err"; +import { StackAggregationCommitAuthCommand_Err } from "./pox_StackAggregationCommitAuthCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_ALREADY_STACKED: 3, @@ -407,6 +408,92 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, ), ), + // StackAggregationCommitAuthCommand_Err_Stacking_Threshold_Not_Met + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitAuthCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitAuthCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + operator.lockedAddresses.length > 0 && + !(operator.amountToCommit >= model.stackingMinimum) && + operator.amountToCommit > 0 + ) { + model.trackCommandRun( + "StackAggregationCommitAuthCommand_Err_Stacking_Threshold_Not_Met", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_THRESHOLD_NOT_MET, + ), + ), + // StackAggregationCommitAuthCommand_Err_Stacking_No_Such_Principal_1 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitAuthCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitAuthCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + operator.lockedAddresses.length > 0 && + !(operator.amountToCommit >= model.stackingMinimum) && + operator.amountToCommit === 0 + ) { + model.trackCommandRun( + "StackAggregationCommitAuthCommand_Err_Stacking_No_Such_Principal_1", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), + // StackAggregationCommitAuthCommand_Err_Stacking_No_Such_Principal_2 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitAuthCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitAuthCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + !(operator.lockedAddresses.length > 0) && + !(operator.amountToCommit >= model.stackingMinimum) + ) { + model.trackCommandRun( + "StackAggregationCommitAuthCommand_Err_Stacking_No_Such_Principal_2", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand_Err.ts new file mode 100644 index 0000000000..3580061fae --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand_Err.ts @@ -0,0 +1,127 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { poxAddressToTuple } from "@stacks/stacking"; +import { expect } from "vitest"; +import { Cl } from "@stacks/transactions"; +import { currentCycle } from "./pox_Commands.ts"; +import { tx } from "@hirosystems/clarinet-sdk"; + +type CheckFunc = ( + this: StackAggregationCommitAuthCommand_Err, + model: Readonly, +) => boolean; + +export class StackAggregationCommitAuthCommand_Err implements PoxCommand { + readonly operator: Wallet; + readonly authId: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackAggregationCommitAuthCommand_Err` to lock uSTX for stacking. + * + * @param operator - Represents the `Operator`'s wallet. + * @param authId - Unique `auth-id` for the authorization. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + operator: Wallet, + authId: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.operator = operator; + this.authId = authId; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const currentRewCycle = currentCycle(real.network); + const operatorWallet = model.stackers.get(this.operator.stxAddress)!; + const committedAmount = operatorWallet.amountToCommit; + + // Include the authorization and the `stack-aggregation-commit` transactions + // in a single block. This way we ensure both the authorization and the + // stack-aggregation-commit transactions are called during the same reward + // cycle, so the authorization currentRewCycle param is relevant for the + // upcoming stack-aggregation-commit call. + const block = real.network.mineBlock([ + tx.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "set-signer-key-authorization", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.operator.btcAddress), + // (period uint) + Cl.uint(1), + // (reward-cycle uint) + Cl.uint(currentRewCycle + 1), + // (topic (string-ascii 14)) + Cl.stringAscii("agg-commit"), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.operator.signerPubKey), + // (allowed bool) + Cl.bool(true), + // (max-amount uint) + Cl.uint(committedAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.operator.stxAddress, + ), + tx.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-aggregation-commit", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.operator.btcAddress), + // (reward-cycle uint) + Cl.uint(currentRewCycle + 1), + // (signer-sig (optional (buff 65))) + Cl.none(), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.operator.signerPubKey), + // (max-amount uint) + Cl.uint(committedAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.operator.stxAddress, + ), + ]); + + // Assert + expect(block[0].result).toBeOk(Cl.bool(true)); + expect(block[1].result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.operator.label}`, + "stack-agg-commit", + "amount committed", + committedAmount.toString(), + "authorization", + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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}`; + } +} From b5929989ecb5971c61b25d7b6f3ba9cfe6a96f3c Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 27 May 2024 13:31:25 +0300 Subject: [PATCH 15/72] Remove `StackAggregationCommitAuthCommand_Err` from statistics The command run tracking was moved inside the command's check function. No need to report the run using the file name anymore. --- .../tests/pox-4/pox-4.stateful-prop.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts index edf454eafa..e007cbb2ad 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts @@ -114,7 +114,8 @@ it("statefully interacts with PoX-4", async () => { file !== "pox_StackStxSigCommand_Err.ts" && file !== "pox_RevokeDelegateStxCommand_Err.ts" && file !== "pox_DelegateStxCommand_Err.ts" && - file !== "pox_StackAggregationCommitSigCommand_Err.ts" + file !== "pox_StackAggregationCommitSigCommand_Err.ts" && + file !== "pox_StackAggregationCommitAuthCommand_Err.ts" ).map((file) => file.slice(4, -3)); // Remove "pox_" prefix and ".ts" suffix. // This is the initial state of the model. From 41c0386b4fc40499e677e5c78d474b3d82b83652 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 27 May 2024 15:23:30 +0300 Subject: [PATCH 16/72] Use strict equality inside generator --- .../tests/pox-4/err_Commands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 8276d02f12..3c3d242d33 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -369,7 +369,7 @@ export function ErrCommands( if ( operator.lockedAddresses.length > 0 && !(operator.amountToCommit >= model.stackingMinimum) && - operator.amountToCommit == 0 + operator.amountToCommit === 0 ) { model.trackCommandRun( "StackAggregationCommitSigCommand_Err_Stacking_No_Such_Principal_1", From 788f986652015d5ccb6b4eac173c6936cb83b908 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 27 May 2024 15:44:36 +0300 Subject: [PATCH 17/72] Order statistics alphabetically This commit improves the unhappy paths execution visibility after the test suite run is complete. --- .../tests/pox-4/pox_CommandModel.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_CommandModel.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_CommandModel.ts index 6d4d582b58..b867994889 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_CommandModel.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_CommandModel.ts @@ -46,7 +46,13 @@ export class Stub { reportCommandRuns() { console.log("Command run method execution counts:"); - this.statistics.forEach((count, commandName) => { + const orderedStatistics = Array.from(this.statistics.entries()).sort( + ([keyA], [keyB]) => { + return keyA.localeCompare(keyB); + }, + ); + + orderedStatistics.forEach(([commandName, count]) => { console.log(`${commandName}: ${count}`); }); } From 23e6bc1f91e236547911aed3384407688b4e9c35 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 27 May 2024 22:53:56 +0300 Subject: [PATCH 18/72] Add the unhappy path cases for `StackAggCommitIndexedSigCommand_Err` This commit: - adds 3 unhappy path cases for the `stack-aggregation-commit-indexed` PoX-4 method, called using a signature. - adds the command run tracking inside the `check` method. --- .../tests/pox-4/err_Commands.ts | 87 ++++++++++++ ...kAggregationCommitIndexedSigCommand_Err.ts | 124 ++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 3c3d242d33..bfe600e594 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -13,6 +13,7 @@ import { RevokeDelegateStxCommand_Err } from "./pox_RevokeDelegateStxCommand_Err import { DelegateStxCommand_Err } from "./pox_DelegateStxCommand_Err"; import { StackAggregationCommitSigCommand_Err } from "./pox_StackAggregationCommitSigCommand_Err"; import { StackAggregationCommitAuthCommand_Err } from "./pox_StackAggregationCommitAuthCommand_Err"; +import { StackAggregationCommitIndexedSigCommand_Err } from "./pox_StackAggregationCommitIndexedSigCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_ALREADY_STACKED: 3, @@ -494,6 +495,92 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, ), ), + // StackAggregationCommitIndexedSigCommand_Err_Stacking_Threshold_Not_Met + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitIndexedSigCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitIndexedSigCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + operator.lockedAddresses.length > 0 && + !(operator.amountToCommit >= model.stackingMinimum) && + operator.amountToCommit > 0 + ) { + model.trackCommandRun( + "StackAggregationCommitIndexedSigCommand_Err_Stacking_Threshold_Not_Met", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_THRESHOLD_NOT_MET, + ), + ), + // StackAggregationCommitIndexedSigCommand_Err_Stacking_No_Such_Principal_1 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitIndexedSigCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitIndexedSigCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + operator.lockedAddresses.length > 0 && + !(operator.amountToCommit >= model.stackingMinimum) && + !(operator.amountToCommit > 0) + ) { + model.trackCommandRun( + "StackAggregationCommitIndexedSigCommand_Err_Stacking_No_Such_Principal_1", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), + // StackAggregationCommitIndexedSigCommand_Err_Stacking_No_Such_Principal_2 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitIndexedSigCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitIndexedSigCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + !(operator.lockedAddresses.length > 0) && + !(operator.amountToCommit >= model.stackingMinimum) + ) { + model.trackCommandRun( + "StackAggregationCommitIndexedSigCommand_Err_Stacking_No_Such_Principal_2", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand_Err.ts new file mode 100644 index 0000000000..22b5a4f923 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand_Err.ts @@ -0,0 +1,124 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { Pox4SignatureTopic, poxAddressToTuple } from "@stacks/stacking"; +import { expect } from "vitest"; +import { Cl } from "@stacks/transactions"; +import { bufferFromHex } from "@stacks/transactions/dist/cl"; +import { currentCycle } from "./pox_Commands.ts"; + +type CheckFunc = ( + this: StackAggregationCommitIndexedSigCommand_Err, + model: Readonly, +) => boolean; + +export class StackAggregationCommitIndexedSigCommand_Err implements PoxCommand { + readonly operator: Wallet; + readonly authId: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackAggregationCommitIndexedSigCommand_Err` to lock uSTX + * for stacking. + * + * @param operator - Represents the `Operator`'s wallet. + * @param authId - Unique `auth-id` for the authorization. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + operator: Wallet, + authId: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.operator = operator; + this.authId = authId; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const currentRewCycle = currentCycle(real.network); + const operatorWallet = model.stackers.get(this.operator.stxAddress)!; + const committedAmount = operatorWallet.amountToCommit; + + const signerSig = this.operator.stackingClient.signPoxSignature({ + // The signer key being authorized. + signerPrivateKey: this.operator.signerPrvKey, + // The reward cycle for which the authorization is valid. + // For stack-stx and stack-extend, this refers to the reward cycle + // where the transaction is confirmed. For stack-aggregation-commit, + // this refers to the reward cycle argument in that function. + rewardCycle: currentRewCycle + 1, + // For stack-stx, this refers to lock-period. For stack-extend, + // this refers to extend-count. For stack-aggregation-commit, this is + // u1. + period: 1, + // A string representing the function where this authorization is valid. + // Either stack-stx, stack-extend, stack-increase or agg-commit. + topic: Pox4SignatureTopic.AggregateCommit, + // The PoX address that can be used with this signer key. + poxAddress: this.operator.btcAddress, + // The unique auth-id for this authorization. + authId: this.authId, + // The maximum amount of uSTX that can be used (per tx) with this signer + // key. + maxAmount: committedAmount, + }); + + // Act + const stackAggregationCommitIndexed = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-aggregation-commit-indexed", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.operator.btcAddress), + // (reward-cycle uint) + Cl.uint(currentRewCycle + 1), + // (signer-sig (optional (buff 65))) + Cl.some(bufferFromHex(signerSig)), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.operator.signerPubKey), + // (max-amount uint) + Cl.uint(committedAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.operator.stxAddress, + ); + + // Assert + expect(stackAggregationCommitIndexed.result).toBeErr( + Cl.int(this.errorCode), + ); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.operator.label}`, + "stack-agg-commit-indexed", + "amount committed", + committedAmount.toString(), + "authorization", + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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}`; + } +} From 74b4a92e221d5360072362893f31f721fb4f4504 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Tue, 28 May 2024 14:59:18 +0300 Subject: [PATCH 19/72] Add the unhappy path cases for `StackAggCommitIndexedAuthCommand_Err` This commit: - adds 3 unhappy path cases for the `stack-aggregation-commit-indexed` PoX-4 method, called using an authorization. - adds the command run tracking inside the `check` method. --- .../tests/pox-4/err_Commands.ts | 87 ++++++++++++ ...AggregationCommitIndexedAuthCommand_Err.ts | 133 ++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index bfe600e594..4bdb6f5da3 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -14,6 +14,7 @@ import { DelegateStxCommand_Err } from "./pox_DelegateStxCommand_Err"; import { StackAggregationCommitSigCommand_Err } from "./pox_StackAggregationCommitSigCommand_Err"; import { StackAggregationCommitAuthCommand_Err } from "./pox_StackAggregationCommitAuthCommand_Err"; import { StackAggregationCommitIndexedSigCommand_Err } from "./pox_StackAggregationCommitIndexedSigCommand_Err"; +import { StackAggregationCommitIndexedAuthCommand_Err } from "./pox_StackAggregationCommitIndexedAuthCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_ALREADY_STACKED: 3, @@ -581,6 +582,92 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, ), ), + // StackAggregationCommitIndexedAuthCommand_Err_Stacking_No_Such_Principal_1 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitIndexedAuthCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitIndexedAuthCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + operator.lockedAddresses.length > 0 && + !(operator.amountToCommit >= model.stackingMinimum) && + !(operator.amountToCommit > 0) + ) { + model.trackCommandRun( + "StackAggregationCommitIndexedAuthCommand_Err_Stacking_No_Such_Principal_1", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), + // StackAggregationCommitIndexedAuthCommand_Err_Stacking_No_Such_Principal_2 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitIndexedAuthCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitIndexedAuthCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + !(operator.lockedAddresses.length > 0) && + !(operator.amountToCommit >= model.stackingMinimum) + ) { + model.trackCommandRun( + "StackAggregationCommitIndexedAuthCommand_Err_Stacking_No_Such_Principal_2", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), + // StackAggregationCommitIndexedAuthCommand_Err_Stacking_Threshold_Not_Met + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitIndexedAuthCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitIndexedAuthCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + operator.lockedAddresses.length > 0 && + !(operator.amountToCommit >= model.stackingMinimum) && + operator.amountToCommit > 0 + ) { + model.trackCommandRun( + "StackAggregationCommitIndexedAuthCommand_Err_Stacking_Threshold_Not_Met", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_THRESHOLD_NOT_MET, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand_Err.ts new file mode 100644 index 0000000000..92ebfa0d19 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand_Err.ts @@ -0,0 +1,133 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { poxAddressToTuple } from "@stacks/stacking"; +import { expect } from "vitest"; +import { Cl } from "@stacks/transactions"; +import { currentCycle } from "./pox_Commands.ts"; +import { tx } from "@hirosystems/clarinet-sdk"; + +type CheckFunc = ( + this: StackAggregationCommitIndexedAuthCommand_Err, + model: Readonly, +) => boolean; + +export class StackAggregationCommitIndexedAuthCommand_Err + implements PoxCommand { + readonly operator: Wallet; + readonly authId: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackAggregationCommitIndexedAuthCommand_Err` to lock uSTX + * for stacking. + * + * @param operator - Represents the `Operator`'s wallet. + * @param authId - Unique `auth-id` for the authorization. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + operator: Wallet, + authId: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.operator = operator; + this.authId = authId; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const currentRewCycle = currentCycle(real.network); + const operatorWallet = model.stackers.get(this.operator.stxAddress)!; + const committedAmount = operatorWallet.amountToCommit; + + // Act + + // Include the authorization and the `stack-aggregation-commit-indexed` + // transactions in a single block. This way we ensure both the authorization + // and the stack-aggregation-commit-indexed transactions are called during + // the same reward cycle, so the authorization currentRewCycle param is + // relevant for the upcoming stack-aggregation-commit-indexed call. + const block = real.network.mineBlock([ + tx.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "set-signer-key-authorization", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.operator.btcAddress), + // (period uint) + Cl.uint(1), + // (reward-cycle uint) + Cl.uint(currentRewCycle + 1), + // (topic (string-ascii 14)) + Cl.stringAscii("agg-commit"), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.operator.signerPubKey), + // (allowed bool) + Cl.bool(true), + // (max-amount uint) + Cl.uint(committedAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.operator.stxAddress, + ), + tx.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-aggregation-commit-indexed", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.operator.btcAddress), + // (reward-cycle uint) + Cl.uint(currentRewCycle + 1), + // (signer-sig (optional (buff 65))) + Cl.none(), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.operator.signerPubKey), + // (max-amount uint) + Cl.uint(committedAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.operator.stxAddress, + ), + ]); + + // Assert + expect(block[0].result).toBeOk(Cl.bool(true)); + expect(block[1].result).toBeErr( + Cl.int(this.errorCode), + ); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.operator.label}`, + "stack-agg-commit-indexed", + "amount committed", + committedAmount.toString(), + "authorization", + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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}`; + } +} From 91f99db0e1b1a8a671f1a54689361d2eb5124b6d Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Tue, 28 May 2024 15:09:19 +0300 Subject: [PATCH 20/72] Remove all files containing `_Err` from command tracking The command run tracking for the unhappy paths was moved inside the commands' check function. No need to report the run using the file name anymore. --- .../tests/pox-4/pox-4.stateful-prop.test.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts index e007cbb2ad..1f15e6a56c 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts @@ -110,12 +110,7 @@ it("statefully interacts with PoX-4", async () => { const statistics = fs.readdirSync(path.join(__dirname)).filter((file) => file.startsWith("pox_") && file.endsWith(".ts") && file !== "pox_CommandModel.ts" && file !== "pox_Commands.ts" && - file !== "pox_StackStxAuthCommand_Err.ts" && - file !== "pox_StackStxSigCommand_Err.ts" && - file !== "pox_RevokeDelegateStxCommand_Err.ts" && - file !== "pox_DelegateStxCommand_Err.ts" && - file !== "pox_StackAggregationCommitSigCommand_Err.ts" && - file !== "pox_StackAggregationCommitAuthCommand_Err.ts" + !file.includes("_Err") ).map((file) => file.slice(4, -3)); // Remove "pox_" prefix and ".ts" suffix. // This is the initial state of the model. From 48a759aa574f8fd6baab91b650445e0482a3d360 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Tue, 28 May 2024 16:00:05 +0300 Subject: [PATCH 21/72] Add one unhappy path case for `StackAggIncreaseCommand_Err` This commit: - adds one unhappy path case for the `stack-aggregation-increase` PoX-4 method, called using an authorization. - adds the command run tracking inside the `check` method. --- .../tests/pox-4/err_Commands.ts | 44 ++++++ ...pox_StackAggregationIncreaseCommand_Err.ts | 143 ++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationIncreaseCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 4bdb6f5da3..26da594c60 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -15,6 +15,7 @@ import { StackAggregationCommitSigCommand_Err } from "./pox_StackAggregationComm import { StackAggregationCommitAuthCommand_Err } from "./pox_StackAggregationCommitAuthCommand_Err"; import { StackAggregationCommitIndexedSigCommand_Err } from "./pox_StackAggregationCommitIndexedSigCommand_Err"; import { StackAggregationCommitIndexedAuthCommand_Err } from "./pox_StackAggregationCommitIndexedAuthCommand_Err"; +import { StackAggregationIncreaseCommand_Err } from "./pox_StackAggregationIncreaseCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_ALREADY_STACKED: 3, @@ -668,6 +669,49 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_THRESHOLD_NOT_MET, ), ), + // StackAggregationIncreaseCommand_Err_Stacking_No_Such_Principal + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).chain((r) => { + const operator = stackers.get(r.wallet.stxAddress)!; + const committedRewCycleIndexesOrFallback = + operator.committedRewCycleIndexes.length > 0 + ? operator.committedRewCycleIndexes + : [-1]; + return fc + .record({ + rewardCycleIndex: fc.constantFrom( + ...committedRewCycleIndexesOrFallback, + ), + }) + .map((cycleIndex) => ({ ...r, ...cycleIndex })); + }) + .map( + (r: { wallet: Wallet; rewardCycleIndex: number; authId: number }) => + new StackAggregationIncreaseCommand_Err( + r.wallet, + r.rewardCycleIndex, + r.authId, + function ( + this: StackAggregationIncreaseCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + if ( + operator.lockedAddresses.length > 0 && + this.rewardCycleIndex >= 0 && + !(operator.amountToCommit > 0) + ) { + model.trackCommandRun( + "StackAggregationIncreaseCommand_Err_Stacking_No_Such_Principal", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationIncreaseCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationIncreaseCommand_Err.ts new file mode 100644 index 0000000000..26fc49eb60 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationIncreaseCommand_Err.ts @@ -0,0 +1,143 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { Pox4SignatureTopic, poxAddressToTuple } from "@stacks/stacking"; +import { expect } from "vitest"; +import { Cl, cvToJSON } from "@stacks/transactions"; +import { bufferFromHex } from "@stacks/transactions/dist/cl"; +import { currentCycle } from "./pox_Commands.ts"; + +type CheckFunc = ( + this: StackAggregationIncreaseCommand_Err, + model: Readonly, +) => boolean; + +export class StackAggregationIncreaseCommand_Err implements PoxCommand { + readonly operator: Wallet; + readonly rewardCycleIndex: number; + readonly authId: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackAggregationIncreaseCommand_Err` to commit partially + * stacked STX to a PoX address which has already received some STX. + * + * @param operator - Represents the `Operator`'s wallet. + * @param rewardCycleIndex - The cycle index to increase the commit for. + * @param authId - Unique `auth-id` for the authorization. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + operator: Wallet, + rewardCycleIndex: number, + authId: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.operator = operator; + this.rewardCycleIndex = rewardCycleIndex; + this.authId = authId; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const currentRewCycle = currentCycle(real.network); + + const operatorWallet = model.stackers.get(this.operator.stxAddress)!; + const committedAmount = operatorWallet.amountToCommit; + + const existingEntryCV = real.network.getMapEntry( + "ST000000000000000000002AMW42H.pox-4", + "reward-cycle-pox-address-list", + Cl.tuple({ + index: Cl.uint(this.rewardCycleIndex), + "reward-cycle": Cl.uint(currentRewCycle + 1), + }), + ); + + const totalStackedBefore = + cvToJSON(existingEntryCV).value.value["total-ustx"].value; + const maxAmount = committedAmount + Number(totalStackedBefore); + + const signerSig = this.operator.stackingClient.signPoxSignature({ + // The signer key being authorized. + signerPrivateKey: this.operator.signerPrvKey, + // The reward cycle for which the authorization is valid. + // For stack-stx and stack-extend, this refers to the reward cycle + // where the transaction is confirmed. For stack-aggregation-commit, + // this refers to the reward cycle argument in that function. + rewardCycle: currentRewCycle + 1, + // For stack-stx, this refers to lock-period. For stack-extend, + // this refers to extend-count. For stack-aggregation-commit, this is + // u1. + period: 1, + // A string representing the function where this authorization is valid. + // Either stack-stx, stack-extend, stack-increase, agg-commit or agg-increase. + topic: Pox4SignatureTopic.AggregateIncrease, + // The PoX address that can be used with this signer key. + poxAddress: this.operator.btcAddress, + // The unique auth-id for this authorization. + authId: this.authId, + // The maximum amount of uSTX that can be used (per tx) with this signer + // key. + maxAmount: maxAmount, + }); + + // Act + const stackAggregationIncrease = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-aggregation-increase", + [ + // (pox-addr { version: (buff 1), hashbytes: (buff 32) }) + poxAddressToTuple(this.operator.btcAddress), + // (reward-cycle uint) + Cl.uint(currentRewCycle + 1), + // (reward-cycle-index uint)) + Cl.uint(this.rewardCycleIndex), + // (signer-sig (optional (buff 65))) + Cl.some(bufferFromHex(signerSig)), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.operator.signerPubKey), + // (max-amount uint) + Cl.uint(maxAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.operator.stxAddress, + ); + + // Assert + expect(stackAggregationIncrease.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.operator.label}`, + "stack-agg-increase", + "amount committed", + committedAmount.toString(), + "cycle index", + this.rewardCycleIndex.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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-increase for index ${this.rewardCycleIndex}`; + } +} From 70990a4d7bee04ea1448b5ea416f3977a3051d2c Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Tue, 28 May 2024 20:22:13 +0300 Subject: [PATCH 22/72] Format using `deno` according to the other generators --- .../tests/pox-4/err_Commands.ts | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 26da594c60..4a01ac827a 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -686,32 +686,31 @@ export function ErrCommands( ), }) .map((cycleIndex) => ({ ...r, ...cycleIndex })); - }) - .map( - (r: { wallet: Wallet; rewardCycleIndex: number; authId: number }) => - new StackAggregationIncreaseCommand_Err( - r.wallet, - r.rewardCycleIndex, - r.authId, - function ( - this: StackAggregationIncreaseCommand_Err, - model: Readonly, - ): boolean { - const operator = model.stackers.get(this.operator.stxAddress)!; - if ( - operator.lockedAddresses.length > 0 && - this.rewardCycleIndex >= 0 && - !(operator.amountToCommit > 0) - ) { - model.trackCommandRun( - "StackAggregationIncreaseCommand_Err_Stacking_No_Such_Principal", - ); - return true; - } else return false; - }, - POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, - ), - ), + }).map( + (r: { wallet: Wallet; rewardCycleIndex: number; authId: number }) => + new StackAggregationIncreaseCommand_Err( + r.wallet, + r.rewardCycleIndex, + r.authId, + function ( + this: StackAggregationIncreaseCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + if ( + operator.lockedAddresses.length > 0 && + this.rewardCycleIndex >= 0 && + !(operator.amountToCommit > 0) + ) { + model.trackCommandRun( + "StackAggregationIncreaseCommand_Err_Stacking_No_Such_Principal", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), ]; return cmds; From c66e6794b8022e3f1aefcc663af3d7e1699ed375 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Wed, 29 May 2024 14:18:18 +0300 Subject: [PATCH 23/72] Add unhappy path cases for `DelegateStackStxCommand_Err` This commit: - adds 3 unhappy path cases for the `delegate-stack-stx` PoX-4 method. - adds the command run tracking inside the `check` method. - adds the expected `delegate-stack-stx` PoX-4 errors to the `POX_4_ERRORS` dictionary. - exports the `nextCycleFirstBlock` method from pox_commands, as it is used inside err_Commands. --- .../tests/pox-4/err_Commands.ts | 226 ++++++++++++++++++ .../tests/pox-4/pox_Commands.ts | 2 +- .../pox-4/pox_DelegateStackStxCommand_Err.ts | 105 ++++++++ 3 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 4a01ac827a..74c0170af5 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -16,12 +16,16 @@ import { StackAggregationCommitAuthCommand_Err } from "./pox_StackAggregationCom import { StackAggregationCommitIndexedSigCommand_Err } from "./pox_StackAggregationCommitIndexedSigCommand_Err"; import { StackAggregationCommitIndexedAuthCommand_Err } from "./pox_StackAggregationCommitIndexedAuthCommand_Err"; import { StackAggregationIncreaseCommand_Err } from "./pox_StackAggregationIncreaseCommand_Err"; +import { currentCycleFirstBlock, nextCycleFirstBlock } from "./pox_Commands"; +import { DelegateStackStxCommand_Err } from "./pox_DelegateStackStxCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_ALREADY_STACKED: 3, ERR_STACKING_NO_SUCH_PRINCIPAL: 4, + ERR_STACKING_PERMISSION_DENIED: 9, ERR_STACKING_THRESHOLD_NOT_MET: 11, ERR_STACKING_ALREADY_DELEGATED: 20, + ERR_DELEGATION_TOO_MUCH_LOCKED: 22, ERR_DELEGATION_ALREADY_REVOKED: 34, }; @@ -711,6 +715,228 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, ), ), + // DelegateStackStxCommand_Err_Delegation_Too_Much_Locked + fc.record({ + operator: fc.constantFrom(...wallets.values()), + startBurnHt: fc.integer({ + min: currentCycleFirstBlock(network), + max: nextCycleFirstBlock(network), + }), + period: fc.integer({ min: 1, max: 12 }), + }).chain((r) => { + const operator = stackers.get(r.operator.stxAddress)!; + // Determine available stackers based on the operator + const availableStackers = operator.poolMembers.length > 0 + ? operator.poolMembers + : [r.operator.stxAddress]; + + return fc.record({ + stacker: fc.constantFrom(...availableStackers), + }).map((stacker) => ({ + ...r, + stacker: wallets.get(stacker.stacker)!, + })).chain((resultWithStacker) => { + return fc.record({ + unlockBurnHt: fc.constant( + currentCycleFirstBlock(network) + + 1050 * (resultWithStacker.period + 1), + ), + }).map((additionalProps) => ({ + ...resultWithStacker, + ...additionalProps, + })); + }).chain((resultWithUnlockHeight) => { + return fc.record({ + amount: fc.bigInt({ + min: 0n, + max: 100_000_000_000_000n, + }), + }).map((amountProps) => ({ + ...resultWithUnlockHeight, + ...amountProps, + })); + }); + }).map((finalResult) => { + return new DelegateStackStxCommand_Err( + finalResult.operator, + finalResult.stacker, + finalResult.period, + finalResult.amount, + finalResult.unlockBurnHt, + function ( + this: DelegateStackStxCommand_Err, + model: Readonly, + ): boolean { + const operatorWallet = model.stackers.get(this.operator.stxAddress)!; + const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; + if ( + model.stackingMinimum > 0 && + !stackerWallet.isStacking && + stackerWallet.hasDelegated && + !(stackerWallet.delegatedMaxAmount >= Number(this.amountUstx)) && + Number(this.amountUstx) <= stackerWallet.ustxBalance && + Number(this.amountUstx) >= model.stackingMinimum && + operatorWallet.poolMembers.includes(this.stacker.stxAddress) && + this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt + ) { + model.trackCommandRun( + "DelegateStackStxCommand_Err_Delegation_Too_Much_Locked", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_DELEGATION_TOO_MUCH_LOCKED, + ); + }), + // DelegateStackStxCommand_Err_Stacking_Permission_Denied + fc.record({ + operator: fc.constantFrom(...wallets.values()), + startBurnHt: fc.integer({ + min: currentCycleFirstBlock(network), + max: nextCycleFirstBlock(network), + }), + period: fc.integer({ min: 1, max: 12 }), + }).chain((r) => { + const operator = stackers.get(r.operator.stxAddress)!; + // Determine available stackers based on the operator + const availableStackers = operator.poolMembers.length > 0 + ? operator.poolMembers + : [r.operator.stxAddress]; + + return fc.record({ + stacker: fc.constantFrom(...availableStackers), + }).map((stacker) => ({ + ...r, + stacker: wallets.get(stacker.stacker)!, + })).chain((resultWithStacker) => { + return fc.record({ + unlockBurnHt: fc.constant( + currentCycleFirstBlock(network) + + 1050 * (resultWithStacker.period + 1), + ), + }).map((additionalProps) => ({ + ...resultWithStacker, + ...additionalProps, + })); + }).chain((resultWithUnlockHeight) => { + return fc.record({ + amount: fc.bigInt({ + min: 0n, + max: BigInt( + stackers.get(resultWithUnlockHeight.stacker.stxAddress)! + .delegatedMaxAmount, + ), + }), + }).map((amountProps) => ({ + ...resultWithUnlockHeight, + ...amountProps, + })); + }); + }).map((finalResult) => { + return new DelegateStackStxCommand_Err( + finalResult.operator, + finalResult.stacker, + finalResult.period, + finalResult.amount, + finalResult.unlockBurnHt, + function ( + this: DelegateStackStxCommand_Err, + model: Readonly, + ): boolean { + const operatorWallet = model.stackers.get(this.operator.stxAddress)!; + const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; + if ( + model.stackingMinimum > 0 && + !stackerWallet.isStacking && + stackerWallet.hasDelegated && + stackerWallet.delegatedMaxAmount >= Number(this.amountUstx) && + Number(this.amountUstx) <= stackerWallet.ustxBalance && + Number(this.amountUstx) >= model.stackingMinimum && + !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && + this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt + ) { + model.trackCommandRun( + "DelegateStackStxCommand_Err_Stacking_Permission_Denied_1", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_PERMISSION_DENIED, + ); + }), + // DelegateStackStxCommand_Err_Stacking_Permission_Denied_2 + fc.record({ + operator: fc.constantFrom(...wallets.values()), + startBurnHt: fc.integer({ + min: currentCycleFirstBlock(network), + max: nextCycleFirstBlock(network), + }), + period: fc.integer({ min: 1, max: 12 }), + }).chain((r) => { + const operator = stackers.get(r.operator.stxAddress)!; + // Determine available stackers based on the operator + const availableStackers = operator.poolMembers.length > 0 + ? operator.poolMembers + : [r.operator.stxAddress]; + + return fc.record({ + stacker: fc.constantFrom(...availableStackers), + }).map((stacker) => ({ + ...r, + stacker: wallets.get(stacker.stacker)!, + })).chain((resultWithStacker) => { + return fc.record({ + unlockBurnHt: fc.constant( + currentCycleFirstBlock(network) + + 1050 * (resultWithStacker.period + 1), + ), + }).map((additionalProps) => ({ + ...resultWithStacker, + ...additionalProps, + })); + }).chain((resultWithUnlockHeight) => { + return fc.record({ + amount: fc.bigInt({ + min: 0n, + max: 100_000_000_000_000n, + }), + }).map((amountProps) => ({ + ...resultWithUnlockHeight, + ...amountProps, + })); + }); + }).map((finalResult) => { + return new DelegateStackStxCommand_Err( + finalResult.operator, + finalResult.stacker, + finalResult.period, + finalResult.amount, + finalResult.unlockBurnHt, + function ( + this: DelegateStackStxCommand_Err, + model: Readonly, + ): boolean { + const operatorWallet = model.stackers.get(this.operator.stxAddress)!; + const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; + if ( + model.stackingMinimum > 0 && + !stackerWallet.isStacking && + !(stackerWallet.hasDelegated) && + !(stackerWallet.delegatedMaxAmount >= Number(this.amountUstx)) && + Number(this.amountUstx) <= stackerWallet.ustxBalance && + Number(this.amountUstx) >= model.stackingMinimum && + !(operatorWallet.poolMembers.includes(this.stacker.stxAddress)) && + !(this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt) + ) { + model.trackCommandRun( + "DelegateStackStxCommand_Err_Stacking_Permission_Denied_2", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_PERMISSION_DENIED, + ); + }), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts index d44ef23b22..f937efa16f 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts @@ -476,7 +476,7 @@ export const currentCycleFirstBlock = (network: Simnet) => ).result, )); -const nextCycleFirstBlock = (network: Simnet) => +export const nextCycleFirstBlock = (network: Simnet) => Number(cvToValue( network.callReadOnlyFn( "ST000000000000000000002AMW42H.pox-4", diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand_Err.ts new file mode 100644 index 0000000000..b4e5a491dd --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand_Err.ts @@ -0,0 +1,105 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { poxAddressToTuple } from "@stacks/stacking"; +import { expect } from "vitest"; +import { Cl, ClarityValue, cvToValue } from "@stacks/transactions"; + +type CheckFunc = ( + this: DelegateStackStxCommand_Err, + model: Readonly, +) => boolean; + +export class DelegateStackStxCommand_Err implements PoxCommand { + readonly operator: Wallet; + readonly stacker: Wallet; + readonly period: number; + readonly amountUstx: bigint; + readonly unlockBurnHt: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `DelegateStackStxCommand` to lock uSTX as a Pool Operator + * on behalf of a Stacker. + * + * @param operator - Represents the Pool Operator's wallet. + * @param stacker - Represents the STacker's wallet. + * @param period - Number of reward cycles to lock uSTX. + * @param amountUstx - The uSTX amount stacked by the Operator on behalf + * of the Stacker. + * @param unlockBurnHt - The burn height at which the uSTX is unlocked. + */ + constructor( + operator: Wallet, + stacker: Wallet, + period: number, + amountUstx: bigint, + unlockBurnHt: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.operator = operator; + this.stacker = stacker; + this.period = period; + this.amountUstx = amountUstx; + this.unlockBurnHt = unlockBurnHt; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const burnBlockHeightCV = real.network.runSnippet("burn-block-height"); + const burnBlockHeight = Number( + cvToValue(burnBlockHeightCV as ClarityValue), + ); + + // Act + const delegateStackStx = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "delegate-stack-stx", + [ + // (stacker principal) + Cl.principal(this.stacker.stxAddress), + // (amount-ustx uint) + Cl.uint(this.amountUstx), + // (pox-addr { version: (buff 1), hashbytes: (buff 32) }) + poxAddressToTuple(this.operator.btcAddress), + // (start-burn-ht uint) + Cl.uint(burnBlockHeight), + // (lock-period uint) + Cl.uint(this.period), + ], + this.operator.stxAddress, + ); + + // Assert + expect(delegateStackStx.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✓ ${this.operator.label} Ӿ ${this.stacker.label}`, + "delegate-stack-stx", + "lock-amount", + this.amountUstx.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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} delegate-stack-stx stacker ${this.stacker.label} period ${this.period}`; + } +} From 17fddaf35f4cdc95ce1ee8145aa04cb29acf1eeb Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Wed, 29 May 2024 21:47:33 +0300 Subject: [PATCH 24/72] Add unhappy path cases for `StackIncreaseSigCommand_Err` This commit: - adds 3 unhappy path cases for the `stack-increase` PoX-4 method, called using a signature. - adds the command run tracking inside the `check` method. - adds the expected `stack-increase` PoX-4 errors to the `POX_4_ERRORS` dictionary. --- .../tests/pox-4/err_Commands.ts | 106 +++++++++++++ .../pox-4/pox_StackIncreaseSigCommand_Err.ts | 143 ++++++++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 74c0170af5..113d52ef46 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -18,14 +18,18 @@ import { StackAggregationCommitIndexedAuthCommand_Err } from "./pox_StackAggrega import { StackAggregationIncreaseCommand_Err } from "./pox_StackAggregationIncreaseCommand_Err"; import { currentCycleFirstBlock, nextCycleFirstBlock } from "./pox_Commands"; import { DelegateStackStxCommand_Err } from "./pox_DelegateStackStxCommand_Err"; +import { StackIncreaseSigCommand_Err } from "./pox_StackIncreaseSigCommand_Err"; const POX_4_ERRORS = { + ERR_STACKING_INSUFFICIENT_FUNDS: 1, ERR_STACKING_ALREADY_STACKED: 3, ERR_STACKING_NO_SUCH_PRINCIPAL: 4, ERR_STACKING_PERMISSION_DENIED: 9, ERR_STACKING_THRESHOLD_NOT_MET: 11, + ERR_STACKING_INVALID_AMOUNT: 18, ERR_STACKING_ALREADY_DELEGATED: 20, ERR_DELEGATION_TOO_MUCH_LOCKED: 22, + ERR_STACKING_IS_DELEGATED: 30, ERR_DELEGATION_ALREADY_REVOKED: 34, }; @@ -937,6 +941,108 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_PERMISSION_DENIED, ); }), + // StackIncreaseSigCommand_Err_Stacking_Is_Delegated + fc.record({ + operator: fc.constantFrom(...wallets.values()), + increaseBy: fc.nat(), + authId: fc.nat(), + }).map( + (r) => + new StackIncreaseSigCommand_Err( + r.operator, + r.increaseBy, + r.authId, + function ( + this: StackIncreaseSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + !stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + this.increaseBy <= stacker.amountUnlocked && + this.increaseBy >= 1 + ) { + model.trackCommandRun( + "StackIncreaseSigCommand_Err_Stacking_Is_Delegated", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_IS_DELEGATED, + ), + ), + // StackIncreaseSigCommand_Err_Stacking_Insufficient_Funds + fc.record({ + operator: fc.constantFrom(...wallets.values()), + increaseBy: fc.constant(100_000_000_000_000), + authId: fc.nat(), + }).map( + (r) => + new StackIncreaseSigCommand_Err( + r.operator, + r.increaseBy, + r.authId, + function ( + this: StackIncreaseSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + !(this.increaseBy <= stacker.amountUnlocked) && + this.increaseBy >= 1 + ) { + model.trackCommandRun( + "StackIncreaseSigCommand_Err_Stacking_Insufficient_Funds", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_INSUFFICIENT_FUNDS, + ), + ), + // StackIncreaseSigCommand_Err_Stacking_Invalid_Amount + fc.record({ + operator: fc.constantFrom(...wallets.values()), + increaseBy: fc.constant(0), + authId: fc.nat(), + }).map( + (r) => + new StackIncreaseSigCommand_Err( + r.operator, + r.increaseBy, + r.authId, + function ( + this: StackIncreaseSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + this.increaseBy <= stacker.amountUnlocked && + !(this.increaseBy >= 1) + ) { + model.trackCommandRun( + "StackIncreaseSigCommand_Err_Stacking_Invalid_Amount", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_INVALID_AMOUNT, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand_Err.ts new file mode 100644 index 0000000000..4a122784b3 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand_Err.ts @@ -0,0 +1,143 @@ +import { Pox4SignatureTopic } from "@stacks/stacking"; +import { logCommand, PoxCommand, Real, Stub, Wallet } from "./pox_CommandModel"; +import { + Cl, + ClarityType, + ClarityValue, + cvToJSON, + cvToValue, + isClarityType, +} from "@stacks/transactions"; +import { assert, expect } from "vitest"; + +type CheckFunc = ( + this: StackIncreaseSigCommand_Err, + model: Readonly, +) => boolean; + +export class StackIncreaseSigCommand_Err implements PoxCommand { + readonly wallet: Wallet; + readonly increaseBy: number; + readonly authId: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackIncreaseSigCommand_Err` to lock uSTX for stacking. + * + * @param wallet - Represents the Stacker's wallet. + * @param increaseBy - Represents the locked amount to be increased by. + * @param authId - Unique auth-id for the authorization. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + wallet: Wallet, + increaseBy: number, + authId: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.wallet = wallet; + this.increaseBy = increaseBy; + this.authId = authId; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const maxAmount = stacker.amountLocked + this.increaseBy; + + const burnBlockHeightCV = real.network.runSnippet("burn-block-height"); + const burnBlockHeight = Number( + cvToValue(burnBlockHeightCV as ClarityValue), + ); + + const { result: rewardCycleNextBlockCV } = real.network.callReadOnlyFn( + "ST000000000000000000002AMW42H.pox-4", + "burn-height-to-reward-cycle", + [Cl.uint(burnBlockHeight + 1)], + this.wallet.stxAddress, + ); + assert(isClarityType(rewardCycleNextBlockCV, ClarityType.UInt)); + + const rewardCycleNextBlock = cvToValue(rewardCycleNextBlockCV); + + // Get the lock period from the stacking state. This will be used for correctly + // issuing the authorization. + const stackingStateCV = real.network.getMapEntry( + "ST000000000000000000002AMW42H.pox-4", + "stacking-state", + Cl.tuple({ stacker: Cl.principal(this.wallet.stxAddress) }), + ); + const period = cvToJSON(stackingStateCV).value.value["lock-period"].value; + + const signerSig = this.wallet.stackingClient.signPoxSignature({ + // The signer key being authorized. + signerPrivateKey: this.wallet.signerPrvKey, + // The reward cycle for which the authorization is valid. + // For `stack-stx` and `stack-extend`, this refers to the reward cycle + // where the transaction is confirmed. For `stack-aggregation-commit`, + // this refers to the reward cycle argument in that function. + rewardCycle: rewardCycleNextBlock, + // For `stack-stx`, this refers to `lock-period`. For `stack-extend`, + // this refers to `extend-count`. For `stack-aggregation-commit`, this is + // `u1`. + period: period, + // A string representing the function where this authorization is valid. + // Either `stack-stx`, `stack-extend`, `stack-increase` or `agg-commit`. + topic: Pox4SignatureTopic.StackIncrease, + // The PoX address that can be used with this signer key. + poxAddress: this.wallet.btcAddress, + // The unique auth-id for this authorization. + authId: this.authId, + // The maximum amount of uSTX that can be used (per tx) with this signer + // key. + maxAmount: maxAmount, + }); + + const stackIncrease = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-increase", + [ + // (increase-by uint) + Cl.uint(this.increaseBy), + // (signer-sig (optional (buff 65))) + Cl.some(Cl.bufferFromHex(signerSig)), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (max-amount uint) + Cl.uint(maxAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ); + + expect(stackIncrease.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.wallet.label}`, + "stack-increase-sig", + "increase-by", + this.increaseBy.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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.wallet.label} stack-increase sig increase-by ${this.increaseBy}`; + } +} From 2a7135f0ee07cb2756422a0a149da41909001dae Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Thu, 30 May 2024 13:21:34 +0300 Subject: [PATCH 25/72] Add unhappy path cases for `StackIncreaseAuthCommand_Err` This commit: - adds 3 unhappy path cases for the `stack-increase` PoX-4 method, called using an authorization. - adds the command run tracking inside the `check` method. --- .../tests/pox-4/err_Commands.ts | 103 ++++++++++++++ .../pox-4/pox_StackIncreaseAuthCommand_Err.ts | 133 ++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 113d52ef46..e51283f3d1 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -19,6 +19,7 @@ import { StackAggregationIncreaseCommand_Err } from "./pox_StackAggregationIncre import { currentCycleFirstBlock, nextCycleFirstBlock } from "./pox_Commands"; import { DelegateStackStxCommand_Err } from "./pox_DelegateStackStxCommand_Err"; import { StackIncreaseSigCommand_Err } from "./pox_StackIncreaseSigCommand_Err"; +import { StackIncreaseAuthCommand_Err } from "./pox_StackIncreaseAuthCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_INSUFFICIENT_FUNDS: 1, @@ -1043,6 +1044,108 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_INVALID_AMOUNT, ), ), + // StackIncreaseAuthCommand_Err_Stacking_Is_Delegated + fc.record({ + operator: fc.constantFrom(...wallets.values()), + increaseBy: fc.nat(), + authId: fc.nat(), + }).map( + (r) => + new StackIncreaseAuthCommand_Err( + r.operator, + r.increaseBy, + r.authId, + function ( + this: StackIncreaseAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + !stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + this.increaseBy <= stacker.amountUnlocked && + this.increaseBy >= 1 + ) { + model.trackCommandRun( + "StackIncreaseAuthCommand_Err_Stacking_Is_Delegated", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_IS_DELEGATED, + ), + ), + // StackIncreaseAuthCommand_Err_Stacking_Insufficient_Funds + fc.record({ + operator: fc.constantFrom(...wallets.values()), + increaseBy: fc.constant(100_000_000_000_000), + authId: fc.nat(), + }).map( + (r) => + new StackIncreaseAuthCommand_Err( + r.operator, + r.increaseBy, + r.authId, + function ( + this: StackIncreaseAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + !(this.increaseBy <= stacker.amountUnlocked) && + this.increaseBy >= 1 + ) { + model.trackCommandRun( + "StackIncreaseAuthCommand_Err_Stacking_Insufficient_Funds", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_INSUFFICIENT_FUNDS, + ), + ), + // StackIncreaseAuthCommand_Err_Stacking_Invalid_Amount + fc.record({ + operator: fc.constantFrom(...wallets.values()), + increaseBy: fc.constant(0), + authId: fc.nat(), + }).map( + (r) => + new StackIncreaseAuthCommand_Err( + r.operator, + r.increaseBy, + r.authId, + function ( + this: StackIncreaseAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + this.increaseBy <= stacker.amountUnlocked && + !(this.increaseBy >= 1) + ) { + model.trackCommandRun( + "StackIncreaseAuthCommand_Err_Stacking_Invalid_Amount", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_INVALID_AMOUNT, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand_Err.ts new file mode 100644 index 0000000000..a74aa3c211 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand_Err.ts @@ -0,0 +1,133 @@ +import { Pox4SignatureTopic, poxAddressToTuple } from "@stacks/stacking"; +import { logCommand, PoxCommand, Real, Stub, Wallet } from "./pox_CommandModel"; +import { currentCycle } from "./pox_Commands"; +import { Cl, cvToJSON } from "@stacks/transactions"; +import { expect } from "vitest"; +import { tx } from "@hirosystems/clarinet-sdk"; + +type CheckFunc = ( + this: StackIncreaseAuthCommand_Err, + model: Readonly, +) => boolean; + +export class StackIncreaseAuthCommand_Err implements PoxCommand { + readonly wallet: Wallet; + readonly increaseBy: number; + readonly authId: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackIncreaseAuthCommand` to increase lock uSTX for stacking. + * + * @param wallet - Represents the Stacker's wallet. + * @param increaseBy - Represents the locked amount to be increased by. + * @param authId - Unique auth-id for the authorization. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + wallet: Wallet, + increaseBy: number, + authId: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.wallet = wallet; + this.increaseBy = increaseBy; + this.authId = authId; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const currentRewCycle = currentCycle(real.network); + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + // Get the lock period from the stacking state. This will be used for correctly + // issuing the authorization. + const stackingStateCV = real.network.getMapEntry( + "ST000000000000000000002AMW42H.pox-4", + "stacking-state", + Cl.tuple({ stacker: Cl.principal(this.wallet.stxAddress) }), + ); + const period = cvToJSON(stackingStateCV).value.value["lock-period"].value; + + const maxAmount = stacker.amountLocked + this.increaseBy; + + // Act + + // Include the authorization and the `stack-increase` transactions in a single + // block. This way we ensure both the authorization and the stack-increase + // transactions are called during the same reward cycle and avoid the clarity + // error `ERR_INVALID_REWARD_CYCLE`. + const block = real.network.mineBlock([ + tx.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "set-signer-key-authorization", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.wallet.btcAddress), + // (period uint) + Cl.uint(period), + // (reward-cycle uint) + Cl.uint(currentRewCycle), + // (topic (string-ascii 14)) + Cl.stringAscii(Pox4SignatureTopic.StackIncrease), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (allowed bool) + Cl.bool(true), + // (max-amount uint) + Cl.uint(maxAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ), + tx.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-increase", + [ + // (increase-by uint) + Cl.uint(this.increaseBy), + // (signer-sig (optional (buff 65))) + Cl.none(), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (max-amount uint) + Cl.uint(maxAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ), + ]); + + // Assert + expect(block[0].result).toBeOk(Cl.bool(true)); + expect(block[1].result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.wallet.label}`, + "stack-increase-auth", + "increase-by", + this.increaseBy.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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.wallet.label} stack-increase auth increase-by ${this.increaseBy}`; + } +} From 9a4f7752042bf40d42a3e50c2eee33f77830f817 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Thu, 30 May 2024 18:37:44 +0300 Subject: [PATCH 26/72] Add unhappy path cases for `StackExtendSigCommand_Err` This commit: - adds 5 unhappy path cases for the `stack-increase` PoX-4 method, called using a signature. - adds the command run tracking inside the `check` method. - adds the expected `stack-extend` PoX-4 errors to the `POX_4_ERRORS` dictionary. --- .../tests/pox-4/err_Commands.ts | 271 +++++++++++++++++- .../pox-4/pox_StackExtendSigCommand_Err.ts | 121 ++++++++ 2 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index e51283f3d1..27264b04fb 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -16,13 +16,21 @@ import { StackAggregationCommitAuthCommand_Err } from "./pox_StackAggregationCom import { StackAggregationCommitIndexedSigCommand_Err } from "./pox_StackAggregationCommitIndexedSigCommand_Err"; import { StackAggregationCommitIndexedAuthCommand_Err } from "./pox_StackAggregationCommitIndexedAuthCommand_Err"; import { StackAggregationIncreaseCommand_Err } from "./pox_StackAggregationIncreaseCommand_Err"; -import { currentCycleFirstBlock, nextCycleFirstBlock } from "./pox_Commands"; +import { + currentCycle, + currentCycleFirstBlock, + FIRST_BURNCHAIN_BLOCK_HEIGHT, + nextCycleFirstBlock, + REWARD_CYCLE_LENGTH, +} from "./pox_Commands"; import { DelegateStackStxCommand_Err } from "./pox_DelegateStackStxCommand_Err"; import { StackIncreaseSigCommand_Err } from "./pox_StackIncreaseSigCommand_Err"; import { StackIncreaseAuthCommand_Err } from "./pox_StackIncreaseAuthCommand_Err"; +import { StackExtendSigCommand_Err } from "./pox_StackExtendSigCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_INSUFFICIENT_FUNDS: 1, + ERR_STACKING_INVALID_LOCK_PERIOD: 2, ERR_STACKING_ALREADY_STACKED: 3, ERR_STACKING_NO_SUCH_PRINCIPAL: 4, ERR_STACKING_PERMISSION_DENIED: 9, @@ -30,6 +38,7 @@ const POX_4_ERRORS = { ERR_STACKING_INVALID_AMOUNT: 18, ERR_STACKING_ALREADY_DELEGATED: 20, ERR_DELEGATION_TOO_MUCH_LOCKED: 22, + ERR_STACK_EXTEND_NOT_LOCKED: 26, ERR_STACKING_IS_DELEGATED: 30, ERR_DELEGATION_ALREADY_REVOKED: 34, }; @@ -1146,6 +1155,266 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_INVALID_AMOUNT, ), ), + // StackExtendSigCommand_Err_Stacking_Is_Delegated_1 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer({ min: 1, max: 12 }), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendSigCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + !stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + stacker.poolMembers.length === 0 && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "StackExtendSigCommand_Err_Stacking_Is_Delegated_1", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_IS_DELEGATED, + ), + ), + // StackExtendSigCommand_Err_Stacking_Is_Delegated_2 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer({ min: 1, max: 12 }), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendSigCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + !stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + !(stacker.poolMembers.length === 0) && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "StackExtendSigCommand_Err_Stacking_Is_Delegated_2", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_IS_DELEGATED, + ), + ), + // StackExtendSigCommand_Err_Stacking_Already_Delegated + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer({ min: 1, max: 12 }), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendSigCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.isStackingSolo && + stacker.hasDelegated && + stacker.amountLocked > 0 && + stacker.poolMembers.length === 0 && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "StackExtendSigCommand_Err_Stacking_Already_Delegated", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, + ), + ), + // StackExtendSigCommand_Err_Stacking_Invalid_Lock_Period + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer(), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendSigCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + stacker.poolMembers.length === 0 && + !(totalPeriod <= 12) + ) { + model.trackCommandRun( + "StackExtendSigCommand_Err_Stacking_Invalid_Lock_Period", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_INVALID_LOCK_PERIOD, + ), + ), + // StackExtendSigCommand_Err_Stack_Extend_Not_Locked + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer({ min: 1, max: 12 }), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendSigCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + !stacker.isStacking && + !stacker.isStackingSolo && + !stacker.hasDelegated && + !(stacker.amountLocked > 0) && + stacker.poolMembers.length === 0 && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "StackExtendSigCommand_Err_Stack_Extend_Not_Locked", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACK_EXTEND_NOT_LOCKED, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand_Err.ts new file mode 100644 index 0000000000..185f2796d1 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand_Err.ts @@ -0,0 +1,121 @@ +import { Pox4SignatureTopic, poxAddressToTuple } from "@stacks/stacking"; +import { logCommand, PoxCommand, Real, Stub, Wallet } from "./pox_CommandModel"; +import { currentCycle } from "./pox_Commands"; +import { Cl } from "@stacks/transactions"; +import { expect } from "vitest"; + +type CheckFunc = ( + this: StackExtendSigCommand_Err, + model: Readonly, +) => boolean; + +export class StackExtendSigCommand_Err implements PoxCommand { + readonly wallet: Wallet; + readonly extendCount: number; + readonly authId: number; + readonly currentCycle: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackExtendSigCommand` to lock uSTX for stacking. + * + * This command calls `stack-extend` using a `signature`. + * + * @param wallet - Represents the Stacker's wallet. + * @param extendCount - Represents the cycles to extend the stack with. + * @param authId - Unique auth-id for the authorization. + * @param currentCycle - Represents the current PoX reward cycle. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + wallet: Wallet, + extendCount: number, + authId: number, + currentCycle: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.wallet = wallet; + this.extendCount = extendCount; + this.authId = authId; + this.currentCycle = currentCycle; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const currentRewCycle = currentCycle(real.network); + + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const signerSig = this.wallet.stackingClient.signPoxSignature({ + // The signer key being authorized. + signerPrivateKey: this.wallet.signerPrvKey, + // The reward cycle for which the authorization is valid. + // For `stack-stx` and `stack-extend`, this refers to the reward cycle + // where the transaction is confirmed. For `stack-aggregation-commit`, + // this refers to the reward cycle argument in that function. + rewardCycle: currentRewCycle, + // For `stack-stx`, this refers to `lock-period`. For `stack-extend`, + // this refers to `extend-count`. For `stack-aggregation-commit`, this is + // `u1`. + period: this.extendCount, + // A string representing the function where this authorization is valid. + // Either `stack-stx`, `stack-extend`, `stack-increase` or `agg-commit`. + topic: Pox4SignatureTopic.StackExtend, + // The PoX address that can be used with this signer key. + poxAddress: this.wallet.btcAddress, + // The unique auth-id for this authorization. + authId: this.authId, + // The maximum amount of uSTX that can be used (per tx) with this signer + // key. + maxAmount: stacker.amountLocked, + }); + + const stackExtend = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-extend", + [ + // (extend-count uint) + Cl.uint(this.extendCount), + // (pox-addr { version: (buff 1), hashbytes: (buff 32) }) + poxAddressToTuple(this.wallet.btcAddress), + // (signer-sig (optional (buff 65))) + Cl.some(Cl.bufferFromHex(signerSig)), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (max-amount uint) + Cl.uint(stacker.amountLocked), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ); + + expect(stackExtend.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.wallet.label}`, + "stack-extend-sig", + "extend-count", + this.extendCount.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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.wallet.label} stack-extend sig extend-count ${this.extendCount}`; + } +} From eaf9274f31a19bb3e33eeb5ac165535ca6f7c717 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Thu, 30 May 2024 18:52:05 +0300 Subject: [PATCH 27/72] Add unhappy path cases for `StackExtendAuthCommand_Err` This commit: - adds 5 unhappy path cases for the `stack-extend` PoX-4 method, called using an authorization. - adds the command run tracking inside the `check` method. --- .../tests/pox-4/err_Commands.ts | 261 ++++++++++++++++++ .../pox-4/pox_StackExtendAuthCommand_Err.ts | 123 +++++++++ 2 files changed, 384 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 27264b04fb..83a9566605 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -27,6 +27,7 @@ import { DelegateStackStxCommand_Err } from "./pox_DelegateStackStxCommand_Err"; import { StackIncreaseSigCommand_Err } from "./pox_StackIncreaseSigCommand_Err"; import { StackIncreaseAuthCommand_Err } from "./pox_StackIncreaseAuthCommand_Err"; import { StackExtendSigCommand_Err } from "./pox_StackExtendSigCommand_Err"; +import { StackExtendAuthCommand_Err } from "./pox_StackExtendAuthCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_INSUFFICIENT_FUNDS: 1, @@ -1415,6 +1416,266 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACK_EXTEND_NOT_LOCKED, ), ), + // StackExtendAuthCommand_Err_Stacking_Is_Delegated_1 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer({ min: 1, max: 12 }), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendAuthCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + !stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + stacker.poolMembers.length === 0 && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "StackExtendAuthCommand_Err_Stacking_Is_Delegated_1", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_IS_DELEGATED, + ), + ), + // StackExtendAuthCommand_Err_Stacking_Is_Delegated_2 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer({ min: 1, max: 12 }), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendAuthCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + !stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + !(stacker.poolMembers.length === 0) && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "StackExtendAuthCommand_Err_Stacking_Is_Delegated_2", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_IS_DELEGATED, + ), + ), + // StackExtendAuthCommand_Err_Stacking_Already_Delegated + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer({ min: 1, max: 12 }), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendAuthCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.isStackingSolo && + stacker.hasDelegated && + stacker.amountLocked > 0 && + stacker.poolMembers.length === 0 && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "StackExtendAuthCommand_Err_Stacking_Already_Delegated", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, + ), + ), + // StackExtendAuthCommand_Err_Stacking_Invalid_Lock_Period + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer(), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendAuthCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + stacker.poolMembers.length === 0 && + !(totalPeriod <= 12) + ) { + model.trackCommandRun( + "StackExtendAuthCommand_Err_Stacking_Invalid_Lock_Period", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_INVALID_LOCK_PERIOD, + ), + ), + // StackExtendAuthCommand_Err_Stack_Extend_Not_Locked + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer({ min: 1, max: 12 }), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendAuthCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + !stacker.isStacking && + !stacker.isStackingSolo && + !stacker.hasDelegated && + !(stacker.amountLocked > 0) && + stacker.poolMembers.length === 0 && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "StackExtendAuthCommand_Err_Stack_Extend_Not_Locked", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACK_EXTEND_NOT_LOCKED, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand_Err.ts new file mode 100644 index 0000000000..e3deed040c --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand_Err.ts @@ -0,0 +1,123 @@ +import { poxAddressToTuple } from "@stacks/stacking"; +import { logCommand, PoxCommand, Real, Stub, Wallet } from "./pox_CommandModel"; +import { currentCycle } from "./pox_Commands"; +import { Cl } from "@stacks/transactions"; +import { expect } from "vitest"; +import { tx } from "@hirosystems/clarinet-sdk"; + +type CheckFunc = ( + this: StackExtendAuthCommand_Err, + model: Readonly, +) => boolean; + +export class StackExtendAuthCommand_Err implements PoxCommand { + readonly wallet: Wallet; + readonly extendCount: number; + readonly authId: number; + readonly currentCycle: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackExtendAuthCommand` to lock uSTX for stacking. + * + * This command calls `stack-extend` using an `authorization`. + * + * @param wallet - Represents the Stacker's wallet. + * @param extendCount - Represents the cycles to extend the stack with. + * @param authId - Unique auth-id for the authorization. + * @param currentCycle - Represents the current PoX reward cycle. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + wallet: Wallet, + extendCount: number, + authId: number, + currentCycle: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.wallet = wallet; + this.extendCount = extendCount; + this.authId = authId; + this.currentCycle = currentCycle; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const currentRewCycle = currentCycle(real.network); + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const block = real.network.mineBlock([ + tx.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "set-signer-key-authorization", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.wallet.btcAddress), + // (period uint) + Cl.uint(this.extendCount), + // (reward-cycle uint) + Cl.uint(currentRewCycle), + // (topic (string-ascii 14)) + Cl.stringAscii("stack-extend"), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (allowed bool) + Cl.bool(true), + // (max-amount uint) + Cl.uint(stacker.amountLocked), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ), + tx.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-extend", + [ + // (extend-count uint) + Cl.uint(this.extendCount), + // (pox-addr { version: (buff 1), hashbytes: (buff 32) }) + poxAddressToTuple(this.wallet.btcAddress), + // (signer-sig (optional (buff 65))) + Cl.none(), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (max-amount uint) + Cl.uint(stacker.amountLocked), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ), + ]); + + expect(block[0].result).toBeOk(Cl.bool(true)); + expect(block[1].result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.wallet.label}`, + "stack-extend-auth", + "extend-count", + this.extendCount.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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.wallet.label} stack-extend auth extend-count ${this.extendCount}`; + } +} From be39b1a97c4cec510eb04c902f76209ef332f234 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 31 May 2024 15:58:27 +0300 Subject: [PATCH 28/72] Add unhappy path cases for `DelegateStackExtendCommand_Err` This commit: - adds 4 unhappy path cases for the `delegate-stack-extend` PoX-4 method. - adds the command run tracking inside the `check` method. - adds the expected `delegate-stack-extend` PoX-4 error to the `POX_4_ERRORS` dictionary. --- .../tests/pox-4/err_Commands.ts | 302 ++++++++++++++++++ .../pox_DelegateStackExtendCommand_Err.ts | 96 ++++++ 2 files changed, 398 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 83a9566605..1ad4815cf7 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -28,6 +28,7 @@ import { StackIncreaseSigCommand_Err } from "./pox_StackIncreaseSigCommand_Err"; import { StackIncreaseAuthCommand_Err } from "./pox_StackIncreaseAuthCommand_Err"; import { StackExtendSigCommand_Err } from "./pox_StackExtendSigCommand_Err"; import { StackExtendAuthCommand_Err } from "./pox_StackExtendAuthCommand_Err"; +import { DelegateStackExtendCommand_Err } from "./pox_DelegateStackExtendCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_INSUFFICIENT_FUNDS: 1, @@ -41,6 +42,7 @@ const POX_4_ERRORS = { ERR_DELEGATION_TOO_MUCH_LOCKED: 22, ERR_STACK_EXTEND_NOT_LOCKED: 26, ERR_STACKING_IS_DELEGATED: 30, + ERR_STACKING_NOT_DELEGATED: 31, ERR_DELEGATION_ALREADY_REVOKED: 34, }; @@ -1676,6 +1678,306 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACK_EXTEND_NOT_LOCKED, ), ), + // DelegateStackExtendCommand_Err_Stacking_Invalid_Lock_Period + fc.record({ + operator: fc.constantFrom(...wallets.values()), + extendCount: fc.constant(100000000000000), + }).chain((r) => { + const operator = stackers.get(r.operator.stxAddress)!; + const delegatorsList = operator.poolMembers; + const availableStackers = delegatorsList.filter((delegator) => { + const delegatorWallet = stackers.get(delegator)!; + return delegatorWallet.unlockHeight > nextCycleFirstBlock(network); + }); + + const availableStackersOrFallback = availableStackers.length === 0 + ? [r.operator.stxAddress] + : availableStackers; + + return fc.record({ + stacker: fc.constantFrom(...availableStackersOrFallback), + currentCycle: fc.constant(currentCycle(network)), + }).map((additionalProps) => ({ + ...r, + stacker: wallets.get(additionalProps.stacker)!, + currentCycle: additionalProps.currentCycle, + })); + }).map( + (final) => + new DelegateStackExtendCommand_Err( + final.operator, + final.stacker, + final.extendCount, + final.currentCycle, + function ( + this: DelegateStackExtendCommand_Err, + model: Readonly, + ): boolean { + const operatorWallet = model.stackers.get( + this.operator.stxAddress, + )!; + const stackerWallet = model.stackers.get( + this.stacker.stxAddress, + )!; + + const firstRewardCycle = + this.currentCycle > stackerWallet.firstLockedRewardCycle + ? this.currentCycle + : stackerWallet.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stackerWallet.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + const newUnlockHeight = + REWARD_CYCLE_LENGTH * (firstRewardCycle + totalPeriod - 1) + + FIRST_BURNCHAIN_BLOCK_HEIGHT; + const stackedAmount = stackerWallet.amountLocked; + + if ( + stackerWallet.amountLocked > 0 && + stackerWallet.hasDelegated === true && + stackerWallet.isStacking === true && + stackerWallet.delegatedTo === this.operator.stxAddress && + !(stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) && + stackerWallet.delegatedMaxAmount >= stackedAmount && + operatorWallet.poolMembers.includes(this.stacker.stxAddress) && + operatorWallet.lockedAddresses.includes( + this.stacker.stxAddress, + ) && + !(totalPeriod <= 12) + ) { + model.trackCommandRun( + "DelegateStackExtendCommand_Err_Stacking_Invalid_Lock_Period", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_INVALID_LOCK_PERIOD, + ), + ), + // DelegateStackExtendCommand_Err_Stacking_Not_Delegated + fc.record({ + operator: fc.constantFrom(...wallets.values()), + extendCount: fc.integer({ min: 1, max: 11 }), + }).chain((r) => { + const operator = stackers.get(r.operator.stxAddress)!; + const delegatorsList = operator.poolMembers; + const availableStackers = delegatorsList.filter((delegator) => { + const delegatorWallet = stackers.get(delegator)!; + return delegatorWallet.unlockHeight > nextCycleFirstBlock(network); + }); + + const availableStackersOrFallback = availableStackers.length === 0 + ? [r.operator.stxAddress] + : availableStackers; + + return fc + .record({ + stacker: fc.constantFrom(...availableStackersOrFallback), + currentCycle: fc.constant(currentCycle(network)), + }) + .map((additionalProps) => ({ + ...r, + stacker: wallets.get(additionalProps.stacker)!, + currentCycle: additionalProps.currentCycle, + })); + }).map( + (final) => + new DelegateStackExtendCommand_Err( + final.operator, + final.stacker, + final.extendCount, + final.currentCycle, + function ( + this: DelegateStackExtendCommand_Err, + model: Readonly, + ): boolean { + const operatorWallet = model.stackers.get( + this.operator.stxAddress, + )!; + const stackerWallet = model.stackers.get( + this.stacker.stxAddress, + )!; + + const firstRewardCycle = + this.currentCycle > stackerWallet.firstLockedRewardCycle + ? this.currentCycle + : stackerWallet.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stackerWallet.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + const newUnlockHeight = + REWARD_CYCLE_LENGTH * (firstRewardCycle + totalPeriod - 1) + + FIRST_BURNCHAIN_BLOCK_HEIGHT; + const stackedAmount = stackerWallet.amountLocked; + + if ( + stackerWallet.amountLocked > 0 && + !(stackerWallet.hasDelegated === true) && + stackerWallet.isStacking === true && + stackerWallet.isStackingSolo === true && + !(stackerWallet.delegatedTo === this.operator.stxAddress) && + !(stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) && + !(stackerWallet.delegatedMaxAmount >= stackedAmount) && + !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && + !operatorWallet.lockedAddresses.includes( + this.stacker.stxAddress, + ) && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "DelegateStackExtendCommand_Err_Stacking_Not_Delegated", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NOT_DELEGATED, + ), + ), + // DelegateStackExtendCommand_Err_Stack_Extend_Not_Locked + fc.record({ + operator: fc.constantFrom(...wallets.values()), + extendCount: fc.integer({ min: 1, max: 11 }), + }).chain((r) => { + const operator = stackers.get(r.operator.stxAddress)!; + const delegatorsList = operator.poolMembers; + const availableStackers = delegatorsList.filter((delegator) => { + const delegatorWallet = stackers.get(delegator)!; + return delegatorWallet.unlockHeight > nextCycleFirstBlock(network); + }); + + const availableStackersOrFallback = availableStackers.length === 0 + ? [r.operator.stxAddress] + : availableStackers; + + return fc.record({ + stacker: fc.constantFrom(...availableStackersOrFallback), + currentCycle: fc.constant(currentCycle(network)), + }).map((additionalProps) => ({ + ...r, + stacker: wallets.get(additionalProps.stacker)!, + currentCycle: additionalProps.currentCycle, + })); + }).map( + (final) => + new DelegateStackExtendCommand_Err( + final.operator, + final.stacker, + final.extendCount, + final.currentCycle, + function ( + this: DelegateStackExtendCommand_Err, + model: Readonly, + ): boolean { + const operatorWallet = model.stackers.get( + this.operator.stxAddress, + )!; + const stackerWallet = model.stackers.get( + this.stacker.stxAddress, + )!; + + const firstRewardCycle = + this.currentCycle > stackerWallet.firstLockedRewardCycle + ? this.currentCycle + : stackerWallet.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stackerWallet.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + const newUnlockHeight = + REWARD_CYCLE_LENGTH * (firstRewardCycle + totalPeriod - 1) + + FIRST_BURNCHAIN_BLOCK_HEIGHT; + const stackedAmount = stackerWallet.amountLocked; + if ( + !(stackerWallet.amountLocked > 0) && + stackerWallet.hasDelegated === true && + !(stackerWallet.isStacking === true) && + !(stackerWallet.delegatedTo === this.operator.stxAddress) && + stackerWallet.delegatedUntilBurnHt >= newUnlockHeight && + stackerWallet.delegatedMaxAmount >= stackedAmount && + !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && + !operatorWallet.lockedAddresses.includes( + this.stacker.stxAddress, + ) && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "DelegateStackExtendCommand_Err_Stack_Extend_Not_Locked", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACK_EXTEND_NOT_LOCKED, + ), + ), + // DelegateStackExtendCommand_Err_Stacking_Permission_Denied + fc.record({ + operator: fc.constantFrom(...wallets.values()), + extendCount: fc.integer({ min: 1, max: 11 }), + stacker: fc.constantFrom(...wallets.values()), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (final) => + new DelegateStackExtendCommand_Err( + final.operator, + final.stacker, + final.extendCount, + final.currentCycle, + function ( + this: DelegateStackExtendCommand_Err, + model: Readonly, + ): boolean { + const operatorWallet = model.stackers.get( + this.operator.stxAddress, + )!; + const stackerWallet = model.stackers.get( + this.stacker.stxAddress, + )!; + + const firstRewardCycle = + this.currentCycle > stackerWallet.firstLockedRewardCycle + ? this.currentCycle + : stackerWallet.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stackerWallet.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + const newUnlockHeight = + REWARD_CYCLE_LENGTH * (firstRewardCycle + totalPeriod - 1) + + FIRST_BURNCHAIN_BLOCK_HEIGHT; + const stackedAmount = stackerWallet.amountLocked; + + if ( + stackerWallet.amountLocked > 0 && + !(stackerWallet.hasDelegated === true) && + stackerWallet.isStacking === true && + !(stackerWallet.delegatedTo === this.operator.stxAddress) && + !(stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) && + !(stackerWallet.delegatedMaxAmount >= stackedAmount) && + !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && + operatorWallet.lockedAddresses.includes( + this.stacker.stxAddress, + ) && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "DelegateStackExtendCommand_Err_Stacking_Permission_Denied", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_PERMISSION_DENIED, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand_Err.ts new file mode 100644 index 0000000000..830fb7d182 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand_Err.ts @@ -0,0 +1,96 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { poxAddressToTuple } from "@stacks/stacking"; +import { expect } from "vitest"; +import { Cl } from "@stacks/transactions"; + +type CheckFunc = ( + this: DelegateStackExtendCommand_Err, + model: Readonly, +) => boolean; + +export class DelegateStackExtendCommand_Err implements PoxCommand { + readonly operator: Wallet; + readonly stacker: Wallet; + readonly extendCount: number; + readonly currentCycle: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `DelegateStackExtendCommand_Err` to extend the unlock + * height as a Pool Operator on behalf of a Stacker. + * + * @param operator - Represents the Pool Operator's wallet. + * @param stacker - Represents the STacker's wallet. + * @param extendCount - Represents the cycles to be expended. + * @param currentCycle - Represents the current PoX reward cycle. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + operator: Wallet, + stacker: Wallet, + extendCount: number, + currentCycle: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.operator = operator; + this.stacker = stacker; + this.extendCount = extendCount; + this.currentCycle = currentCycle; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; + + // Act + const delegateStackExtend = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "delegate-stack-extend", + [ + // (stacker principal) + Cl.principal(this.stacker.stxAddress), + // (pox-addr { version: (buff 1), hashbytes: (buff 32) }) + poxAddressToTuple(this.operator.btcAddress), + // (extend-count uint) + Cl.uint(this.extendCount), + ], + this.operator.stxAddress, + ); + + expect(delegateStackExtend.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.operator.label} Ӿ ${this.stacker.label}`, + "delegate-stack-extend", + "extend count", + this.extendCount.toString(), + "new unlock height", + stackerWallet.unlockHeight.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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} Ӿ ${this.stacker.label} delegate-stack-extend extend count ${this.extendCount}`; + } +} From 64e2ece5af054d8539a1031679f115afea42ce1e Mon Sep 17 00:00:00 2001 From: Nikos Baxevanis Date: Wed, 22 May 2024 00:08:29 +0200 Subject: [PATCH 29/72] feat(pox-4-tests): add check function delegate to PoxCommand-derived types - Added import for StackStxSigCommand_Err and StackStxAuthCommand_Err - Added StackStxAuthCommand_Err with a custom check function delegate to PoxCommands - Added StackStxSigCommand_Err with a custom check function delegate to PoxCommands This allows the check function to be parameterized, reducing the need for copy-pasting classes. Note: This is a very work in progress. --- .../tests/pox-4/pox_Commands.ts | 62 +++++++ .../pox-4/pox_StackStxAuthCommand_Err.ts | 172 ++++++++++++++++++ .../tests/pox-4/pox_StackStxSigCommand_Err.ts | 170 +++++++++++++++++ 3 files changed, 404 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts index bafbe38a43..628e1d2d7a 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts @@ -3,7 +3,9 @@ import { Real, Stacker, Stub, StxAddress, Wallet } from "./pox_CommandModel"; import { GetStackingMinimumCommand } from "./pox_GetStackingMinimumCommand"; import { GetStxAccountCommand } from "./pox_GetStxAccountCommand"; import { StackStxSigCommand } from "./pox_StackStxSigCommand"; +import { StackStxSigCommand_Err } from "./pox_StackStxSigCommand_Err"; import { StackStxAuthCommand } from "./pox_StackStxAuthCommand"; +import { StackStxAuthCommand_Err } from "./pox_StackStxAuthCommand_Err"; import { DelegateStxCommand } from "./pox_DelegateStxCommand"; import { DelegateStackStxCommand } from "./pox_DelegateStackStxCommand"; import { Simnet } from "@hirosystems/clarinet-sdk"; @@ -83,6 +85,36 @@ export function PoxCommands( r.margin, ) ), + // StackStxAuthCommand_Err + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + period: fc.integer({ min: 1, max: 12 }), + margin: fc.integer({ min: 1, max: 9 }), + }).map(( + r: { + wallet: Wallet; + authId: number; + period: number; + margin: number; + }, + ) => + new StackStxAuthCommand_Err( + r.wallet, + r.authId, + r.period, + r.margin, + function (this: StackStxAuthCommand_Err, model: Readonly): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + console.log("I in StackStxAuthCommand_Err stacker", stacker); + return ( + model.stackingMinimum > 0 && !stacker.isStacking && + !stacker.hasDelegated + ); + }, + 123, + ) + ), // StackExtendAuthCommand fc .record({ @@ -105,6 +137,36 @@ export function PoxCommands( r.currentCycle, ), ), + // StackStxSigCommand_Err + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + period: fc.integer({ min: 1, max: 12 }), + margin: fc.integer({ min: 1, max: 9 }), + }).map(( + r: { + wallet: Wallet; + authId: number; + period: number; + margin: number; + }, + ) => + new StackStxSigCommand_Err( + r.wallet, + r.authId, + r.period, + r.margin, + function (this: StackStxSigCommand_Err, model: Readonly): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + console.log("I in StackStxSigCommand_Err stacker", stacker); + return ( + model.stackingMinimum > 0 && !stacker.isStacking && + !stacker.hasDelegated + ); + }, + 123, + ) + ), // StackExtendSigCommand fc .record({ diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts new file mode 100644 index 0000000000..ad310fef9a --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts @@ -0,0 +1,172 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { poxAddressToTuple } from "@stacks/stacking"; +import { assert, expect } from "vitest"; +import { + Cl, + ClarityType, + ClarityValue, + cvToValue, + isClarityType, +} from "@stacks/transactions"; +import { currentCycle } from "./pox_Commands.ts"; + +type CheckFunc = ( + this: StackStxAuthCommand_Err, + model: Readonly, +) => boolean; + +export class StackStxAuthCommand_Err implements PoxCommand { + readonly wallet: Wallet; + readonly authId: number; + readonly period: number; + readonly margin: number; + + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackStxAuthCommand_Err` to lock uSTX for stacking. + * + * @param wallet - Represents the Stacker's wallet. + * @param authId - Unique auth-id for the authorization. + * @param period - Number of reward cycles to lock uSTX. + * @param margin - Multiplier for minimum required uSTX to stack so that each + * Stacker locks a different amount of uSTX across test runs. + * @param checkFunc - A function to check constraints for running this command. + */ + constructor( + wallet: Wallet, + authId: number, + period: number, + margin: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.wallet = wallet; + this.authId = authId; + this.period = period; + this.margin = margin; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + model.trackCommandRun(this.constructor.name); + const currentRewCycle = currentCycle(real.network); + + // The maximum amount of uSTX that can be used (per tx) with this signer + // key. For our tests, we will use the minimum amount of uSTX to be stacked + // in the given reward cycle multiplied by the margin, which is a randomly + // generated number passed to the constructor of this class. + const maxAmount = model.stackingMinimum * this.margin; + + const { result: setAuthorization } = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "set-signer-key-authorization", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.wallet.btcAddress), + // (period uint) + Cl.uint(this.period), + // (reward-cycle uint) + Cl.uint(currentRewCycle), + // (topic (string-ascii 14)) + Cl.stringAscii("stack-stx"), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (allowed bool) + Cl.bool(true), + // (max-amount uint) + Cl.uint(maxAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ); + + expect(setAuthorization).toBeOk(Cl.bool(true)); + const burnBlockHeightCV = real.network.runSnippet("burn-block-height"); + const burnBlockHeight = Number( + cvToValue(burnBlockHeightCV as ClarityValue), + ); + + // The amount of uSTX to be locked in the reward cycle. For this test, we + // will use the maximum amount of uSTX that can be used (per tx) with this + // signer key. + const amountUstx = maxAmount; + + // Act + const stackStx = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-stx", + [ + // (amount-ustx uint) + Cl.uint(amountUstx), + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.wallet.btcAddress), + // (start-burn-ht uint) + Cl.uint(burnBlockHeight), + // (lock-period uint) + Cl.uint(this.period), + // (signer-sig (optional (buff 65))) + Cl.none(), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (max-amount uint) + Cl.uint(maxAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ); + + const { result: rewardCycle } = real.network.callReadOnlyFn( + "ST000000000000000000002AMW42H.pox-4", + "burn-height-to-reward-cycle", + [Cl.uint(burnBlockHeight)], + this.wallet.stxAddress, + ); + assert(isClarityType(rewardCycle, ClarityType.UInt)); + + const { result: unlockBurnHeight } = real.network.callReadOnlyFn( + "ST000000000000000000002AMW42H.pox-4", + "reward-cycle-to-burn-height", + [Cl.uint(Number(rewardCycle.value) + this.period + 1)], + this.wallet.stxAddress, + ); + assert(isClarityType(unlockBurnHeight, ClarityType.UInt)); + + // Assert + expect(stackStx.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.wallet.label}`, + "stack-stx-auth", + "lock-amount", + amountUstx.toString(), + "period", + this.period.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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.wallet.label} stack-stx auth auth-id ${this.authId} and period ${this.period}`; + } +} diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts new file mode 100644 index 0000000000..4c5f7ce149 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts @@ -0,0 +1,170 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { Pox4SignatureTopic, poxAddressToTuple } from "@stacks/stacking"; +import { assert, expect } from "vitest"; +import { + Cl, + ClarityType, + ClarityValue, + cvToValue, + isClarityType, +} from "@stacks/transactions"; +import { currentCycle } from "./pox_Commands.ts"; + +type CheckFunc = ( + this: StackStxSigCommand_Err, + model: Readonly, +) => boolean; + +export class StackStxSigCommand_Err implements PoxCommand { + readonly wallet: Wallet; + readonly authId: number; + readonly period: number; + readonly margin: number; + + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackStxSigCommand` to lock uSTX for stacking. + * + * @param wallet - Represents the Stacker's wallet. + * @param authId - Unique auth-id for the authorization. + * @param period - Number of reward cycles to lock uSTX. + * @param margin - Multiplier for minimum required uSTX to stack so that each + * Stacker locks a different amount of uSTX across test runs. + * @param checkFunc - A function to check constraints for running this command. + */ + constructor( + wallet: Wallet, + authId: number, + period: number, + margin: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.wallet = wallet; + this.authId = authId; + this.period = period; + this.margin = margin; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + model.trackCommandRun(this.constructor.name); + const burnBlockHeightCV = real.network.runSnippet("burn-block-height"); + const burnBlockHeight = Number( + cvToValue(burnBlockHeightCV as ClarityValue), + ); + const currentRewCycle = currentCycle(real.network); + + // The maximum amount of uSTX that can be used (per tx) with this signer + // key. For our tests, we will use the minimum amount of uSTX to be stacked + // in the given reward cycle multiplied by the margin, which is a randomly + // generated number passed to the constructor of this class. + const maxAmount = model.stackingMinimum * this.margin; + + const signerSig = this.wallet.stackingClient.signPoxSignature({ + // The signer key being authorized. + signerPrivateKey: this.wallet.signerPrvKey, + // The reward cycle for which the authorization is valid. + // For `stack-stx` and `stack-extend`, this refers to the reward cycle + // where the transaction is confirmed. For `stack-aggregation-commit`, + // this refers to the reward cycle argument in that function. + rewardCycle: currentRewCycle, + // For `stack-stx`, this refers to `lock-period`. For `stack-extend`, + // this refers to `extend-count`. For `stack-aggregation-commit`, this is + // `u1`. + period: this.period, + // A string representing the function where this authorization is valid. + // Either `stack-stx`, `stack-extend`, `stack-increase` or `agg-commit`. + topic: Pox4SignatureTopic.StackStx, + // The PoX address that can be used with this signer key. + poxAddress: this.wallet.btcAddress, + // The unique auth-id for this authorization. + authId: this.authId, + // The maximum amount of uSTX that can be used (per tx) with this signer + // key. + maxAmount: maxAmount, + }); + + // The amount of uSTX to be locked in the reward cycle. For this test, we + // will use the maximum amount of uSTX that can be used (per tx) with this + // signer key. + const amountUstx = maxAmount; + + // Act + const stackStx = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-stx", + [ + // (amount-ustx uint) + Cl.uint(amountUstx), + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.wallet.btcAddress), + // (start-burn-ht uint) + Cl.uint(burnBlockHeight), + // (lock-period uint) + Cl.uint(this.period), + // (signer-sig (optional (buff 65))) + Cl.some(Cl.bufferFromHex(signerSig)), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (max-amount uint) + Cl.uint(maxAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ); + + const { result: rewardCycle } = real.network.callReadOnlyFn( + "ST000000000000000000002AMW42H.pox-4", + "burn-height-to-reward-cycle", + [Cl.uint(burnBlockHeight)], + this.wallet.stxAddress, + ); + assert(isClarityType(rewardCycle, ClarityType.UInt)); + + const { result: unlockBurnHeight } = real.network.callReadOnlyFn( + "ST000000000000000000002AMW42H.pox-4", + "reward-cycle-to-burn-height", + [Cl.uint(Number(rewardCycle.value) + this.period + 1)], + this.wallet.stxAddress, + ); + assert(isClarityType(unlockBurnHeight, ClarityType.UInt)); + + // Assert + expect(stackStx.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.wallet.label}`, + "stack-stx-sig", + "lock-amount", + amountUstx.toString(), + "period", + this.period.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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.wallet.label} stack-stx sig auth-id ${this.authId} and period ${this.period}`; + } +} From 7f4b53673c22fb7d9f908dfa1ec3b39a2fd33c7c Mon Sep 17 00:00:00 2001 From: Nikos Baxevanis Date: Wed, 22 May 2024 08:27:17 +0200 Subject: [PATCH 30/72] feat(pox-4-tests): add check function delegate to PoxCommand-derived types - Separate success paths from failure paths to keep pox_Commands.ts focused on success cases only. This prevents the file from growing with out-of-scope cases. Note: This is a work in progress. --- .../tests/pox-4/err_Commands.ts | 74 +++++++++++++++++++ .../tests/pox-4/pox-4.stateful-prop.test.ts | 8 +- .../tests/pox-4/pox_Commands.ts | 70 +----------------- .../pox-4/pox_StackStxAuthCommand_Err.ts | 2 +- .../tests/pox-4/pox_StackStxSigCommand_Err.ts | 2 +- 5 files changed, 86 insertions(+), 70 deletions(-) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts new file mode 100644 index 0000000000..2e4259f740 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -0,0 +1,74 @@ +import fc from "fast-check"; +import { PoxCommand, Stacker, Stub, StxAddress, Wallet } from "./pox_CommandModel"; +import { StackStxSigCommand_Err } from "./pox_StackStxSigCommand_Err"; +import { StackStxAuthCommand_Err } from "./pox_StackStxAuthCommand_Err"; +import { Simnet } from "@hirosystems/clarinet-sdk"; + +export function ErrCommands( + wallets: Map, + stackers: Map, + network: Simnet, +): fc.Arbitrary[] { + const cmds = [ + // StackStxAuthCommand_Err + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + period: fc.integer({ min: 1, max: 12 }), + margin: fc.integer({ min: 1, max: 9 }), + }).map(( + r: { + wallet: Wallet; + authId: number; + period: number; + margin: number; + }, + ) => + new StackStxAuthCommand_Err( + r.wallet, + r.authId, + r.period, + r.margin, + function (this: StackStxAuthCommand_Err, model: Readonly): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + return ( + model.stackingMinimum > 0 && !stacker.isStacking && + !stacker.hasDelegated + ); + }, + 123, + ) + ), + // StackStxSigCommand_Err + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + period: fc.integer({ min: 1, max: 12 }), + margin: fc.integer({ min: 1, max: 9 }), + }).map(( + r: { + wallet: Wallet; + authId: number; + period: number; + margin: number; + }, + ) => + new StackStxSigCommand_Err( + r.wallet, + r.authId, + r.period, + r.margin, + function (this: StackStxSigCommand_Err, model: Readonly): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + return ( + model.stackingMinimum > 0 && !stacker.isStacking && + !stacker.hasDelegated + ); + }, + 123, + ) + ), + ]; + + return cmds; +} diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts index bf8b63ffe7..a2be09f593 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts @@ -16,6 +16,7 @@ import { StackingClient } from "@stacks/stacking"; import fc from "fast-check"; import { PoxCommands } from "./pox_Commands.ts"; +import { ErrCommands } from "./err_Commands.ts"; import fs from "fs"; import path from "path"; @@ -143,9 +144,14 @@ it("statefully interacts with PoX-4", async () => { simnet.setEpoch("3.0"); + const successPath = PoxCommands(model.wallets, model.stackers, sut.network); + const failurePath = ErrCommands(model.wallets, model.stackers, sut.network); + fc.assert( fc.property( - PoxCommands(model.wallets, model.stackers, sut.network), + // More on size: https://github.com/dubzzz/fast-check/discussions/2978 + // More on cmds: https://github.com/dubzzz/fast-check/discussions/3026 + fc.commands(successPath.concat(failurePath), { size: "xsmall" }), (cmds) => { const initialState = () => ({ model: model, real: sut }); fc.modelRun(initialState, cmds); diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts index 628e1d2d7a..0a1fd6f3ea 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts @@ -1,11 +1,9 @@ import fc from "fast-check"; -import { Real, Stacker, Stub, StxAddress, Wallet } from "./pox_CommandModel"; +import { PoxCommand, Stacker, StxAddress, Wallet } from "./pox_CommandModel"; import { GetStackingMinimumCommand } from "./pox_GetStackingMinimumCommand"; import { GetStxAccountCommand } from "./pox_GetStxAccountCommand"; import { StackStxSigCommand } from "./pox_StackStxSigCommand"; -import { StackStxSigCommand_Err } from "./pox_StackStxSigCommand_Err"; import { StackStxAuthCommand } from "./pox_StackStxAuthCommand"; -import { StackStxAuthCommand_Err } from "./pox_StackStxAuthCommand_Err"; import { DelegateStxCommand } from "./pox_DelegateStxCommand"; import { DelegateStackStxCommand } from "./pox_DelegateStackStxCommand"; import { Simnet } from "@hirosystems/clarinet-sdk"; @@ -29,7 +27,7 @@ export function PoxCommands( wallets: Map, stackers: Map, network: Simnet, -): fc.Arbitrary>> { +): fc.Arbitrary[] { const cmds = [ // GetStackingMinimumCommand fc.record({ @@ -85,36 +83,6 @@ export function PoxCommands( r.margin, ) ), - // StackStxAuthCommand_Err - fc.record({ - wallet: fc.constantFrom(...wallets.values()), - authId: fc.nat(), - period: fc.integer({ min: 1, max: 12 }), - margin: fc.integer({ min: 1, max: 9 }), - }).map(( - r: { - wallet: Wallet; - authId: number; - period: number; - margin: number; - }, - ) => - new StackStxAuthCommand_Err( - r.wallet, - r.authId, - r.period, - r.margin, - function (this: StackStxAuthCommand_Err, model: Readonly): boolean { - const stacker = model.stackers.get(this.wallet.stxAddress)!; - console.log("I in StackStxAuthCommand_Err stacker", stacker); - return ( - model.stackingMinimum > 0 && !stacker.isStacking && - !stacker.hasDelegated - ); - }, - 123, - ) - ), // StackExtendAuthCommand fc .record({ @@ -137,36 +105,6 @@ export function PoxCommands( r.currentCycle, ), ), - // StackStxSigCommand_Err - fc.record({ - wallet: fc.constantFrom(...wallets.values()), - authId: fc.nat(), - period: fc.integer({ min: 1, max: 12 }), - margin: fc.integer({ min: 1, max: 9 }), - }).map(( - r: { - wallet: Wallet; - authId: number; - period: number; - margin: number; - }, - ) => - new StackStxSigCommand_Err( - r.wallet, - r.authId, - r.period, - r.margin, - function (this: StackStxSigCommand_Err, model: Readonly): boolean { - const stacker = model.stackers.get(this.wallet.stxAddress)!; - console.log("I in StackStxSigCommand_Err stacker", stacker); - return ( - model.stackingMinimum > 0 && !stacker.isStacking && - !stacker.hasDelegated - ); - }, - 123, - ) - ), // StackExtendSigCommand fc .record({ @@ -514,9 +452,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: "xsmall" }); + return cmds; } export const REWARD_CYCLE_LENGTH = 1050; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts index ad310fef9a..e1d0a2e113 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts @@ -26,7 +26,6 @@ export class StackStxAuthCommand_Err implements PoxCommand { readonly authId: number; readonly period: number; readonly margin: number; - readonly checkFunc: CheckFunc; readonly errorCode: number; @@ -39,6 +38,7 @@ export class StackStxAuthCommand_Err implements PoxCommand { * @param margin - Multiplier for minimum required uSTX to stack so that each * Stacker locks a different amount of uSTX across test runs. * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. */ constructor( wallet: Wallet, diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts index 4c5f7ce149..db6af5c5ba 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts @@ -26,7 +26,6 @@ export class StackStxSigCommand_Err implements PoxCommand { readonly authId: number; readonly period: number; readonly margin: number; - readonly checkFunc: CheckFunc; readonly errorCode: number; @@ -39,6 +38,7 @@ export class StackStxSigCommand_Err implements PoxCommand { * @param margin - Multiplier for minimum required uSTX to stack so that each * Stacker locks a different amount of uSTX across test runs. * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. */ constructor( wallet: Wallet, From 124abd4e2d6474c06e0561a556aba53aed85e65f Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Wed, 22 May 2024 18:32:24 +0300 Subject: [PATCH 31/72] Remove command tracking from the command's `run` method The command run tracking will be added to the command's `check` method. --- .../tests/pox-4/pox_StackStxAuthCommand_Err.ts | 1 - .../tests/pox-4/pox_StackStxSigCommand_Err.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts index e1d0a2e113..35212e0320 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts @@ -59,7 +59,6 @@ export class StackStxAuthCommand_Err implements PoxCommand { check = (model: Readonly): boolean => this.checkFunc.call(this, model); run(model: Stub, real: Real): void { - model.trackCommandRun(this.constructor.name); const currentRewCycle = currentCycle(real.network); // The maximum amount of uSTX that can be used (per tx) with this signer diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts index db6af5c5ba..58092109f0 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts @@ -59,7 +59,6 @@ export class StackStxSigCommand_Err implements PoxCommand { check = (model: Readonly): boolean => this.checkFunc.call(this, model); run(model: Stub, real: Real): void { - model.trackCommandRun(this.constructor.name); const burnBlockHeightCV = real.network.runSnippet("burn-block-height"); const burnBlockHeight = Number( cvToValue(burnBlockHeightCV as ClarityValue), From dfb72304508f5510e9203fda275a1e3b5d7ce88e Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Wed, 22 May 2024 18:36:52 +0300 Subject: [PATCH 32/72] Pass the incremented burn height when calling `stack-stx` If not passed incremented, the call will result in an `ERR_INVALID_START_BURN_HEIGHT` when being sent at the limit between 2 cycles. --- .../tests/pox-4/pox_StackStxAuthCommand_Err.ts | 2 +- .../tests/pox-4/pox_StackStxSigCommand_Err.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts index 35212e0320..6889e89917 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts @@ -112,7 +112,7 @@ export class StackStxAuthCommand_Err implements PoxCommand { // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) poxAddressToTuple(this.wallet.btcAddress), // (start-burn-ht uint) - Cl.uint(burnBlockHeight), + Cl.uint(burnBlockHeight + 1), // (lock-period uint) Cl.uint(this.period), // (signer-sig (optional (buff 65))) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts index 58092109f0..f9c2cdc8d4 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts @@ -110,7 +110,7 @@ export class StackStxSigCommand_Err implements PoxCommand { // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) poxAddressToTuple(this.wallet.btcAddress), // (start-burn-ht uint) - Cl.uint(burnBlockHeight), + Cl.uint(burnBlockHeight + 1), // (lock-period uint) Cl.uint(this.period), // (signer-sig (optional (buff 65))) From 1de982d87ab1b82f6280b926df3bbaec57c38de8 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Wed, 22 May 2024 19:01:19 +0300 Subject: [PATCH 33/72] Add the unhappy path cases for `StackStxXCommand_Err` This commit: - adds 6 unhappy path cases for the `stack-stx` PoX-4 method, 3 for each signing method (authorization or signature) - adds a dictionary that contains the PoX-4 error names and the error codes - adds the command run tracking inside the `check` method, resulting in displaying all the paths hit and the number of times. --- .../tests/pox-4/err_Commands.ts | 207 ++++++++++++++++-- 1 file changed, 194 insertions(+), 13 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 2e4259f740..08a911e68a 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -1,16 +1,27 @@ import fc from "fast-check"; -import { PoxCommand, Stacker, Stub, StxAddress, Wallet } from "./pox_CommandModel"; +import { + PoxCommand, + Stacker, + Stub, + StxAddress, + Wallet, +} from "./pox_CommandModel"; import { StackStxSigCommand_Err } from "./pox_StackStxSigCommand_Err"; import { StackStxAuthCommand_Err } from "./pox_StackStxAuthCommand_Err"; import { Simnet } from "@hirosystems/clarinet-sdk"; +const POX_4_ERRORS = { + ERR_STACKING_ALREADY_STACKED: 3, + ERR_STACKING_ALREADY_DELEGATED: 20, +}; + export function ErrCommands( wallets: Map, stackers: Map, network: Simnet, ): fc.Arbitrary[] { const cmds = [ - // StackStxAuthCommand_Err + // StackStxAuthCommand_Err_Stacking_Already_Stacked_1 fc.record({ wallet: fc.constantFrom(...wallets.values()), authId: fc.nat(), @@ -29,17 +40,102 @@ export function ErrCommands( r.authId, r.period, r.margin, - function (this: StackStxAuthCommand_Err, model: Readonly): boolean { + function ( + this: StackStxAuthCommand_Err, + model: Readonly, + ): boolean { const stacker = model.stackers.get(this.wallet.stxAddress)!; - return ( - model.stackingMinimum > 0 && !stacker.isStacking && + if ( + model.stackingMinimum > 0 && + stacker.isStacking && !stacker.hasDelegated - ); + ) { + model.trackCommandRun( + "StackStxAuthCommand_Err_Stacking_Already_Stacked_1", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_ALREADY_STACKED, + ) + ), + // StackStxAuthCommand_Err_Stacking_Already_Stacked_2 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + period: fc.integer({ min: 1, max: 12 }), + margin: fc.integer({ min: 1, max: 9 }), + }).map(( + r: { + wallet: Wallet; + authId: number; + period: number; + margin: number; + }, + ) => + new StackStxAuthCommand_Err( + r.wallet, + r.authId, + r.period, + r.margin, + function ( + this: StackStxAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.hasDelegated + ) { + model.trackCommandRun( + "StackStxAuthCommand_Err_Stacking_Already_Stacked_2", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_ALREADY_STACKED, + ) + ), + // StackStxAuthCommand_Err_Stacking_Already_Delegated + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + period: fc.integer({ min: 1, max: 12 }), + margin: fc.integer({ min: 1, max: 9 }), + }).map(( + r: { + wallet: Wallet; + authId: number; + period: number; + margin: number; + }, + ) => + new StackStxAuthCommand_Err( + r.wallet, + r.authId, + r.period, + r.margin, + function ( + this: StackStxAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + !stacker.isStacking && + stacker.hasDelegated + ) { + model.trackCommandRun( + "StackStxAuthCommand_Err_Stacking_Already_Delegated", + ); + return true; + } else return false; }, - 123, + POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, ) ), - // StackStxSigCommand_Err + // StackStxSigCommand_Err_Stacking_Already_Stacked_1 fc.record({ wallet: fc.constantFrom(...wallets.values()), authId: fc.nat(), @@ -58,14 +154,99 @@ export function ErrCommands( r.authId, r.period, r.margin, - function (this: StackStxSigCommand_Err, model: Readonly): boolean { + function ( + this: StackStxSigCommand_Err, + model: Readonly, + ): boolean { const stacker = model.stackers.get(this.wallet.stxAddress)!; - return ( - model.stackingMinimum > 0 && !stacker.isStacking && + if ( + model.stackingMinimum > 0 && + stacker.isStacking && !stacker.hasDelegated - ); + ) { + model.trackCommandRun( + "StackStxSigCommand_Err_Stacking_Already_Stacked_1", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_ALREADY_STACKED, + ) + ), + // StackStxSigCommand_Err_Stacking_Already_Stacked_2 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + period: fc.integer({ min: 1, max: 12 }), + margin: fc.integer({ min: 1, max: 9 }), + }).map(( + r: { + wallet: Wallet; + authId: number; + period: number; + margin: number; + }, + ) => + new StackStxSigCommand_Err( + r.wallet, + r.authId, + r.period, + r.margin, + function ( + this: StackStxSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.hasDelegated + ) { + model.trackCommandRun( + "StackStxSigCommand_Err_Stacking_Already_Stacked_2", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_ALREADY_STACKED, + ) + ), + // StackStxSigCommand_Err_Stacking_Already_Delegated + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + period: fc.integer({ min: 1, max: 12 }), + margin: fc.integer({ min: 1, max: 9 }), + }).map(( + r: { + wallet: Wallet; + authId: number; + period: number; + margin: number; + }, + ) => + new StackStxSigCommand_Err( + r.wallet, + r.authId, + r.period, + r.margin, + function ( + this: StackStxSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + !stacker.isStacking && + stacker.hasDelegated + ) { + model.trackCommandRun( + "StackStxSigCommand_Err_Stacking_Already_Delegated", + ); + return true; + } else return false; }, - 123, + POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, ) ), ]; From be6966c3603d573a4148363806f441f0e3220167 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Wed, 22 May 2024 19:02:27 +0300 Subject: [PATCH 34/72] Remove `StackStxXCommand.ts` from statistics They needed to be excluded as we have removed the command run tracking from the run method. --- .../tests/pox-4/pox-4.stateful-prop.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts index a2be09f593..ceadd9ec4e 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts @@ -109,7 +109,9 @@ it("statefully interacts with PoX-4", async () => { // commands are run at least once. const statistics = fs.readdirSync(path.join(__dirname)).filter((file) => file.startsWith("pox_") && file.endsWith(".ts") && - file !== "pox_CommandModel.ts" && file !== "pox_Commands.ts" + file !== "pox_CommandModel.ts" && file !== "pox_Commands.ts" && + file !== "pox_StackStxAuthCommand_Err.ts" && + file !== "pox_StackStxSigCommand_Err.ts" ).map((file) => file.slice(4, -3)); // Remove "pox_" prefix and ".ts" suffix. // This is the initial state of the model. @@ -159,7 +161,7 @@ it("statefully interacts with PoX-4", async () => { ), { // Defines the number of test iterations to run; default is 100. - numRuns: 1000, + numRuns: 20000, // 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 From 544382cc0d351aa594c8155a25897cff86a2a6c1 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 24 May 2024 14:08:26 +0300 Subject: [PATCH 35/72] =?UTF-8?q?Add=20unhappy=20path=20for=C2=A0`revoke-d?= =?UTF-8?q?elegate-stx`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The added unhappy path tries to call revoke-delegate-stx with an address that is not delegating. --- .../tests/pox-4/err_Commands.ts | 30 +++++++++ .../pox-4/pox_RevokeDelegateStxCommand_Err.ts | 66 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 08a911e68a..a7e05dc5c7 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -9,10 +9,12 @@ import { import { StackStxSigCommand_Err } from "./pox_StackStxSigCommand_Err"; import { StackStxAuthCommand_Err } from "./pox_StackStxAuthCommand_Err"; import { Simnet } from "@hirosystems/clarinet-sdk"; +import { RevokeDelegateStxCommand_Err } from "./pox_RevokeDelegateStxCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_ALREADY_STACKED: 3, ERR_STACKING_ALREADY_DELEGATED: 20, + ERR_DELEGATION_ALREADY_REVOKED: 34, }; export function ErrCommands( @@ -249,6 +251,34 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, ) ), + // RevokeDelegateStxCommand_Err_Delegation_Already_Revoked + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + }).map(( + r: { + wallet: Wallet; + }, + ) => + new RevokeDelegateStxCommand_Err( + r.wallet, + function ( + this: RevokeDelegateStxCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + !stacker.hasDelegated + ) { + model.trackCommandRun( + "RevokeDelegateStxCommand_Err_Delegation_Already_Revoked", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_DELEGATION_ALREADY_REVOKED, + ) + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand_Err.ts new file mode 100644 index 0000000000..60b3439e8e --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand_Err.ts @@ -0,0 +1,66 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { expect } from "vitest"; +import { Cl } from "@stacks/transactions"; + +type CheckFunc = ( + this: RevokeDelegateStxCommand_Err, + model: Readonly, +) => boolean; + +export class RevokeDelegateStxCommand_Err implements PoxCommand { + readonly wallet: Wallet; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `RevokeDelegateStxCommand_Err` to revoke delegate uSTX for stacking. + * + * @param wallet - Represents the Stacker's wallet. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor(wallet: Wallet, checkFunc: CheckFunc, errorCode: number) { + this.wallet = wallet; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + // Act + const revokeDelegateStx = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "revoke-delegate-stx", + [], + this.wallet.stxAddress, + ); + + // Assert + expect(revokeDelegateStx.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.wallet.label}`, + "revoke-delegate-stx", + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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.wallet.stxAddress} revoke-delegate-stx`; + } +} From 3227bc41c31107a34f06570721ec059e7198688d Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 24 May 2024 14:16:45 +0300 Subject: [PATCH 36/72] Remove `RevokeDelegateStxCommand_Err` from statistics The command run tracking was moved inside the command's check function. No need to report the run using the file name anymore. --- .../tests/pox-4/pox-4.stateful-prop.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts index ceadd9ec4e..2f811b0db1 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts @@ -111,7 +111,8 @@ it("statefully interacts with PoX-4", async () => { file.startsWith("pox_") && file.endsWith(".ts") && file !== "pox_CommandModel.ts" && file !== "pox_Commands.ts" && file !== "pox_StackStxAuthCommand_Err.ts" && - file !== "pox_StackStxSigCommand_Err.ts" + file !== "pox_StackStxSigCommand_Err.ts" && + file !== "pox_RevokeDelegateStxCommand_Err.ts" ).map((file) => file.slice(4, -3)); // Remove "pox_" prefix and ".ts" suffix. // This is the initial state of the model. From e7610e3fa1b14bd52760f17a6f219e32517963a4 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 24 May 2024 14:21:58 +0300 Subject: [PATCH 37/72] =?UTF-8?q?Add=20unhappy=20path=20for=C2=A0`delegate?= =?UTF-8?q?-stx`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The added unhappy path tries to call delegate-stx with an address that is already delegating. --- .../tests/pox-4/err_Commands.ts | 39 +++++++ .../tests/pox-4/pox_DelegateStxCommand_Err.ts | 104 ++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStxCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index a7e05dc5c7..6a6c3b7028 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -10,6 +10,7 @@ import { StackStxSigCommand_Err } from "./pox_StackStxSigCommand_Err"; import { StackStxAuthCommand_Err } from "./pox_StackStxAuthCommand_Err"; import { Simnet } from "@hirosystems/clarinet-sdk"; import { RevokeDelegateStxCommand_Err } from "./pox_RevokeDelegateStxCommand_Err"; +import { DelegateStxCommand_Err } from "./pox_DelegateStxCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_ALREADY_STACKED: 3, @@ -279,6 +280,44 @@ export function ErrCommands( POX_4_ERRORS.ERR_DELEGATION_ALREADY_REVOKED, ) ), + // DelegateStxCommand_Err_Stacking_Already_Delegated + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + delegateTo: fc.constantFrom(...wallets.values()), + untilBurnHt: fc.integer({ min: 1 }), + amount: fc.bigInt({ min: 0n, max: 100_000_000_000_000n }), + }) + .map(( + r: { + wallet: Wallet; + delegateTo: Wallet; + untilBurnHt: number; + amount: bigint; + }, + ) => + new DelegateStxCommand_Err( + r.wallet, + r.delegateTo, + r.untilBurnHt, + r.amount, + function ( + this: DelegateStxCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + stacker.hasDelegated + ) { + model.trackCommandRun( + "DelegateStxCommand_Err_Stacking_Already_Delegated", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, + ) + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStxCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStxCommand_Err.ts new file mode 100644 index 0000000000..138d99265f --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStxCommand_Err.ts @@ -0,0 +1,104 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { poxAddressToTuple } from "@stacks/stacking"; +import { expect } from "vitest"; +import { Cl } from "@stacks/transactions"; + +type CheckFunc = ( + this: DelegateStxCommand_Err, + model: Readonly, +) => boolean; + +export class DelegateStxCommand_Err implements PoxCommand { + readonly wallet: Wallet; + readonly delegateTo: Wallet; + readonly untilBurnHt: number; + readonly amount: bigint; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `DelegateStxCommand_Err` to delegate uSTX for stacking. + * + * @param wallet - Represents the Stacker's wallet. + * @param delegateTo - Represents the Delegatee's STX address. + * @param untilBurnHt - The burn block height until the delegation is valid. + * @param amount - The maximum amount the `Stacker` delegates the `Delegatee` + * to stack on his behalf. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + wallet: Wallet, + delegateTo: Wallet, + untilBurnHt: number, + amount: bigint, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.wallet = wallet; + this.delegateTo = delegateTo; + this.untilBurnHt = untilBurnHt; + this.amount = amount; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + // The amount of uSTX delegated by the Stacker to the Delegatee. + // Even if there are no constraints about the delegated amount, + // it will be checked in the future, when calling delegate-stack-stx. + const amountUstx = Number(this.amount); + + // Act + const delegateStx = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "delegate-stx", + [ + // (amount-ustx uint) + Cl.uint(amountUstx), + // (delegate-to principal) + Cl.principal(this.delegateTo.stxAddress), + // (until-burn-ht (optional uint)) + Cl.some(Cl.uint(this.untilBurnHt)), + // (pox-addr (optional { version: (buff 1), hashbytes: (buff 32) })) + Cl.some(poxAddressToTuple(this.delegateTo.btcAddress)), + ], + this.wallet.stxAddress, + ); + + // Assert + expect(delegateStx.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.wallet.label}`, + "delegate-stx", + "amount", + amountUstx.toString(), + "delegated to", + this.delegateTo.label, + "until", + this.untilBurnHt.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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.wallet.label} delegate-stx to ${this.delegateTo.label} until burn ht ${this.untilBurnHt}`; + } +} From 232d38830439598596e909839bd668e9d5fb9f1c Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 24 May 2024 14:24:20 +0300 Subject: [PATCH 38/72] Remove `DelegateStxCommand_Err` from statistics The command run tracking was moved inside the command's check function. No need to report the run using the file name anymore. --- .../tests/pox-4/pox-4.stateful-prop.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts index 2f811b0db1..25adfde9d9 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts @@ -112,7 +112,8 @@ it("statefully interacts with PoX-4", async () => { file !== "pox_CommandModel.ts" && file !== "pox_Commands.ts" && file !== "pox_StackStxAuthCommand_Err.ts" && file !== "pox_StackStxSigCommand_Err.ts" && - file !== "pox_RevokeDelegateStxCommand_Err.ts" + file !== "pox_RevokeDelegateStxCommand_Err.ts" && + file !== "pox_DelegateStxCommand_Err.ts" ).map((file) => file.slice(4, -3)); // Remove "pox_" prefix and ".ts" suffix. // This is the initial state of the model. From bdcee6bd8eadcb6a824b71bb2aa0d9aa86b2b995 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 24 May 2024 16:26:27 +0300 Subject: [PATCH 39/72] Use simnet `mineBlock` inside `StackStxAuthCommand_Err` This commit: - includes the authorization and the function call in the same block. It is needed because otherwise, it can result in issuing the authorization for the wrong reward cycle. - updates the passed start-burn-ht param, different from the StackStxSigCommand. If not doing it like this, the test fails when the command is called at the limit between 2 reward cycles. - removes unnecessary operations: retrieving the reward cycle, retrieving the unlockBurnHeight. --- .../pox-4/pox_StackStxAuthCommand_Err.ts | 102 +++++++----------- 1 file changed, 39 insertions(+), 63 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts index 6889e89917..37f32a5458 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand_Err.ts @@ -6,15 +6,10 @@ import { Wallet, } from "./pox_CommandModel.ts"; import { poxAddressToTuple } from "@stacks/stacking"; -import { assert, expect } from "vitest"; -import { - Cl, - ClarityType, - ClarityValue, - cvToValue, - isClarityType, -} from "@stacks/transactions"; +import { expect } from "vitest"; +import { Cl, ClarityValue, cvToValue } from "@stacks/transactions"; import { currentCycle } from "./pox_Commands.ts"; +import { tx } from "@hirosystems/clarinet-sdk"; type CheckFunc = ( this: StackStxAuthCommand_Err, @@ -66,53 +61,50 @@ export class StackStxAuthCommand_Err implements PoxCommand { // in the given reward cycle multiplied by the margin, which is a randomly // generated number passed to the constructor of this class. const maxAmount = model.stackingMinimum * this.margin; + const amountUstx = maxAmount; - const { result: setAuthorization } = real.network.callPublicFn( - "ST000000000000000000002AMW42H.pox-4", - "set-signer-key-authorization", - [ - // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) - poxAddressToTuple(this.wallet.btcAddress), - // (period uint) - Cl.uint(this.period), - // (reward-cycle uint) - Cl.uint(currentRewCycle), - // (topic (string-ascii 14)) - Cl.stringAscii("stack-stx"), - // (signer-key (buff 33)) - Cl.bufferFromHex(this.wallet.signerPubKey), - // (allowed bool) - Cl.bool(true), - // (max-amount uint) - Cl.uint(maxAmount), - // (auth-id uint) - Cl.uint(this.authId), - ], - this.wallet.stxAddress, - ); - - expect(setAuthorization).toBeOk(Cl.bool(true)); const burnBlockHeightCV = real.network.runSnippet("burn-block-height"); const burnBlockHeight = Number( cvToValue(burnBlockHeightCV as ClarityValue), ); - // The amount of uSTX to be locked in the reward cycle. For this test, we - // will use the maximum amount of uSTX that can be used (per tx) with this - // signer key. - const amountUstx = maxAmount; - // Act - const stackStx = real.network.callPublicFn( - "ST000000000000000000002AMW42H.pox-4", - "stack-stx", - [ + + // Include the authorization and the `stack-stx` transactions in a single + // block. This way we ensure both the authorization and the stack-stx + // transactions are called during the same reward cycle, so the authorization + // currentRewCycle param is relevant for the upcoming stack-stx call. + const block = real.network.mineBlock([ + tx.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "set-signer-key-authorization", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.wallet.btcAddress), + // (period uint) + Cl.uint(this.period), + // (reward-cycle uint) + Cl.uint(currentRewCycle), + // (topic (string-ascii 14)) + Cl.stringAscii("stack-stx"), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (allowed bool) + Cl.bool(true), + // (max-amount uint) + Cl.uint(maxAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ), + tx.callPublicFn("ST000000000000000000002AMW42H.pox-4", "stack-stx", [ // (amount-ustx uint) Cl.uint(amountUstx), // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) poxAddressToTuple(this.wallet.btcAddress), // (start-burn-ht uint) - Cl.uint(burnBlockHeight + 1), + Cl.uint(burnBlockHeight), // (lock-period uint) Cl.uint(this.period), // (signer-sig (optional (buff 65))) @@ -123,28 +115,12 @@ export class StackStxAuthCommand_Err implements PoxCommand { Cl.uint(maxAmount), // (auth-id uint) Cl.uint(this.authId), - ], - this.wallet.stxAddress, - ); - - const { result: rewardCycle } = real.network.callReadOnlyFn( - "ST000000000000000000002AMW42H.pox-4", - "burn-height-to-reward-cycle", - [Cl.uint(burnBlockHeight)], - this.wallet.stxAddress, - ); - assert(isClarityType(rewardCycle, ClarityType.UInt)); - - const { result: unlockBurnHeight } = real.network.callReadOnlyFn( - "ST000000000000000000002AMW42H.pox-4", - "reward-cycle-to-burn-height", - [Cl.uint(Number(rewardCycle.value) + this.period + 1)], - this.wallet.stxAddress, - ); - assert(isClarityType(unlockBurnHeight, ClarityType.UInt)); + ], this.wallet.stxAddress), + ]); // Assert - expect(stackStx.result).toBeErr(Cl.int(this.errorCode)); + expect(block[0].result).toBeOk(Cl.bool(true)); + expect(block[1].result).toBeErr(Cl.int(this.errorCode)); // 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. From 190d7a5f88df8d8f2111ef3d584661e82dd756fa Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 27 May 2024 12:22:29 +0300 Subject: [PATCH 40/72] Add the unhappy path cases for `StackAggCommitSigCommand_Err` This commit: - adds 3 unhappy path cases for the `stack-aggregation-commit` PoX-4 method, called using a signature. - adds the command run tracking inside the `check` method. - adds the expected `stack-aggregation-commit` PoX-4 errors to the POX_4_ERRORS dictionary. --- .../tests/pox-4/err_Commands.ts | 89 +++++++++++++ ...ox_StackAggregationCommitSigCommand_Err.ts | 121 ++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 6a6c3b7028..8c1bdf774d 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -11,9 +11,12 @@ import { StackStxAuthCommand_Err } from "./pox_StackStxAuthCommand_Err"; import { Simnet } from "@hirosystems/clarinet-sdk"; import { RevokeDelegateStxCommand_Err } from "./pox_RevokeDelegateStxCommand_Err"; import { DelegateStxCommand_Err } from "./pox_DelegateStxCommand_Err"; +import { StackAggregationCommitSigCommand_Err } from "./pox_StackAggregationCommitSigCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_ALREADY_STACKED: 3, + ERR_STACKING_NO_SUCH_PRINCIPAL: 4, + ERR_STACKING_THRESHOLD_NOT_MET: 11, ERR_STACKING_ALREADY_DELEGATED: 20, ERR_DELEGATION_ALREADY_REVOKED: 34, }; @@ -318,6 +321,92 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, ) ), + // StackAggregationCommitSigCommand_Err_Stacking_Threshold_Not_Met + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitSigCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitSigCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + operator.lockedAddresses.length > 0 && + !(operator.amountToCommit >= model.stackingMinimum) && + operator.amountToCommit > 0 + ) { + model.trackCommandRun( + "StackAggregationCommitSigCommand_Err_Stacking_Threshold_Not_Met", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_THRESHOLD_NOT_MET, + ), + ), + // StackAggregationCommitSigCommand_Err_Stacking_No_Such_Principal_1 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitSigCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitSigCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + operator.lockedAddresses.length > 0 && + !(operator.amountToCommit >= model.stackingMinimum) && + operator.amountToCommit == 0 + ) { + model.trackCommandRun( + "StackAggregationCommitSigCommand_Err_Stacking_No_Such_Principal_1", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), + // StackAggregationCommitSigCommand_Err_Stacking_No_Such_Principal_2 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitSigCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitSigCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + !(operator.lockedAddresses.length > 0) && + !(operator.amountToCommit >= model.stackingMinimum) + ) { + model.trackCommandRun( + "StackAggregationCommitSigCommand_Err_Stacking_No_Such_Principal_2", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand_Err.ts new file mode 100644 index 0000000000..ca53b56d1c --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand_Err.ts @@ -0,0 +1,121 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { Pox4SignatureTopic, poxAddressToTuple } from "@stacks/stacking"; +import { expect } from "vitest"; +import { Cl } from "@stacks/transactions"; +import { bufferFromHex } from "@stacks/transactions/dist/cl"; +import { currentCycle } from "./pox_Commands.ts"; + +type CheckFunc = ( + this: StackAggregationCommitSigCommand_Err, + model: Readonly, +) => boolean; + +export class StackAggregationCommitSigCommand_Err implements PoxCommand { + readonly operator: Wallet; + readonly authId: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackAggregationCommitAuthCommand_Err` to lock uSTX for stacking. + * + * @param operator - Represents the `Operator`'s wallet. + * @param authId - Unique `auth-id` for the authorization. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + operator: Wallet, + authId: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.operator = operator; + this.authId = authId; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const currentRewCycle = currentCycle(real.network); + const operatorWallet = model.stackers.get(this.operator.stxAddress)!; + const committedAmount = operatorWallet.amountToCommit; + + const signerSig = this.operator.stackingClient.signPoxSignature({ + // The signer key being authorized. + signerPrivateKey: this.operator.signerPrvKey, + // The reward cycle for which the authorization is valid. + // For stack-stx and stack-extend, this refers to the reward cycle + // where the transaction is confirmed. For stack-aggregation-commit, + // this refers to the reward cycle argument in that function. + rewardCycle: currentRewCycle + 1, + // For stack-stx, this refers to lock-period. For stack-extend, + // this refers to extend-count. For stack-aggregation-commit, this is + // u1. + period: 1, + // A string representing the function where this authorization is valid. + // Either stack-stx, stack-extend, stack-increase or agg-commit. + topic: Pox4SignatureTopic.AggregateCommit, + // The PoX address that can be used with this signer key. + poxAddress: this.operator.btcAddress, + // The unique auth-id for this authorization. + authId: this.authId, + // The maximum amount of uSTX that can be used (per tx) with this signer + // key. + maxAmount: committedAmount, + }); + + // Act + const stackAggregationCommit = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-aggregation-commit", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.operator.btcAddress), + // (reward-cycle uint) + Cl.uint(currentRewCycle + 1), + // (signer-sig (optional (buff 65))) + Cl.some(bufferFromHex(signerSig)), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.operator.signerPubKey), + // (max-amount uint) + Cl.uint(committedAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.operator.stxAddress, + ); + + // Assert + expect(stackAggregationCommit.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.operator.label}`, + "stack-agg-commit", + "amount committed", + committedAmount.toString(), + "signature", + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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}`; + } +} From b0f8464e153ca8a8d188ff060ad85f12785d22d8 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 27 May 2024 12:26:54 +0300 Subject: [PATCH 41/72] Remove `StackAggregationCommitSigCommand_Err` from statistics The command run tracking was moved inside the command's check function. No need to report the run using the file name anymore. --- .../tests/pox-4/pox-4.stateful-prop.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts index 25adfde9d9..7a89fafaf6 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts @@ -113,7 +113,8 @@ it("statefully interacts with PoX-4", async () => { file !== "pox_StackStxAuthCommand_Err.ts" && file !== "pox_StackStxSigCommand_Err.ts" && file !== "pox_RevokeDelegateStxCommand_Err.ts" && - file !== "pox_DelegateStxCommand_Err.ts" + file !== "pox_DelegateStxCommand_Err.ts" && + file !== "pox_StackAggregationCommitSigCommand_Err.ts" ).map((file) => file.slice(4, -3)); // Remove "pox_" prefix and ".ts" suffix. // This is the initial state of the model. From 367e89a613f9e556dfed50f4c4a196e09d66fbb5 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 27 May 2024 13:29:41 +0300 Subject: [PATCH 42/72] Add the unhappy path cases for `StackAggCommitAuthCommand_Err` This commit: - adds 3 unhappy path cases for the `stack-aggregation-commit` PoX-4 method, called using an authorization. - adds the command run tracking inside the `check` method. --- .../tests/pox-4/err_Commands.ts | 87 ++++++++++++ ...x_StackAggregationCommitAuthCommand_Err.ts | 127 ++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 8c1bdf774d..8276d02f12 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -12,6 +12,7 @@ import { Simnet } from "@hirosystems/clarinet-sdk"; import { RevokeDelegateStxCommand_Err } from "./pox_RevokeDelegateStxCommand_Err"; import { DelegateStxCommand_Err } from "./pox_DelegateStxCommand_Err"; import { StackAggregationCommitSigCommand_Err } from "./pox_StackAggregationCommitSigCommand_Err"; +import { StackAggregationCommitAuthCommand_Err } from "./pox_StackAggregationCommitAuthCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_ALREADY_STACKED: 3, @@ -407,6 +408,92 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, ), ), + // StackAggregationCommitAuthCommand_Err_Stacking_Threshold_Not_Met + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitAuthCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitAuthCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + operator.lockedAddresses.length > 0 && + !(operator.amountToCommit >= model.stackingMinimum) && + operator.amountToCommit > 0 + ) { + model.trackCommandRun( + "StackAggregationCommitAuthCommand_Err_Stacking_Threshold_Not_Met", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_THRESHOLD_NOT_MET, + ), + ), + // StackAggregationCommitAuthCommand_Err_Stacking_No_Such_Principal_1 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitAuthCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitAuthCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + operator.lockedAddresses.length > 0 && + !(operator.amountToCommit >= model.stackingMinimum) && + operator.amountToCommit === 0 + ) { + model.trackCommandRun( + "StackAggregationCommitAuthCommand_Err_Stacking_No_Such_Principal_1", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), + // StackAggregationCommitAuthCommand_Err_Stacking_No_Such_Principal_2 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitAuthCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitAuthCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + !(operator.lockedAddresses.length > 0) && + !(operator.amountToCommit >= model.stackingMinimum) + ) { + model.trackCommandRun( + "StackAggregationCommitAuthCommand_Err_Stacking_No_Such_Principal_2", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand_Err.ts new file mode 100644 index 0000000000..3580061fae --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand_Err.ts @@ -0,0 +1,127 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { poxAddressToTuple } from "@stacks/stacking"; +import { expect } from "vitest"; +import { Cl } from "@stacks/transactions"; +import { currentCycle } from "./pox_Commands.ts"; +import { tx } from "@hirosystems/clarinet-sdk"; + +type CheckFunc = ( + this: StackAggregationCommitAuthCommand_Err, + model: Readonly, +) => boolean; + +export class StackAggregationCommitAuthCommand_Err implements PoxCommand { + readonly operator: Wallet; + readonly authId: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackAggregationCommitAuthCommand_Err` to lock uSTX for stacking. + * + * @param operator - Represents the `Operator`'s wallet. + * @param authId - Unique `auth-id` for the authorization. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + operator: Wallet, + authId: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.operator = operator; + this.authId = authId; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const currentRewCycle = currentCycle(real.network); + const operatorWallet = model.stackers.get(this.operator.stxAddress)!; + const committedAmount = operatorWallet.amountToCommit; + + // Include the authorization and the `stack-aggregation-commit` transactions + // in a single block. This way we ensure both the authorization and the + // stack-aggregation-commit transactions are called during the same reward + // cycle, so the authorization currentRewCycle param is relevant for the + // upcoming stack-aggregation-commit call. + const block = real.network.mineBlock([ + tx.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "set-signer-key-authorization", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.operator.btcAddress), + // (period uint) + Cl.uint(1), + // (reward-cycle uint) + Cl.uint(currentRewCycle + 1), + // (topic (string-ascii 14)) + Cl.stringAscii("agg-commit"), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.operator.signerPubKey), + // (allowed bool) + Cl.bool(true), + // (max-amount uint) + Cl.uint(committedAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.operator.stxAddress, + ), + tx.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-aggregation-commit", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.operator.btcAddress), + // (reward-cycle uint) + Cl.uint(currentRewCycle + 1), + // (signer-sig (optional (buff 65))) + Cl.none(), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.operator.signerPubKey), + // (max-amount uint) + Cl.uint(committedAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.operator.stxAddress, + ), + ]); + + // Assert + expect(block[0].result).toBeOk(Cl.bool(true)); + expect(block[1].result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.operator.label}`, + "stack-agg-commit", + "amount committed", + committedAmount.toString(), + "authorization", + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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}`; + } +} From abdf8bd39dee744d1adfbacc3808af3906f5c973 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 27 May 2024 13:31:25 +0300 Subject: [PATCH 43/72] Remove `StackAggregationCommitAuthCommand_Err` from statistics The command run tracking was moved inside the command's check function. No need to report the run using the file name anymore. --- .../tests/pox-4/pox-4.stateful-prop.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts index 7a89fafaf6..a77156a87a 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts @@ -114,7 +114,8 @@ it("statefully interacts with PoX-4", async () => { file !== "pox_StackStxSigCommand_Err.ts" && file !== "pox_RevokeDelegateStxCommand_Err.ts" && file !== "pox_DelegateStxCommand_Err.ts" && - file !== "pox_StackAggregationCommitSigCommand_Err.ts" + file !== "pox_StackAggregationCommitSigCommand_Err.ts" && + file !== "pox_StackAggregationCommitAuthCommand_Err.ts" ).map((file) => file.slice(4, -3)); // Remove "pox_" prefix and ".ts" suffix. // This is the initial state of the model. From 3ab6e61e2c36223b45f4bccb3b3a7bd7aa793202 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 27 May 2024 15:23:30 +0300 Subject: [PATCH 44/72] Use strict equality inside generator --- .../tests/pox-4/err_Commands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 8276d02f12..3c3d242d33 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -369,7 +369,7 @@ export function ErrCommands( if ( operator.lockedAddresses.length > 0 && !(operator.amountToCommit >= model.stackingMinimum) && - operator.amountToCommit == 0 + operator.amountToCommit === 0 ) { model.trackCommandRun( "StackAggregationCommitSigCommand_Err_Stacking_No_Such_Principal_1", From 91a7c43b2b7b4cbff198757fef9fc45aa2afa82a Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 27 May 2024 15:44:36 +0300 Subject: [PATCH 45/72] Order statistics alphabetically This commit improves the unhappy paths execution visibility after the test suite run is complete. --- .../tests/pox-4/pox_CommandModel.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_CommandModel.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_CommandModel.ts index ce1d2a28b4..3d4b7415f9 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_CommandModel.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_CommandModel.ts @@ -46,7 +46,13 @@ export class Stub { reportCommandRuns() { console.log("Command run method execution counts:"); - this.statistics.forEach((count, commandName) => { + const orderedStatistics = Array.from(this.statistics.entries()).sort( + ([keyA], [keyB]) => { + return keyA.localeCompare(keyB); + }, + ); + + orderedStatistics.forEach(([commandName, count]) => { console.log(`${commandName}: ${count}`); }); } From ab686c4186db8169cabe0c43948646805e52adab Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 27 May 2024 22:53:56 +0300 Subject: [PATCH 46/72] Add the unhappy path cases for `StackAggCommitIndexedSigCommand_Err` This commit: - adds 3 unhappy path cases for the `stack-aggregation-commit-indexed` PoX-4 method, called using a signature. - adds the command run tracking inside the `check` method. --- .../tests/pox-4/err_Commands.ts | 87 ++++++++++++ ...kAggregationCommitIndexedSigCommand_Err.ts | 124 ++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 3c3d242d33..bfe600e594 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -13,6 +13,7 @@ import { RevokeDelegateStxCommand_Err } from "./pox_RevokeDelegateStxCommand_Err import { DelegateStxCommand_Err } from "./pox_DelegateStxCommand_Err"; import { StackAggregationCommitSigCommand_Err } from "./pox_StackAggregationCommitSigCommand_Err"; import { StackAggregationCommitAuthCommand_Err } from "./pox_StackAggregationCommitAuthCommand_Err"; +import { StackAggregationCommitIndexedSigCommand_Err } from "./pox_StackAggregationCommitIndexedSigCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_ALREADY_STACKED: 3, @@ -494,6 +495,92 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, ), ), + // StackAggregationCommitIndexedSigCommand_Err_Stacking_Threshold_Not_Met + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitIndexedSigCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitIndexedSigCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + operator.lockedAddresses.length > 0 && + !(operator.amountToCommit >= model.stackingMinimum) && + operator.amountToCommit > 0 + ) { + model.trackCommandRun( + "StackAggregationCommitIndexedSigCommand_Err_Stacking_Threshold_Not_Met", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_THRESHOLD_NOT_MET, + ), + ), + // StackAggregationCommitIndexedSigCommand_Err_Stacking_No_Such_Principal_1 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitIndexedSigCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitIndexedSigCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + operator.lockedAddresses.length > 0 && + !(operator.amountToCommit >= model.stackingMinimum) && + !(operator.amountToCommit > 0) + ) { + model.trackCommandRun( + "StackAggregationCommitIndexedSigCommand_Err_Stacking_No_Such_Principal_1", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), + // StackAggregationCommitIndexedSigCommand_Err_Stacking_No_Such_Principal_2 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitIndexedSigCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitIndexedSigCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + !(operator.lockedAddresses.length > 0) && + !(operator.amountToCommit >= model.stackingMinimum) + ) { + model.trackCommandRun( + "StackAggregationCommitIndexedSigCommand_Err_Stacking_No_Such_Principal_2", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand_Err.ts new file mode 100644 index 0000000000..22b5a4f923 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand_Err.ts @@ -0,0 +1,124 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { Pox4SignatureTopic, poxAddressToTuple } from "@stacks/stacking"; +import { expect } from "vitest"; +import { Cl } from "@stacks/transactions"; +import { bufferFromHex } from "@stacks/transactions/dist/cl"; +import { currentCycle } from "./pox_Commands.ts"; + +type CheckFunc = ( + this: StackAggregationCommitIndexedSigCommand_Err, + model: Readonly, +) => boolean; + +export class StackAggregationCommitIndexedSigCommand_Err implements PoxCommand { + readonly operator: Wallet; + readonly authId: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackAggregationCommitIndexedSigCommand_Err` to lock uSTX + * for stacking. + * + * @param operator - Represents the `Operator`'s wallet. + * @param authId - Unique `auth-id` for the authorization. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + operator: Wallet, + authId: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.operator = operator; + this.authId = authId; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const currentRewCycle = currentCycle(real.network); + const operatorWallet = model.stackers.get(this.operator.stxAddress)!; + const committedAmount = operatorWallet.amountToCommit; + + const signerSig = this.operator.stackingClient.signPoxSignature({ + // The signer key being authorized. + signerPrivateKey: this.operator.signerPrvKey, + // The reward cycle for which the authorization is valid. + // For stack-stx and stack-extend, this refers to the reward cycle + // where the transaction is confirmed. For stack-aggregation-commit, + // this refers to the reward cycle argument in that function. + rewardCycle: currentRewCycle + 1, + // For stack-stx, this refers to lock-period. For stack-extend, + // this refers to extend-count. For stack-aggregation-commit, this is + // u1. + period: 1, + // A string representing the function where this authorization is valid. + // Either stack-stx, stack-extend, stack-increase or agg-commit. + topic: Pox4SignatureTopic.AggregateCommit, + // The PoX address that can be used with this signer key. + poxAddress: this.operator.btcAddress, + // The unique auth-id for this authorization. + authId: this.authId, + // The maximum amount of uSTX that can be used (per tx) with this signer + // key. + maxAmount: committedAmount, + }); + + // Act + const stackAggregationCommitIndexed = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-aggregation-commit-indexed", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.operator.btcAddress), + // (reward-cycle uint) + Cl.uint(currentRewCycle + 1), + // (signer-sig (optional (buff 65))) + Cl.some(bufferFromHex(signerSig)), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.operator.signerPubKey), + // (max-amount uint) + Cl.uint(committedAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.operator.stxAddress, + ); + + // Assert + expect(stackAggregationCommitIndexed.result).toBeErr( + Cl.int(this.errorCode), + ); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.operator.label}`, + "stack-agg-commit-indexed", + "amount committed", + committedAmount.toString(), + "authorization", + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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}`; + } +} From 50475b75bc25e209447f70caedac5cb5d22d9c92 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Tue, 28 May 2024 14:59:18 +0300 Subject: [PATCH 47/72] Add the unhappy path cases for `StackAggCommitIndexedAuthCommand_Err` This commit: - adds 3 unhappy path cases for the `stack-aggregation-commit-indexed` PoX-4 method, called using an authorization. - adds the command run tracking inside the `check` method. --- .../tests/pox-4/err_Commands.ts | 87 ++++++++++++ ...AggregationCommitIndexedAuthCommand_Err.ts | 133 ++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index bfe600e594..4bdb6f5da3 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -14,6 +14,7 @@ import { DelegateStxCommand_Err } from "./pox_DelegateStxCommand_Err"; import { StackAggregationCommitSigCommand_Err } from "./pox_StackAggregationCommitSigCommand_Err"; import { StackAggregationCommitAuthCommand_Err } from "./pox_StackAggregationCommitAuthCommand_Err"; import { StackAggregationCommitIndexedSigCommand_Err } from "./pox_StackAggregationCommitIndexedSigCommand_Err"; +import { StackAggregationCommitIndexedAuthCommand_Err } from "./pox_StackAggregationCommitIndexedAuthCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_ALREADY_STACKED: 3, @@ -581,6 +582,92 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, ), ), + // StackAggregationCommitIndexedAuthCommand_Err_Stacking_No_Such_Principal_1 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitIndexedAuthCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitIndexedAuthCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + operator.lockedAddresses.length > 0 && + !(operator.amountToCommit >= model.stackingMinimum) && + !(operator.amountToCommit > 0) + ) { + model.trackCommandRun( + "StackAggregationCommitIndexedAuthCommand_Err_Stacking_No_Such_Principal_1", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), + // StackAggregationCommitIndexedAuthCommand_Err_Stacking_No_Such_Principal_2 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitIndexedAuthCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitIndexedAuthCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + !(operator.lockedAddresses.length > 0) && + !(operator.amountToCommit >= model.stackingMinimum) + ) { + model.trackCommandRun( + "StackAggregationCommitIndexedAuthCommand_Err_Stacking_No_Such_Principal_2", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), + // StackAggregationCommitIndexedAuthCommand_Err_Stacking_Threshold_Not_Met + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).map( + (r: { wallet: Wallet; authId: number }) => + new StackAggregationCommitIndexedAuthCommand_Err( + r.wallet, + r.authId, + function ( + this: StackAggregationCommitIndexedAuthCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + + if ( + operator.lockedAddresses.length > 0 && + !(operator.amountToCommit >= model.stackingMinimum) && + operator.amountToCommit > 0 + ) { + model.trackCommandRun( + "StackAggregationCommitIndexedAuthCommand_Err_Stacking_Threshold_Not_Met", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_THRESHOLD_NOT_MET, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand_Err.ts new file mode 100644 index 0000000000..92ebfa0d19 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand_Err.ts @@ -0,0 +1,133 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { poxAddressToTuple } from "@stacks/stacking"; +import { expect } from "vitest"; +import { Cl } from "@stacks/transactions"; +import { currentCycle } from "./pox_Commands.ts"; +import { tx } from "@hirosystems/clarinet-sdk"; + +type CheckFunc = ( + this: StackAggregationCommitIndexedAuthCommand_Err, + model: Readonly, +) => boolean; + +export class StackAggregationCommitIndexedAuthCommand_Err + implements PoxCommand { + readonly operator: Wallet; + readonly authId: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackAggregationCommitIndexedAuthCommand_Err` to lock uSTX + * for stacking. + * + * @param operator - Represents the `Operator`'s wallet. + * @param authId - Unique `auth-id` for the authorization. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + operator: Wallet, + authId: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.operator = operator; + this.authId = authId; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const currentRewCycle = currentCycle(real.network); + const operatorWallet = model.stackers.get(this.operator.stxAddress)!; + const committedAmount = operatorWallet.amountToCommit; + + // Act + + // Include the authorization and the `stack-aggregation-commit-indexed` + // transactions in a single block. This way we ensure both the authorization + // and the stack-aggregation-commit-indexed transactions are called during + // the same reward cycle, so the authorization currentRewCycle param is + // relevant for the upcoming stack-aggregation-commit-indexed call. + const block = real.network.mineBlock([ + tx.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "set-signer-key-authorization", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.operator.btcAddress), + // (period uint) + Cl.uint(1), + // (reward-cycle uint) + Cl.uint(currentRewCycle + 1), + // (topic (string-ascii 14)) + Cl.stringAscii("agg-commit"), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.operator.signerPubKey), + // (allowed bool) + Cl.bool(true), + // (max-amount uint) + Cl.uint(committedAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.operator.stxAddress, + ), + tx.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-aggregation-commit-indexed", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.operator.btcAddress), + // (reward-cycle uint) + Cl.uint(currentRewCycle + 1), + // (signer-sig (optional (buff 65))) + Cl.none(), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.operator.signerPubKey), + // (max-amount uint) + Cl.uint(committedAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.operator.stxAddress, + ), + ]); + + // Assert + expect(block[0].result).toBeOk(Cl.bool(true)); + expect(block[1].result).toBeErr( + Cl.int(this.errorCode), + ); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.operator.label}`, + "stack-agg-commit-indexed", + "amount committed", + committedAmount.toString(), + "authorization", + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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}`; + } +} From 7173ad680e044a79059da07edb8ef414b7cf2ddc Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Tue, 28 May 2024 15:09:19 +0300 Subject: [PATCH 48/72] Remove all files containing `_Err` from command tracking The command run tracking for the unhappy paths was moved inside the commands' check function. No need to report the run using the file name anymore. --- .../tests/pox-4/pox-4.stateful-prop.test.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts index a77156a87a..31a9239a44 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox-4.stateful-prop.test.ts @@ -110,12 +110,7 @@ it("statefully interacts with PoX-4", async () => { const statistics = fs.readdirSync(path.join(__dirname)).filter((file) => file.startsWith("pox_") && file.endsWith(".ts") && file !== "pox_CommandModel.ts" && file !== "pox_Commands.ts" && - file !== "pox_StackStxAuthCommand_Err.ts" && - file !== "pox_StackStxSigCommand_Err.ts" && - file !== "pox_RevokeDelegateStxCommand_Err.ts" && - file !== "pox_DelegateStxCommand_Err.ts" && - file !== "pox_StackAggregationCommitSigCommand_Err.ts" && - file !== "pox_StackAggregationCommitAuthCommand_Err.ts" + !file.includes("_Err") ).map((file) => file.slice(4, -3)); // Remove "pox_" prefix and ".ts" suffix. // This is the initial state of the model. From c6ff82d17ce4b132c1f96c01fa7e1ea601925a6d Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Tue, 28 May 2024 16:00:05 +0300 Subject: [PATCH 49/72] Add one unhappy path case for `StackAggIncreaseCommand_Err` This commit: - adds one unhappy path case for the `stack-aggregation-increase` PoX-4 method, called using an authorization. - adds the command run tracking inside the `check` method. --- .../tests/pox-4/err_Commands.ts | 44 ++++++ ...pox_StackAggregationIncreaseCommand_Err.ts | 143 ++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationIncreaseCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 4bdb6f5da3..26da594c60 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -15,6 +15,7 @@ import { StackAggregationCommitSigCommand_Err } from "./pox_StackAggregationComm import { StackAggregationCommitAuthCommand_Err } from "./pox_StackAggregationCommitAuthCommand_Err"; import { StackAggregationCommitIndexedSigCommand_Err } from "./pox_StackAggregationCommitIndexedSigCommand_Err"; import { StackAggregationCommitIndexedAuthCommand_Err } from "./pox_StackAggregationCommitIndexedAuthCommand_Err"; +import { StackAggregationIncreaseCommand_Err } from "./pox_StackAggregationIncreaseCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_ALREADY_STACKED: 3, @@ -668,6 +669,49 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_THRESHOLD_NOT_MET, ), ), + // StackAggregationIncreaseCommand_Err_Stacking_No_Such_Principal + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + }).chain((r) => { + const operator = stackers.get(r.wallet.stxAddress)!; + const committedRewCycleIndexesOrFallback = + operator.committedRewCycleIndexes.length > 0 + ? operator.committedRewCycleIndexes + : [-1]; + return fc + .record({ + rewardCycleIndex: fc.constantFrom( + ...committedRewCycleIndexesOrFallback, + ), + }) + .map((cycleIndex) => ({ ...r, ...cycleIndex })); + }) + .map( + (r: { wallet: Wallet; rewardCycleIndex: number; authId: number }) => + new StackAggregationIncreaseCommand_Err( + r.wallet, + r.rewardCycleIndex, + r.authId, + function ( + this: StackAggregationIncreaseCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + if ( + operator.lockedAddresses.length > 0 && + this.rewardCycleIndex >= 0 && + !(operator.amountToCommit > 0) + ) { + model.trackCommandRun( + "StackAggregationIncreaseCommand_Err_Stacking_No_Such_Principal", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationIncreaseCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationIncreaseCommand_Err.ts new file mode 100644 index 0000000000..26fc49eb60 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationIncreaseCommand_Err.ts @@ -0,0 +1,143 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { Pox4SignatureTopic, poxAddressToTuple } from "@stacks/stacking"; +import { expect } from "vitest"; +import { Cl, cvToJSON } from "@stacks/transactions"; +import { bufferFromHex } from "@stacks/transactions/dist/cl"; +import { currentCycle } from "./pox_Commands.ts"; + +type CheckFunc = ( + this: StackAggregationIncreaseCommand_Err, + model: Readonly, +) => boolean; + +export class StackAggregationIncreaseCommand_Err implements PoxCommand { + readonly operator: Wallet; + readonly rewardCycleIndex: number; + readonly authId: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackAggregationIncreaseCommand_Err` to commit partially + * stacked STX to a PoX address which has already received some STX. + * + * @param operator - Represents the `Operator`'s wallet. + * @param rewardCycleIndex - The cycle index to increase the commit for. + * @param authId - Unique `auth-id` for the authorization. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + operator: Wallet, + rewardCycleIndex: number, + authId: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.operator = operator; + this.rewardCycleIndex = rewardCycleIndex; + this.authId = authId; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const currentRewCycle = currentCycle(real.network); + + const operatorWallet = model.stackers.get(this.operator.stxAddress)!; + const committedAmount = operatorWallet.amountToCommit; + + const existingEntryCV = real.network.getMapEntry( + "ST000000000000000000002AMW42H.pox-4", + "reward-cycle-pox-address-list", + Cl.tuple({ + index: Cl.uint(this.rewardCycleIndex), + "reward-cycle": Cl.uint(currentRewCycle + 1), + }), + ); + + const totalStackedBefore = + cvToJSON(existingEntryCV).value.value["total-ustx"].value; + const maxAmount = committedAmount + Number(totalStackedBefore); + + const signerSig = this.operator.stackingClient.signPoxSignature({ + // The signer key being authorized. + signerPrivateKey: this.operator.signerPrvKey, + // The reward cycle for which the authorization is valid. + // For stack-stx and stack-extend, this refers to the reward cycle + // where the transaction is confirmed. For stack-aggregation-commit, + // this refers to the reward cycle argument in that function. + rewardCycle: currentRewCycle + 1, + // For stack-stx, this refers to lock-period. For stack-extend, + // this refers to extend-count. For stack-aggregation-commit, this is + // u1. + period: 1, + // A string representing the function where this authorization is valid. + // Either stack-stx, stack-extend, stack-increase, agg-commit or agg-increase. + topic: Pox4SignatureTopic.AggregateIncrease, + // The PoX address that can be used with this signer key. + poxAddress: this.operator.btcAddress, + // The unique auth-id for this authorization. + authId: this.authId, + // The maximum amount of uSTX that can be used (per tx) with this signer + // key. + maxAmount: maxAmount, + }); + + // Act + const stackAggregationIncrease = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-aggregation-increase", + [ + // (pox-addr { version: (buff 1), hashbytes: (buff 32) }) + poxAddressToTuple(this.operator.btcAddress), + // (reward-cycle uint) + Cl.uint(currentRewCycle + 1), + // (reward-cycle-index uint)) + Cl.uint(this.rewardCycleIndex), + // (signer-sig (optional (buff 65))) + Cl.some(bufferFromHex(signerSig)), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.operator.signerPubKey), + // (max-amount uint) + Cl.uint(maxAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.operator.stxAddress, + ); + + // Assert + expect(stackAggregationIncrease.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.operator.label}`, + "stack-agg-increase", + "amount committed", + committedAmount.toString(), + "cycle index", + this.rewardCycleIndex.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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-increase for index ${this.rewardCycleIndex}`; + } +} From a267a044d6d8c027be624e27fb77a772c03da3c9 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Tue, 28 May 2024 20:22:13 +0300 Subject: [PATCH 50/72] Format using `deno` according to the other generators --- .../tests/pox-4/err_Commands.ts | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 26da594c60..4a01ac827a 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -686,32 +686,31 @@ export function ErrCommands( ), }) .map((cycleIndex) => ({ ...r, ...cycleIndex })); - }) - .map( - (r: { wallet: Wallet; rewardCycleIndex: number; authId: number }) => - new StackAggregationIncreaseCommand_Err( - r.wallet, - r.rewardCycleIndex, - r.authId, - function ( - this: StackAggregationIncreaseCommand_Err, - model: Readonly, - ): boolean { - const operator = model.stackers.get(this.operator.stxAddress)!; - if ( - operator.lockedAddresses.length > 0 && - this.rewardCycleIndex >= 0 && - !(operator.amountToCommit > 0) - ) { - model.trackCommandRun( - "StackAggregationIncreaseCommand_Err_Stacking_No_Such_Principal", - ); - return true; - } else return false; - }, - POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, - ), - ), + }).map( + (r: { wallet: Wallet; rewardCycleIndex: number; authId: number }) => + new StackAggregationIncreaseCommand_Err( + r.wallet, + r.rewardCycleIndex, + r.authId, + function ( + this: StackAggregationIncreaseCommand_Err, + model: Readonly, + ): boolean { + const operator = model.stackers.get(this.operator.stxAddress)!; + if ( + operator.lockedAddresses.length > 0 && + this.rewardCycleIndex >= 0 && + !(operator.amountToCommit > 0) + ) { + model.trackCommandRun( + "StackAggregationIncreaseCommand_Err_Stacking_No_Such_Principal", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, + ), + ), ]; return cmds; From 1bd9c78d95a5485a2d47046b3f8e85192b4ec234 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Wed, 29 May 2024 14:18:18 +0300 Subject: [PATCH 51/72] Add unhappy path cases for `DelegateStackStxCommand_Err` This commit: - adds 3 unhappy path cases for the `delegate-stack-stx` PoX-4 method. - adds the command run tracking inside the `check` method. - adds the expected `delegate-stack-stx` PoX-4 errors to the `POX_4_ERRORS` dictionary. - exports the `nextCycleFirstBlock` method from pox_commands, as it is used inside err_Commands. --- .../tests/pox-4/err_Commands.ts | 226 ++++++++++++++++++ .../tests/pox-4/pox_Commands.ts | 2 +- .../pox-4/pox_DelegateStackStxCommand_Err.ts | 105 ++++++++ 3 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 4a01ac827a..74c0170af5 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -16,12 +16,16 @@ import { StackAggregationCommitAuthCommand_Err } from "./pox_StackAggregationCom import { StackAggregationCommitIndexedSigCommand_Err } from "./pox_StackAggregationCommitIndexedSigCommand_Err"; import { StackAggregationCommitIndexedAuthCommand_Err } from "./pox_StackAggregationCommitIndexedAuthCommand_Err"; import { StackAggregationIncreaseCommand_Err } from "./pox_StackAggregationIncreaseCommand_Err"; +import { currentCycleFirstBlock, nextCycleFirstBlock } from "./pox_Commands"; +import { DelegateStackStxCommand_Err } from "./pox_DelegateStackStxCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_ALREADY_STACKED: 3, ERR_STACKING_NO_SUCH_PRINCIPAL: 4, + ERR_STACKING_PERMISSION_DENIED: 9, ERR_STACKING_THRESHOLD_NOT_MET: 11, ERR_STACKING_ALREADY_DELEGATED: 20, + ERR_DELEGATION_TOO_MUCH_LOCKED: 22, ERR_DELEGATION_ALREADY_REVOKED: 34, }; @@ -711,6 +715,228 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, ), ), + // DelegateStackStxCommand_Err_Delegation_Too_Much_Locked + fc.record({ + operator: fc.constantFrom(...wallets.values()), + startBurnHt: fc.integer({ + min: currentCycleFirstBlock(network), + max: nextCycleFirstBlock(network), + }), + period: fc.integer({ min: 1, max: 12 }), + }).chain((r) => { + const operator = stackers.get(r.operator.stxAddress)!; + // Determine available stackers based on the operator + const availableStackers = operator.poolMembers.length > 0 + ? operator.poolMembers + : [r.operator.stxAddress]; + + return fc.record({ + stacker: fc.constantFrom(...availableStackers), + }).map((stacker) => ({ + ...r, + stacker: wallets.get(stacker.stacker)!, + })).chain((resultWithStacker) => { + return fc.record({ + unlockBurnHt: fc.constant( + currentCycleFirstBlock(network) + + 1050 * (resultWithStacker.period + 1), + ), + }).map((additionalProps) => ({ + ...resultWithStacker, + ...additionalProps, + })); + }).chain((resultWithUnlockHeight) => { + return fc.record({ + amount: fc.bigInt({ + min: 0n, + max: 100_000_000_000_000n, + }), + }).map((amountProps) => ({ + ...resultWithUnlockHeight, + ...amountProps, + })); + }); + }).map((finalResult) => { + return new DelegateStackStxCommand_Err( + finalResult.operator, + finalResult.stacker, + finalResult.period, + finalResult.amount, + finalResult.unlockBurnHt, + function ( + this: DelegateStackStxCommand_Err, + model: Readonly, + ): boolean { + const operatorWallet = model.stackers.get(this.operator.stxAddress)!; + const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; + if ( + model.stackingMinimum > 0 && + !stackerWallet.isStacking && + stackerWallet.hasDelegated && + !(stackerWallet.delegatedMaxAmount >= Number(this.amountUstx)) && + Number(this.amountUstx) <= stackerWallet.ustxBalance && + Number(this.amountUstx) >= model.stackingMinimum && + operatorWallet.poolMembers.includes(this.stacker.stxAddress) && + this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt + ) { + model.trackCommandRun( + "DelegateStackStxCommand_Err_Delegation_Too_Much_Locked", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_DELEGATION_TOO_MUCH_LOCKED, + ); + }), + // DelegateStackStxCommand_Err_Stacking_Permission_Denied + fc.record({ + operator: fc.constantFrom(...wallets.values()), + startBurnHt: fc.integer({ + min: currentCycleFirstBlock(network), + max: nextCycleFirstBlock(network), + }), + period: fc.integer({ min: 1, max: 12 }), + }).chain((r) => { + const operator = stackers.get(r.operator.stxAddress)!; + // Determine available stackers based on the operator + const availableStackers = operator.poolMembers.length > 0 + ? operator.poolMembers + : [r.operator.stxAddress]; + + return fc.record({ + stacker: fc.constantFrom(...availableStackers), + }).map((stacker) => ({ + ...r, + stacker: wallets.get(stacker.stacker)!, + })).chain((resultWithStacker) => { + return fc.record({ + unlockBurnHt: fc.constant( + currentCycleFirstBlock(network) + + 1050 * (resultWithStacker.period + 1), + ), + }).map((additionalProps) => ({ + ...resultWithStacker, + ...additionalProps, + })); + }).chain((resultWithUnlockHeight) => { + return fc.record({ + amount: fc.bigInt({ + min: 0n, + max: BigInt( + stackers.get(resultWithUnlockHeight.stacker.stxAddress)! + .delegatedMaxAmount, + ), + }), + }).map((amountProps) => ({ + ...resultWithUnlockHeight, + ...amountProps, + })); + }); + }).map((finalResult) => { + return new DelegateStackStxCommand_Err( + finalResult.operator, + finalResult.stacker, + finalResult.period, + finalResult.amount, + finalResult.unlockBurnHt, + function ( + this: DelegateStackStxCommand_Err, + model: Readonly, + ): boolean { + const operatorWallet = model.stackers.get(this.operator.stxAddress)!; + const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; + if ( + model.stackingMinimum > 0 && + !stackerWallet.isStacking && + stackerWallet.hasDelegated && + stackerWallet.delegatedMaxAmount >= Number(this.amountUstx) && + Number(this.amountUstx) <= stackerWallet.ustxBalance && + Number(this.amountUstx) >= model.stackingMinimum && + !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && + this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt + ) { + model.trackCommandRun( + "DelegateStackStxCommand_Err_Stacking_Permission_Denied_1", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_PERMISSION_DENIED, + ); + }), + // DelegateStackStxCommand_Err_Stacking_Permission_Denied_2 + fc.record({ + operator: fc.constantFrom(...wallets.values()), + startBurnHt: fc.integer({ + min: currentCycleFirstBlock(network), + max: nextCycleFirstBlock(network), + }), + period: fc.integer({ min: 1, max: 12 }), + }).chain((r) => { + const operator = stackers.get(r.operator.stxAddress)!; + // Determine available stackers based on the operator + const availableStackers = operator.poolMembers.length > 0 + ? operator.poolMembers + : [r.operator.stxAddress]; + + return fc.record({ + stacker: fc.constantFrom(...availableStackers), + }).map((stacker) => ({ + ...r, + stacker: wallets.get(stacker.stacker)!, + })).chain((resultWithStacker) => { + return fc.record({ + unlockBurnHt: fc.constant( + currentCycleFirstBlock(network) + + 1050 * (resultWithStacker.period + 1), + ), + }).map((additionalProps) => ({ + ...resultWithStacker, + ...additionalProps, + })); + }).chain((resultWithUnlockHeight) => { + return fc.record({ + amount: fc.bigInt({ + min: 0n, + max: 100_000_000_000_000n, + }), + }).map((amountProps) => ({ + ...resultWithUnlockHeight, + ...amountProps, + })); + }); + }).map((finalResult) => { + return new DelegateStackStxCommand_Err( + finalResult.operator, + finalResult.stacker, + finalResult.period, + finalResult.amount, + finalResult.unlockBurnHt, + function ( + this: DelegateStackStxCommand_Err, + model: Readonly, + ): boolean { + const operatorWallet = model.stackers.get(this.operator.stxAddress)!; + const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; + if ( + model.stackingMinimum > 0 && + !stackerWallet.isStacking && + !(stackerWallet.hasDelegated) && + !(stackerWallet.delegatedMaxAmount >= Number(this.amountUstx)) && + Number(this.amountUstx) <= stackerWallet.ustxBalance && + Number(this.amountUstx) >= model.stackingMinimum && + !(operatorWallet.poolMembers.includes(this.stacker.stxAddress)) && + !(this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt) + ) { + model.trackCommandRun( + "DelegateStackStxCommand_Err_Stacking_Permission_Denied_2", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_PERMISSION_DENIED, + ); + }), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts index 0a1fd6f3ea..a42cb6278e 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_Commands.ts @@ -479,7 +479,7 @@ export const currentCycleFirstBlock = (network: Simnet) => ).result, )); -const nextCycleFirstBlock = (network: Simnet) => +export const nextCycleFirstBlock = (network: Simnet) => Number(cvToValue( network.callReadOnlyFn( "ST000000000000000000002AMW42H.pox-4", diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand_Err.ts new file mode 100644 index 0000000000..b4e5a491dd --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand_Err.ts @@ -0,0 +1,105 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { poxAddressToTuple } from "@stacks/stacking"; +import { expect } from "vitest"; +import { Cl, ClarityValue, cvToValue } from "@stacks/transactions"; + +type CheckFunc = ( + this: DelegateStackStxCommand_Err, + model: Readonly, +) => boolean; + +export class DelegateStackStxCommand_Err implements PoxCommand { + readonly operator: Wallet; + readonly stacker: Wallet; + readonly period: number; + readonly amountUstx: bigint; + readonly unlockBurnHt: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `DelegateStackStxCommand` to lock uSTX as a Pool Operator + * on behalf of a Stacker. + * + * @param operator - Represents the Pool Operator's wallet. + * @param stacker - Represents the STacker's wallet. + * @param period - Number of reward cycles to lock uSTX. + * @param amountUstx - The uSTX amount stacked by the Operator on behalf + * of the Stacker. + * @param unlockBurnHt - The burn height at which the uSTX is unlocked. + */ + constructor( + operator: Wallet, + stacker: Wallet, + period: number, + amountUstx: bigint, + unlockBurnHt: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.operator = operator; + this.stacker = stacker; + this.period = period; + this.amountUstx = amountUstx; + this.unlockBurnHt = unlockBurnHt; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const burnBlockHeightCV = real.network.runSnippet("burn-block-height"); + const burnBlockHeight = Number( + cvToValue(burnBlockHeightCV as ClarityValue), + ); + + // Act + const delegateStackStx = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "delegate-stack-stx", + [ + // (stacker principal) + Cl.principal(this.stacker.stxAddress), + // (amount-ustx uint) + Cl.uint(this.amountUstx), + // (pox-addr { version: (buff 1), hashbytes: (buff 32) }) + poxAddressToTuple(this.operator.btcAddress), + // (start-burn-ht uint) + Cl.uint(burnBlockHeight), + // (lock-period uint) + Cl.uint(this.period), + ], + this.operator.stxAddress, + ); + + // Assert + expect(delegateStackStx.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✓ ${this.operator.label} Ӿ ${this.stacker.label}`, + "delegate-stack-stx", + "lock-amount", + this.amountUstx.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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} delegate-stack-stx stacker ${this.stacker.label} period ${this.period}`; + } +} From b66b19cd3884a258647e56ee147f46e09955f8a5 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Wed, 29 May 2024 21:47:33 +0300 Subject: [PATCH 52/72] Add unhappy path cases for `StackIncreaseSigCommand_Err` This commit: - adds 3 unhappy path cases for the `stack-increase` PoX-4 method, called using a signature. - adds the command run tracking inside the `check` method. - adds the expected `stack-increase` PoX-4 errors to the `POX_4_ERRORS` dictionary. --- .../tests/pox-4/err_Commands.ts | 106 +++++++++++++ .../pox-4/pox_StackIncreaseSigCommand_Err.ts | 143 ++++++++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 74c0170af5..113d52ef46 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -18,14 +18,18 @@ import { StackAggregationCommitIndexedAuthCommand_Err } from "./pox_StackAggrega import { StackAggregationIncreaseCommand_Err } from "./pox_StackAggregationIncreaseCommand_Err"; import { currentCycleFirstBlock, nextCycleFirstBlock } from "./pox_Commands"; import { DelegateStackStxCommand_Err } from "./pox_DelegateStackStxCommand_Err"; +import { StackIncreaseSigCommand_Err } from "./pox_StackIncreaseSigCommand_Err"; const POX_4_ERRORS = { + ERR_STACKING_INSUFFICIENT_FUNDS: 1, ERR_STACKING_ALREADY_STACKED: 3, ERR_STACKING_NO_SUCH_PRINCIPAL: 4, ERR_STACKING_PERMISSION_DENIED: 9, ERR_STACKING_THRESHOLD_NOT_MET: 11, + ERR_STACKING_INVALID_AMOUNT: 18, ERR_STACKING_ALREADY_DELEGATED: 20, ERR_DELEGATION_TOO_MUCH_LOCKED: 22, + ERR_STACKING_IS_DELEGATED: 30, ERR_DELEGATION_ALREADY_REVOKED: 34, }; @@ -937,6 +941,108 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_PERMISSION_DENIED, ); }), + // StackIncreaseSigCommand_Err_Stacking_Is_Delegated + fc.record({ + operator: fc.constantFrom(...wallets.values()), + increaseBy: fc.nat(), + authId: fc.nat(), + }).map( + (r) => + new StackIncreaseSigCommand_Err( + r.operator, + r.increaseBy, + r.authId, + function ( + this: StackIncreaseSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + !stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + this.increaseBy <= stacker.amountUnlocked && + this.increaseBy >= 1 + ) { + model.trackCommandRun( + "StackIncreaseSigCommand_Err_Stacking_Is_Delegated", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_IS_DELEGATED, + ), + ), + // StackIncreaseSigCommand_Err_Stacking_Insufficient_Funds + fc.record({ + operator: fc.constantFrom(...wallets.values()), + increaseBy: fc.constant(100_000_000_000_000), + authId: fc.nat(), + }).map( + (r) => + new StackIncreaseSigCommand_Err( + r.operator, + r.increaseBy, + r.authId, + function ( + this: StackIncreaseSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + !(this.increaseBy <= stacker.amountUnlocked) && + this.increaseBy >= 1 + ) { + model.trackCommandRun( + "StackIncreaseSigCommand_Err_Stacking_Insufficient_Funds", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_INSUFFICIENT_FUNDS, + ), + ), + // StackIncreaseSigCommand_Err_Stacking_Invalid_Amount + fc.record({ + operator: fc.constantFrom(...wallets.values()), + increaseBy: fc.constant(0), + authId: fc.nat(), + }).map( + (r) => + new StackIncreaseSigCommand_Err( + r.operator, + r.increaseBy, + r.authId, + function ( + this: StackIncreaseSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + this.increaseBy <= stacker.amountUnlocked && + !(this.increaseBy >= 1) + ) { + model.trackCommandRun( + "StackIncreaseSigCommand_Err_Stacking_Invalid_Amount", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_INVALID_AMOUNT, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand_Err.ts new file mode 100644 index 0000000000..4a122784b3 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand_Err.ts @@ -0,0 +1,143 @@ +import { Pox4SignatureTopic } from "@stacks/stacking"; +import { logCommand, PoxCommand, Real, Stub, Wallet } from "./pox_CommandModel"; +import { + Cl, + ClarityType, + ClarityValue, + cvToJSON, + cvToValue, + isClarityType, +} from "@stacks/transactions"; +import { assert, expect } from "vitest"; + +type CheckFunc = ( + this: StackIncreaseSigCommand_Err, + model: Readonly, +) => boolean; + +export class StackIncreaseSigCommand_Err implements PoxCommand { + readonly wallet: Wallet; + readonly increaseBy: number; + readonly authId: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackIncreaseSigCommand_Err` to lock uSTX for stacking. + * + * @param wallet - Represents the Stacker's wallet. + * @param increaseBy - Represents the locked amount to be increased by. + * @param authId - Unique auth-id for the authorization. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + wallet: Wallet, + increaseBy: number, + authId: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.wallet = wallet; + this.increaseBy = increaseBy; + this.authId = authId; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const maxAmount = stacker.amountLocked + this.increaseBy; + + const burnBlockHeightCV = real.network.runSnippet("burn-block-height"); + const burnBlockHeight = Number( + cvToValue(burnBlockHeightCV as ClarityValue), + ); + + const { result: rewardCycleNextBlockCV } = real.network.callReadOnlyFn( + "ST000000000000000000002AMW42H.pox-4", + "burn-height-to-reward-cycle", + [Cl.uint(burnBlockHeight + 1)], + this.wallet.stxAddress, + ); + assert(isClarityType(rewardCycleNextBlockCV, ClarityType.UInt)); + + const rewardCycleNextBlock = cvToValue(rewardCycleNextBlockCV); + + // Get the lock period from the stacking state. This will be used for correctly + // issuing the authorization. + const stackingStateCV = real.network.getMapEntry( + "ST000000000000000000002AMW42H.pox-4", + "stacking-state", + Cl.tuple({ stacker: Cl.principal(this.wallet.stxAddress) }), + ); + const period = cvToJSON(stackingStateCV).value.value["lock-period"].value; + + const signerSig = this.wallet.stackingClient.signPoxSignature({ + // The signer key being authorized. + signerPrivateKey: this.wallet.signerPrvKey, + // The reward cycle for which the authorization is valid. + // For `stack-stx` and `stack-extend`, this refers to the reward cycle + // where the transaction is confirmed. For `stack-aggregation-commit`, + // this refers to the reward cycle argument in that function. + rewardCycle: rewardCycleNextBlock, + // For `stack-stx`, this refers to `lock-period`. For `stack-extend`, + // this refers to `extend-count`. For `stack-aggregation-commit`, this is + // `u1`. + period: period, + // A string representing the function where this authorization is valid. + // Either `stack-stx`, `stack-extend`, `stack-increase` or `agg-commit`. + topic: Pox4SignatureTopic.StackIncrease, + // The PoX address that can be used with this signer key. + poxAddress: this.wallet.btcAddress, + // The unique auth-id for this authorization. + authId: this.authId, + // The maximum amount of uSTX that can be used (per tx) with this signer + // key. + maxAmount: maxAmount, + }); + + const stackIncrease = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-increase", + [ + // (increase-by uint) + Cl.uint(this.increaseBy), + // (signer-sig (optional (buff 65))) + Cl.some(Cl.bufferFromHex(signerSig)), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (max-amount uint) + Cl.uint(maxAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ); + + expect(stackIncrease.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.wallet.label}`, + "stack-increase-sig", + "increase-by", + this.increaseBy.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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.wallet.label} stack-increase sig increase-by ${this.increaseBy}`; + } +} From 50bd1b5cc15b6ff1a8489c258eb79d114238166c Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Thu, 30 May 2024 13:21:34 +0300 Subject: [PATCH 53/72] Add unhappy path cases for `StackIncreaseAuthCommand_Err` This commit: - adds 3 unhappy path cases for the `stack-increase` PoX-4 method, called using an authorization. - adds the command run tracking inside the `check` method. --- .../tests/pox-4/err_Commands.ts | 103 ++++++++++++++ .../pox-4/pox_StackIncreaseAuthCommand_Err.ts | 133 ++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 113d52ef46..e51283f3d1 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -19,6 +19,7 @@ import { StackAggregationIncreaseCommand_Err } from "./pox_StackAggregationIncre import { currentCycleFirstBlock, nextCycleFirstBlock } from "./pox_Commands"; import { DelegateStackStxCommand_Err } from "./pox_DelegateStackStxCommand_Err"; import { StackIncreaseSigCommand_Err } from "./pox_StackIncreaseSigCommand_Err"; +import { StackIncreaseAuthCommand_Err } from "./pox_StackIncreaseAuthCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_INSUFFICIENT_FUNDS: 1, @@ -1043,6 +1044,108 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_INVALID_AMOUNT, ), ), + // StackIncreaseAuthCommand_Err_Stacking_Is_Delegated + fc.record({ + operator: fc.constantFrom(...wallets.values()), + increaseBy: fc.nat(), + authId: fc.nat(), + }).map( + (r) => + new StackIncreaseAuthCommand_Err( + r.operator, + r.increaseBy, + r.authId, + function ( + this: StackIncreaseAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + !stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + this.increaseBy <= stacker.amountUnlocked && + this.increaseBy >= 1 + ) { + model.trackCommandRun( + "StackIncreaseAuthCommand_Err_Stacking_Is_Delegated", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_IS_DELEGATED, + ), + ), + // StackIncreaseAuthCommand_Err_Stacking_Insufficient_Funds + fc.record({ + operator: fc.constantFrom(...wallets.values()), + increaseBy: fc.constant(100_000_000_000_000), + authId: fc.nat(), + }).map( + (r) => + new StackIncreaseAuthCommand_Err( + r.operator, + r.increaseBy, + r.authId, + function ( + this: StackIncreaseAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + !(this.increaseBy <= stacker.amountUnlocked) && + this.increaseBy >= 1 + ) { + model.trackCommandRun( + "StackIncreaseAuthCommand_Err_Stacking_Insufficient_Funds", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_INSUFFICIENT_FUNDS, + ), + ), + // StackIncreaseAuthCommand_Err_Stacking_Invalid_Amount + fc.record({ + operator: fc.constantFrom(...wallets.values()), + increaseBy: fc.constant(0), + authId: fc.nat(), + }).map( + (r) => + new StackIncreaseAuthCommand_Err( + r.operator, + r.increaseBy, + r.authId, + function ( + this: StackIncreaseAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + this.increaseBy <= stacker.amountUnlocked && + !(this.increaseBy >= 1) + ) { + model.trackCommandRun( + "StackIncreaseAuthCommand_Err_Stacking_Invalid_Amount", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_INVALID_AMOUNT, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand_Err.ts new file mode 100644 index 0000000000..a74aa3c211 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand_Err.ts @@ -0,0 +1,133 @@ +import { Pox4SignatureTopic, poxAddressToTuple } from "@stacks/stacking"; +import { logCommand, PoxCommand, Real, Stub, Wallet } from "./pox_CommandModel"; +import { currentCycle } from "./pox_Commands"; +import { Cl, cvToJSON } from "@stacks/transactions"; +import { expect } from "vitest"; +import { tx } from "@hirosystems/clarinet-sdk"; + +type CheckFunc = ( + this: StackIncreaseAuthCommand_Err, + model: Readonly, +) => boolean; + +export class StackIncreaseAuthCommand_Err implements PoxCommand { + readonly wallet: Wallet; + readonly increaseBy: number; + readonly authId: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackIncreaseAuthCommand` to increase lock uSTX for stacking. + * + * @param wallet - Represents the Stacker's wallet. + * @param increaseBy - Represents the locked amount to be increased by. + * @param authId - Unique auth-id for the authorization. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + wallet: Wallet, + increaseBy: number, + authId: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.wallet = wallet; + this.increaseBy = increaseBy; + this.authId = authId; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const currentRewCycle = currentCycle(real.network); + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + // Get the lock period from the stacking state. This will be used for correctly + // issuing the authorization. + const stackingStateCV = real.network.getMapEntry( + "ST000000000000000000002AMW42H.pox-4", + "stacking-state", + Cl.tuple({ stacker: Cl.principal(this.wallet.stxAddress) }), + ); + const period = cvToJSON(stackingStateCV).value.value["lock-period"].value; + + const maxAmount = stacker.amountLocked + this.increaseBy; + + // Act + + // Include the authorization and the `stack-increase` transactions in a single + // block. This way we ensure both the authorization and the stack-increase + // transactions are called during the same reward cycle and avoid the clarity + // error `ERR_INVALID_REWARD_CYCLE`. + const block = real.network.mineBlock([ + tx.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "set-signer-key-authorization", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.wallet.btcAddress), + // (period uint) + Cl.uint(period), + // (reward-cycle uint) + Cl.uint(currentRewCycle), + // (topic (string-ascii 14)) + Cl.stringAscii(Pox4SignatureTopic.StackIncrease), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (allowed bool) + Cl.bool(true), + // (max-amount uint) + Cl.uint(maxAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ), + tx.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-increase", + [ + // (increase-by uint) + Cl.uint(this.increaseBy), + // (signer-sig (optional (buff 65))) + Cl.none(), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (max-amount uint) + Cl.uint(maxAmount), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ), + ]); + + // Assert + expect(block[0].result).toBeOk(Cl.bool(true)); + expect(block[1].result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.wallet.label}`, + "stack-increase-auth", + "increase-by", + this.increaseBy.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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.wallet.label} stack-increase auth increase-by ${this.increaseBy}`; + } +} From 4c2f3b6c384a2e8a28859194537e574686a8d1ca Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Thu, 30 May 2024 18:37:44 +0300 Subject: [PATCH 54/72] Add unhappy path cases for `StackExtendSigCommand_Err` This commit: - adds 5 unhappy path cases for the `stack-increase` PoX-4 method, called using a signature. - adds the command run tracking inside the `check` method. - adds the expected `stack-extend` PoX-4 errors to the `POX_4_ERRORS` dictionary. --- .../tests/pox-4/err_Commands.ts | 271 +++++++++++++++++- .../pox-4/pox_StackExtendSigCommand_Err.ts | 121 ++++++++ 2 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index e51283f3d1..27264b04fb 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -16,13 +16,21 @@ import { StackAggregationCommitAuthCommand_Err } from "./pox_StackAggregationCom import { StackAggregationCommitIndexedSigCommand_Err } from "./pox_StackAggregationCommitIndexedSigCommand_Err"; import { StackAggregationCommitIndexedAuthCommand_Err } from "./pox_StackAggregationCommitIndexedAuthCommand_Err"; import { StackAggregationIncreaseCommand_Err } from "./pox_StackAggregationIncreaseCommand_Err"; -import { currentCycleFirstBlock, nextCycleFirstBlock } from "./pox_Commands"; +import { + currentCycle, + currentCycleFirstBlock, + FIRST_BURNCHAIN_BLOCK_HEIGHT, + nextCycleFirstBlock, + REWARD_CYCLE_LENGTH, +} from "./pox_Commands"; import { DelegateStackStxCommand_Err } from "./pox_DelegateStackStxCommand_Err"; import { StackIncreaseSigCommand_Err } from "./pox_StackIncreaseSigCommand_Err"; import { StackIncreaseAuthCommand_Err } from "./pox_StackIncreaseAuthCommand_Err"; +import { StackExtendSigCommand_Err } from "./pox_StackExtendSigCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_INSUFFICIENT_FUNDS: 1, + ERR_STACKING_INVALID_LOCK_PERIOD: 2, ERR_STACKING_ALREADY_STACKED: 3, ERR_STACKING_NO_SUCH_PRINCIPAL: 4, ERR_STACKING_PERMISSION_DENIED: 9, @@ -30,6 +38,7 @@ const POX_4_ERRORS = { ERR_STACKING_INVALID_AMOUNT: 18, ERR_STACKING_ALREADY_DELEGATED: 20, ERR_DELEGATION_TOO_MUCH_LOCKED: 22, + ERR_STACK_EXTEND_NOT_LOCKED: 26, ERR_STACKING_IS_DELEGATED: 30, ERR_DELEGATION_ALREADY_REVOKED: 34, }; @@ -1146,6 +1155,266 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_INVALID_AMOUNT, ), ), + // StackExtendSigCommand_Err_Stacking_Is_Delegated_1 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer({ min: 1, max: 12 }), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendSigCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + !stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + stacker.poolMembers.length === 0 && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "StackExtendSigCommand_Err_Stacking_Is_Delegated_1", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_IS_DELEGATED, + ), + ), + // StackExtendSigCommand_Err_Stacking_Is_Delegated_2 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer({ min: 1, max: 12 }), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendSigCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + !stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + !(stacker.poolMembers.length === 0) && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "StackExtendSigCommand_Err_Stacking_Is_Delegated_2", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_IS_DELEGATED, + ), + ), + // StackExtendSigCommand_Err_Stacking_Already_Delegated + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer({ min: 1, max: 12 }), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendSigCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.isStackingSolo && + stacker.hasDelegated && + stacker.amountLocked > 0 && + stacker.poolMembers.length === 0 && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "StackExtendSigCommand_Err_Stacking_Already_Delegated", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, + ), + ), + // StackExtendSigCommand_Err_Stacking_Invalid_Lock_Period + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer(), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendSigCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + stacker.poolMembers.length === 0 && + !(totalPeriod <= 12) + ) { + model.trackCommandRun( + "StackExtendSigCommand_Err_Stacking_Invalid_Lock_Period", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_INVALID_LOCK_PERIOD, + ), + ), + // StackExtendSigCommand_Err_Stack_Extend_Not_Locked + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer({ min: 1, max: 12 }), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendSigCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendSigCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + !stacker.isStacking && + !stacker.isStackingSolo && + !stacker.hasDelegated && + !(stacker.amountLocked > 0) && + stacker.poolMembers.length === 0 && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "StackExtendSigCommand_Err_Stack_Extend_Not_Locked", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACK_EXTEND_NOT_LOCKED, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand_Err.ts new file mode 100644 index 0000000000..185f2796d1 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand_Err.ts @@ -0,0 +1,121 @@ +import { Pox4SignatureTopic, poxAddressToTuple } from "@stacks/stacking"; +import { logCommand, PoxCommand, Real, Stub, Wallet } from "./pox_CommandModel"; +import { currentCycle } from "./pox_Commands"; +import { Cl } from "@stacks/transactions"; +import { expect } from "vitest"; + +type CheckFunc = ( + this: StackExtendSigCommand_Err, + model: Readonly, +) => boolean; + +export class StackExtendSigCommand_Err implements PoxCommand { + readonly wallet: Wallet; + readonly extendCount: number; + readonly authId: number; + readonly currentCycle: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackExtendSigCommand` to lock uSTX for stacking. + * + * This command calls `stack-extend` using a `signature`. + * + * @param wallet - Represents the Stacker's wallet. + * @param extendCount - Represents the cycles to extend the stack with. + * @param authId - Unique auth-id for the authorization. + * @param currentCycle - Represents the current PoX reward cycle. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + wallet: Wallet, + extendCount: number, + authId: number, + currentCycle: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.wallet = wallet; + this.extendCount = extendCount; + this.authId = authId; + this.currentCycle = currentCycle; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const currentRewCycle = currentCycle(real.network); + + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const signerSig = this.wallet.stackingClient.signPoxSignature({ + // The signer key being authorized. + signerPrivateKey: this.wallet.signerPrvKey, + // The reward cycle for which the authorization is valid. + // For `stack-stx` and `stack-extend`, this refers to the reward cycle + // where the transaction is confirmed. For `stack-aggregation-commit`, + // this refers to the reward cycle argument in that function. + rewardCycle: currentRewCycle, + // For `stack-stx`, this refers to `lock-period`. For `stack-extend`, + // this refers to `extend-count`. For `stack-aggregation-commit`, this is + // `u1`. + period: this.extendCount, + // A string representing the function where this authorization is valid. + // Either `stack-stx`, `stack-extend`, `stack-increase` or `agg-commit`. + topic: Pox4SignatureTopic.StackExtend, + // The PoX address that can be used with this signer key. + poxAddress: this.wallet.btcAddress, + // The unique auth-id for this authorization. + authId: this.authId, + // The maximum amount of uSTX that can be used (per tx) with this signer + // key. + maxAmount: stacker.amountLocked, + }); + + const stackExtend = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-extend", + [ + // (extend-count uint) + Cl.uint(this.extendCount), + // (pox-addr { version: (buff 1), hashbytes: (buff 32) }) + poxAddressToTuple(this.wallet.btcAddress), + // (signer-sig (optional (buff 65))) + Cl.some(Cl.bufferFromHex(signerSig)), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (max-amount uint) + Cl.uint(stacker.amountLocked), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ); + + expect(stackExtend.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.wallet.label}`, + "stack-extend-sig", + "extend-count", + this.extendCount.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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.wallet.label} stack-extend sig extend-count ${this.extendCount}`; + } +} From add9d5592ea1c47e38cfd5121bac4d7d74127787 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Thu, 30 May 2024 18:52:05 +0300 Subject: [PATCH 55/72] Add unhappy path cases for `StackExtendAuthCommand_Err` This commit: - adds 5 unhappy path cases for the `stack-extend` PoX-4 method, called using an authorization. - adds the command run tracking inside the `check` method. --- .../tests/pox-4/err_Commands.ts | 261 ++++++++++++++++++ .../pox-4/pox_StackExtendAuthCommand_Err.ts | 123 +++++++++ 2 files changed, 384 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 27264b04fb..83a9566605 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -27,6 +27,7 @@ import { DelegateStackStxCommand_Err } from "./pox_DelegateStackStxCommand_Err"; import { StackIncreaseSigCommand_Err } from "./pox_StackIncreaseSigCommand_Err"; import { StackIncreaseAuthCommand_Err } from "./pox_StackIncreaseAuthCommand_Err"; import { StackExtendSigCommand_Err } from "./pox_StackExtendSigCommand_Err"; +import { StackExtendAuthCommand_Err } from "./pox_StackExtendAuthCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_INSUFFICIENT_FUNDS: 1, @@ -1415,6 +1416,266 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACK_EXTEND_NOT_LOCKED, ), ), + // StackExtendAuthCommand_Err_Stacking_Is_Delegated_1 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer({ min: 1, max: 12 }), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendAuthCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + !stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + stacker.poolMembers.length === 0 && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "StackExtendAuthCommand_Err_Stacking_Is_Delegated_1", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_IS_DELEGATED, + ), + ), + // StackExtendAuthCommand_Err_Stacking_Is_Delegated_2 + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer({ min: 1, max: 12 }), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendAuthCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + !stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + !(stacker.poolMembers.length === 0) && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "StackExtendAuthCommand_Err_Stacking_Is_Delegated_2", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_IS_DELEGATED, + ), + ), + // StackExtendAuthCommand_Err_Stacking_Already_Delegated + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer({ min: 1, max: 12 }), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendAuthCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.isStackingSolo && + stacker.hasDelegated && + stacker.amountLocked > 0 && + stacker.poolMembers.length === 0 && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "StackExtendAuthCommand_Err_Stacking_Already_Delegated", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, + ), + ), + // StackExtendAuthCommand_Err_Stacking_Invalid_Lock_Period + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer(), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendAuthCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + stacker.isStacking && + stacker.isStackingSolo && + !stacker.hasDelegated && + stacker.amountLocked > 0 && + stacker.poolMembers.length === 0 && + !(totalPeriod <= 12) + ) { + model.trackCommandRun( + "StackExtendAuthCommand_Err_Stacking_Invalid_Lock_Period", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_INVALID_LOCK_PERIOD, + ), + ), + // StackExtendAuthCommand_Err_Stack_Extend_Not_Locked + fc.record({ + wallet: fc.constantFrom(...wallets.values()), + authId: fc.nat(), + extendCount: fc.integer({ min: 1, max: 12 }), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (r: { + wallet: Wallet; + extendCount: number; + authId: number; + currentCycle: number; + }) => + new StackExtendAuthCommand_Err( + r.wallet, + r.extendCount, + r.authId, + r.currentCycle, + function ( + this: StackExtendAuthCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const firstRewardCycle = + stacker.firstLockedRewardCycle < this.currentCycle + ? this.currentCycle + : stacker.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + if ( + model.stackingMinimum > 0 && + !stacker.isStacking && + !stacker.isStackingSolo && + !stacker.hasDelegated && + !(stacker.amountLocked > 0) && + stacker.poolMembers.length === 0 && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "StackExtendAuthCommand_Err_Stack_Extend_Not_Locked", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACK_EXTEND_NOT_LOCKED, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand_Err.ts new file mode 100644 index 0000000000..e3deed040c --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand_Err.ts @@ -0,0 +1,123 @@ +import { poxAddressToTuple } from "@stacks/stacking"; +import { logCommand, PoxCommand, Real, Stub, Wallet } from "./pox_CommandModel"; +import { currentCycle } from "./pox_Commands"; +import { Cl } from "@stacks/transactions"; +import { expect } from "vitest"; +import { tx } from "@hirosystems/clarinet-sdk"; + +type CheckFunc = ( + this: StackExtendAuthCommand_Err, + model: Readonly, +) => boolean; + +export class StackExtendAuthCommand_Err implements PoxCommand { + readonly wallet: Wallet; + readonly extendCount: number; + readonly authId: number; + readonly currentCycle: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `StackExtendAuthCommand` to lock uSTX for stacking. + * + * This command calls `stack-extend` using an `authorization`. + * + * @param wallet - Represents the Stacker's wallet. + * @param extendCount - Represents the cycles to extend the stack with. + * @param authId - Unique auth-id for the authorization. + * @param currentCycle - Represents the current PoX reward cycle. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + wallet: Wallet, + extendCount: number, + authId: number, + currentCycle: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.wallet = wallet; + this.extendCount = extendCount; + this.authId = authId; + this.currentCycle = currentCycle; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const currentRewCycle = currentCycle(real.network); + const stacker = model.stackers.get(this.wallet.stxAddress)!; + + const block = real.network.mineBlock([ + tx.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "set-signer-key-authorization", + [ + // (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) + poxAddressToTuple(this.wallet.btcAddress), + // (period uint) + Cl.uint(this.extendCount), + // (reward-cycle uint) + Cl.uint(currentRewCycle), + // (topic (string-ascii 14)) + Cl.stringAscii("stack-extend"), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (allowed bool) + Cl.bool(true), + // (max-amount uint) + Cl.uint(stacker.amountLocked), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ), + tx.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "stack-extend", + [ + // (extend-count uint) + Cl.uint(this.extendCount), + // (pox-addr { version: (buff 1), hashbytes: (buff 32) }) + poxAddressToTuple(this.wallet.btcAddress), + // (signer-sig (optional (buff 65))) + Cl.none(), + // (signer-key (buff 33)) + Cl.bufferFromHex(this.wallet.signerPubKey), + // (max-amount uint) + Cl.uint(stacker.amountLocked), + // (auth-id uint) + Cl.uint(this.authId), + ], + this.wallet.stxAddress, + ), + ]); + + expect(block[0].result).toBeOk(Cl.bool(true)); + expect(block[1].result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.wallet.label}`, + "stack-extend-auth", + "extend-count", + this.extendCount.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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.wallet.label} stack-extend auth extend-count ${this.extendCount}`; + } +} From 3f3da1639b3b3c916ad1e4ce79292c021b4c1f5c Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 31 May 2024 15:58:27 +0300 Subject: [PATCH 56/72] Add unhappy path cases for `DelegateStackExtendCommand_Err` This commit: - adds 4 unhappy path cases for the `delegate-stack-extend` PoX-4 method. - adds the command run tracking inside the `check` method. - adds the expected `delegate-stack-extend` PoX-4 error to the `POX_4_ERRORS` dictionary. --- .../tests/pox-4/err_Commands.ts | 302 ++++++++++++++++++ .../pox_DelegateStackExtendCommand_Err.ts | 96 ++++++ 2 files changed, 398 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 83a9566605..1ad4815cf7 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -28,6 +28,7 @@ import { StackIncreaseSigCommand_Err } from "./pox_StackIncreaseSigCommand_Err"; import { StackIncreaseAuthCommand_Err } from "./pox_StackIncreaseAuthCommand_Err"; import { StackExtendSigCommand_Err } from "./pox_StackExtendSigCommand_Err"; import { StackExtendAuthCommand_Err } from "./pox_StackExtendAuthCommand_Err"; +import { DelegateStackExtendCommand_Err } from "./pox_DelegateStackExtendCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_INSUFFICIENT_FUNDS: 1, @@ -41,6 +42,7 @@ const POX_4_ERRORS = { ERR_DELEGATION_TOO_MUCH_LOCKED: 22, ERR_STACK_EXTEND_NOT_LOCKED: 26, ERR_STACKING_IS_DELEGATED: 30, + ERR_STACKING_NOT_DELEGATED: 31, ERR_DELEGATION_ALREADY_REVOKED: 34, }; @@ -1676,6 +1678,306 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACK_EXTEND_NOT_LOCKED, ), ), + // DelegateStackExtendCommand_Err_Stacking_Invalid_Lock_Period + fc.record({ + operator: fc.constantFrom(...wallets.values()), + extendCount: fc.constant(100000000000000), + }).chain((r) => { + const operator = stackers.get(r.operator.stxAddress)!; + const delegatorsList = operator.poolMembers; + const availableStackers = delegatorsList.filter((delegator) => { + const delegatorWallet = stackers.get(delegator)!; + return delegatorWallet.unlockHeight > nextCycleFirstBlock(network); + }); + + const availableStackersOrFallback = availableStackers.length === 0 + ? [r.operator.stxAddress] + : availableStackers; + + return fc.record({ + stacker: fc.constantFrom(...availableStackersOrFallback), + currentCycle: fc.constant(currentCycle(network)), + }).map((additionalProps) => ({ + ...r, + stacker: wallets.get(additionalProps.stacker)!, + currentCycle: additionalProps.currentCycle, + })); + }).map( + (final) => + new DelegateStackExtendCommand_Err( + final.operator, + final.stacker, + final.extendCount, + final.currentCycle, + function ( + this: DelegateStackExtendCommand_Err, + model: Readonly, + ): boolean { + const operatorWallet = model.stackers.get( + this.operator.stxAddress, + )!; + const stackerWallet = model.stackers.get( + this.stacker.stxAddress, + )!; + + const firstRewardCycle = + this.currentCycle > stackerWallet.firstLockedRewardCycle + ? this.currentCycle + : stackerWallet.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stackerWallet.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + const newUnlockHeight = + REWARD_CYCLE_LENGTH * (firstRewardCycle + totalPeriod - 1) + + FIRST_BURNCHAIN_BLOCK_HEIGHT; + const stackedAmount = stackerWallet.amountLocked; + + if ( + stackerWallet.amountLocked > 0 && + stackerWallet.hasDelegated === true && + stackerWallet.isStacking === true && + stackerWallet.delegatedTo === this.operator.stxAddress && + !(stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) && + stackerWallet.delegatedMaxAmount >= stackedAmount && + operatorWallet.poolMembers.includes(this.stacker.stxAddress) && + operatorWallet.lockedAddresses.includes( + this.stacker.stxAddress, + ) && + !(totalPeriod <= 12) + ) { + model.trackCommandRun( + "DelegateStackExtendCommand_Err_Stacking_Invalid_Lock_Period", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_INVALID_LOCK_PERIOD, + ), + ), + // DelegateStackExtendCommand_Err_Stacking_Not_Delegated + fc.record({ + operator: fc.constantFrom(...wallets.values()), + extendCount: fc.integer({ min: 1, max: 11 }), + }).chain((r) => { + const operator = stackers.get(r.operator.stxAddress)!; + const delegatorsList = operator.poolMembers; + const availableStackers = delegatorsList.filter((delegator) => { + const delegatorWallet = stackers.get(delegator)!; + return delegatorWallet.unlockHeight > nextCycleFirstBlock(network); + }); + + const availableStackersOrFallback = availableStackers.length === 0 + ? [r.operator.stxAddress] + : availableStackers; + + return fc + .record({ + stacker: fc.constantFrom(...availableStackersOrFallback), + currentCycle: fc.constant(currentCycle(network)), + }) + .map((additionalProps) => ({ + ...r, + stacker: wallets.get(additionalProps.stacker)!, + currentCycle: additionalProps.currentCycle, + })); + }).map( + (final) => + new DelegateStackExtendCommand_Err( + final.operator, + final.stacker, + final.extendCount, + final.currentCycle, + function ( + this: DelegateStackExtendCommand_Err, + model: Readonly, + ): boolean { + const operatorWallet = model.stackers.get( + this.operator.stxAddress, + )!; + const stackerWallet = model.stackers.get( + this.stacker.stxAddress, + )!; + + const firstRewardCycle = + this.currentCycle > stackerWallet.firstLockedRewardCycle + ? this.currentCycle + : stackerWallet.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stackerWallet.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + const newUnlockHeight = + REWARD_CYCLE_LENGTH * (firstRewardCycle + totalPeriod - 1) + + FIRST_BURNCHAIN_BLOCK_HEIGHT; + const stackedAmount = stackerWallet.amountLocked; + + if ( + stackerWallet.amountLocked > 0 && + !(stackerWallet.hasDelegated === true) && + stackerWallet.isStacking === true && + stackerWallet.isStackingSolo === true && + !(stackerWallet.delegatedTo === this.operator.stxAddress) && + !(stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) && + !(stackerWallet.delegatedMaxAmount >= stackedAmount) && + !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && + !operatorWallet.lockedAddresses.includes( + this.stacker.stxAddress, + ) && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "DelegateStackExtendCommand_Err_Stacking_Not_Delegated", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NOT_DELEGATED, + ), + ), + // DelegateStackExtendCommand_Err_Stack_Extend_Not_Locked + fc.record({ + operator: fc.constantFrom(...wallets.values()), + extendCount: fc.integer({ min: 1, max: 11 }), + }).chain((r) => { + const operator = stackers.get(r.operator.stxAddress)!; + const delegatorsList = operator.poolMembers; + const availableStackers = delegatorsList.filter((delegator) => { + const delegatorWallet = stackers.get(delegator)!; + return delegatorWallet.unlockHeight > nextCycleFirstBlock(network); + }); + + const availableStackersOrFallback = availableStackers.length === 0 + ? [r.operator.stxAddress] + : availableStackers; + + return fc.record({ + stacker: fc.constantFrom(...availableStackersOrFallback), + currentCycle: fc.constant(currentCycle(network)), + }).map((additionalProps) => ({ + ...r, + stacker: wallets.get(additionalProps.stacker)!, + currentCycle: additionalProps.currentCycle, + })); + }).map( + (final) => + new DelegateStackExtendCommand_Err( + final.operator, + final.stacker, + final.extendCount, + final.currentCycle, + function ( + this: DelegateStackExtendCommand_Err, + model: Readonly, + ): boolean { + const operatorWallet = model.stackers.get( + this.operator.stxAddress, + )!; + const stackerWallet = model.stackers.get( + this.stacker.stxAddress, + )!; + + const firstRewardCycle = + this.currentCycle > stackerWallet.firstLockedRewardCycle + ? this.currentCycle + : stackerWallet.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stackerWallet.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + const newUnlockHeight = + REWARD_CYCLE_LENGTH * (firstRewardCycle + totalPeriod - 1) + + FIRST_BURNCHAIN_BLOCK_HEIGHT; + const stackedAmount = stackerWallet.amountLocked; + if ( + !(stackerWallet.amountLocked > 0) && + stackerWallet.hasDelegated === true && + !(stackerWallet.isStacking === true) && + !(stackerWallet.delegatedTo === this.operator.stxAddress) && + stackerWallet.delegatedUntilBurnHt >= newUnlockHeight && + stackerWallet.delegatedMaxAmount >= stackedAmount && + !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && + !operatorWallet.lockedAddresses.includes( + this.stacker.stxAddress, + ) && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "DelegateStackExtendCommand_Err_Stack_Extend_Not_Locked", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACK_EXTEND_NOT_LOCKED, + ), + ), + // DelegateStackExtendCommand_Err_Stacking_Permission_Denied + fc.record({ + operator: fc.constantFrom(...wallets.values()), + extendCount: fc.integer({ min: 1, max: 11 }), + stacker: fc.constantFrom(...wallets.values()), + currentCycle: fc.constant(currentCycle(network)), + }).map( + (final) => + new DelegateStackExtendCommand_Err( + final.operator, + final.stacker, + final.extendCount, + final.currentCycle, + function ( + this: DelegateStackExtendCommand_Err, + model: Readonly, + ): boolean { + const operatorWallet = model.stackers.get( + this.operator.stxAddress, + )!; + const stackerWallet = model.stackers.get( + this.stacker.stxAddress, + )!; + + const firstRewardCycle = + this.currentCycle > stackerWallet.firstLockedRewardCycle + ? this.currentCycle + : stackerWallet.firstLockedRewardCycle; + const firstExtendCycle = Math.floor( + (stackerWallet.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + REWARD_CYCLE_LENGTH, + ); + const lastExtendCycle = firstExtendCycle + this.extendCount - 1; + const totalPeriod = lastExtendCycle - firstRewardCycle + 1; + const newUnlockHeight = + REWARD_CYCLE_LENGTH * (firstRewardCycle + totalPeriod - 1) + + FIRST_BURNCHAIN_BLOCK_HEIGHT; + const stackedAmount = stackerWallet.amountLocked; + + if ( + stackerWallet.amountLocked > 0 && + !(stackerWallet.hasDelegated === true) && + stackerWallet.isStacking === true && + !(stackerWallet.delegatedTo === this.operator.stxAddress) && + !(stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) && + !(stackerWallet.delegatedMaxAmount >= stackedAmount) && + !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && + operatorWallet.lockedAddresses.includes( + this.stacker.stxAddress, + ) && + totalPeriod <= 12 + ) { + model.trackCommandRun( + "DelegateStackExtendCommand_Err_Stacking_Permission_Denied", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_PERMISSION_DENIED, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand_Err.ts new file mode 100644 index 0000000000..830fb7d182 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand_Err.ts @@ -0,0 +1,96 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { poxAddressToTuple } from "@stacks/stacking"; +import { expect } from "vitest"; +import { Cl } from "@stacks/transactions"; + +type CheckFunc = ( + this: DelegateStackExtendCommand_Err, + model: Readonly, +) => boolean; + +export class DelegateStackExtendCommand_Err implements PoxCommand { + readonly operator: Wallet; + readonly stacker: Wallet; + readonly extendCount: number; + readonly currentCycle: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a `DelegateStackExtendCommand_Err` to extend the unlock + * height as a Pool Operator on behalf of a Stacker. + * + * @param operator - Represents the Pool Operator's wallet. + * @param stacker - Represents the STacker's wallet. + * @param extendCount - Represents the cycles to be expended. + * @param currentCycle - Represents the current PoX reward cycle. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + operator: Wallet, + stacker: Wallet, + extendCount: number, + currentCycle: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.operator = operator; + this.stacker = stacker; + this.extendCount = extendCount; + this.currentCycle = currentCycle; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; + + // Act + const delegateStackExtend = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "delegate-stack-extend", + [ + // (stacker principal) + Cl.principal(this.stacker.stxAddress), + // (pox-addr { version: (buff 1), hashbytes: (buff 32) }) + poxAddressToTuple(this.operator.btcAddress), + // (extend-count uint) + Cl.uint(this.extendCount), + ], + this.operator.stxAddress, + ); + + expect(delegateStackExtend.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.operator.label} Ӿ ${this.stacker.label}`, + "delegate-stack-extend", + "extend count", + this.extendCount.toString(), + "new unlock height", + stackerWallet.unlockHeight.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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} Ӿ ${this.stacker.label} delegate-stack-extend extend count ${this.extendCount}`; + } +} From 115a3855c4e9ef9fe8c77448b93ba27c4e0152e9 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 31 May 2024 16:09:19 +0300 Subject: [PATCH 57/72] Update `err_Commands` to include the `delegatedUntilBurnHt` none branch This commit adds the undefined check in the `err_Commands` comparisons that involve `delegatedUntilBurnHt`. --- .../tests/pox-4/err_Commands.ts | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 1ad4815cf7..9287df41a2 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -794,7 +794,8 @@ export function ErrCommands( Number(this.amountUstx) <= stackerWallet.ustxBalance && Number(this.amountUstx) >= model.stackingMinimum && operatorWallet.poolMembers.includes(this.stacker.stxAddress) && - this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt + (stackerWallet.delegatedUntilBurnHt === undefined || + this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt) ) { model.trackCommandRun( "DelegateStackStxCommand_Err_Delegation_Too_Much_Locked", @@ -870,7 +871,8 @@ export function ErrCommands( Number(this.amountUstx) <= stackerWallet.ustxBalance && Number(this.amountUstx) >= model.stackingMinimum && !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && - this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt + (stackerWallet.delegatedUntilBurnHt === undefined || + this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt) ) { model.trackCommandRun( "DelegateStackStxCommand_Err_Stacking_Permission_Denied_1", @@ -943,7 +945,8 @@ export function ErrCommands( Number(this.amountUstx) <= stackerWallet.ustxBalance && Number(this.amountUstx) >= model.stackingMinimum && !(operatorWallet.poolMembers.includes(this.stacker.stxAddress)) && - !(this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt) + !(stackerWallet.delegatedUntilBurnHt === undefined || + this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt) ) { model.trackCommandRun( "DelegateStackStxCommand_Err_Stacking_Permission_Denied_2", @@ -1740,7 +1743,8 @@ export function ErrCommands( stackerWallet.hasDelegated === true && stackerWallet.isStacking === true && stackerWallet.delegatedTo === this.operator.stxAddress && - !(stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) && + !(stackerWallet.delegatedUntilBurnHt === undefined || + stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) && stackerWallet.delegatedMaxAmount >= stackedAmount && operatorWallet.poolMembers.includes(this.stacker.stxAddress) && operatorWallet.lockedAddresses.includes( @@ -1822,7 +1826,8 @@ export function ErrCommands( stackerWallet.isStacking === true && stackerWallet.isStackingSolo === true && !(stackerWallet.delegatedTo === this.operator.stxAddress) && - !(stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) && + !(stackerWallet.delegatedUntilBurnHt === undefined || + stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) && !(stackerWallet.delegatedMaxAmount >= stackedAmount) && !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && !operatorWallet.lockedAddresses.includes( @@ -1900,7 +1905,8 @@ export function ErrCommands( stackerWallet.hasDelegated === true && !(stackerWallet.isStacking === true) && !(stackerWallet.delegatedTo === this.operator.stxAddress) && - stackerWallet.delegatedUntilBurnHt >= newUnlockHeight && + (stackerWallet.delegatedUntilBurnHt === undefined || + stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) && stackerWallet.delegatedMaxAmount >= stackedAmount && !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && !operatorWallet.lockedAddresses.includes( @@ -1961,7 +1967,8 @@ export function ErrCommands( !(stackerWallet.hasDelegated === true) && stackerWallet.isStacking === true && !(stackerWallet.delegatedTo === this.operator.stxAddress) && - !(stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) && + !(stackerWallet.delegatedUntilBurnHt === undefined || + stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) && !(stackerWallet.delegatedMaxAmount >= stackedAmount) && !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && operatorWallet.lockedAddresses.includes( From 6eb8eec5d561eaa41c7389971a798980fe74b45b Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 31 May 2024 16:30:50 +0300 Subject: [PATCH 58/72] Add unhappy path cases for `DelegateStackIncreaseCommand_Err` This commit: - adds 4 unhappy path cases for the `delegate-stack-increase` PoX-4 method. - adds the command run tracking inside the `check` method. --- .../tests/pox-4/err_Commands.ts | 258 ++++++++++++++++++ .../pox_DelegateStackIncreaseCommand_Err.ts | 95 +++++++ 2 files changed, 353 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 9287df41a2..d3833003eb 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -29,6 +29,7 @@ import { StackIncreaseAuthCommand_Err } from "./pox_StackIncreaseAuthCommand_Err import { StackExtendSigCommand_Err } from "./pox_StackExtendSigCommand_Err"; import { StackExtendAuthCommand_Err } from "./pox_StackExtendAuthCommand_Err"; import { DelegateStackExtendCommand_Err } from "./pox_DelegateStackExtendCommand_Err"; +import { DelegateStackIncreaseCommand_Err } from "./pox_DelegateStackIncreaseCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_INSUFFICIENT_FUNDS: 1, @@ -1985,6 +1986,263 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_PERMISSION_DENIED, ), ), + // DelegateStackIncreaseCommand_Err_Stacking_Insufficient_Funds + fc.record({ + operator: fc.constantFrom(...wallets.values()), + increaseBy: fc.constant(Number.MAX_SAFE_INTEGER), + }).chain((r) => { + const operator = stackers.get(r.operator.stxAddress)!; + const delegatorsList = operator.poolMembers; + + const availableStackers = delegatorsList.filter((delegator) => { + const delegatorWallet = stackers.get(delegator)!; + return delegatorWallet.unlockHeight > nextCycleFirstBlock(network); + }); + + const availableStackersOrFallback = availableStackers.length === 0 + ? [r.operator.stxAddress] + : availableStackers; + + return fc.record({ + stacker: fc.constantFrom(...availableStackersOrFallback), + }).map((stacker) => ({ + ...r, + stacker: wallets.get(stacker.stacker)!, + })); + }).map( + (final) => + new DelegateStackIncreaseCommand_Err( + final.operator, + final.stacker, + final.increaseBy, + function ( + this: DelegateStackIncreaseCommand_Err, + model: Readonly, + ): boolean { + const operatorWallet = model.stackers.get( + this.operator.stxAddress, + )!; + const stackerWallet = model.stackers.get( + this.stacker.stxAddress, + )!; + + if ( + stackerWallet.amountLocked > 0 && + stackerWallet.hasDelegated === true && + stackerWallet.isStacking === true && + this.increaseBy > 0 && + operatorWallet.poolMembers.includes(this.stacker.stxAddress) && + !(stackerWallet.amountUnlocked >= this.increaseBy) && + !( + stackerWallet.delegatedMaxAmount >= + this.increaseBy + stackerWallet.amountLocked + ) && + operatorWallet.lockedAddresses.indexOf( + this.stacker.stxAddress, + ) > -1 + ) { + model.trackCommandRun( + "DelegateStackIncreaseCommand_Err_Stacking_Insufficient_Funds", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_INSUFFICIENT_FUNDS, + ), + ), + // DelegateStackIncreaseCommand_Err_Stacking_Invalid_Amount + fc.record({ + operator: fc.constantFrom(...wallets.values()), + increaseBy: fc.constant(0), + }).chain((r) => { + const operator = stackers.get(r.operator.stxAddress)!; + const delegatorsList = operator.poolMembers; + + const availableStackers = delegatorsList.filter((delegator) => { + const delegatorWallet = stackers.get(delegator)!; + return delegatorWallet.unlockHeight > nextCycleFirstBlock(network); + }); + + const availableStackersOrFallback = availableStackers.length === 0 + ? [r.operator.stxAddress] + : availableStackers; + + return fc.record({ + stacker: fc.constantFrom(...availableStackersOrFallback), + }).map((stacker) => ({ + ...r, + stacker: wallets.get(stacker.stacker)!, + })); + }).map( + (final) => + new DelegateStackIncreaseCommand_Err( + final.operator, + final.stacker, + final.increaseBy, + function ( + this: DelegateStackIncreaseCommand_Err, + model: Readonly, + ): boolean { + const operatorWallet = model.stackers.get( + this.operator.stxAddress, + )!; + const stackerWallet = model.stackers.get( + this.stacker.stxAddress, + )!; + + if ( + stackerWallet.amountLocked > 0 && + stackerWallet.hasDelegated === true && + stackerWallet.isStacking === true && + !(this.increaseBy > 0) && + operatorWallet.poolMembers.includes(this.stacker.stxAddress) && + stackerWallet.amountUnlocked >= this.increaseBy && + stackerWallet.delegatedMaxAmount >= + this.increaseBy + stackerWallet.amountLocked && + operatorWallet.lockedAddresses.indexOf( + this.stacker.stxAddress, + ) > -1 + ) { + model.trackCommandRun( + "DelegateStackIncreaseCommand_Err_Stacking_Invalid_Amount", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_INVALID_AMOUNT, + ), + ), + // DelegateStackIncreaseCommand_Err_Stacking_Not_Delegated + fc.record({ + operator: fc.constantFrom(...wallets.values()), + increaseBy: fc.nat(), + }).chain((r) => { + const operator = stackers.get(r.operator.stxAddress)!; + const delegatorsList = operator.poolMembers; + + const availableStackers = delegatorsList.filter((delegator) => { + const delegatorWallet = stackers.get(delegator)!; + return delegatorWallet.unlockHeight > nextCycleFirstBlock(network); + }); + + const availableStackersOrFallback = availableStackers.length === 0 + ? [r.operator.stxAddress] + : availableStackers; + + return fc.record({ + stacker: fc.constantFrom(...availableStackersOrFallback), + }).map((stacker) => ({ + ...r, + stacker: wallets.get(stacker.stacker)!, + })); + }).map( + (final) => + new DelegateStackIncreaseCommand_Err( + final.operator, + final.stacker, + final.increaseBy, + function ( + this: DelegateStackIncreaseCommand_Err, + model: Readonly, + ): boolean { + const operatorWallet = model.stackers.get( + this.operator.stxAddress, + )!; + const stackerWallet = model.stackers.get( + this.stacker.stxAddress, + )!; + + if ( + stackerWallet.amountLocked > 0 && + !(stackerWallet.hasDelegated === true) && + stackerWallet.isStacking === true && + stackerWallet.isStackingSolo === true && + this.increaseBy > 0 && + !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && + stackerWallet.amountUnlocked >= this.increaseBy && + !( + stackerWallet.delegatedMaxAmount >= + this.increaseBy + stackerWallet.amountLocked + ) && + !( + operatorWallet.lockedAddresses.indexOf( + this.stacker.stxAddress, + ) > -1 + ) + ) { + model.trackCommandRun( + "DelegateStackIncreaseCommand_Err_Stacking_Not_Delegated", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_NOT_DELEGATED, + ), + ), + // DelegateStackIncreaseCommand_Err_Stacking_Permission_Denied + fc.record({ + operator: fc.constantFrom(...wallets.values()), + increaseBy: fc.nat(), + }).chain((r) => { + const operator = stackers.get(r.operator.stxAddress)!; + const delegatorsList = operator.poolMembers; + + const availableStackers = delegatorsList.filter((delegator) => { + const delegatorWallet = stackers.get(delegator)!; + return delegatorWallet.unlockHeight > nextCycleFirstBlock(network); + }); + + const availableStackersOrFallback = availableStackers.length === 0 + ? [r.operator.stxAddress] + : availableStackers; + + return fc.record({ + stacker: fc.constantFrom(...availableStackersOrFallback), + }).map((stacker) => ({ + ...r, + stacker: wallets.get(stacker.stacker)!, + })); + }).map( + (final) => + new DelegateStackIncreaseCommand_Err( + final.operator, + final.stacker, + final.increaseBy, + function ( + this: DelegateStackIncreaseCommand_Err, + model: Readonly, + ): boolean { + const operatorWallet = model.stackers.get( + this.operator.stxAddress, + )!; + const stackerWallet = model.stackers.get( + this.stacker.stxAddress, + )!; + + if ( + stackerWallet.amountLocked > 0 && + !(stackerWallet.hasDelegated === true) && + stackerWallet.isStacking === true && + this.increaseBy > 0 && + !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && + stackerWallet.amountUnlocked >= this.increaseBy && + !( + stackerWallet.delegatedMaxAmount >= + this.increaseBy + stackerWallet.amountLocked + ) && + operatorWallet.lockedAddresses.indexOf( + this.stacker.stxAddress, + ) > -1 + ) { + model.trackCommandRun( + "DelegateStackIncreaseCommand_Err_Stacking_Permission_Denied", + ); + return true; + } else return false; + }, + POX_4_ERRORS.ERR_STACKING_PERMISSION_DENIED, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand_Err.ts new file mode 100644 index 0000000000..d54853dcb6 --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand_Err.ts @@ -0,0 +1,95 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { poxAddressToTuple } from "@stacks/stacking"; +import { expect } from "vitest"; +import { Cl } from "@stacks/transactions"; + +type CheckFunc = ( + this: DelegateStackIncreaseCommand_Err, + model: Readonly, +) => boolean; + +export class DelegateStackIncreaseCommand_Err implements PoxCommand { + readonly operator: Wallet; + readonly stacker: Wallet; + readonly increaseBy: number; + readonly checkFunc: CheckFunc; + readonly errorCode: number; + + /** + * Constructs a DelegateStackIncreaseCommand_Err to increase the uSTX amount + * previously locked on behalf of a Stacker. + * + * @param operator - Represents the Pool Operator's wallet. + * @param stacker - Represents the Stacker's wallet. + * @param increaseBy - Represents the locked amount to be increased by. + * @param checkFunc - A function to check constraints for running this command. + * @param errorCode - The expected error code when running this command. + */ + constructor( + operator: Wallet, + stacker: Wallet, + increaseBy: number, + checkFunc: CheckFunc, + errorCode: number, + ) { + this.operator = operator; + this.stacker = stacker; + this.increaseBy = increaseBy; + this.checkFunc = checkFunc; + this.errorCode = errorCode; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); + + run(model: Stub, real: Real): void { + const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; + const prevLocked = stackerWallet.amountLocked; + // Act + const delegateStackIncrease = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "delegate-stack-increase", + [ + // (stacker principal) + Cl.principal(this.stacker.stxAddress), + // (pox-addr { version: (buff 1), hashbytes: (buff 32) }) + poxAddressToTuple(this.operator.btcAddress), + // (increase-by uint) + Cl.uint(this.increaseBy), + ], + this.operator.stxAddress, + ); + + // Assert + expect(delegateStackIncrease.result).toBeErr(Cl.int(this.errorCode)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.operator.label} Ӿ ${this.stacker.label}`, + "delegate-stack-increase", + "increased by", + this.increaseBy.toString(), + "previously locked", + prevLocked.toString(), + "total locked", + stackerWallet.amountLocked.toString(), + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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} delegate-stack-increase by ${this.increaseBy}`; + } +} From da87ec804093beec35deba5cf099c57f8ba4ec90 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 31 May 2024 22:28:09 +0300 Subject: [PATCH 59/72] Order imports inside `err_Commands` --- .../tests/pox-4/err_Commands.ts | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index d3833003eb..4154de6c92 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -1,4 +1,5 @@ import fc from "fast-check"; +import { Simnet } from "@hirosystems/clarinet-sdk"; import { PoxCommand, Stacker, @@ -6,16 +7,6 @@ import { StxAddress, Wallet, } from "./pox_CommandModel"; -import { StackStxSigCommand_Err } from "./pox_StackStxSigCommand_Err"; -import { StackStxAuthCommand_Err } from "./pox_StackStxAuthCommand_Err"; -import { Simnet } from "@hirosystems/clarinet-sdk"; -import { RevokeDelegateStxCommand_Err } from "./pox_RevokeDelegateStxCommand_Err"; -import { DelegateStxCommand_Err } from "./pox_DelegateStxCommand_Err"; -import { StackAggregationCommitSigCommand_Err } from "./pox_StackAggregationCommitSigCommand_Err"; -import { StackAggregationCommitAuthCommand_Err } from "./pox_StackAggregationCommitAuthCommand_Err"; -import { StackAggregationCommitIndexedSigCommand_Err } from "./pox_StackAggregationCommitIndexedSigCommand_Err"; -import { StackAggregationCommitIndexedAuthCommand_Err } from "./pox_StackAggregationCommitIndexedAuthCommand_Err"; -import { StackAggregationIncreaseCommand_Err } from "./pox_StackAggregationIncreaseCommand_Err"; import { currentCycle, currentCycleFirstBlock, @@ -23,13 +14,22 @@ import { nextCycleFirstBlock, REWARD_CYCLE_LENGTH, } from "./pox_Commands"; -import { DelegateStackStxCommand_Err } from "./pox_DelegateStackStxCommand_Err"; -import { StackIncreaseSigCommand_Err } from "./pox_StackIncreaseSigCommand_Err"; -import { StackIncreaseAuthCommand_Err } from "./pox_StackIncreaseAuthCommand_Err"; -import { StackExtendSigCommand_Err } from "./pox_StackExtendSigCommand_Err"; -import { StackExtendAuthCommand_Err } from "./pox_StackExtendAuthCommand_Err"; import { DelegateStackExtendCommand_Err } from "./pox_DelegateStackExtendCommand_Err"; import { DelegateStackIncreaseCommand_Err } from "./pox_DelegateStackIncreaseCommand_Err"; +import { DelegateStackStxCommand_Err } from "./pox_DelegateStackStxCommand_Err"; +import { DelegateStxCommand_Err } from "./pox_DelegateStxCommand_Err"; +import { RevokeDelegateStxCommand_Err } from "./pox_RevokeDelegateStxCommand_Err"; +import { StackAggregationCommitAuthCommand_Err } from "./pox_StackAggregationCommitAuthCommand_Err"; +import { StackAggregationCommitIndexedAuthCommand_Err } from "./pox_StackAggregationCommitIndexedAuthCommand_Err"; +import { StackAggregationCommitIndexedSigCommand_Err } from "./pox_StackAggregationCommitIndexedSigCommand_Err"; +import { StackAggregationCommitSigCommand_Err } from "./pox_StackAggregationCommitSigCommand_Err"; +import { StackAggregationIncreaseCommand_Err } from "./pox_StackAggregationIncreaseCommand_Err"; +import { StackExtendAuthCommand_Err } from "./pox_StackExtendAuthCommand_Err"; +import { StackExtendSigCommand_Err } from "./pox_StackExtendSigCommand_Err"; +import { StackIncreaseAuthCommand_Err } from "./pox_StackIncreaseAuthCommand_Err"; +import { StackIncreaseSigCommand_Err } from "./pox_StackIncreaseSigCommand_Err"; +import { StackStxAuthCommand_Err } from "./pox_StackStxAuthCommand_Err"; +import { StackStxSigCommand_Err } from "./pox_StackStxSigCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_INSUFFICIENT_FUNDS: 1, From 9365892102d9c2c6285621399ef168cbc49271c3 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 31 May 2024 22:47:05 +0300 Subject: [PATCH 60/72] Add unhappy path case for `DisallowContractCallerCommand_Err` This commit: - adds one unhappy path case for the `disallow-contract-caller` PoX-4 method. - adds the command run tracking inside the `check` method. --- .../tests/pox-4/err_Commands.ts | 34 +++++++++ .../pox_DisallowContractCallerCommand_Err.ts | 73 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DisallowContractCallerCommand_Err.ts diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 4154de6c92..c24c028f8d 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -30,6 +30,7 @@ import { StackIncreaseAuthCommand_Err } from "./pox_StackIncreaseAuthCommand_Err import { StackIncreaseSigCommand_Err } from "./pox_StackIncreaseSigCommand_Err"; import { StackStxAuthCommand_Err } from "./pox_StackStxAuthCommand_Err"; import { StackStxSigCommand_Err } from "./pox_StackStxSigCommand_Err"; +import { DisallowContractCallerCommand_Err } from "./pox_DisallowContractCallerCommand_Err"; const POX_4_ERRORS = { ERR_STACKING_INSUFFICIENT_FUNDS: 1, @@ -2243,6 +2244,39 @@ export function ErrCommands( POX_4_ERRORS.ERR_STACKING_PERMISSION_DENIED, ), ), + // DisallowContractCallerCommand_Err + fc.record({ + stacker: fc.constantFrom(...wallets.values()), + callerToDisallow: fc.constantFrom(...wallets.values()), + }).map( + (r: { stacker: Wallet; callerToDisallow: Wallet }) => + new DisallowContractCallerCommand_Err( + r.stacker, + r.callerToDisallow, + function ( + this: DisallowContractCallerCommand_Err, + model: Readonly, + ): boolean { + const stacker = model.stackers.get(this.stacker.stxAddress)!; + const callerToDisallow = model.stackers.get( + this.callerToDisallow.stxAddress, + )!; + if ( + !stacker.allowedContractCallers.includes( + this.callerToDisallow.stxAddress, + ) && + !callerToDisallow.callerAllowedBy.includes( + this.stacker.stxAddress, + ) === true + ) { + model.trackCommandRun( + "DisallowContractCallerCommand_Err", + ); + return true; + } else return false; + }, + ), + ), ]; return cmds; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DisallowContractCallerCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DisallowContractCallerCommand_Err.ts new file mode 100644 index 0000000000..028457b41e --- /dev/null +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DisallowContractCallerCommand_Err.ts @@ -0,0 +1,73 @@ +import { + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel.ts"; +import { expect } from "vitest"; +import { Cl } from "@stacks/transactions"; + +type CheckFunc = ( + this: DisallowContractCallerCommand_Err, + model: Readonly, +) => boolean; + +export class DisallowContractCallerCommand_Err implements PoxCommand { + readonly stacker: Wallet; + readonly callerToDisallow: Wallet; + readonly checkFunc: CheckFunc; + + /** + * Constructs a `DisallowContractCallerComand` to revoke authorization + * for calling stacking methods. + * + * @param stacker - Represents the `Stacker`'s wallet. + * @param callerToDisallow - The `contract-caller` to be revoked. + * @param checkFunc - A function to check constraints for running this command. + */ + constructor(stacker: Wallet, callerToDisallow: Wallet, checkFunc: CheckFunc) { + this.stacker = stacker; + this.callerToDisallow = callerToDisallow; + this.checkFunc = checkFunc; + } + + check = (model: Readonly): boolean => this.checkFunc.call(this, model); // Constraints for running this command include: + // - The Caller to be disallowed must have been previously allowed + // by the Operator. + + run(model: Stub, real: Real): void { + // Act + const disallowContractCaller = real.network.callPublicFn( + "ST000000000000000000002AMW42H.pox-4", + "disallow-contract-caller", + [ + // (caller principal) + Cl.principal(this.callerToDisallow.stxAddress), + ], + this.stacker.stxAddress, + ); + + // Assert + expect(disallowContractCaller.result).toBeOk(Cl.bool(false)); + + // 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( + `₿ ${model.burnBlockHeight}`, + `✗ ${this.stacker.label}`, + "disallow-contract-caller", + this.callerToDisallow.label, + ); + + // Refresh the model's state if the network gets to the next reward cycle. + model.refreshStateForNextRewardCycle(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.stacker.label} disallow-contract-caller ${this.callerToDisallow.label}`; + } +} From f03bedf9f24c5a5ecd06b4196302b439faf95eb9 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 3 Jun 2024 13:11:19 +0300 Subject: [PATCH 61/72] Add tree logging for test-runs statistics --- .../tests/pox-4/pox_CommandModel.ts | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_CommandModel.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_CommandModel.ts index 3d4b7415f9..653a1acbff 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_CommandModel.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_CommandModel.ts @@ -52,9 +52,43 @@ export class Stub { }, ); - orderedStatistics.forEach(([commandName, count]) => { - console.log(`${commandName}: ${count}`); + this.logAsTree(orderedStatistics); + } + + private logAsTree(statistics: [string, number][]) { + const tree: { [key: string]: any } = {}; + + statistics.forEach(([commandName, count]) => { + const split = commandName.split("_"); + let root: string = split[0], + rest: string = "base"; + + if (split.length > 1) { + rest = split.slice(1).join("_"); + } + if (!tree[root]) { + tree[root] = {}; + } + tree[root][rest] = count; }); + + const printTree = (node: any, indent: string = "") => { + const keys = Object.keys(node); + keys.forEach((key, index) => { + const isLast = index === keys.length - 1; + const boxChar = isLast ? "└─ " : "├─ "; + if (key !== "base") { + if (typeof node[key] === "object") { + console.log(`${indent}${boxChar}${key}: ${node[key]["base"]}`); + printTree(node[key], indent + (isLast ? " " : "│ ")); + } else { + console.log(`${indent}${boxChar}${key}: ${node[key]}`); + } + } + }); + }; + + printTree(tree); } refreshStateForNextRewardCycle(real: Real) { From dc9e01966f386b2c0b982165c8b03fe8a4e45c79 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 10 Jun 2024 11:32:14 +0300 Subject: [PATCH 62/72] Fix PoX-4 stateful prop tests comments This commit brings typo fixes for all the comments inside the PoX-4 stateful property tests. --- .../tests/pox-4/pox_AllowContractCallerCommand.ts | 1 - .../tests/pox-4/pox_DelegateStackExtendCommand.ts | 5 +++-- .../tests/pox-4/pox_DelegateStackExtendCommand_Err.ts | 4 ++-- .../tests/pox-4/pox_DelegateStackIncreaseCommand.ts | 4 ++-- .../tests/pox-4/pox_DelegateStackIncreaseCommand_Err.ts | 2 +- .../tests/pox-4/pox_DelegateStackStxCommand.ts | 4 ++-- .../tests/pox-4/pox_DelegateStackStxCommand_Err.ts | 2 +- .../tests/pox-4/pox_DelegateStxCommand.ts | 2 -- .../tests/pox-4/pox_RevokeDelegateStxCommand.ts | 2 +- .../tests/pox-4/pox_RevokeDelegateStxCommand_Err.ts | 2 +- .../tests/pox-4/pox_StackAggregationCommitAuthCommand.ts | 3 ++- .../pox-4/pox_StackAggregationCommitAuthCommand_Err.ts | 3 ++- .../pox-4/pox_StackAggregationCommitIndexedAuthCommand.ts | 4 ++-- .../pox_StackAggregationCommitIndexedAuthCommand_Err.ts | 4 ++-- .../pox-4/pox_StackAggregationCommitIndexedSigCommand.ts | 4 ++-- .../pox_StackAggregationCommitIndexedSigCommand_Err.ts | 4 ++-- .../tests/pox-4/pox_StackAggregationCommitSigCommand.ts | 3 ++- .../tests/pox-4/pox_StackAggregationCommitSigCommand_Err.ts | 3 ++- .../tests/pox-4/pox_StackExtendAuthCommand.ts | 2 +- .../tests/pox-4/pox_StackExtendAuthCommand_Err.ts | 6 +++++- .../tests/pox-4/pox_StackExtendSigCommand.ts | 2 +- .../tests/pox-4/pox_StackExtendSigCommand_Err.ts | 2 +- .../tests/pox-4/pox_StackIncreaseAuthCommand.ts | 2 +- .../tests/pox-4/pox_StackIncreaseAuthCommand_Err.ts | 2 +- .../tests/pox-4/pox_StackIncreaseSigCommand.ts | 2 +- .../tests/pox-4/pox_StackIncreaseSigCommand_Err.ts | 2 +- .../tests/pox-4/pox_StackStxSigCommand_Err.ts | 2 +- 27 files changed, 42 insertions(+), 36 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_AllowContractCallerCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_AllowContractCallerCommand.ts index 141676cdae..931326ce1f 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_AllowContractCallerCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_AllowContractCallerCommand.ts @@ -77,7 +77,6 @@ export class AllowContractCallerCommand implements PoxCommand { const callerToAllow = model.stackers.get(this.allowanceTo.stxAddress)!; // Update model so that we know this wallet has authorized a contract-caller. - // If the caller is already allowed, there's no need to add it again. const callerToAllowIndexInAllowedList = wallet.allowedContractCallers .indexOf(this.allowanceTo.stxAddress); diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand.ts index 2875551342..0151967932 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand.ts @@ -37,8 +37,9 @@ export class DelegateStackExtendCommand implements PoxCommand { * height as a Pool Operator on behalf of a Stacker. * * @param operator - Represents the Pool Operator's wallet. - * @param stacker - Represents the STacker's wallet. - * @param extendCount - Represents the cycles to be expended. + * @param stacker - Represents the Stacker's wallet. + * @param extendCount - Represents the number of cycles to extend + * the stack for. * @param currentCycle - Represents the current PoX reward cycle. */ constructor( diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand_Err.ts index 830fb7d182..680532bef6 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand_Err.ts @@ -27,8 +27,8 @@ export class DelegateStackExtendCommand_Err implements PoxCommand { * height as a Pool Operator on behalf of a Stacker. * * @param operator - Represents the Pool Operator's wallet. - * @param stacker - Represents the STacker's wallet. - * @param extendCount - Represents the cycles to be expended. + * @param stacker - Represents the Stacker's wallet. + * @param extendCount - Represents the number of cycles to extend the stack for. * @param currentCycle - Represents the current PoX reward cycle. * @param checkFunc - A function to check constraints for running this command. * @param errorCode - The expected error code when running this command. diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand.ts index b9ec4a837c..6a5837f48b 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand.ts @@ -10,7 +10,7 @@ import { expect } from "vitest"; import { Cl } from "@stacks/transactions"; /** - * The DelegateStackIncreaseCommand allows a pool operator to + * The `DelegateStackIncreaseCommand` allows a pool operator to * increase an active stacking lock, issuing a "partial commitment" * for the increased cycles. * @@ -33,7 +33,7 @@ export class DelegateStackIncreaseCommand implements PoxCommand { readonly increaseBy: number; /** - * Constructs a DelegateStackIncreaseCommand to increase the uSTX amount + * Constructs a `DelegateStackIncreaseCommand` to increase the uSTX amount * previously locked on behalf of a Stacker. * * @param operator - Represents the Pool Operator's wallet. diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand_Err.ts index d54853dcb6..fe33805264 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand_Err.ts @@ -22,7 +22,7 @@ export class DelegateStackIncreaseCommand_Err implements PoxCommand { readonly errorCode: number; /** - * Constructs a DelegateStackIncreaseCommand_Err to increase the uSTX amount + * Constructs a `DelegateStackIncreaseCommand_Err` to increase the uSTX amount * previously locked on behalf of a Stacker. * * @param operator - Represents the Pool Operator's wallet. diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand.ts index e3d9dd25c1..c284975ae0 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand.ts @@ -26,7 +26,7 @@ import { currentCycle } from "./pox_Commands.ts"; * `get-stacking-minimum` function at the time of this call. * - The Stacker cannot currently be engaged in another stacking operation. * - The Stacker has to currently be delegating to the Operator. - * - The stacked STX amount should be less than or equal to the delegated + * - The stacked uSTX amount should be less than or equal to the delegated * amount. * - The stacked uSTX amount should be less than or equal to the Stacker's * balance. @@ -47,7 +47,7 @@ export class DelegateStackStxCommand implements PoxCommand { * on behalf of a Stacker. * * @param operator - Represents the Pool Operator's wallet. - * @param stacker - Represents the STacker's wallet. + * @param stacker - Represents the Stacker's wallet. * @param period - Number of reward cycles to lock uSTX. * @param amountUstx - The uSTX amount stacked by the Operator on behalf * of the Stacker. diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand_Err.ts index b4e5a491dd..d064d8b4cd 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand_Err.ts @@ -28,7 +28,7 @@ export class DelegateStackStxCommand_Err implements PoxCommand { * on behalf of a Stacker. * * @param operator - Represents the Pool Operator's wallet. - * @param stacker - Represents the STacker's wallet. + * @param stacker - Represents the Stacker's wallet. * @param period - Number of reward cycles to lock uSTX. * @param amountUstx - The uSTX amount stacked by the Operator on behalf * of the Stacker. diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStxCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStxCommand.ts index e70d466c9d..836b7d5162 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStxCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStxCommand.ts @@ -24,8 +24,6 @@ import { * * Constraints for running this command include: * - The Stacker cannot currently be a delegator in another delegation. - * - The PoX address provided should have a valid version (between 0 and 6 - * inclusive). */ export class DelegateStxCommand implements PoxCommand { readonly wallet: Wallet; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand.ts index c39a1a5e42..2c3593f27d 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand.ts @@ -21,7 +21,7 @@ export class RevokeDelegateStxCommand implements PoxCommand { readonly wallet: Wallet; /** - * Constructs a RevokeDelegateStxCommand to revoke delegate uSTX for stacking. + * Constructs a `RevokeDelegateStxCommand` to revoke a stacking delegation. * * @param wallet - Represents the Stacker's wallet. */ diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand_Err.ts index 60b3439e8e..a7a4cb0a6e 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand_Err.ts @@ -19,7 +19,7 @@ export class RevokeDelegateStxCommand_Err implements PoxCommand { readonly errorCode: number; /** - * Constructs a `RevokeDelegateStxCommand_Err` to revoke delegate uSTX for stacking. + * Constructs a `RevokeDelegateStxCommand_Err` to revoke a stacking delegation. * * @param wallet - Represents the Stacker's wallet. * @param checkFunc - A function to check constraints for running this command. diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand.ts index 62622f4bd3..999aa2f5b0 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand.ts @@ -31,7 +31,8 @@ export class StackAggregationCommitAuthCommand implements PoxCommand { readonly authId: number; /** - * Constructs a `StackAggregationCommitAuthCommand` to lock uSTX for stacking. + * Constructs a `StackAggregationCommitAuthCommand` to commit partially + * locked uSTX. * * @param operator - Represents the `Operator`'s wallet. * @param authId - Unique `auth-id` for the authorization. diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand_Err.ts index 3580061fae..ddc986f1a4 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand_Err.ts @@ -23,7 +23,8 @@ export class StackAggregationCommitAuthCommand_Err implements PoxCommand { readonly errorCode: number; /** - * Constructs a `StackAggregationCommitAuthCommand_Err` to lock uSTX for stacking. + * Constructs a `StackAggregationCommitAuthCommand_Err` to commit partially + * locked uSTX. * * @param operator - Represents the `Operator`'s wallet. * @param authId - Unique `auth-id` for the authorization. diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand.ts index cfafccc674..926c923135 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand.ts @@ -33,8 +33,8 @@ export class StackAggregationCommitIndexedAuthCommand implements PoxCommand { readonly authId: number; /** - * Constructs a `StackAggregationCommitIndexedAuthCommand` to lock uSTX - * for stacking. + * Constructs a `StackAggregationCommitIndexedAuthCommand` to commit partially + * locked uSTX. * * @param operator - Represents the `Operator`'s wallet. * @param authId - Unique `auth-id` for the authorization. diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand_Err.ts index 92ebfa0d19..1c891df270 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand_Err.ts @@ -24,8 +24,8 @@ export class StackAggregationCommitIndexedAuthCommand_Err readonly errorCode: number; /** - * Constructs a `StackAggregationCommitIndexedAuthCommand_Err` to lock uSTX - * for stacking. + * Constructs a `StackAggregationCommitIndexedAuthCommand_Err` to commit partially + * locked uSTX. * * @param operator - Represents the `Operator`'s wallet. * @param authId - Unique `auth-id` for the authorization. diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand.ts index 59707e21f4..712706d156 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand.ts @@ -33,8 +33,8 @@ export class StackAggregationCommitIndexedSigCommand implements PoxCommand { readonly authId: number; /** - * Constructs a `StackAggregationCommitIndexedSigCommand` to lock uSTX - * for stacking. + * Constructs a `StackAggregationCommitIndexedSigCommand` to commit partially + * locked uSTX. * * @param operator - Represents the `Operator`'s wallet. * @param authId - Unique `auth-id` for the authorization. diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand_Err.ts index 22b5a4f923..045142f3b4 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand_Err.ts @@ -23,8 +23,8 @@ export class StackAggregationCommitIndexedSigCommand_Err implements PoxCommand { readonly errorCode: number; /** - * Constructs a `StackAggregationCommitIndexedSigCommand_Err` to lock uSTX - * for stacking. + * Constructs a `StackAggregationCommitIndexedSigCommand_Err` to commit partially + * locked uSTX. * * @param operator - Represents the `Operator`'s wallet. * @param authId - Unique `auth-id` for the authorization. diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand.ts index 32fe552477..cda1d9cd96 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand.ts @@ -31,7 +31,8 @@ export class StackAggregationCommitSigCommand implements PoxCommand { readonly authId: number; /** - * Constructs a `StackAggregationCommitSigCommand` to lock uSTX for stacking. + * Constructs a `StackAggregationCommitSigCommand` to commit partially + * locked uSTX. * * @param operator - Represents the `Operator`'s wallet. * @param authId - Unique `auth-id` for the authorization. diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand_Err.ts index ca53b56d1c..1238a4f32b 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand_Err.ts @@ -23,7 +23,8 @@ export class StackAggregationCommitSigCommand_Err implements PoxCommand { readonly errorCode: number; /** - * Constructs a `StackAggregationCommitAuthCommand_Err` to lock uSTX for stacking. + * Constructs a `StackAggregationCommitAuthCommand_Err` to commit partially + * locked uSTX. * * @param operator - Represents the `Operator`'s wallet. * @param authId - Unique `auth-id` for the authorization. diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand.ts index fa796673ea..13a835347f 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand.ts @@ -16,7 +16,7 @@ export class StackExtendAuthCommand implements PoxCommand { readonly currentCycle: number; /** - * Constructs a `StackExtendAuthCommand` to lock uSTX for stacking. + * Constructs a `StackExtendAuthCommand` to extend an active stacking lock. * * This command calls `stack-extend` using an `authorization`. * diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand_Err.ts index e3deed040c..46b8ce173e 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand_Err.ts @@ -19,7 +19,7 @@ export class StackExtendAuthCommand_Err implements PoxCommand { readonly errorCode: number; /** - * Constructs a `StackExtendAuthCommand` to lock uSTX for stacking. + * Constructs a `StackExtendAuthCommand_Err` to extend an active stacking lock. * * This command calls `stack-extend` using an `authorization`. * @@ -52,6 +52,10 @@ export class StackExtendAuthCommand_Err implements PoxCommand { const currentRewCycle = currentCycle(real.network); const stacker = model.stackers.get(this.wallet.stxAddress)!; + // Include the authorization and the `stack-extend` transactions in a single + // block. This way we ensure both the authorization and the stack-extend + // transactions are called during the same reward cycle, so the authorization + // currentRewCycle param is relevant for the upcoming stack-extend call. const block = real.network.mineBlock([ tx.callPublicFn( "ST000000000000000000002AMW42H.pox-4", diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand.ts index 56848d9448..5b23d021f6 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand.ts @@ -15,7 +15,7 @@ export class StackExtendSigCommand implements PoxCommand { readonly currentCycle: number; /** - * Constructs a `StackExtendSigCommand` to lock uSTX for stacking. + * Constructs a `StackExtendSigCommand` to extend an active stacking lock. * * This command calls `stack-extend` using a `signature`. * diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand_Err.ts index 185f2796d1..9c37b96a60 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand_Err.ts @@ -18,7 +18,7 @@ export class StackExtendSigCommand_Err implements PoxCommand { readonly errorCode: number; /** - * Constructs a `StackExtendSigCommand` to lock uSTX for stacking. + * Constructs a `StackExtendSigCommand_Err` to extend an active stacking lock. * * This command calls `stack-extend` using a `signature`. * diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand.ts index 127ea1d984..cd802b1b88 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand.ts @@ -25,7 +25,7 @@ export class StackIncreaseAuthCommand implements PoxCommand { readonly authId: number; /** - * Constructs a `StackIncreaseAuthCommand` to increase lock uSTX for stacking. + * Constructs a `StackIncreaseAuthCommand` to increase the locked uSTX amount. * * @param wallet - Represents the Stacker's wallet. * @param increaseBy - Represents the locked amount to be increased by. diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand_Err.ts index a74aa3c211..5722b50236 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand_Err.ts @@ -18,7 +18,7 @@ export class StackIncreaseAuthCommand_Err implements PoxCommand { readonly errorCode: number; /** - * Constructs a `StackIncreaseAuthCommand` to increase lock uSTX for stacking. + * Constructs a `StackIncreaseAuthCommand_Err` to increase the locked uSTX amount. * * @param wallet - Represents the Stacker's wallet. * @param increaseBy - Represents the locked amount to be increased by. diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand.ts index ec51e3d7e4..1bd50691b6 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand.ts @@ -30,7 +30,7 @@ export class StackIncreaseSigCommand implements PoxCommand { readonly authId: number; /** - * Constructs a `StackIncreaseSigCommand` to lock uSTX for stacking. + * Constructs a `StackIncreaseSigCommand` to increase the locked uSTX amount. * * @param wallet - Represents the Stacker's wallet. * @param increaseBy - Represents the locked amount to be increased by. diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand_Err.ts index 4a122784b3..4d0297b624 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand_Err.ts @@ -23,7 +23,7 @@ export class StackIncreaseSigCommand_Err implements PoxCommand { readonly errorCode: number; /** - * Constructs a `StackIncreaseSigCommand_Err` to lock uSTX for stacking. + * Constructs a `StackIncreaseSigCommand_Err` to increase the locked uSTX amount. * * @param wallet - Represents the Stacker's wallet. * @param increaseBy - Represents the locked amount to be increased by. diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts index f9c2cdc8d4..919fa56c76 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand_Err.ts @@ -30,7 +30,7 @@ export class StackStxSigCommand_Err implements PoxCommand { readonly errorCode: number; /** - * Constructs a `StackStxSigCommand` to lock uSTX for stacking. + * Constructs a `StackStxSigCommand_Err` to lock uSTX for stacking. * * @param wallet - Represents the Stacker's wallet. * @param authId - Unique auth-id for the authorization. From f460834a2b70b31e7b54e2fed97303e1372f8cff Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Tue, 18 Jun 2024 14:21:55 +0300 Subject: [PATCH 63/72] Remove types from `err_Commands` `check` functions This update simplifies the err_Commands generators file. --- .../tests/pox-4/err_Commands.ts | 245 ++++-------------- 1 file changed, 49 insertions(+), 196 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index c24c028f8d..8cac237fc7 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -73,10 +73,7 @@ export function ErrCommands( r.authId, r.period, r.margin, - function ( - this: StackStxAuthCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( model.stackingMinimum > 0 && @@ -111,10 +108,7 @@ export function ErrCommands( r.authId, r.period, r.margin, - function ( - this: StackStxAuthCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( model.stackingMinimum > 0 && @@ -149,10 +143,7 @@ export function ErrCommands( r.authId, r.period, r.margin, - function ( - this: StackStxAuthCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( model.stackingMinimum > 0 && @@ -187,10 +178,7 @@ export function ErrCommands( r.authId, r.period, r.margin, - function ( - this: StackStxSigCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( model.stackingMinimum > 0 && @@ -225,10 +213,7 @@ export function ErrCommands( r.authId, r.period, r.margin, - function ( - this: StackStxSigCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( model.stackingMinimum > 0 && @@ -263,10 +248,7 @@ export function ErrCommands( r.authId, r.period, r.margin, - function ( - this: StackStxSigCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( model.stackingMinimum > 0 && @@ -292,10 +274,7 @@ export function ErrCommands( ) => new RevokeDelegateStxCommand_Err( r.wallet, - function ( - this: RevokeDelegateStxCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( model.stackingMinimum > 0 && @@ -330,10 +309,7 @@ export function ErrCommands( r.delegateTo, r.untilBurnHt, r.amount, - function ( - this: DelegateStxCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( model.stackingMinimum > 0 && @@ -357,10 +333,7 @@ export function ErrCommands( new StackAggregationCommitSigCommand_Err( r.wallet, r.authId, - function ( - this: StackAggregationCommitSigCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operator = model.stackers.get(this.operator.stxAddress)!; if ( @@ -386,10 +359,7 @@ export function ErrCommands( new StackAggregationCommitSigCommand_Err( r.wallet, r.authId, - function ( - this: StackAggregationCommitSigCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operator = model.stackers.get(this.operator.stxAddress)!; if ( @@ -415,10 +385,7 @@ export function ErrCommands( new StackAggregationCommitSigCommand_Err( r.wallet, r.authId, - function ( - this: StackAggregationCommitSigCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operator = model.stackers.get(this.operator.stxAddress)!; if ( @@ -443,10 +410,7 @@ export function ErrCommands( new StackAggregationCommitAuthCommand_Err( r.wallet, r.authId, - function ( - this: StackAggregationCommitAuthCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operator = model.stackers.get(this.operator.stxAddress)!; if ( @@ -472,10 +436,7 @@ export function ErrCommands( new StackAggregationCommitAuthCommand_Err( r.wallet, r.authId, - function ( - this: StackAggregationCommitAuthCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operator = model.stackers.get(this.operator.stxAddress)!; if ( @@ -501,10 +462,7 @@ export function ErrCommands( new StackAggregationCommitAuthCommand_Err( r.wallet, r.authId, - function ( - this: StackAggregationCommitAuthCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operator = model.stackers.get(this.operator.stxAddress)!; if ( @@ -529,10 +487,7 @@ export function ErrCommands( new StackAggregationCommitIndexedSigCommand_Err( r.wallet, r.authId, - function ( - this: StackAggregationCommitIndexedSigCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operator = model.stackers.get(this.operator.stxAddress)!; if ( @@ -558,10 +513,7 @@ export function ErrCommands( new StackAggregationCommitIndexedSigCommand_Err( r.wallet, r.authId, - function ( - this: StackAggregationCommitIndexedSigCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operator = model.stackers.get(this.operator.stxAddress)!; if ( @@ -587,10 +539,7 @@ export function ErrCommands( new StackAggregationCommitIndexedSigCommand_Err( r.wallet, r.authId, - function ( - this: StackAggregationCommitIndexedSigCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operator = model.stackers.get(this.operator.stxAddress)!; if ( @@ -615,10 +564,7 @@ export function ErrCommands( new StackAggregationCommitIndexedAuthCommand_Err( r.wallet, r.authId, - function ( - this: StackAggregationCommitIndexedAuthCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operator = model.stackers.get(this.operator.stxAddress)!; if ( @@ -644,10 +590,7 @@ export function ErrCommands( new StackAggregationCommitIndexedAuthCommand_Err( r.wallet, r.authId, - function ( - this: StackAggregationCommitIndexedAuthCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operator = model.stackers.get(this.operator.stxAddress)!; if ( @@ -672,10 +615,7 @@ export function ErrCommands( new StackAggregationCommitIndexedAuthCommand_Err( r.wallet, r.authId, - function ( - this: StackAggregationCommitIndexedAuthCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operator = model.stackers.get(this.operator.stxAddress)!; if ( @@ -715,10 +655,7 @@ export function ErrCommands( r.wallet, r.rewardCycleIndex, r.authId, - function ( - this: StackAggregationIncreaseCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operator = model.stackers.get(this.operator.stxAddress)!; if ( operator.lockedAddresses.length > 0 && @@ -782,10 +719,7 @@ export function ErrCommands( finalResult.period, finalResult.amount, finalResult.unlockBurnHt, - function ( - this: DelegateStackStxCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operatorWallet = model.stackers.get(this.operator.stxAddress)!; const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; if ( @@ -859,10 +793,7 @@ export function ErrCommands( finalResult.period, finalResult.amount, finalResult.unlockBurnHt, - function ( - this: DelegateStackStxCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operatorWallet = model.stackers.get(this.operator.stxAddress)!; const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; if ( @@ -933,10 +864,7 @@ export function ErrCommands( finalResult.period, finalResult.amount, finalResult.unlockBurnHt, - function ( - this: DelegateStackStxCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operatorWallet = model.stackers.get(this.operator.stxAddress)!; const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; if ( @@ -970,10 +898,7 @@ export function ErrCommands( r.operator, r.increaseBy, r.authId, - function ( - this: StackIncreaseSigCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( model.stackingMinimum > 0 && @@ -1004,10 +929,7 @@ export function ErrCommands( r.operator, r.increaseBy, r.authId, - function ( - this: StackIncreaseSigCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( model.stackingMinimum > 0 && @@ -1038,10 +960,7 @@ export function ErrCommands( r.operator, r.increaseBy, r.authId, - function ( - this: StackIncreaseSigCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( model.stackingMinimum > 0 && @@ -1072,10 +991,7 @@ export function ErrCommands( r.operator, r.increaseBy, r.authId, - function ( - this: StackIncreaseAuthCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( model.stackingMinimum > 0 && @@ -1106,10 +1022,7 @@ export function ErrCommands( r.operator, r.increaseBy, r.authId, - function ( - this: StackIncreaseAuthCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( model.stackingMinimum > 0 && @@ -1140,10 +1053,7 @@ export function ErrCommands( r.operator, r.increaseBy, r.authId, - function ( - this: StackIncreaseAuthCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( model.stackingMinimum > 0 && @@ -1181,10 +1091,7 @@ export function ErrCommands( r.extendCount, r.authId, r.currentCycle, - function ( - this: StackExtendSigCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; const firstRewardCycle = @@ -1233,10 +1140,7 @@ export function ErrCommands( r.extendCount, r.authId, r.currentCycle, - function ( - this: StackExtendSigCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; const firstRewardCycle = @@ -1285,10 +1189,7 @@ export function ErrCommands( r.extendCount, r.authId, r.currentCycle, - function ( - this: StackExtendSigCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; const firstRewardCycle = @@ -1337,10 +1238,7 @@ export function ErrCommands( r.extendCount, r.authId, r.currentCycle, - function ( - this: StackExtendSigCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; const firstRewardCycle = @@ -1389,10 +1287,7 @@ export function ErrCommands( r.extendCount, r.authId, r.currentCycle, - function ( - this: StackExtendSigCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; const firstRewardCycle = @@ -1441,10 +1336,7 @@ export function ErrCommands( r.extendCount, r.authId, r.currentCycle, - function ( - this: StackExtendAuthCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; const firstRewardCycle = @@ -1493,10 +1385,7 @@ export function ErrCommands( r.extendCount, r.authId, r.currentCycle, - function ( - this: StackExtendAuthCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; const firstRewardCycle = @@ -1545,10 +1434,7 @@ export function ErrCommands( r.extendCount, r.authId, r.currentCycle, - function ( - this: StackExtendAuthCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; const firstRewardCycle = @@ -1597,10 +1483,7 @@ export function ErrCommands( r.extendCount, r.authId, r.currentCycle, - function ( - this: StackExtendAuthCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; const firstRewardCycle = @@ -1649,10 +1532,7 @@ export function ErrCommands( r.extendCount, r.authId, r.currentCycle, - function ( - this: StackExtendAuthCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; const firstRewardCycle = @@ -1714,10 +1594,7 @@ export function ErrCommands( final.stacker, final.extendCount, final.currentCycle, - function ( - this: DelegateStackExtendCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operatorWallet = model.stackers.get( this.operator.stxAddress, )!; @@ -1796,10 +1673,7 @@ export function ErrCommands( final.stacker, final.extendCount, final.currentCycle, - function ( - this: DelegateStackExtendCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operatorWallet = model.stackers.get( this.operator.stxAddress, )!; @@ -1877,10 +1751,7 @@ export function ErrCommands( final.stacker, final.extendCount, final.currentCycle, - function ( - this: DelegateStackExtendCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operatorWallet = model.stackers.get( this.operator.stxAddress, )!; @@ -1938,10 +1809,7 @@ export function ErrCommands( final.stacker, final.extendCount, final.currentCycle, - function ( - this: DelegateStackExtendCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operatorWallet = model.stackers.get( this.operator.stxAddress, )!; @@ -2016,10 +1884,7 @@ export function ErrCommands( final.operator, final.stacker, final.increaseBy, - function ( - this: DelegateStackIncreaseCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operatorWallet = model.stackers.get( this.operator.stxAddress, )!; @@ -2080,10 +1945,7 @@ export function ErrCommands( final.operator, final.stacker, final.increaseBy, - function ( - this: DelegateStackIncreaseCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operatorWallet = model.stackers.get( this.operator.stxAddress, )!; @@ -2142,10 +2004,7 @@ export function ErrCommands( final.operator, final.stacker, final.increaseBy, - function ( - this: DelegateStackIncreaseCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operatorWallet = model.stackers.get( this.operator.stxAddress, )!; @@ -2209,10 +2068,7 @@ export function ErrCommands( final.operator, final.stacker, final.increaseBy, - function ( - this: DelegateStackIncreaseCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const operatorWallet = model.stackers.get( this.operator.stxAddress, )!; @@ -2253,10 +2109,7 @@ export function ErrCommands( new DisallowContractCallerCommand_Err( r.stacker, r.callerToDisallow, - function ( - this: DisallowContractCallerCommand_Err, - model: Readonly, - ): boolean { + function (this, model) { const stacker = model.stackers.get(this.stacker.stxAddress)!; const callerToDisallow = model.stackers.get( this.callerToDisallow.stxAddress, From 452d7bd89dda08d5af27167cfc951250121b148c Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Thu, 20 Jun 2024 14:58:34 +0300 Subject: [PATCH 64/72] Add an additional condition inside `DelegateStackIncreaseCommand` The added condition ensures that an active lock does not expire at the end of the current cycle, and avoids the PoX-4 Clarity error 2 - ERR_STACKING_INVALID_LOCK_PERIOD --- .../tests/pox-4/pox_DelegateStackIncreaseCommand.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand.ts index 6a5837f48b..b78fe187bb 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand.ts @@ -8,6 +8,7 @@ import { import { poxAddressToTuple } from "@stacks/stacking"; import { expect } from "vitest"; import { Cl } from "@stacks/transactions"; +import { REWARD_CYCLE_LENGTH } from "./pox_Commands.ts"; /** * The `DelegateStackIncreaseCommand` allows a pool operator to @@ -69,7 +70,8 @@ export class DelegateStackIncreaseCommand implements PoxCommand { stackerWallet.amountUnlocked >= this.increaseBy && stackerWallet.delegatedMaxAmount >= this.increaseBy + stackerWallet.amountLocked && - operatorWallet.lockedAddresses.indexOf(this.stacker.stxAddress) > -1 + operatorWallet.lockedAddresses.indexOf(this.stacker.stxAddress) > -1 && + stackerWallet.unlockHeight > model.burnBlockHeight + REWARD_CYCLE_LENGTH ); } From 90d4a2bb418abee7b08c83be38df1b0c94a09c39 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Thu, 20 Jun 2024 15:01:37 +0300 Subject: [PATCH 65/72] Fix typo in `DelegateStackStxCommand_Err` logging --- .../tests/pox-4/pox_DelegateStackStxCommand_Err.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand_Err.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand_Err.ts index d064d8b4cd..fdec28a355 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand_Err.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand_Err.ts @@ -86,7 +86,7 @@ export class DelegateStackStxCommand_Err implements PoxCommand { // test to pass but it is useful for debugging and eyeballing the test. logCommand( `₿ ${model.burnBlockHeight}`, - `✓ ${this.operator.label} Ӿ ${this.stacker.label}`, + `✗ ${this.operator.label} Ӿ ${this.stacker.label}`, "delegate-stack-stx", "lock-amount", this.amountUstx.toString(), From fa5b78f919b440db2f6b058bbd545f1bb6b50a83 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Thu, 20 Jun 2024 15:17:45 +0300 Subject: [PATCH 66/72] Update the err_Commands check functions to return the false first This commit refactors all the unhappy path check functions, returning early the false and removing the else branch. --- .../tests/pox-4/err_Commands.ts | 1172 +++++++++-------- 1 file changed, 590 insertions(+), 582 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 8cac237fc7..3d9738fefb 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -76,15 +76,15 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - model.stackingMinimum > 0 && - stacker.isStacking && - !stacker.hasDelegated - ) { - model.trackCommandRun( - "StackStxAuthCommand_Err_Stacking_Already_Stacked_1", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.isStacking || + stacker.hasDelegated + ) return false; + + model.trackCommandRun( + "StackStxAuthCommand_Err_Stacking_Already_Stacked_1", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_ALREADY_STACKED, ) @@ -111,15 +111,15 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - model.stackingMinimum > 0 && - stacker.isStacking && - stacker.hasDelegated - ) { - model.trackCommandRun( - "StackStxAuthCommand_Err_Stacking_Already_Stacked_2", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.isStacking || + !stacker.hasDelegated + ) return false; + + model.trackCommandRun( + "StackStxAuthCommand_Err_Stacking_Already_Stacked_2", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_ALREADY_STACKED, ) @@ -146,15 +146,15 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - model.stackingMinimum > 0 && - !stacker.isStacking && - stacker.hasDelegated - ) { - model.trackCommandRun( - "StackStxAuthCommand_Err_Stacking_Already_Delegated", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + stacker.isStacking || + !stacker.hasDelegated + ) return false; + + model.trackCommandRun( + "StackStxAuthCommand_Err_Stacking_Already_Delegated", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, ) @@ -181,15 +181,15 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - model.stackingMinimum > 0 && - stacker.isStacking && - !stacker.hasDelegated - ) { - model.trackCommandRun( - "StackStxSigCommand_Err_Stacking_Already_Stacked_1", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.isStacking || + stacker.hasDelegated + ) return false; + + model.trackCommandRun( + "StackStxSigCommand_Err_Stacking_Already_Stacked_1", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_ALREADY_STACKED, ) @@ -216,15 +216,15 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - model.stackingMinimum > 0 && - stacker.isStacking && - stacker.hasDelegated - ) { - model.trackCommandRun( - "StackStxSigCommand_Err_Stacking_Already_Stacked_2", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.isStacking || + !stacker.hasDelegated + ) return false; + + model.trackCommandRun( + "StackStxSigCommand_Err_Stacking_Already_Stacked_2", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_ALREADY_STACKED, ) @@ -251,15 +251,15 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - model.stackingMinimum > 0 && - !stacker.isStacking && - stacker.hasDelegated - ) { - model.trackCommandRun( - "StackStxSigCommand_Err_Stacking_Already_Delegated", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + stacker.isStacking || + !stacker.hasDelegated + ) return false; + + model.trackCommandRun( + "StackStxSigCommand_Err_Stacking_Already_Delegated", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, ) @@ -277,14 +277,14 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - model.stackingMinimum > 0 && - !stacker.hasDelegated - ) { - model.trackCommandRun( - "RevokeDelegateStxCommand_Err_Delegation_Already_Revoked", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + stacker.hasDelegated + ) return false; + + model.trackCommandRun( + "RevokeDelegateStxCommand_Err_Delegation_Already_Revoked", + ); + return true; }, POX_4_ERRORS.ERR_DELEGATION_ALREADY_REVOKED, ) @@ -312,14 +312,14 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - model.stackingMinimum > 0 && - stacker.hasDelegated - ) { - model.trackCommandRun( - "DelegateStxCommand_Err_Stacking_Already_Delegated", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.hasDelegated + ) return false; + + model.trackCommandRun( + "DelegateStxCommand_Err_Stacking_Already_Delegated", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, ) @@ -337,15 +337,15 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - operator.lockedAddresses.length > 0 && - !(operator.amountToCommit >= model.stackingMinimum) && - operator.amountToCommit > 0 - ) { - model.trackCommandRun( - "StackAggregationCommitSigCommand_Err_Stacking_Threshold_Not_Met", - ); - return true; - } else return false; + !(operator.lockedAddresses.length > 0) || + operator.amountToCommit >= model.stackingMinimum || + !(operator.amountToCommit > 0) + ) return false; + + model.trackCommandRun( + "StackAggregationCommitSigCommand_Err_Stacking_Threshold_Not_Met", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_THRESHOLD_NOT_MET, ), @@ -363,15 +363,15 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - operator.lockedAddresses.length > 0 && - !(operator.amountToCommit >= model.stackingMinimum) && - operator.amountToCommit === 0 - ) { - model.trackCommandRun( - "StackAggregationCommitSigCommand_Err_Stacking_No_Such_Principal_1", - ); - return true; - } else return false; + !(operator.lockedAddresses.length > 0) || + (operator.amountToCommit >= model.stackingMinimum) || + operator.amountToCommit !== 0 + ) return false; + + model.trackCommandRun( + "StackAggregationCommitSigCommand_Err_Stacking_No_Such_Principal_1", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, ), @@ -389,14 +389,14 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - !(operator.lockedAddresses.length > 0) && - !(operator.amountToCommit >= model.stackingMinimum) - ) { - model.trackCommandRun( - "StackAggregationCommitSigCommand_Err_Stacking_No_Such_Principal_2", - ); - return true; - } else return false; + operator.lockedAddresses.length > 0 || + operator.amountToCommit >= model.stackingMinimum + ) return false; + + model.trackCommandRun( + "StackAggregationCommitSigCommand_Err_Stacking_No_Such_Principal_2", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, ), @@ -414,15 +414,15 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - operator.lockedAddresses.length > 0 && - !(operator.amountToCommit >= model.stackingMinimum) && - operator.amountToCommit > 0 - ) { - model.trackCommandRun( - "StackAggregationCommitAuthCommand_Err_Stacking_Threshold_Not_Met", - ); - return true; - } else return false; + !(operator.lockedAddresses.length > 0) || + operator.amountToCommit >= model.stackingMinimum || + !(operator.amountToCommit > 0) + ) return false; + + model.trackCommandRun( + "StackAggregationCommitAuthCommand_Err_Stacking_Threshold_Not_Met", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_THRESHOLD_NOT_MET, ), @@ -440,15 +440,15 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - operator.lockedAddresses.length > 0 && - !(operator.amountToCommit >= model.stackingMinimum) && - operator.amountToCommit === 0 - ) { - model.trackCommandRun( - "StackAggregationCommitAuthCommand_Err_Stacking_No_Such_Principal_1", - ); - return true; - } else return false; + !(operator.lockedAddresses.length > 0) || + operator.amountToCommit >= model.stackingMinimum || + !(operator.amountToCommit === 0) + ) return false; + + model.trackCommandRun( + "StackAggregationCommitAuthCommand_Err_Stacking_No_Such_Principal_1", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, ), @@ -466,14 +466,14 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - !(operator.lockedAddresses.length > 0) && - !(operator.amountToCommit >= model.stackingMinimum) - ) { - model.trackCommandRun( - "StackAggregationCommitAuthCommand_Err_Stacking_No_Such_Principal_2", - ); - return true; - } else return false; + operator.lockedAddresses.length > 0 || + operator.amountToCommit >= model.stackingMinimum + ) return false; + + model.trackCommandRun( + "StackAggregationCommitAuthCommand_Err_Stacking_No_Such_Principal_2", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, ), @@ -491,15 +491,15 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - operator.lockedAddresses.length > 0 && - !(operator.amountToCommit >= model.stackingMinimum) && - operator.amountToCommit > 0 - ) { - model.trackCommandRun( - "StackAggregationCommitIndexedSigCommand_Err_Stacking_Threshold_Not_Met", - ); - return true; - } else return false; + !(operator.lockedAddresses.length > 0) || + operator.amountToCommit >= model.stackingMinimum || + !(operator.amountToCommit > 0) + ) return false; + + model.trackCommandRun( + "StackAggregationCommitIndexedSigCommand_Err_Stacking_Threshold_Not_Met", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_THRESHOLD_NOT_MET, ), @@ -517,15 +517,15 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - operator.lockedAddresses.length > 0 && - !(operator.amountToCommit >= model.stackingMinimum) && - !(operator.amountToCommit > 0) - ) { - model.trackCommandRun( - "StackAggregationCommitIndexedSigCommand_Err_Stacking_No_Such_Principal_1", - ); - return true; - } else return false; + !(operator.lockedAddresses.length > 0) || + operator.amountToCommit >= model.stackingMinimum || + operator.amountToCommit > 0 + ) return false; + + model.trackCommandRun( + "StackAggregationCommitIndexedSigCommand_Err_Stacking_No_Such_Principal_1", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, ), @@ -543,14 +543,14 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - !(operator.lockedAddresses.length > 0) && - !(operator.amountToCommit >= model.stackingMinimum) - ) { - model.trackCommandRun( - "StackAggregationCommitIndexedSigCommand_Err_Stacking_No_Such_Principal_2", - ); - return true; - } else return false; + operator.lockedAddresses.length > 0 || + operator.amountToCommit >= model.stackingMinimum + ) return false; + + model.trackCommandRun( + "StackAggregationCommitIndexedSigCommand_Err_Stacking_No_Such_Principal_2", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, ), @@ -568,15 +568,15 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - operator.lockedAddresses.length > 0 && - !(operator.amountToCommit >= model.stackingMinimum) && - !(operator.amountToCommit > 0) - ) { - model.trackCommandRun( - "StackAggregationCommitIndexedAuthCommand_Err_Stacking_No_Such_Principal_1", - ); - return true; - } else return false; + !(operator.lockedAddresses.length > 0) || + operator.amountToCommit >= model.stackingMinimum || + operator.amountToCommit > 0 + ) return false; + + model.trackCommandRun( + "StackAggregationCommitIndexedAuthCommand_Err_Stacking_No_Such_Principal_1", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, ), @@ -594,14 +594,14 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - !(operator.lockedAddresses.length > 0) && - !(operator.amountToCommit >= model.stackingMinimum) - ) { - model.trackCommandRun( - "StackAggregationCommitIndexedAuthCommand_Err_Stacking_No_Such_Principal_2", - ); - return true; - } else return false; + operator.lockedAddresses.length > 0 || + operator.amountToCommit >= model.stackingMinimum + ) return false; + + model.trackCommandRun( + "StackAggregationCommitIndexedAuthCommand_Err_Stacking_No_Such_Principal_2", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, ), @@ -619,15 +619,15 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - operator.lockedAddresses.length > 0 && - !(operator.amountToCommit >= model.stackingMinimum) && - operator.amountToCommit > 0 - ) { - model.trackCommandRun( - "StackAggregationCommitIndexedAuthCommand_Err_Stacking_Threshold_Not_Met", - ); - return true; - } else return false; + !(operator.lockedAddresses.length > 0) || + operator.amountToCommit >= model.stackingMinimum || + !(operator.amountToCommit > 0) + ) return false; + + model.trackCommandRun( + "StackAggregationCommitIndexedAuthCommand_Err_Stacking_Threshold_Not_Met", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_THRESHOLD_NOT_MET, ), @@ -658,15 +658,15 @@ export function ErrCommands( function (this, model) { const operator = model.stackers.get(this.operator.stxAddress)!; if ( - operator.lockedAddresses.length > 0 && - this.rewardCycleIndex >= 0 && - !(operator.amountToCommit > 0) - ) { - model.trackCommandRun( - "StackAggregationIncreaseCommand_Err_Stacking_No_Such_Principal", - ); - return true; - } else return false; + !(operator.lockedAddresses.length > 0) || + !(this.rewardCycleIndex >= 0) || + operator.amountToCommit > 0 + ) return false; + + model.trackCommandRun( + "StackAggregationIncreaseCommand_Err_Stacking_No_Such_Principal", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_NO_SUCH_PRINCIPAL, ), @@ -723,21 +723,21 @@ export function ErrCommands( const operatorWallet = model.stackers.get(this.operator.stxAddress)!; const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; if ( - model.stackingMinimum > 0 && - !stackerWallet.isStacking && - stackerWallet.hasDelegated && - !(stackerWallet.delegatedMaxAmount >= Number(this.amountUstx)) && - Number(this.amountUstx) <= stackerWallet.ustxBalance && - Number(this.amountUstx) >= model.stackingMinimum && - operatorWallet.poolMembers.includes(this.stacker.stxAddress) && - (stackerWallet.delegatedUntilBurnHt === undefined || + !(model.stackingMinimum > 0) || + stackerWallet.isStacking || + !(stackerWallet.hasDelegated) || + stackerWallet.delegatedMaxAmount >= Number(this.amountUstx) || + !(Number(this.amountUstx) <= stackerWallet.ustxBalance) || + !(Number(this.amountUstx) >= model.stackingMinimum) || + !(operatorWallet.poolMembers.includes(this.stacker.stxAddress)) || + !(stackerWallet.delegatedUntilBurnHt === undefined || this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt) - ) { - model.trackCommandRun( - "DelegateStackStxCommand_Err_Delegation_Too_Much_Locked", - ); - return true; - } else return false; + ) return false; + + model.trackCommandRun( + "DelegateStackStxCommand_Err_Delegation_Too_Much_Locked", + ); + return true; }, POX_4_ERRORS.ERR_DELEGATION_TOO_MUCH_LOCKED, ); @@ -797,21 +797,21 @@ export function ErrCommands( const operatorWallet = model.stackers.get(this.operator.stxAddress)!; const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; if ( - model.stackingMinimum > 0 && - !stackerWallet.isStacking && - stackerWallet.hasDelegated && - stackerWallet.delegatedMaxAmount >= Number(this.amountUstx) && - Number(this.amountUstx) <= stackerWallet.ustxBalance && - Number(this.amountUstx) >= model.stackingMinimum && - !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && - (stackerWallet.delegatedUntilBurnHt === undefined || + !(model.stackingMinimum > 0) || + stackerWallet.isStacking || + !stackerWallet.hasDelegated || + !(stackerWallet.delegatedMaxAmount >= Number(this.amountUstx)) || + !(Number(this.amountUstx) <= stackerWallet.ustxBalance) || + !(Number(this.amountUstx) >= model.stackingMinimum) || + operatorWallet.poolMembers.includes(this.stacker.stxAddress) || + !(stackerWallet.delegatedUntilBurnHt === undefined || this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt) - ) { - model.trackCommandRun( - "DelegateStackStxCommand_Err_Stacking_Permission_Denied_1", - ); - return true; - } else return false; + ) return false; + + model.trackCommandRun( + "DelegateStackStxCommand_Err_Stacking_Permission_Denied_1", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_PERMISSION_DENIED, ); @@ -868,21 +868,21 @@ export function ErrCommands( const operatorWallet = model.stackers.get(this.operator.stxAddress)!; const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; if ( - model.stackingMinimum > 0 && - !stackerWallet.isStacking && - !(stackerWallet.hasDelegated) && - !(stackerWallet.delegatedMaxAmount >= Number(this.amountUstx)) && - Number(this.amountUstx) <= stackerWallet.ustxBalance && - Number(this.amountUstx) >= model.stackingMinimum && - !(operatorWallet.poolMembers.includes(this.stacker.stxAddress)) && - !(stackerWallet.delegatedUntilBurnHt === undefined || - this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt) - ) { - model.trackCommandRun( - "DelegateStackStxCommand_Err_Stacking_Permission_Denied_2", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + stackerWallet.isStacking || + stackerWallet.hasDelegated || + stackerWallet.delegatedMaxAmount >= Number(this.amountUstx) || + !(Number(this.amountUstx) <= stackerWallet.ustxBalance) || + !(Number(this.amountUstx) >= model.stackingMinimum) || + operatorWallet.poolMembers.includes(this.stacker.stxAddress) && + (stackerWallet.delegatedUntilBurnHt === undefined || + this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt) + ) return false; + + model.trackCommandRun( + "DelegateStackStxCommand_Err_Stacking_Permission_Denied_2", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_PERMISSION_DENIED, ); @@ -901,19 +901,19 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - model.stackingMinimum > 0 && - stacker.isStacking && - !stacker.isStackingSolo && - !stacker.hasDelegated && - stacker.amountLocked > 0 && - this.increaseBy <= stacker.amountUnlocked && - this.increaseBy >= 1 - ) { - model.trackCommandRun( - "StackIncreaseSigCommand_Err_Stacking_Is_Delegated", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.isStacking || + stacker.isStackingSolo || + stacker.hasDelegated || + !(stacker.amountLocked > 0) || + !(this.increaseBy <= stacker.amountUnlocked) || + !(this.increaseBy >= 1) + ) return false; + + model.trackCommandRun( + "StackIncreaseSigCommand_Err_Stacking_Is_Delegated", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_IS_DELEGATED, ), @@ -932,19 +932,19 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - model.stackingMinimum > 0 && - stacker.isStacking && - stacker.isStackingSolo && - !stacker.hasDelegated && - stacker.amountLocked > 0 && - !(this.increaseBy <= stacker.amountUnlocked) && - this.increaseBy >= 1 - ) { - model.trackCommandRun( - "StackIncreaseSigCommand_Err_Stacking_Insufficient_Funds", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.isStacking || + !stacker.isStackingSolo || + stacker.hasDelegated || + !(stacker.amountLocked > 0) || + this.increaseBy <= stacker.amountUnlocked || + !(this.increaseBy >= 1) + ) return false; + + model.trackCommandRun( + "StackIncreaseSigCommand_Err_Stacking_Insufficient_Funds", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_INSUFFICIENT_FUNDS, ), @@ -963,19 +963,19 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - model.stackingMinimum > 0 && - stacker.isStacking && - stacker.isStackingSolo && - !stacker.hasDelegated && - stacker.amountLocked > 0 && - this.increaseBy <= stacker.amountUnlocked && - !(this.increaseBy >= 1) - ) { - model.trackCommandRun( - "StackIncreaseSigCommand_Err_Stacking_Invalid_Amount", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.isStacking || + !stacker.isStackingSolo || + stacker.hasDelegated || + !(stacker.amountLocked > 0) || + !(this.increaseBy <= stacker.amountUnlocked) || + this.increaseBy >= 1 + ) return false; + + model.trackCommandRun( + "StackIncreaseSigCommand_Err_Stacking_Invalid_Amount", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_INVALID_AMOUNT, ), @@ -994,19 +994,19 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - model.stackingMinimum > 0 && - stacker.isStacking && - !stacker.isStackingSolo && - !stacker.hasDelegated && - stacker.amountLocked > 0 && - this.increaseBy <= stacker.amountUnlocked && - this.increaseBy >= 1 - ) { - model.trackCommandRun( - "StackIncreaseAuthCommand_Err_Stacking_Is_Delegated", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.isStacking || + stacker.isStackingSolo || + stacker.hasDelegated || + !(stacker.amountLocked > 0) || + !(this.increaseBy <= stacker.amountUnlocked) || + !(this.increaseBy >= 1) + ) return false; + + model.trackCommandRun( + "StackIncreaseAuthCommand_Err_Stacking_Is_Delegated", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_IS_DELEGATED, ), @@ -1025,19 +1025,19 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - model.stackingMinimum > 0 && - stacker.isStacking && - stacker.isStackingSolo && - !stacker.hasDelegated && - stacker.amountLocked > 0 && - !(this.increaseBy <= stacker.amountUnlocked) && - this.increaseBy >= 1 - ) { - model.trackCommandRun( - "StackIncreaseAuthCommand_Err_Stacking_Insufficient_Funds", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.isStacking || + !stacker.isStackingSolo || + stacker.hasDelegated || + !(stacker.amountLocked > 0) || + this.increaseBy <= stacker.amountUnlocked || + !(this.increaseBy >= 1) + ) return false; + + model.trackCommandRun( + "StackIncreaseAuthCommand_Err_Stacking_Insufficient_Funds", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_INSUFFICIENT_FUNDS, ), @@ -1056,19 +1056,19 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - model.stackingMinimum > 0 && - stacker.isStacking && - stacker.isStackingSolo && - !stacker.hasDelegated && - stacker.amountLocked > 0 && - this.increaseBy <= stacker.amountUnlocked && - !(this.increaseBy >= 1) - ) { - model.trackCommandRun( - "StackIncreaseAuthCommand_Err_Stacking_Invalid_Amount", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.isStacking || + !stacker.isStackingSolo || + stacker.hasDelegated || + !(stacker.amountLocked > 0) || + !(this.increaseBy <= stacker.amountUnlocked) || + this.increaseBy >= 1 + ) return false; + + model.trackCommandRun( + "StackIncreaseAuthCommand_Err_Stacking_Invalid_Amount", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_INVALID_AMOUNT, ), @@ -1105,19 +1105,19 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - model.stackingMinimum > 0 && - stacker.isStacking && - !stacker.isStackingSolo && - !stacker.hasDelegated && - stacker.amountLocked > 0 && - stacker.poolMembers.length === 0 && - totalPeriod <= 12 - ) { - model.trackCommandRun( - "StackExtendSigCommand_Err_Stacking_Is_Delegated_1", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.isStacking || + stacker.isStackingSolo || + stacker.hasDelegated || + !(stacker.amountLocked > 0) || + !(stacker.poolMembers.length === 0) || + !(totalPeriod <= 12) + ) return false; + + model.trackCommandRun( + "StackExtendSigCommand_Err_Stacking_Is_Delegated_1", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_IS_DELEGATED, ), @@ -1154,19 +1154,19 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - model.stackingMinimum > 0 && - stacker.isStacking && - !stacker.isStackingSolo && - !stacker.hasDelegated && - stacker.amountLocked > 0 && - !(stacker.poolMembers.length === 0) && - totalPeriod <= 12 - ) { - model.trackCommandRun( - "StackExtendSigCommand_Err_Stacking_Is_Delegated_2", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.isStacking || + stacker.isStackingSolo || + stacker.hasDelegated || + !(stacker.amountLocked > 0) || + stacker.poolMembers.length === 0 || + !(totalPeriod <= 12) + ) return false; + + model.trackCommandRun( + "StackExtendSigCommand_Err_Stacking_Is_Delegated_2", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_IS_DELEGATED, ), @@ -1203,19 +1203,19 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - model.stackingMinimum > 0 && - stacker.isStacking && - stacker.isStackingSolo && - stacker.hasDelegated && - stacker.amountLocked > 0 && - stacker.poolMembers.length === 0 && - totalPeriod <= 12 - ) { - model.trackCommandRun( - "StackExtendSigCommand_Err_Stacking_Already_Delegated", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.isStacking || + !stacker.isStackingSolo || + !stacker.hasDelegated || + !(stacker.amountLocked > 0) || + !(stacker.poolMembers.length === 0) || + !(totalPeriod <= 12) + ) return false; + + model.trackCommandRun( + "StackExtendSigCommand_Err_Stacking_Already_Delegated", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, ), @@ -1252,19 +1252,19 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - model.stackingMinimum > 0 && - stacker.isStacking && - stacker.isStackingSolo && - !stacker.hasDelegated && - stacker.amountLocked > 0 && - stacker.poolMembers.length === 0 && - !(totalPeriod <= 12) - ) { - model.trackCommandRun( - "StackExtendSigCommand_Err_Stacking_Invalid_Lock_Period", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.isStacking || + !stacker.isStackingSolo || + stacker.hasDelegated || + !(stacker.amountLocked > 0) || + !(stacker.poolMembers.length === 0) || + totalPeriod <= 12 + ) return false; + + model.trackCommandRun( + "StackExtendSigCommand_Err_Stacking_Invalid_Lock_Period", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_INVALID_LOCK_PERIOD, ), @@ -1301,19 +1301,19 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - model.stackingMinimum > 0 && - !stacker.isStacking && - !stacker.isStackingSolo && - !stacker.hasDelegated && - !(stacker.amountLocked > 0) && - stacker.poolMembers.length === 0 && - totalPeriod <= 12 - ) { - model.trackCommandRun( - "StackExtendSigCommand_Err_Stack_Extend_Not_Locked", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + stacker.isStacking || + stacker.isStackingSolo || + stacker.hasDelegated || + stacker.amountLocked > 0 || + !(stacker.poolMembers.length === 0) || + !(totalPeriod <= 12) + ) return false; + + model.trackCommandRun( + "StackExtendSigCommand_Err_Stack_Extend_Not_Locked", + ); + return true; }, POX_4_ERRORS.ERR_STACK_EXTEND_NOT_LOCKED, ), @@ -1350,19 +1350,19 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - model.stackingMinimum > 0 && - stacker.isStacking && - !stacker.isStackingSolo && - !stacker.hasDelegated && - stacker.amountLocked > 0 && - stacker.poolMembers.length === 0 && - totalPeriod <= 12 - ) { - model.trackCommandRun( - "StackExtendAuthCommand_Err_Stacking_Is_Delegated_1", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.isStacking || + stacker.isStackingSolo || + stacker.hasDelegated || + !(stacker.amountLocked > 0) || + !(stacker.poolMembers.length === 0) || + !(totalPeriod <= 12) + ) return false; + + model.trackCommandRun( + "StackExtendAuthCommand_Err_Stacking_Is_Delegated_1", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_IS_DELEGATED, ), @@ -1399,19 +1399,19 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - model.stackingMinimum > 0 && - stacker.isStacking && - !stacker.isStackingSolo && - !stacker.hasDelegated && - stacker.amountLocked > 0 && - !(stacker.poolMembers.length === 0) && - totalPeriod <= 12 - ) { - model.trackCommandRun( - "StackExtendAuthCommand_Err_Stacking_Is_Delegated_2", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.isStacking || + stacker.isStackingSolo || + stacker.hasDelegated || + !(stacker.amountLocked > 0) || + stacker.poolMembers.length === 0 || + !(totalPeriod <= 12) + ) return false; + + model.trackCommandRun( + "StackExtendAuthCommand_Err_Stacking_Is_Delegated_2", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_IS_DELEGATED, ), @@ -1448,19 +1448,19 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - model.stackingMinimum > 0 && - stacker.isStacking && - stacker.isStackingSolo && - stacker.hasDelegated && - stacker.amountLocked > 0 && - stacker.poolMembers.length === 0 && - totalPeriod <= 12 - ) { - model.trackCommandRun( - "StackExtendAuthCommand_Err_Stacking_Already_Delegated", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.isStacking || + !stacker.isStackingSolo || + !stacker.hasDelegated || + !(stacker.amountLocked > 0) || + !(stacker.poolMembers.length === 0) || + !(totalPeriod <= 12) + ) return false; + + model.trackCommandRun( + "StackExtendAuthCommand_Err_Stacking_Already_Delegated", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_ALREADY_DELEGATED, ), @@ -1497,19 +1497,19 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - model.stackingMinimum > 0 && - stacker.isStacking && - stacker.isStackingSolo && - !stacker.hasDelegated && - stacker.amountLocked > 0 && - stacker.poolMembers.length === 0 && - !(totalPeriod <= 12) - ) { - model.trackCommandRun( - "StackExtendAuthCommand_Err_Stacking_Invalid_Lock_Period", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + !stacker.isStacking || + !stacker.isStackingSolo || + stacker.hasDelegated || + !(stacker.amountLocked > 0) || + !(stacker.poolMembers.length === 0) || + totalPeriod <= 12 + ) return false; + + model.trackCommandRun( + "StackExtendAuthCommand_Err_Stacking_Invalid_Lock_Period", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_INVALID_LOCK_PERIOD, ), @@ -1546,19 +1546,19 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - model.stackingMinimum > 0 && - !stacker.isStacking && - !stacker.isStackingSolo && - !stacker.hasDelegated && - !(stacker.amountLocked > 0) && - stacker.poolMembers.length === 0 && - totalPeriod <= 12 - ) { - model.trackCommandRun( - "StackExtendAuthCommand_Err_Stack_Extend_Not_Locked", - ); - return true; - } else return false; + !(model.stackingMinimum > 0) || + stacker.isStacking || + stacker.isStackingSolo || + stacker.hasDelegated || + stacker.amountLocked > 0 || + !(stacker.poolMembers.length === 0) || + !(totalPeriod <= 12) + ) return false; + + model.trackCommandRun( + "StackExtendAuthCommand_Err_Stack_Extend_Not_Locked", + ); + return true; }, POX_4_ERRORS.ERR_STACK_EXTEND_NOT_LOCKED, ), @@ -1618,24 +1618,24 @@ export function ErrCommands( const stackedAmount = stackerWallet.amountLocked; if ( - stackerWallet.amountLocked > 0 && - stackerWallet.hasDelegated === true && - stackerWallet.isStacking === true && - stackerWallet.delegatedTo === this.operator.stxAddress && - !(stackerWallet.delegatedUntilBurnHt === undefined || - stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) && - stackerWallet.delegatedMaxAmount >= stackedAmount && - operatorWallet.poolMembers.includes(this.stacker.stxAddress) && - operatorWallet.lockedAddresses.includes( + !(stackerWallet.amountLocked > 0) || + !stackerWallet.hasDelegated || + !stackerWallet.isStacking || + !(stackerWallet.delegatedTo === this.operator.stxAddress) || + (stackerWallet.delegatedUntilBurnHt === undefined || + stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) || + !(stackerWallet.delegatedMaxAmount >= stackedAmount) || + !(operatorWallet.poolMembers.includes(this.stacker.stxAddress)) || + !operatorWallet.lockedAddresses.includes( this.stacker.stxAddress, - ) && - !(totalPeriod <= 12) - ) { - model.trackCommandRun( - "DelegateStackExtendCommand_Err_Stacking_Invalid_Lock_Period", - ); - return true; - } else return false; + ) || + totalPeriod <= 12 + ) return false; + + model.trackCommandRun( + "DelegateStackExtendCommand_Err_Stacking_Invalid_Lock_Period", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_INVALID_LOCK_PERIOD, ), @@ -1697,25 +1697,25 @@ export function ErrCommands( const stackedAmount = stackerWallet.amountLocked; if ( - stackerWallet.amountLocked > 0 && - !(stackerWallet.hasDelegated === true) && - stackerWallet.isStacking === true && - stackerWallet.isStackingSolo === true && - !(stackerWallet.delegatedTo === this.operator.stxAddress) && - !(stackerWallet.delegatedUntilBurnHt === undefined || - stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) && - !(stackerWallet.delegatedMaxAmount >= stackedAmount) && - !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && - !operatorWallet.lockedAddresses.includes( + !(stackerWallet.amountLocked > 0) || + stackerWallet.hasDelegated || + !stackerWallet.isStacking || + !stackerWallet.isStackingSolo || + stackerWallet.delegatedTo === this.operator.stxAddress || + (stackerWallet.delegatedUntilBurnHt === undefined || + stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) || + stackerWallet.delegatedMaxAmount >= stackedAmount || + operatorWallet.poolMembers.includes(this.stacker.stxAddress) || + operatorWallet.lockedAddresses.includes( this.stacker.stxAddress, - ) && - totalPeriod <= 12 - ) { - model.trackCommandRun( - "DelegateStackExtendCommand_Err_Stacking_Not_Delegated", - ); - return true; - } else return false; + ) || + !(totalPeriod <= 12) + ) return false; + + model.trackCommandRun( + "DelegateStackExtendCommand_Err_Stacking_Not_Delegated", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_NOT_DELEGATED, ), @@ -1774,24 +1774,24 @@ export function ErrCommands( FIRST_BURNCHAIN_BLOCK_HEIGHT; const stackedAmount = stackerWallet.amountLocked; if ( - !(stackerWallet.amountLocked > 0) && - stackerWallet.hasDelegated === true && - !(stackerWallet.isStacking === true) && - !(stackerWallet.delegatedTo === this.operator.stxAddress) && - (stackerWallet.delegatedUntilBurnHt === undefined || - stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) && - stackerWallet.delegatedMaxAmount >= stackedAmount && - !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && - !operatorWallet.lockedAddresses.includes( + stackerWallet.amountLocked > 0 || + !stackerWallet.hasDelegated || + stackerWallet.isStacking || + stackerWallet.delegatedTo === this.operator.stxAddress || + !(stackerWallet.delegatedUntilBurnHt === undefined || + stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) || + stackerWallet.delegatedMaxAmount >= stackedAmount || + operatorWallet.poolMembers.includes(this.stacker.stxAddress) || + operatorWallet.lockedAddresses.includes( this.stacker.stxAddress, - ) && - totalPeriod <= 12 - ) { - model.trackCommandRun( - "DelegateStackExtendCommand_Err_Stack_Extend_Not_Locked", - ); - return true; - } else return false; + ) || + !(totalPeriod <= 12) + ) return false; + + model.trackCommandRun( + "DelegateStackExtendCommand_Err_Stack_Extend_Not_Locked", + ); + return true; }, POX_4_ERRORS.ERR_STACK_EXTEND_NOT_LOCKED, ), @@ -1833,24 +1833,24 @@ export function ErrCommands( const stackedAmount = stackerWallet.amountLocked; if ( - stackerWallet.amountLocked > 0 && - !(stackerWallet.hasDelegated === true) && - stackerWallet.isStacking === true && - !(stackerWallet.delegatedTo === this.operator.stxAddress) && - !(stackerWallet.delegatedUntilBurnHt === undefined || - stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) && - !(stackerWallet.delegatedMaxAmount >= stackedAmount) && - !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && - operatorWallet.lockedAddresses.includes( + !(stackerWallet.amountLocked > 0) || + stackerWallet.hasDelegated || + !stackerWallet.isStacking || + stackerWallet.delegatedTo === this.operator.stxAddress || + (stackerWallet.delegatedUntilBurnHt === undefined || + stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) || + stackerWallet.delegatedMaxAmount >= stackedAmount || + operatorWallet.poolMembers.includes(this.stacker.stxAddress) || + !operatorWallet.lockedAddresses.includes( this.stacker.stxAddress, - ) && - totalPeriod <= 12 - ) { - model.trackCommandRun( - "DelegateStackExtendCommand_Err_Stacking_Permission_Denied", - ); - return true; - } else return false; + ) || + !(totalPeriod <= 12) + ) return false; + + model.trackCommandRun( + "DelegateStackExtendCommand_Err_Stacking_Permission_Denied", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_PERMISSION_DENIED, ), @@ -1893,25 +1893,27 @@ export function ErrCommands( )!; if ( - stackerWallet.amountLocked > 0 && - stackerWallet.hasDelegated === true && - stackerWallet.isStacking === true && - this.increaseBy > 0 && - operatorWallet.poolMembers.includes(this.stacker.stxAddress) && - !(stackerWallet.amountUnlocked >= this.increaseBy) && - !( + !(stackerWallet.amountLocked > 0) || + !(stackerWallet.hasDelegated) || + !stackerWallet.isStacking || + !(this.increaseBy > 0) || + !operatorWallet.poolMembers.includes(this.stacker.stxAddress) || + stackerWallet.amountUnlocked >= this.increaseBy || + ( stackerWallet.delegatedMaxAmount >= this.increaseBy + stackerWallet.amountLocked - ) && - operatorWallet.lockedAddresses.indexOf( - this.stacker.stxAddress, - ) > -1 - ) { - model.trackCommandRun( - "DelegateStackIncreaseCommand_Err_Stacking_Insufficient_Funds", - ); - return true; - } else return false; + ) || + !(operatorWallet.lockedAddresses.indexOf( + this.stacker.stxAddress, + ) > -1) || + !(stackerWallet.unlockHeight > + model.burnBlockHeight + REWARD_CYCLE_LENGTH) + ) return false; + + model.trackCommandRun( + "DelegateStackIncreaseCommand_Err_Stacking_Insufficient_Funds", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_INSUFFICIENT_FUNDS, ), @@ -1954,23 +1956,25 @@ export function ErrCommands( )!; if ( - stackerWallet.amountLocked > 0 && - stackerWallet.hasDelegated === true && - stackerWallet.isStacking === true && - !(this.increaseBy > 0) && - operatorWallet.poolMembers.includes(this.stacker.stxAddress) && - stackerWallet.amountUnlocked >= this.increaseBy && - stackerWallet.delegatedMaxAmount >= - this.increaseBy + stackerWallet.amountLocked && - operatorWallet.lockedAddresses.indexOf( - this.stacker.stxAddress, - ) > -1 - ) { - model.trackCommandRun( - "DelegateStackIncreaseCommand_Err_Stacking_Invalid_Amount", - ); - return true; - } else return false; + !(stackerWallet.amountLocked > 0) || + !stackerWallet.hasDelegated || + !stackerWallet.isStacking || + this.increaseBy > 0 || + !operatorWallet.poolMembers.includes(this.stacker.stxAddress) || + !(stackerWallet.amountUnlocked >= this.increaseBy) || + !(stackerWallet.delegatedMaxAmount >= + this.increaseBy + stackerWallet.amountLocked) || + !(operatorWallet.lockedAddresses.indexOf( + this.stacker.stxAddress, + ) > -1) || + !(stackerWallet.unlockHeight > + model.burnBlockHeight + REWARD_CYCLE_LENGTH) + ) return false; + + model.trackCommandRun( + "DelegateStackIncreaseCommand_Err_Stacking_Invalid_Amount", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_INVALID_AMOUNT, ), @@ -2013,28 +2017,30 @@ export function ErrCommands( )!; if ( - stackerWallet.amountLocked > 0 && - !(stackerWallet.hasDelegated === true) && - stackerWallet.isStacking === true && - stackerWallet.isStackingSolo === true && - this.increaseBy > 0 && - !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && - stackerWallet.amountUnlocked >= this.increaseBy && - !( + !(stackerWallet.amountLocked > 0) || + stackerWallet.hasDelegated || + !stackerWallet.isStacking || + !stackerWallet.isStackingSolo || + !(this.increaseBy > 0) || + operatorWallet.poolMembers.includes(this.stacker.stxAddress) || + !(stackerWallet.amountUnlocked >= this.increaseBy) || + ( stackerWallet.delegatedMaxAmount >= this.increaseBy + stackerWallet.amountLocked - ) && - !( + ) || + ( operatorWallet.lockedAddresses.indexOf( this.stacker.stxAddress, ) > -1 - ) - ) { - model.trackCommandRun( - "DelegateStackIncreaseCommand_Err_Stacking_Not_Delegated", - ); - return true; - } else return false; + ) || + !(stackerWallet.unlockHeight > + model.burnBlockHeight + REWARD_CYCLE_LENGTH) + ) return false; + + model.trackCommandRun( + "DelegateStackIncreaseCommand_Err_Stacking_Not_Delegated", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_NOT_DELEGATED, ), @@ -2077,25 +2083,27 @@ export function ErrCommands( )!; if ( - stackerWallet.amountLocked > 0 && - !(stackerWallet.hasDelegated === true) && - stackerWallet.isStacking === true && - this.increaseBy > 0 && - !operatorWallet.poolMembers.includes(this.stacker.stxAddress) && - stackerWallet.amountUnlocked >= this.increaseBy && - !( + !(stackerWallet.amountLocked > 0) || + stackerWallet.hasDelegated || + !stackerWallet.isStacking || + !(this.increaseBy > 0) || + operatorWallet.poolMembers.includes(this.stacker.stxAddress) || + !(stackerWallet.amountUnlocked >= this.increaseBy) || + ( stackerWallet.delegatedMaxAmount >= this.increaseBy + stackerWallet.amountLocked - ) && - operatorWallet.lockedAddresses.indexOf( - this.stacker.stxAddress, - ) > -1 - ) { - model.trackCommandRun( - "DelegateStackIncreaseCommand_Err_Stacking_Permission_Denied", - ); - return true; - } else return false; + ) || + !(operatorWallet.lockedAddresses.indexOf( + this.stacker.stxAddress, + ) > -1) || + !(stackerWallet.unlockHeight > + model.burnBlockHeight + REWARD_CYCLE_LENGTH) + ) return false; + + model.trackCommandRun( + "DelegateStackIncreaseCommand_Err_Stacking_Permission_Denied", + ); + return true; }, POX_4_ERRORS.ERR_STACKING_PERMISSION_DENIED, ), @@ -2115,18 +2123,18 @@ export function ErrCommands( this.callerToDisallow.stxAddress, )!; if ( - !stacker.allowedContractCallers.includes( + stacker.allowedContractCallers.includes( this.callerToDisallow.stxAddress, - ) && - !callerToDisallow.callerAllowedBy.includes( - this.stacker.stxAddress, - ) === true - ) { - model.trackCommandRun( - "DisallowContractCallerCommand_Err", - ); - return true; - } else return false; + ) || + callerToDisallow.callerAllowedBy.includes( + this.stacker.stxAddress, + ) + ) return false; + + model.trackCommandRun( + "DisallowContractCallerCommand_Err", + ); + return true; }, ), ), From d10d7600a69470220b736f01e9a4635e3248c6ce Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Thu, 20 Jun 2024 15:30:59 +0300 Subject: [PATCH 67/72] Update the way `firstRewardCycle` is calculated This commit: - simplifies the way firstRewardCycle is calculated, replacing the ternary operator with the max Math method. - standardizes the const name of the stacker retrieved from the model. --- .../tests/pox-4/err_Commands.ts | 218 +++++++++--------- 1 file changed, 109 insertions(+), 109 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 3d9738fefb..916c3cba09 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -1094,10 +1094,10 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; - const firstRewardCycle = - stacker.firstLockedRewardCycle < this.currentCycle - ? this.currentCycle - : stacker.firstLockedRewardCycle; + const firstRewardCycle = Math.max( + stacker.firstLockedRewardCycle, + this.currentCycle, + ); const firstExtendCycle = Math.floor( (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / REWARD_CYCLE_LENGTH, @@ -1143,10 +1143,10 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; - const firstRewardCycle = - stacker.firstLockedRewardCycle < this.currentCycle - ? this.currentCycle - : stacker.firstLockedRewardCycle; + const firstRewardCycle = Math.max( + stacker.firstLockedRewardCycle, + this.currentCycle, + ); const firstExtendCycle = Math.floor( (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / REWARD_CYCLE_LENGTH, @@ -1192,10 +1192,10 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; - const firstRewardCycle = - stacker.firstLockedRewardCycle < this.currentCycle - ? this.currentCycle - : stacker.firstLockedRewardCycle; + const firstRewardCycle = Math.max( + stacker.firstLockedRewardCycle, + this.currentCycle, + ); const firstExtendCycle = Math.floor( (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / REWARD_CYCLE_LENGTH, @@ -1241,10 +1241,10 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; - const firstRewardCycle = - stacker.firstLockedRewardCycle < this.currentCycle - ? this.currentCycle - : stacker.firstLockedRewardCycle; + const firstRewardCycle = Math.max( + stacker.firstLockedRewardCycle, + this.currentCycle, + ); const firstExtendCycle = Math.floor( (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / REWARD_CYCLE_LENGTH, @@ -1290,10 +1290,10 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; - const firstRewardCycle = - stacker.firstLockedRewardCycle < this.currentCycle - ? this.currentCycle - : stacker.firstLockedRewardCycle; + const firstRewardCycle = Math.max( + stacker.firstLockedRewardCycle, + this.currentCycle, + ); const firstExtendCycle = Math.floor( (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / REWARD_CYCLE_LENGTH, @@ -1339,10 +1339,10 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; - const firstRewardCycle = - stacker.firstLockedRewardCycle < this.currentCycle - ? this.currentCycle - : stacker.firstLockedRewardCycle; + const firstRewardCycle = Math.max( + stacker.firstLockedRewardCycle, + this.currentCycle, + ); const firstExtendCycle = Math.floor( (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / REWARD_CYCLE_LENGTH, @@ -1388,10 +1388,10 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; - const firstRewardCycle = - stacker.firstLockedRewardCycle < this.currentCycle - ? this.currentCycle - : stacker.firstLockedRewardCycle; + const firstRewardCycle = Math.max( + stacker.firstLockedRewardCycle, + this.currentCycle, + ); const firstExtendCycle = Math.floor( (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / REWARD_CYCLE_LENGTH, @@ -1437,10 +1437,10 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; - const firstRewardCycle = - stacker.firstLockedRewardCycle < this.currentCycle - ? this.currentCycle - : stacker.firstLockedRewardCycle; + const firstRewardCycle = Math.max( + stacker.firstLockedRewardCycle, + this.currentCycle, + ); const firstExtendCycle = Math.floor( (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / REWARD_CYCLE_LENGTH, @@ -1486,10 +1486,10 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; - const firstRewardCycle = - stacker.firstLockedRewardCycle < this.currentCycle - ? this.currentCycle - : stacker.firstLockedRewardCycle; + const firstRewardCycle = Math.max( + stacker.firstLockedRewardCycle, + this.currentCycle, + ); const firstExtendCycle = Math.floor( (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / REWARD_CYCLE_LENGTH, @@ -1535,10 +1535,10 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; - const firstRewardCycle = - stacker.firstLockedRewardCycle < this.currentCycle - ? this.currentCycle - : stacker.firstLockedRewardCycle; + const firstRewardCycle = Math.max( + stacker.firstLockedRewardCycle, + this.currentCycle, + ); const firstExtendCycle = Math.floor( (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / REWARD_CYCLE_LENGTH, @@ -1595,19 +1595,19 @@ export function ErrCommands( final.extendCount, final.currentCycle, function (this, model) { - const operatorWallet = model.stackers.get( + const operator = model.stackers.get( this.operator.stxAddress, )!; - const stackerWallet = model.stackers.get( + const stacker = model.stackers.get( this.stacker.stxAddress, )!; - const firstRewardCycle = - this.currentCycle > stackerWallet.firstLockedRewardCycle - ? this.currentCycle - : stackerWallet.firstLockedRewardCycle; + const firstRewardCycle = Math.max( + stacker.firstLockedRewardCycle, + this.currentCycle, + ); const firstExtendCycle = Math.floor( - (stackerWallet.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / REWARD_CYCLE_LENGTH, ); const lastExtendCycle = firstExtendCycle + this.extendCount - 1; @@ -1615,18 +1615,18 @@ export function ErrCommands( const newUnlockHeight = REWARD_CYCLE_LENGTH * (firstRewardCycle + totalPeriod - 1) + FIRST_BURNCHAIN_BLOCK_HEIGHT; - const stackedAmount = stackerWallet.amountLocked; + const stackedAmount = stacker.amountLocked; if ( - !(stackerWallet.amountLocked > 0) || - !stackerWallet.hasDelegated || - !stackerWallet.isStacking || - !(stackerWallet.delegatedTo === this.operator.stxAddress) || - (stackerWallet.delegatedUntilBurnHt === undefined || - stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) || - !(stackerWallet.delegatedMaxAmount >= stackedAmount) || - !(operatorWallet.poolMembers.includes(this.stacker.stxAddress)) || - !operatorWallet.lockedAddresses.includes( + !(stacker.amountLocked > 0) || + !stacker.hasDelegated || + !stacker.isStacking || + !(stacker.delegatedTo === this.operator.stxAddress) || + (stacker.delegatedUntilBurnHt === undefined || + stacker.delegatedUntilBurnHt >= newUnlockHeight) || + !(stacker.delegatedMaxAmount >= stackedAmount) || + !(operator.poolMembers.includes(this.stacker.stxAddress)) || + !operator.lockedAddresses.includes( this.stacker.stxAddress, ) || totalPeriod <= 12 @@ -1674,19 +1674,19 @@ export function ErrCommands( final.extendCount, final.currentCycle, function (this, model) { - const operatorWallet = model.stackers.get( + const operator = model.stackers.get( this.operator.stxAddress, )!; - const stackerWallet = model.stackers.get( + const stacker = model.stackers.get( this.stacker.stxAddress, )!; - const firstRewardCycle = - this.currentCycle > stackerWallet.firstLockedRewardCycle - ? this.currentCycle - : stackerWallet.firstLockedRewardCycle; + const firstRewardCycle = Math.max( + stacker.firstLockedRewardCycle, + this.currentCycle, + ); const firstExtendCycle = Math.floor( - (stackerWallet.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / REWARD_CYCLE_LENGTH, ); const lastExtendCycle = firstExtendCycle + this.extendCount - 1; @@ -1694,19 +1694,19 @@ export function ErrCommands( const newUnlockHeight = REWARD_CYCLE_LENGTH * (firstRewardCycle + totalPeriod - 1) + FIRST_BURNCHAIN_BLOCK_HEIGHT; - const stackedAmount = stackerWallet.amountLocked; + const stackedAmount = stacker.amountLocked; if ( - !(stackerWallet.amountLocked > 0) || - stackerWallet.hasDelegated || - !stackerWallet.isStacking || - !stackerWallet.isStackingSolo || - stackerWallet.delegatedTo === this.operator.stxAddress || - (stackerWallet.delegatedUntilBurnHt === undefined || - stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) || - stackerWallet.delegatedMaxAmount >= stackedAmount || - operatorWallet.poolMembers.includes(this.stacker.stxAddress) || - operatorWallet.lockedAddresses.includes( + !(stacker.amountLocked > 0) || + stacker.hasDelegated || + !stacker.isStacking || + !stacker.isStackingSolo || + stacker.delegatedTo === this.operator.stxAddress || + (stacker.delegatedUntilBurnHt === undefined || + stacker.delegatedUntilBurnHt >= newUnlockHeight) || + stacker.delegatedMaxAmount >= stackedAmount || + operator.poolMembers.includes(this.stacker.stxAddress) || + operator.lockedAddresses.includes( this.stacker.stxAddress, ) || !(totalPeriod <= 12) @@ -1752,19 +1752,19 @@ export function ErrCommands( final.extendCount, final.currentCycle, function (this, model) { - const operatorWallet = model.stackers.get( + const operator = model.stackers.get( this.operator.stxAddress, )!; - const stackerWallet = model.stackers.get( + const stacker = model.stackers.get( this.stacker.stxAddress, )!; - const firstRewardCycle = - this.currentCycle > stackerWallet.firstLockedRewardCycle - ? this.currentCycle - : stackerWallet.firstLockedRewardCycle; + const firstRewardCycle = Math.max( + stacker.firstLockedRewardCycle, + this.currentCycle, + ); const firstExtendCycle = Math.floor( - (stackerWallet.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / REWARD_CYCLE_LENGTH, ); const lastExtendCycle = firstExtendCycle + this.extendCount - 1; @@ -1772,17 +1772,17 @@ export function ErrCommands( const newUnlockHeight = REWARD_CYCLE_LENGTH * (firstRewardCycle + totalPeriod - 1) + FIRST_BURNCHAIN_BLOCK_HEIGHT; - const stackedAmount = stackerWallet.amountLocked; + const stackedAmount = stacker.amountLocked; if ( - stackerWallet.amountLocked > 0 || - !stackerWallet.hasDelegated || - stackerWallet.isStacking || - stackerWallet.delegatedTo === this.operator.stxAddress || - !(stackerWallet.delegatedUntilBurnHt === undefined || - stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) || - stackerWallet.delegatedMaxAmount >= stackedAmount || - operatorWallet.poolMembers.includes(this.stacker.stxAddress) || - operatorWallet.lockedAddresses.includes( + stacker.amountLocked > 0 || + !stacker.hasDelegated || + stacker.isStacking || + stacker.delegatedTo === this.operator.stxAddress || + !(stacker.delegatedUntilBurnHt === undefined || + stacker.delegatedUntilBurnHt >= newUnlockHeight) || + stacker.delegatedMaxAmount >= stackedAmount || + operator.poolMembers.includes(this.stacker.stxAddress) || + operator.lockedAddresses.includes( this.stacker.stxAddress, ) || !(totalPeriod <= 12) @@ -1810,19 +1810,19 @@ export function ErrCommands( final.extendCount, final.currentCycle, function (this, model) { - const operatorWallet = model.stackers.get( + const operator = model.stackers.get( this.operator.stxAddress, )!; - const stackerWallet = model.stackers.get( + const stacker = model.stackers.get( this.stacker.stxAddress, )!; - const firstRewardCycle = - this.currentCycle > stackerWallet.firstLockedRewardCycle - ? this.currentCycle - : stackerWallet.firstLockedRewardCycle; + const firstRewardCycle = Math.max( + stacker.firstLockedRewardCycle, + this.currentCycle, + ); const firstExtendCycle = Math.floor( - (stackerWallet.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / + (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / REWARD_CYCLE_LENGTH, ); const lastExtendCycle = firstExtendCycle + this.extendCount - 1; @@ -1830,18 +1830,18 @@ export function ErrCommands( const newUnlockHeight = REWARD_CYCLE_LENGTH * (firstRewardCycle + totalPeriod - 1) + FIRST_BURNCHAIN_BLOCK_HEIGHT; - const stackedAmount = stackerWallet.amountLocked; + const stackedAmount = stacker.amountLocked; if ( - !(stackerWallet.amountLocked > 0) || - stackerWallet.hasDelegated || - !stackerWallet.isStacking || - stackerWallet.delegatedTo === this.operator.stxAddress || - (stackerWallet.delegatedUntilBurnHt === undefined || - stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) || - stackerWallet.delegatedMaxAmount >= stackedAmount || - operatorWallet.poolMembers.includes(this.stacker.stxAddress) || - !operatorWallet.lockedAddresses.includes( + !(stacker.amountLocked > 0) || + stacker.hasDelegated || + !stacker.isStacking || + stacker.delegatedTo === this.operator.stxAddress || + (stacker.delegatedUntilBurnHt === undefined || + stacker.delegatedUntilBurnHt >= newUnlockHeight) || + stacker.delegatedMaxAmount >= stackedAmount || + operator.poolMembers.includes(this.stacker.stxAddress) || + !operator.lockedAddresses.includes( this.stacker.stxAddress, ) || !(totalPeriod <= 12) From 7c8c540192f6917a7a6925ed822eb000d74466c4 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Thu, 20 Jun 2024 16:29:32 +0300 Subject: [PATCH 68/72] Remove unused `Stub` import inside `err_Commands` --- .../tests/pox-4/err_Commands.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index 916c3cba09..cbf657fd96 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -1,12 +1,6 @@ import fc from "fast-check"; import { Simnet } from "@hirosystems/clarinet-sdk"; -import { - PoxCommand, - Stacker, - Stub, - StxAddress, - Wallet, -} from "./pox_CommandModel"; +import { PoxCommand, Stacker, StxAddress, Wallet } from "./pox_CommandModel"; import { currentCycle, currentCycleFirstBlock, From 934d55423a7d2ba6122b1cc05148ba07c3ff41f3 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Thu, 20 Jun 2024 16:35:03 +0300 Subject: [PATCH 69/72] Update the way `firstRewardCycle` is calculated all over the stateful testing environment --- .../tests/pox-4/pox_DelegateStackExtendCommand.ts | 8 ++++---- .../tests/pox-4/pox_StackExtendAuthCommand.ts | 7 ++++--- .../tests/pox-4/pox_StackExtendSigCommand.ts | 7 ++++--- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand.ts index 0151967932..2db12e5f40 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand.ts @@ -64,10 +64,10 @@ export class DelegateStackExtendCommand implements PoxCommand { const operatorWallet = model.stackers.get(this.operator.stxAddress)!; const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; - const firstRewardCycle = - this.currentCycle > stackerWallet.firstLockedRewardCycle - ? this.currentCycle - : stackerWallet.firstLockedRewardCycle; + const firstRewardCycle = Math.max( + stackerWallet.firstLockedRewardCycle, + this.currentCycle, + ); const firstExtendCycle = Math.floor( (stackerWallet.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / REWARD_CYCLE_LENGTH, diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand.ts index 13a835347f..ffcae01512 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand.ts @@ -51,9 +51,10 @@ export class StackExtendAuthCommand implements PoxCommand { // - The new lock period must be less than or equal to 12. const stacker = model.stackers.get(this.wallet.stxAddress)!; - const firstRewardCycle = stacker.firstLockedRewardCycle < this.currentCycle - ? this.currentCycle - : stacker.firstLockedRewardCycle; + const firstRewardCycle = Math.max( + stacker.firstLockedRewardCycle, + this.currentCycle, + ); const firstExtendCycle = Math.floor( (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / REWARD_CYCLE_LENGTH, diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand.ts index 5b23d021f6..4ac87cd853 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand.ts @@ -50,9 +50,10 @@ export class StackExtendSigCommand implements PoxCommand { // - The new lock period must be less than or equal to 12. const stacker = model.stackers.get(this.wallet.stxAddress)!; - const firstRewardCycle = stacker.firstLockedRewardCycle < this.currentCycle - ? this.currentCycle - : stacker.firstLockedRewardCycle; + const firstRewardCycle = Math.max( + stacker.firstLockedRewardCycle, + this.currentCycle, + ); const firstExtendCycle = Math.floor( (stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) / REWARD_CYCLE_LENGTH, From 3057aaae249b2e186f5bb7b66024b5c80f5bf356 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 21 Jun 2024 02:15:15 +0300 Subject: [PATCH 70/72] Add helper functions to use in the commands' check methods --- .../tests/pox-4/pox_CommandModel.ts | 255 ++++++++++++++++++ ...tackAggregationCommitIndexedAuthCommand.ts | 6 +- ...StackAggregationCommitIndexedSigCommand.ts | 8 +- .../pox_StackAggregationCommitSigCommand.ts | 10 +- .../pox_StackAggregationIncreaseCommand.ts | 9 +- .../tests/pox-4/pox_StackExtendAuthCommand.ts | 29 +- .../tests/pox-4/pox_StackExtendSigCommand.ts | 29 +- .../pox-4/pox_StackIncreaseAuthCommand.ts | 29 +- .../pox-4/pox_StackIncreaseSigCommand.ts | 29 +- .../tests/pox-4/pox_StackStxAuthCommand.ts | 7 +- .../tests/pox-4/pox_StackStxSigCommand.ts | 7 +- 11 files changed, 373 insertions(+), 45 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_CommandModel.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_CommandModel.ts index 653a1acbff..5b6cb95c27 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_CommandModel.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_CommandModel.ts @@ -255,3 +255,258 @@ export const logCommand = (...items: (string | undefined)[]) => { process.stdout.write(prettyPrint.join("")); }; + +/** + * Helper function that checks if the minimum uSTX threshold was set in the model. + * @param model - the model at a given moment in time. + * @returns boolean. + */ +export const isStackingMinimumCalculated = (model: Readonly): boolean => + model.stackingMinimum > 0; + +/** + * Helper function that checks if a stacker is currently stacking. + * @param stacker - the stacker's state at a given moment in time. + * @returns boolean. + */ +export const isStacking = (stacker: Stacker): boolean => + stacker.isStacking; + +/** + * Helper function that checks if a stacker has an active delegation. + * @param stacker - the stacker's state at a given moment in time. + * @returns boolean. + */ +export const isDelegating = (stacker: Stacker): boolean => + stacker.hasDelegated; + +/** + * Helper function that checks if the stacker is stacking using solo + * stacking methods. + * @param stacker - the stacker's state at a given moment in time. + * @returns boolean. + */ +export const isStackingSolo = (stacker: Stacker): boolean => + stacker.isStackingSolo; + +/** + * Helper function that checks if the stacker has locked uSTX. + * @param stacker - the stacker's state at a given moment in time. + * @returns boolean. + */ +export const isAmountLockedPositive = (stacker: Stacker): boolean => + stacker.amountLocked > 0; + +/** + * Helper function that checks if an operator has locked uSTX on + * behalf of at least one stacker. + * @param operator - the operator's state at a given moment in time. + * @returns boolean. + */ +export const hasLockedStackers = (operator: Stacker): boolean => + operator.lockedAddresses.length > 0; + +/** + * Helper function that checks if an operator has uSTX that was not + * yet committed. + * @param operator - the operator's state at a given moment in time. + * @returns boolean. + * + * NOTE: ATC is an abbreviation for "amount to commit". + */ +export const isATCPositive = (operator: Stacker): boolean => + operator.amountToCommit > 0; + +/** + * Helper function that checks if an operator's not committed uSTX + * amount is above the minimum stacking threshold. + * @param operator - the operator's state at a given moment in time. + * @param model - the model at a given moment in time. + * @returns boolean. + * + * NOTE: ATC is an abbreviation for "amount to commit". + */ export const isATCAboveThreshold = ( + operator: Stacker, + model: Readonly, +): boolean => operator.amountToCommit >= model.stackingMinimum; + +/** + * Helper function that checks if a uSTX amount fits within a stacker's + * delegation limit. + * @param stacker - the stacker's state at a given moment in time. + * @param amountToCheck - the uSTX amount to check. + * @returns boolean. + */ +export const isAmountWithinDelegationLimit = ( + stacker: Stacker, + amountToCheck: bigint | number, +): boolean => stacker.delegatedMaxAmount >= Number(amountToCheck); + +/** + * Helper function that checks if a given unlock burn height is within + * a stacker's delegation limit. + * @param stacker - the stacker's state at a given moment in time. + * @param unlockBurnHt - the verified unlock burn height. + * @returns boolean. + * + * NOTE: UBH is an abbreviation for "unlock burn height". + */ +export const isUBHWithinDelegationLimit = ( + stacker: Stacker, + unlockBurnHt: number, +): boolean => + stacker.delegatedUntilBurnHt === undefined || + unlockBurnHt <= stacker.delegatedUntilBurnHt; + +/** + * Helper function that checks if a given amount is within a stacker's + * unlocked uSTX balance. + * @param stacker - the stacker's state at a given moment in time. + * @param amountToCheck - the amount to check. + * @returns boolean. + */ +export const isAmountWithinBalance = ( + stacker: Stacker, + amountToCheck: bigint | number, +): boolean => stacker.ustxBalance >= Number(amountToCheck); + +/** + * Helper function that checks if a given amount is above the minimum + * stacking threshold. + * @param model - the model at a given moment in time. + * @param amountToCheck - the amount to check. + * @returns boolean. + */ +export const isAmountAboveThreshold = ( + model: Readonly, + amountToCheck: bigint | number, +): boolean => Number(amountToCheck) >= model.stackingMinimum; + +/** + * Helper function that checks if an operator has at least one pool + * participant. + * @param operator - the operator's state at a given moment in time. + * @returns boolean. + */ +export const hasPoolMembers = (operator: Stacker): boolean => + operator.poolMembers.length > 0; + +/** + * Helper function that checks if a stacker is a pool member of a + * given operator. + * @param operator - the operator's state at a given moment in time. + * @param stacker - the stacker's state at a given moment in time. + * @returns boolean + */ +export const isStackerInOperatorPool = ( + operator: Stacker, + stacker: Wallet, +): boolean => operator.poolMembers.includes(stacker.stxAddress); + +/** + * Helper function that checks if a given stacker's funds are locked + * by a given operator. + * @param stacker - the stacker's state at a given moment in time. + * @param operator - the operator's state at a given moment in time. + * @returns boolean. + */ +export const isStackerLockedByOperator = ( + operator: Stacker, + stacker: Wallet, +): boolean => + operator.lockedAddresses.includes( + stacker.stxAddress, + ); + +/** + * Helper function that checks if a given stacker's unlock height is + * within the current reward cycle. + * @param stacker - the stacker's state at a given moment in time. + * @param model - the model at a given moment in time. + * @returns boolean. + * + * NOTE: RC is an abbreviation for "reward cycle". + */ +export const isUnlockedWithinCurrentRC = ( + stackerWallet: Stacker, + model: Readonly, +): boolean => (stackerWallet.unlockHeight <= + model.burnBlockHeight + REWARD_CYCLE_LENGTH); + +/** + * Helper function that checks if the increase amount is within a given + * stacker's unlocked balance. + * @param stacker - the stacker's state at a given moment in time. + * @param increaseBy - the increase amount to check. + * @returns boolean. + */ +export const isIncreaseByWithinUnlockedBalance = ( + stacker: Stacker, + increaseBy: number, +): boolean => increaseBy <= stacker.amountUnlocked; + +/** + * Helper function that checks if the increase amount is greater than zero. + * @param increaseBy - the increase amount to check. + * @returns boolean. + */ +export const isIncreaseByGTZero = (increaseBy: number): boolean => + increaseBy >= 1; + +/** + * Helper function that checks if the increase amount does not exceed the + * PoX-4 maximum lock period. + * @param period - the period to check. + * @returns boolean. + */ +export const isPeriodWithinMax = (period: number) => period <= 12; + +/** + * Helper function that checks if a given stacker is currently delegating + * to a given operator. + * @param stacker - the stacker's state at a given moment in time. + * @param operator - the operator's state at a given moment in time. + * @returns boolean. + */ +export const isStackerDelegatingToOperator = ( + stacker: Stacker, + operator: Wallet, +): boolean => stacker.delegatedTo === operator.stxAddress; + +/** + * Helper function that checks if a given increase amount is greater than + * zero. + * @param increaseAmount - the increase amount to check + * @returns boolean. + */ +export const isIncreaseAmountGTZero = (increaseAmount: number): boolean => + increaseAmount > 0; + +/** + * Helper function that checks if a given stacker's has issued an allowance + * to a potential contract caller. + * @param stacker - the stacker's state at a given moment in time. + * @param potentialAllowedStacker - the potential contract caller's state. + * @returns boolean. + */ +export const isAllowedContractCaller = ( + stacker: Stacker, + potentialAllowedStacker: Wallet, +): boolean => + stacker.allowedContractCallers.includes( + potentialAllowedStacker.stxAddress, + ); + +/** + * Helper function that checks if a given contract caller has been allowed by + * a given stacker. + * @param stacker - the stacker's state at a given moment in time. + * @param caller - the contract caller's state. + * @returns boolean. + */ +export const isCallerAllowedByStacker = ( + stacker: Wallet, + caller: Stacker, +): boolean => caller.callerAllowedBy.includes(stacker.stxAddress); + +export const isPositive = (value: number): boolean => value >= 0; \ No newline at end of file diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand.ts index 926c923135..ba9679e639 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedAuthCommand.ts @@ -1,4 +1,6 @@ import { + hasLockedStackers, + isATCAboveThreshold, logCommand, PoxCommand, Real, @@ -55,8 +57,8 @@ export class StackAggregationCommitIndexedAuthCommand implements PoxCommand { const operator = model.stackers.get(this.operator.stxAddress)!; return ( - operator.lockedAddresses.length > 0 && - operator.amountToCommit >= model.stackingMinimum + hasLockedStackers(operator) && + isATCAboveThreshold(operator, model) ); } diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand.ts index 712706d156..beb91ea87f 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitIndexedSigCommand.ts @@ -1,4 +1,6 @@ import { + hasLockedStackers, + isATCAboveThreshold, logCommand, PoxCommand, Real, @@ -33,7 +35,7 @@ export class StackAggregationCommitIndexedSigCommand implements PoxCommand { readonly authId: number; /** - * Constructs a `StackAggregationCommitIndexedSigCommand` to commit partially + * Constructs a `StackAggregationCommitIndexedSigCommand` to commit partially * locked uSTX. * * @param operator - Represents the `Operator`'s wallet. @@ -55,8 +57,8 @@ export class StackAggregationCommitIndexedSigCommand implements PoxCommand { const operator = model.stackers.get(this.operator.stxAddress)!; return ( - operator.lockedAddresses.length > 0 && - operator.amountToCommit >= model.stackingMinimum + hasLockedStackers(operator) && + isATCAboveThreshold(operator, model) ); } diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand.ts index cda1d9cd96..9e6bfd1bde 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitSigCommand.ts @@ -1,4 +1,6 @@ import { + hasLockedStackers, + isATCAboveThreshold, logCommand, PoxCommand, Real, @@ -31,7 +33,7 @@ export class StackAggregationCommitSigCommand implements PoxCommand { readonly authId: number; /** - * Constructs a `StackAggregationCommitSigCommand` to commit partially + * Constructs a `StackAggregationCommitSigCommand` to commit partially * locked uSTX. * * @param operator - Represents the `Operator`'s wallet. @@ -52,8 +54,10 @@ export class StackAggregationCommitSigCommand implements PoxCommand { // stackers has to be greater than the uSTX threshold. const operator = model.stackers.get(this.operator.stxAddress)!; - return operator.lockedAddresses.length > 0 && - operator.amountToCommit >= model.stackingMinimum; + return ( + hasLockedStackers(operator) && + isATCAboveThreshold(operator, model) + ); } run(model: Stub, real: Real): void { diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationIncreaseCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationIncreaseCommand.ts index 22ae0a0bea..80e1950abb 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationIncreaseCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationIncreaseCommand.ts @@ -1,4 +1,7 @@ import { + hasLockedStackers, + isATCPositive, + isPositive, logCommand, PoxCommand, Real, @@ -56,9 +59,9 @@ export class StackAggregationIncreaseCommand implements PoxCommand { // - The Reward Cycle Index must be positive. const operator = model.stackers.get(this.operator.stxAddress)!; return ( - operator.lockedAddresses.length > 0 && - this.rewardCycleIndex >= 0 && - operator.amountToCommit > 0 + hasLockedStackers(operator) && + isPositive(this.rewardCycleIndex) && + isATCPositive(operator) ); } diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand.ts index ffcae01512..203bef86b9 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendAuthCommand.ts @@ -1,5 +1,18 @@ import { poxAddressToTuple } from "@stacks/stacking"; -import { logCommand, PoxCommand, Real, Stub, Wallet } from "./pox_CommandModel"; +import { + hasPoolMembers, + isAmountLockedPositive, + isPeriodWithinMax, + isDelegating, + isStacking, + isStackingSolo, + isStackingMinimumCalculated, + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel"; import { currentCycle, FIRST_BURNCHAIN_BLOCK_HEIGHT, @@ -63,13 +76,13 @@ export class StackExtendAuthCommand implements PoxCommand { const totalPeriod = lastExtendCycle - firstRewardCycle + 1; return ( - model.stackingMinimum > 0 && - stacker.isStacking && - stacker.isStackingSolo && - !stacker.hasDelegated && - stacker.amountLocked > 0 && - stacker.poolMembers.length === 0 && - totalPeriod <= 12 + isStackingMinimumCalculated(model) && + isStacking(stacker) && + isStackingSolo(stacker) && + !isDelegating(stacker) && + isAmountLockedPositive(stacker) && + !hasPoolMembers(stacker) && + isPeriodWithinMax(totalPeriod) ); } diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand.ts index 4ac87cd853..b937b61207 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackExtendSigCommand.ts @@ -1,5 +1,18 @@ import { Pox4SignatureTopic, poxAddressToTuple } from "@stacks/stacking"; -import { logCommand, PoxCommand, Real, Stub, Wallet } from "./pox_CommandModel"; +import { + hasPoolMembers, + isAmountLockedPositive, + isPeriodWithinMax, + isDelegating, + isStacking, + isStackingSolo, + isStackingMinimumCalculated, + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel"; import { currentCycle, FIRST_BURNCHAIN_BLOCK_HEIGHT, @@ -62,13 +75,13 @@ export class StackExtendSigCommand implements PoxCommand { const totalPeriod = lastExtendCycle - firstRewardCycle + 1; return ( - model.stackingMinimum > 0 && - stacker.isStacking && - stacker.isStackingSolo && - !stacker.hasDelegated && - stacker.amountLocked > 0 && - stacker.poolMembers.length === 0 && - totalPeriod <= 12 + isStackingMinimumCalculated(model) && + isStacking(stacker) && + isStackingSolo(stacker) && + !isDelegating(stacker) && + isAmountLockedPositive(stacker) && + !hasPoolMembers(stacker) && + isPeriodWithinMax(totalPeriod) ); } diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand.ts index cd802b1b88..d819a82215 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseAuthCommand.ts @@ -1,5 +1,18 @@ import { Pox4SignatureTopic, poxAddressToTuple } from "@stacks/stacking"; -import { logCommand, PoxCommand, Real, Stub, Wallet } from "./pox_CommandModel"; +import { + isAmountLockedPositive, + isIncreaseAmountGTZero, + isIncreaseByWithinUnlockedBalance, + isDelegating, + isStacking, + isStackingSolo, + isStackingMinimumCalculated, + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel"; import { currentCycle } from "./pox_Commands"; import { Cl, cvToJSON } from "@stacks/transactions"; import { expect } from "vitest"; @@ -47,13 +60,13 @@ export class StackIncreaseAuthCommand implements PoxCommand { const stacker = model.stackers.get(this.wallet.stxAddress)!; return ( - model.stackingMinimum > 0 && - stacker.isStacking && - stacker.isStackingSolo && - !stacker.hasDelegated && - stacker.amountLocked > 0 && - this.increaseBy <= stacker.amountUnlocked && - this.increaseBy >= 1 + isStackingMinimumCalculated(model) && + isStacking(stacker) && + isStackingSolo(stacker) && + !isDelegating(stacker) && + isAmountLockedPositive(stacker) && + isIncreaseByWithinUnlockedBalance(stacker, this.increaseBy) && + isIncreaseAmountGTZero(this.increaseBy) ); } diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand.ts index 1bd50691b6..899be8900e 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackIncreaseSigCommand.ts @@ -1,5 +1,18 @@ import { Pox4SignatureTopic } from "@stacks/stacking"; -import { logCommand, PoxCommand, Real, Stub, Wallet } from "./pox_CommandModel"; +import { + isAmountLockedPositive, + isIncreaseAmountGTZero, + isIncreaseByWithinUnlockedBalance, + isDelegating, + isStacking, + isStackingSolo, + isStackingMinimumCalculated, + logCommand, + PoxCommand, + Real, + Stub, + Wallet, +} from "./pox_CommandModel"; import { Cl, ClarityType, @@ -53,13 +66,13 @@ export class StackIncreaseSigCommand implements PoxCommand { const stacker = model.stackers.get(this.wallet.stxAddress)!; return ( - model.stackingMinimum > 0 && - stacker.isStacking && - stacker.isStackingSolo && - !stacker.hasDelegated && - stacker.amountLocked > 0 && - this.increaseBy <= stacker.amountUnlocked && - this.increaseBy >= 1 + isStackingMinimumCalculated(model) && + isStacking(stacker) && + isStackingSolo(stacker) && + !isDelegating(stacker) && + isAmountLockedPositive(stacker) && + isIncreaseByWithinUnlockedBalance(stacker, this.increaseBy) && + isIncreaseAmountGTZero(this.increaseBy) ); } diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand.ts index 53f34ca0bb..de3bc96964 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxAuthCommand.ts @@ -1,4 +1,7 @@ import { + isDelegating, + isStacking, + isStackingMinimumCalculated, logCommand, PoxCommand, Real, @@ -67,7 +70,9 @@ export class StackStxAuthCommand implements PoxCommand { const stacker = model.stackers.get(this.wallet.stxAddress)!; return ( - model.stackingMinimum > 0 && !stacker.isStacking && !stacker.hasDelegated + isStackingMinimumCalculated(model) && + !isStacking(stacker) && + !isDelegating(stacker) ); } diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand.ts index 100d84a6e0..d397297037 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackStxSigCommand.ts @@ -1,4 +1,7 @@ import { + isDelegating, + isStacking, + isStackingMinimumCalculated, logCommand, PoxCommand, Real, @@ -66,7 +69,9 @@ export class StackStxSigCommand implements PoxCommand { const stacker = model.stackers.get(this.wallet.stxAddress)!; return ( - model.stackingMinimum > 0 && !stacker.isStacking && !stacker.hasDelegated + isStackingMinimumCalculated(model) && + !isStacking(stacker) && + !isDelegating(stacker) ); } From dbaf000e9008cf1aa78e1a148a4b5021fbb6287d Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 21 Jun 2024 02:16:02 +0300 Subject: [PATCH 71/72] Update `err_Commands` check functions to use suggestive helpers --- .../tests/pox-4/err_Commands.ts | 627 +++++++++--------- 1 file changed, 318 insertions(+), 309 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts index cbf657fd96..7e7a4f0e95 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/err_Commands.ts @@ -1,6 +1,34 @@ import fc from "fast-check"; import { Simnet } from "@hirosystems/clarinet-sdk"; -import { PoxCommand, Stacker, StxAddress, Wallet } from "./pox_CommandModel"; +import { + hasLockedStackers, + hasPoolMembers, + isAllowedContractCaller, + isAmountAboveThreshold, + isAmountLockedPositive, + isAmountWithinBalance, + isAmountWithinDelegationLimit, + isATCAboveThreshold, + isATCPositive, + isCallerAllowedByStacker, + isIncreaseByGTZero, + isIncreaseByWithinUnlockedBalance, + isPeriodWithinMax, + isStackerDelegatingToOperator, + isDelegating, + isStacking, + isStackingSolo, + isStackingMinimumCalculated, + isUBHWithinDelegationLimit, + isUnlockedWithinCurrentRC, + isStackerInOperatorPool, + isStackerLockedByOperator, + PoxCommand, + Stacker, + StxAddress, + Wallet, + isPositive, +} from "./pox_CommandModel"; import { currentCycle, currentCycleFirstBlock, @@ -70,9 +98,9 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - !(model.stackingMinimum > 0) || - !stacker.isStacking || - stacker.hasDelegated + !isStackingMinimumCalculated(model) || + !isStacking(stacker) || + isDelegating(stacker) ) return false; model.trackCommandRun( @@ -105,9 +133,9 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - !(model.stackingMinimum > 0) || - !stacker.isStacking || - !stacker.hasDelegated + !isStackingMinimumCalculated(model) || + !isStacking(stacker) || + !isDelegating(stacker) ) return false; model.trackCommandRun( @@ -140,9 +168,9 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - !(model.stackingMinimum > 0) || - stacker.isStacking || - !stacker.hasDelegated + !isStackingMinimumCalculated(model) || + isStacking(stacker) || + !isDelegating(stacker) ) return false; model.trackCommandRun( @@ -175,9 +203,9 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - !(model.stackingMinimum > 0) || - !stacker.isStacking || - stacker.hasDelegated + !(isStackingMinimumCalculated(model)) || + !isStacking(stacker) || + isDelegating(stacker) ) return false; model.trackCommandRun( @@ -210,9 +238,9 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - !(model.stackingMinimum > 0) || - !stacker.isStacking || - !stacker.hasDelegated + !isStackingMinimumCalculated(model) || + !isStacking(stacker) || + !isDelegating(stacker) ) return false; model.trackCommandRun( @@ -245,9 +273,9 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - !(model.stackingMinimum > 0) || - stacker.isStacking || - !stacker.hasDelegated + !isStackingMinimumCalculated(model) || + isStacking(stacker) || + !isDelegating(stacker) ) return false; model.trackCommandRun( @@ -271,8 +299,8 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - !(model.stackingMinimum > 0) || - stacker.hasDelegated + !isStackingMinimumCalculated(model) || + isDelegating(stacker) ) return false; model.trackCommandRun( @@ -306,8 +334,8 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - !(model.stackingMinimum > 0) || - !stacker.hasDelegated + !isStackingMinimumCalculated(model) || + !isDelegating(stacker) ) return false; model.trackCommandRun( @@ -331,9 +359,9 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - !(operator.lockedAddresses.length > 0) || - operator.amountToCommit >= model.stackingMinimum || - !(operator.amountToCommit > 0) + !hasLockedStackers(operator) || + isATCAboveThreshold(operator, model) || + !isATCPositive(operator) ) return false; model.trackCommandRun( @@ -357,9 +385,9 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - !(operator.lockedAddresses.length > 0) || - (operator.amountToCommit >= model.stackingMinimum) || - operator.amountToCommit !== 0 + !hasLockedStackers(operator) || + isATCAboveThreshold(operator, model) || + isATCPositive(operator) ) return false; model.trackCommandRun( @@ -383,8 +411,8 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - operator.lockedAddresses.length > 0 || - operator.amountToCommit >= model.stackingMinimum + hasLockedStackers(operator) || + isATCAboveThreshold(operator, model) ) return false; model.trackCommandRun( @@ -408,9 +436,9 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - !(operator.lockedAddresses.length > 0) || - operator.amountToCommit >= model.stackingMinimum || - !(operator.amountToCommit > 0) + !hasLockedStackers(operator) || + isATCAboveThreshold(operator, model) || + !isATCPositive(operator) ) return false; model.trackCommandRun( @@ -434,9 +462,9 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - !(operator.lockedAddresses.length > 0) || - operator.amountToCommit >= model.stackingMinimum || - !(operator.amountToCommit === 0) + !hasLockedStackers(operator) || + isATCAboveThreshold(operator, model) || + isATCPositive(operator) ) return false; model.trackCommandRun( @@ -460,8 +488,8 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - operator.lockedAddresses.length > 0 || - operator.amountToCommit >= model.stackingMinimum + hasLockedStackers(operator) || + isATCAboveThreshold(operator, model) ) return false; model.trackCommandRun( @@ -485,9 +513,9 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - !(operator.lockedAddresses.length > 0) || - operator.amountToCommit >= model.stackingMinimum || - !(operator.amountToCommit > 0) + !hasLockedStackers(operator) || + isATCAboveThreshold(operator, model) || + !isATCPositive(operator) ) return false; model.trackCommandRun( @@ -511,9 +539,9 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - !(operator.lockedAddresses.length > 0) || - operator.amountToCommit >= model.stackingMinimum || - operator.amountToCommit > 0 + !hasLockedStackers(operator) || + isATCAboveThreshold(operator, model) || + isATCPositive(operator) ) return false; model.trackCommandRun( @@ -537,8 +565,8 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - operator.lockedAddresses.length > 0 || - operator.amountToCommit >= model.stackingMinimum + hasLockedStackers(operator) || + isATCAboveThreshold(operator, model) ) return false; model.trackCommandRun( @@ -562,9 +590,9 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - !(operator.lockedAddresses.length > 0) || - operator.amountToCommit >= model.stackingMinimum || - operator.amountToCommit > 0 + !hasLockedStackers(operator) || + isATCAboveThreshold(operator, model) || + isATCPositive(operator) ) return false; model.trackCommandRun( @@ -588,8 +616,8 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - operator.lockedAddresses.length > 0 || - operator.amountToCommit >= model.stackingMinimum + hasLockedStackers(operator) || + isATCAboveThreshold(operator, model) ) return false; model.trackCommandRun( @@ -613,9 +641,9 @@ export function ErrCommands( const operator = model.stackers.get(this.operator.stxAddress)!; if ( - !(operator.lockedAddresses.length > 0) || - operator.amountToCommit >= model.stackingMinimum || - !(operator.amountToCommit > 0) + !hasLockedStackers(operator) || + isATCAboveThreshold(operator, model) || + !isATCPositive(operator) ) return false; model.trackCommandRun( @@ -652,9 +680,9 @@ export function ErrCommands( function (this, model) { const operator = model.stackers.get(this.operator.stxAddress)!; if ( - !(operator.lockedAddresses.length > 0) || - !(this.rewardCycleIndex >= 0) || - operator.amountToCommit > 0 + !hasLockedStackers(operator) || + !isPositive(this.rewardCycleIndex) || + isATCPositive(operator) ) return false; model.trackCommandRun( @@ -717,15 +745,14 @@ export function ErrCommands( const operatorWallet = model.stackers.get(this.operator.stxAddress)!; const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; if ( - !(model.stackingMinimum > 0) || - stackerWallet.isStacking || - !(stackerWallet.hasDelegated) || - stackerWallet.delegatedMaxAmount >= Number(this.amountUstx) || - !(Number(this.amountUstx) <= stackerWallet.ustxBalance) || - !(Number(this.amountUstx) >= model.stackingMinimum) || - !(operatorWallet.poolMembers.includes(this.stacker.stxAddress)) || - !(stackerWallet.delegatedUntilBurnHt === undefined || - this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt) + !isStackingMinimumCalculated(model) || + isStacking(stackerWallet) || + !isDelegating(stackerWallet) || + isAmountWithinDelegationLimit(stackerWallet, this.amountUstx) || + !isAmountWithinBalance(stackerWallet, this.amountUstx) || + !isAmountAboveThreshold(model, this.amountUstx) || + !isStackerInOperatorPool(operatorWallet, this.stacker) || + !isUBHWithinDelegationLimit(stackerWallet, this.unlockBurnHt) ) return false; model.trackCommandRun( @@ -791,15 +818,14 @@ export function ErrCommands( const operatorWallet = model.stackers.get(this.operator.stxAddress)!; const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; if ( - !(model.stackingMinimum > 0) || - stackerWallet.isStacking || - !stackerWallet.hasDelegated || - !(stackerWallet.delegatedMaxAmount >= Number(this.amountUstx)) || - !(Number(this.amountUstx) <= stackerWallet.ustxBalance) || - !(Number(this.amountUstx) >= model.stackingMinimum) || - operatorWallet.poolMembers.includes(this.stacker.stxAddress) || - !(stackerWallet.delegatedUntilBurnHt === undefined || - this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt) + !isStackingMinimumCalculated(model) || + isStacking(stackerWallet) || + !isDelegating(stackerWallet) || + !isAmountWithinDelegationLimit(stackerWallet, this.amountUstx) || + !isAmountWithinBalance(stackerWallet, this.amountUstx) || + !isAmountAboveThreshold(model, this.amountUstx) || + isStackerInOperatorPool(operatorWallet, this.stacker) || + !isUBHWithinDelegationLimit(stackerWallet, this.unlockBurnHt) ) return false; model.trackCommandRun( @@ -862,15 +888,14 @@ export function ErrCommands( const operatorWallet = model.stackers.get(this.operator.stxAddress)!; const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; if ( - !(model.stackingMinimum > 0) || - stackerWallet.isStacking || - stackerWallet.hasDelegated || - stackerWallet.delegatedMaxAmount >= Number(this.amountUstx) || - !(Number(this.amountUstx) <= stackerWallet.ustxBalance) || - !(Number(this.amountUstx) >= model.stackingMinimum) || - operatorWallet.poolMembers.includes(this.stacker.stxAddress) && - (stackerWallet.delegatedUntilBurnHt === undefined || - this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt) + !isStackingMinimumCalculated(model) || + isStacking(stackerWallet) || + isDelegating(stackerWallet) || + isAmountWithinDelegationLimit(stackerWallet, this.amountUstx) || + !isAmountWithinBalance(stackerWallet, this.amountUstx) || + !isAmountAboveThreshold(model, this.amountUstx) || + isStackerInOperatorPool(operatorWallet, this.stacker) || + isUBHWithinDelegationLimit(stackerWallet, this.unlockBurnHt) ) return false; model.trackCommandRun( @@ -895,13 +920,13 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - !(model.stackingMinimum > 0) || - !stacker.isStacking || - stacker.isStackingSolo || - stacker.hasDelegated || - !(stacker.amountLocked > 0) || - !(this.increaseBy <= stacker.amountUnlocked) || - !(this.increaseBy >= 1) + !isStackingMinimumCalculated(model) || + !isStacking(stacker) || + isStackingSolo(stacker) || + isDelegating(stacker) || + !isAmountLockedPositive(stacker) || + !isIncreaseByWithinUnlockedBalance(stacker, this.increaseBy) || + !isIncreaseByGTZero(this.increaseBy) ) return false; model.trackCommandRun( @@ -926,13 +951,13 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - !(model.stackingMinimum > 0) || - !stacker.isStacking || - !stacker.isStackingSolo || - stacker.hasDelegated || - !(stacker.amountLocked > 0) || - this.increaseBy <= stacker.amountUnlocked || - !(this.increaseBy >= 1) + !isStackingMinimumCalculated(model) || + !isStacking(stacker) || + !isStackingSolo(stacker) || + isDelegating(stacker) || + !isAmountLockedPositive(stacker) || + isIncreaseByWithinUnlockedBalance(stacker, this.increaseBy) || + !isIncreaseByGTZero(this.increaseBy) ) return false; model.trackCommandRun( @@ -957,13 +982,13 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - !(model.stackingMinimum > 0) || - !stacker.isStacking || - !stacker.isStackingSolo || - stacker.hasDelegated || - !(stacker.amountLocked > 0) || - !(this.increaseBy <= stacker.amountUnlocked) || - this.increaseBy >= 1 + !isStackingMinimumCalculated(model) || + !isStacking(stacker) || + !isStackingSolo(stacker) || + isDelegating(stacker) || + !isAmountLockedPositive(stacker) || + !isIncreaseByWithinUnlockedBalance(stacker, this.increaseBy) || + isIncreaseByGTZero(this.increaseBy) ) return false; model.trackCommandRun( @@ -988,13 +1013,13 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - !(model.stackingMinimum > 0) || - !stacker.isStacking || - stacker.isStackingSolo || - stacker.hasDelegated || - !(stacker.amountLocked > 0) || - !(this.increaseBy <= stacker.amountUnlocked) || - !(this.increaseBy >= 1) + !isStackingMinimumCalculated(model) || + !isStacking(stacker) || + isStackingSolo(stacker) || + isDelegating(stacker) || + !isAmountLockedPositive(stacker) || + !isIncreaseByWithinUnlockedBalance(stacker, this.increaseBy) || + !isIncreaseByGTZero(this.increaseBy) ) return false; model.trackCommandRun( @@ -1019,13 +1044,13 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - !(model.stackingMinimum > 0) || - !stacker.isStacking || - !stacker.isStackingSolo || - stacker.hasDelegated || - !(stacker.amountLocked > 0) || - this.increaseBy <= stacker.amountUnlocked || - !(this.increaseBy >= 1) + !isStackingMinimumCalculated(model) || + !isStacking(stacker) || + !isStackingSolo(stacker) || + isDelegating(stacker) || + !isAmountLockedPositive(stacker) || + isIncreaseByWithinUnlockedBalance(stacker, this.increaseBy) || + !isIncreaseByGTZero(this.increaseBy) ) return false; model.trackCommandRun( @@ -1050,13 +1075,13 @@ export function ErrCommands( function (this, model) { const stacker = model.stackers.get(this.wallet.stxAddress)!; if ( - !(model.stackingMinimum > 0) || - !stacker.isStacking || - !stacker.isStackingSolo || - stacker.hasDelegated || - !(stacker.amountLocked > 0) || - !(this.increaseBy <= stacker.amountUnlocked) || - this.increaseBy >= 1 + !isStackingMinimumCalculated(model) || + !isStacking(stacker) || + !isStackingSolo(stacker) || + isDelegating(stacker) || + !isAmountLockedPositive(stacker) || + !isIncreaseByWithinUnlockedBalance(stacker, this.increaseBy) || + isIncreaseByGTZero(this.increaseBy) ) return false; model.trackCommandRun( @@ -1099,13 +1124,13 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - !(model.stackingMinimum > 0) || - !stacker.isStacking || - stacker.isStackingSolo || - stacker.hasDelegated || - !(stacker.amountLocked > 0) || - !(stacker.poolMembers.length === 0) || - !(totalPeriod <= 12) + !isStackingMinimumCalculated(model) || + !isStacking(stacker) || + isStackingSolo(stacker) || + isDelegating(stacker) || + !isAmountLockedPositive(stacker) || + hasLockedStackers(stacker) || + !isPeriodWithinMax(totalPeriod) ) return false; model.trackCommandRun( @@ -1148,13 +1173,13 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - !(model.stackingMinimum > 0) || - !stacker.isStacking || - stacker.isStackingSolo || - stacker.hasDelegated || - !(stacker.amountLocked > 0) || - stacker.poolMembers.length === 0 || - !(totalPeriod <= 12) + !isStackingMinimumCalculated(model) || + !isStacking(stacker) || + isStackingSolo(stacker) || + isDelegating(stacker) || + !isAmountLockedPositive(stacker) || + !hasPoolMembers(stacker) || + !isPeriodWithinMax(totalPeriod) ) return false; model.trackCommandRun( @@ -1197,13 +1222,13 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - !(model.stackingMinimum > 0) || - !stacker.isStacking || - !stacker.isStackingSolo || - !stacker.hasDelegated || - !(stacker.amountLocked > 0) || - !(stacker.poolMembers.length === 0) || - !(totalPeriod <= 12) + !isStackingMinimumCalculated(model) || + !isStacking(stacker) || + !isStackingSolo(stacker) || + !isDelegating(stacker) || + !isAmountLockedPositive(stacker) || + hasLockedStackers(stacker) || + !isPeriodWithinMax(totalPeriod) ) return false; model.trackCommandRun( @@ -1246,13 +1271,13 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - !(model.stackingMinimum > 0) || - !stacker.isStacking || - !stacker.isStackingSolo || - stacker.hasDelegated || - !(stacker.amountLocked > 0) || - !(stacker.poolMembers.length === 0) || - totalPeriod <= 12 + !isStackingMinimumCalculated(model) || + !isStacking(stacker) || + !isStackingSolo(stacker) || + isDelegating(stacker) || + !isAmountLockedPositive(stacker) || + hasLockedStackers(stacker) || + isPeriodWithinMax(totalPeriod) ) return false; model.trackCommandRun( @@ -1295,13 +1320,13 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - !(model.stackingMinimum > 0) || - stacker.isStacking || - stacker.isStackingSolo || - stacker.hasDelegated || - stacker.amountLocked > 0 || - !(stacker.poolMembers.length === 0) || - !(totalPeriod <= 12) + !isStackingMinimumCalculated(model) || + isStacking(stacker) || + isStackingSolo(stacker) || + isDelegating(stacker) || + isAmountLockedPositive(stacker) || + hasLockedStackers(stacker) || + !isPeriodWithinMax(totalPeriod) ) return false; model.trackCommandRun( @@ -1344,13 +1369,13 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - !(model.stackingMinimum > 0) || - !stacker.isStacking || - stacker.isStackingSolo || - stacker.hasDelegated || - !(stacker.amountLocked > 0) || - !(stacker.poolMembers.length === 0) || - !(totalPeriod <= 12) + !isStackingMinimumCalculated(model) || + !isStacking(stacker) || + isStackingSolo(stacker) || + isDelegating(stacker) || + !isAmountLockedPositive(stacker) || + hasLockedStackers(stacker) || + !isPeriodWithinMax(totalPeriod) ) return false; model.trackCommandRun( @@ -1393,13 +1418,13 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - !(model.stackingMinimum > 0) || - !stacker.isStacking || - stacker.isStackingSolo || - stacker.hasDelegated || - !(stacker.amountLocked > 0) || - stacker.poolMembers.length === 0 || - !(totalPeriod <= 12) + !isStackingMinimumCalculated(model) || + !isStacking(stacker) || + isStackingSolo(stacker) || + isDelegating(stacker) || + !isAmountLockedPositive(stacker) || + !hasPoolMembers(stacker) || + !isPeriodWithinMax(totalPeriod) ) return false; model.trackCommandRun( @@ -1442,13 +1467,13 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - !(model.stackingMinimum > 0) || - !stacker.isStacking || - !stacker.isStackingSolo || - !stacker.hasDelegated || - !(stacker.amountLocked > 0) || - !(stacker.poolMembers.length === 0) || - !(totalPeriod <= 12) + !isStackingMinimumCalculated(model) || + !isStacking(stacker) || + !isStackingSolo(stacker) || + !isDelegating(stacker) || + !isAmountLockedPositive(stacker) || + hasLockedStackers(stacker) || + !isPeriodWithinMax(totalPeriod) ) return false; model.trackCommandRun( @@ -1491,13 +1516,13 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - !(model.stackingMinimum > 0) || - !stacker.isStacking || - !stacker.isStackingSolo || - stacker.hasDelegated || - !(stacker.amountLocked > 0) || - !(stacker.poolMembers.length === 0) || - totalPeriod <= 12 + !isStackingMinimumCalculated(model) || + !isStacking(stacker) || + !isStackingSolo(stacker) || + isDelegating(stacker) || + !isAmountLockedPositive(stacker) || + hasLockedStackers(stacker) || + isPeriodWithinMax(totalPeriod) ) return false; model.trackCommandRun( @@ -1540,13 +1565,13 @@ export function ErrCommands( const lastExtendCycle = firstExtendCycle + this.extendCount - 1; const totalPeriod = lastExtendCycle - firstRewardCycle + 1; if ( - !(model.stackingMinimum > 0) || - stacker.isStacking || - stacker.isStackingSolo || - stacker.hasDelegated || - stacker.amountLocked > 0 || - !(stacker.poolMembers.length === 0) || - !(totalPeriod <= 12) + !isStackingMinimumCalculated(model) || + isStacking(stacker) || + isStackingSolo(stacker) || + isDelegating(stacker) || + isAmountLockedPositive(stacker) || + hasLockedStackers(stacker) || + !isPeriodWithinMax(totalPeriod) ) return false; model.trackCommandRun( @@ -1612,18 +1637,15 @@ export function ErrCommands( const stackedAmount = stacker.amountLocked; if ( - !(stacker.amountLocked > 0) || - !stacker.hasDelegated || - !stacker.isStacking || - !(stacker.delegatedTo === this.operator.stxAddress) || - (stacker.delegatedUntilBurnHt === undefined || - stacker.delegatedUntilBurnHt >= newUnlockHeight) || - !(stacker.delegatedMaxAmount >= stackedAmount) || - !(operator.poolMembers.includes(this.stacker.stxAddress)) || - !operator.lockedAddresses.includes( - this.stacker.stxAddress, - ) || - totalPeriod <= 12 + !isAmountLockedPositive(stacker) || + !isDelegating(stacker) || + !isStacking(stacker) || + !isStackerDelegatingToOperator(stacker, this.operator) || + isUBHWithinDelegationLimit(stacker, newUnlockHeight) || + !isAmountWithinDelegationLimit(stacker, stackedAmount) || + !isStackerInOperatorPool(operator, this.stacker) || + !isStackerLockedByOperator(operator, this.stacker) || + isPeriodWithinMax(totalPeriod) ) return false; model.trackCommandRun( @@ -1691,19 +1713,16 @@ export function ErrCommands( const stackedAmount = stacker.amountLocked; if ( - !(stacker.amountLocked > 0) || - stacker.hasDelegated || - !stacker.isStacking || - !stacker.isStackingSolo || - stacker.delegatedTo === this.operator.stxAddress || - (stacker.delegatedUntilBurnHt === undefined || - stacker.delegatedUntilBurnHt >= newUnlockHeight) || - stacker.delegatedMaxAmount >= stackedAmount || - operator.poolMembers.includes(this.stacker.stxAddress) || - operator.lockedAddresses.includes( - this.stacker.stxAddress, - ) || - !(totalPeriod <= 12) + !isAmountLockedPositive(stacker) || + isDelegating(stacker) || + !isStacking(stacker) || + !isStackingSolo(stacker) || + isStackerDelegatingToOperator(stacker, this.operator) || + isUBHWithinDelegationLimit(stacker, newUnlockHeight) || + isAmountWithinDelegationLimit(stacker, stackedAmount) || + isStackerInOperatorPool(operator, this.stacker) || + isStackerLockedByOperator(operator, this.stacker) || + !isPeriodWithinMax(totalPeriod) ) return false; model.trackCommandRun( @@ -1768,18 +1787,15 @@ export function ErrCommands( FIRST_BURNCHAIN_BLOCK_HEIGHT; const stackedAmount = stacker.amountLocked; if ( - stacker.amountLocked > 0 || - !stacker.hasDelegated || - stacker.isStacking || - stacker.delegatedTo === this.operator.stxAddress || - !(stacker.delegatedUntilBurnHt === undefined || - stacker.delegatedUntilBurnHt >= newUnlockHeight) || - stacker.delegatedMaxAmount >= stackedAmount || - operator.poolMembers.includes(this.stacker.stxAddress) || - operator.lockedAddresses.includes( - this.stacker.stxAddress, - ) || - !(totalPeriod <= 12) + isAmountLockedPositive(stacker) || + !isDelegating(stacker) || + isStacking(stacker) || + isStackerDelegatingToOperator(stacker, this.operator) || + !isUBHWithinDelegationLimit(stacker, newUnlockHeight) || + isAmountWithinDelegationLimit(stacker, stackedAmount) || + isStackerInOperatorPool(operator, this.stacker) || + isStackerLockedByOperator(operator, this.stacker) || + !isPeriodWithinMax(totalPeriod) ) return false; model.trackCommandRun( @@ -1827,18 +1843,15 @@ export function ErrCommands( const stackedAmount = stacker.amountLocked; if ( - !(stacker.amountLocked > 0) || - stacker.hasDelegated || - !stacker.isStacking || - stacker.delegatedTo === this.operator.stxAddress || - (stacker.delegatedUntilBurnHt === undefined || - stacker.delegatedUntilBurnHt >= newUnlockHeight) || - stacker.delegatedMaxAmount >= stackedAmount || - operator.poolMembers.includes(this.stacker.stxAddress) || - !operator.lockedAddresses.includes( - this.stacker.stxAddress, - ) || - !(totalPeriod <= 12) + !isAmountLockedPositive(stacker) || + isDelegating(stacker) || + !isStacking(stacker) || + isStackerDelegatingToOperator(stacker, this.operator) || + isUBHWithinDelegationLimit(stacker, newUnlockHeight) || + isAmountWithinDelegationLimit(stacker, stackedAmount) || + isStackerInOperatorPool(operator, this.stacker) || + !isStackerLockedByOperator(operator, this.stacker) || + !isPeriodWithinMax(totalPeriod) ) return false; model.trackCommandRun( @@ -1887,21 +1900,21 @@ export function ErrCommands( )!; if ( - !(stackerWallet.amountLocked > 0) || - !(stackerWallet.hasDelegated) || - !stackerWallet.isStacking || - !(this.increaseBy > 0) || - !operatorWallet.poolMembers.includes(this.stacker.stxAddress) || - stackerWallet.amountUnlocked >= this.increaseBy || - ( - stackerWallet.delegatedMaxAmount >= - this.increaseBy + stackerWallet.amountLocked + !isAmountLockedPositive(stackerWallet) || + !isDelegating(stackerWallet) || + !isStacking(stackerWallet) || + !isIncreaseByGTZero(this.increaseBy) || + !isStackerInOperatorPool(operatorWallet, this.stacker) || + isIncreaseByWithinUnlockedBalance( + stackerWallet, + this.increaseBy, ) || - !(operatorWallet.lockedAddresses.indexOf( - this.stacker.stxAddress, - ) > -1) || - !(stackerWallet.unlockHeight > - model.burnBlockHeight + REWARD_CYCLE_LENGTH) + isAmountWithinDelegationLimit( + stackerWallet, + this.increaseBy + stackerWallet.amountLocked, + ) || + !isStackerLockedByOperator(operatorWallet, this.stacker) || + isUnlockedWithinCurrentRC(stackerWallet, model) ) return false; model.trackCommandRun( @@ -1950,19 +1963,21 @@ export function ErrCommands( )!; if ( - !(stackerWallet.amountLocked > 0) || - !stackerWallet.hasDelegated || - !stackerWallet.isStacking || - this.increaseBy > 0 || - !operatorWallet.poolMembers.includes(this.stacker.stxAddress) || - !(stackerWallet.amountUnlocked >= this.increaseBy) || - !(stackerWallet.delegatedMaxAmount >= - this.increaseBy + stackerWallet.amountLocked) || - !(operatorWallet.lockedAddresses.indexOf( - this.stacker.stxAddress, - ) > -1) || - !(stackerWallet.unlockHeight > - model.burnBlockHeight + REWARD_CYCLE_LENGTH) + !isAmountLockedPositive(stackerWallet) || + !isDelegating(stackerWallet) || + !isStacking(stackerWallet) || + isIncreaseByGTZero(this.increaseBy) || + !isStackerInOperatorPool(operatorWallet, this.stacker) || + !isIncreaseByWithinUnlockedBalance( + stackerWallet, + this.increaseBy, + ) || + !isAmountWithinDelegationLimit( + stackerWallet, + this.increaseBy + stackerWallet.amountLocked, + ) || + !isStackerLockedByOperator(operatorWallet, this.stacker) || + isUnlockedWithinCurrentRC(stackerWallet, model) ) return false; model.trackCommandRun( @@ -2011,24 +2026,22 @@ export function ErrCommands( )!; if ( - !(stackerWallet.amountLocked > 0) || - stackerWallet.hasDelegated || - !stackerWallet.isStacking || - !stackerWallet.isStackingSolo || - !(this.increaseBy > 0) || - operatorWallet.poolMembers.includes(this.stacker.stxAddress) || - !(stackerWallet.amountUnlocked >= this.increaseBy) || - ( - stackerWallet.delegatedMaxAmount >= - this.increaseBy + stackerWallet.amountLocked + !isAmountLockedPositive(stackerWallet) || + isDelegating(stackerWallet) || + !isStacking(stackerWallet) || + !isStackingSolo(stackerWallet) || + !isIncreaseByGTZero(this.increaseBy) || + isStackerInOperatorPool(operatorWallet, this.stacker) || + !isIncreaseByWithinUnlockedBalance( + stackerWallet, + this.increaseBy, ) || - ( - operatorWallet.lockedAddresses.indexOf( - this.stacker.stxAddress, - ) > -1 + isAmountWithinDelegationLimit( + stackerWallet, + this.increaseBy + stackerWallet.amountLocked, ) || - !(stackerWallet.unlockHeight > - model.burnBlockHeight + REWARD_CYCLE_LENGTH) + isStackerLockedByOperator(operatorWallet, this.stacker) || + isUnlockedWithinCurrentRC(stackerWallet, model) ) return false; model.trackCommandRun( @@ -2077,21 +2090,21 @@ export function ErrCommands( )!; if ( - !(stackerWallet.amountLocked > 0) || - stackerWallet.hasDelegated || - !stackerWallet.isStacking || - !(this.increaseBy > 0) || - operatorWallet.poolMembers.includes(this.stacker.stxAddress) || - !(stackerWallet.amountUnlocked >= this.increaseBy) || - ( - stackerWallet.delegatedMaxAmount >= - this.increaseBy + stackerWallet.amountLocked + !isAmountLockedPositive(stackerWallet) || + isDelegating(stackerWallet) || + !isStacking(stackerWallet) || + !isIncreaseByGTZero(this.increaseBy) || + isStackerInOperatorPool(operatorWallet, this.stacker) || + !isIncreaseByWithinUnlockedBalance( + stackerWallet, + this.increaseBy, ) || - !(operatorWallet.lockedAddresses.indexOf( - this.stacker.stxAddress, - ) > -1) || - !(stackerWallet.unlockHeight > - model.burnBlockHeight + REWARD_CYCLE_LENGTH) + isAmountWithinDelegationLimit( + stackerWallet, + this.increaseBy + stackerWallet.amountLocked, + ) || + !isStackerLockedByOperator(operatorWallet, this.stacker) || + isUnlockedWithinCurrentRC(stackerWallet, model) ) return false; model.trackCommandRun( @@ -2117,12 +2130,8 @@ export function ErrCommands( this.callerToDisallow.stxAddress, )!; if ( - stacker.allowedContractCallers.includes( - this.callerToDisallow.stxAddress, - ) || - callerToDisallow.callerAllowedBy.includes( - this.stacker.stxAddress, - ) + isAllowedContractCaller(stacker, this.callerToDisallow) || + isCallerAllowedByStacker(this.stacker, callerToDisallow) ) return false; model.trackCommandRun( From 00c07381c09fba8b2e8268e50c0c3404bcac072d Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Fri, 21 Jun 2024 02:16:59 +0300 Subject: [PATCH 72/72] Update happy path commands check functions to use suggestive helpers --- .../pox-4/pox_DelegateStackExtendCommand.ts | 28 ++++++++++------ .../pox-4/pox_DelegateStackIncreaseCommand.ts | 32 ++++++++++++------- .../pox-4/pox_DelegateStackStxCommand.ts | 26 +++++++++------ .../tests/pox-4/pox_DelegateStxCommand.ts | 7 ++-- .../pox_DisallowContractCallerCommand.ts | 12 +++---- .../pox-4/pox_RevokeDelegateStxCommand.ts | 13 +++++--- .../pox_StackAggregationCommitAuthCommand.ts | 18 +++++++---- 7 files changed, 84 insertions(+), 52 deletions(-) diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand.ts index 2db12e5f40..a3fe2a5f1a 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackExtendCommand.ts @@ -1,4 +1,13 @@ import { + isAmountLockedPositive, + isAmountWithinDelegationLimit, + isPeriodWithinMax, + isStackerDelegatingToOperator, + isDelegating, + isStacking, + isUBHWithinDelegationLimit, + isStackerInOperatorPool, + isStackerLockedByOperator, logCommand, PoxCommand, Real, @@ -80,16 +89,15 @@ export class DelegateStackExtendCommand implements PoxCommand { const stackedAmount = stackerWallet.amountLocked; return ( - stackerWallet.amountLocked > 0 && - stackerWallet.hasDelegated === true && - stackerWallet.isStacking === true && - stackerWallet.delegatedTo === this.operator.stxAddress && - (stackerWallet.delegatedUntilBurnHt === undefined || - stackerWallet.delegatedUntilBurnHt >= newUnlockHeight) && - stackerWallet.delegatedMaxAmount >= stackedAmount && - operatorWallet.poolMembers.includes(this.stacker.stxAddress) && - operatorWallet.lockedAddresses.includes(this.stacker.stxAddress) && - totalPeriod <= 12 + isAmountLockedPositive(stackerWallet) && + isDelegating(stackerWallet) && + isStacking(stackerWallet) && + isStackerDelegatingToOperator(stackerWallet, this.operator) && + isUBHWithinDelegationLimit(stackerWallet, newUnlockHeight) && + isAmountWithinDelegationLimit(stackerWallet, stackedAmount) && + isStackerInOperatorPool(operatorWallet, this.stacker) && + isStackerLockedByOperator(operatorWallet, this.stacker) && + isPeriodWithinMax(totalPeriod) ); } diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand.ts index b78fe187bb..43b6a0473a 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackIncreaseCommand.ts @@ -1,4 +1,13 @@ import { + isAmountLockedPositive, + isAmountWithinDelegationLimit, + isIncreaseAmountGTZero, + isIncreaseByWithinUnlockedBalance, + isStackerDelegatingToOperator, + isDelegating, + isStacking, + isUnlockedWithinCurrentRC, + isStackerLockedByOperator, logCommand, PoxCommand, Real, @@ -8,7 +17,6 @@ import { import { poxAddressToTuple } from "@stacks/stacking"; import { expect } from "vitest"; import { Cl } from "@stacks/transactions"; -import { REWARD_CYCLE_LENGTH } from "./pox_Commands.ts"; /** * The `DelegateStackIncreaseCommand` allows a pool operator to @@ -62,16 +70,18 @@ export class DelegateStackIncreaseCommand implements PoxCommand { const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; return ( - stackerWallet.amountLocked > 0 && - stackerWallet.hasDelegated === true && - stackerWallet.isStacking === true && - this.increaseBy > 0 && - operatorWallet.poolMembers.includes(this.stacker.stxAddress) && - stackerWallet.amountUnlocked >= this.increaseBy && - stackerWallet.delegatedMaxAmount >= - this.increaseBy + stackerWallet.amountLocked && - operatorWallet.lockedAddresses.indexOf(this.stacker.stxAddress) > -1 && - stackerWallet.unlockHeight > model.burnBlockHeight + REWARD_CYCLE_LENGTH + isAmountLockedPositive(stackerWallet) && + isDelegating(stackerWallet) && + isStacking(stackerWallet) && + isIncreaseAmountGTZero(this.increaseBy) && + isStackerDelegatingToOperator(stackerWallet, this.operator) && + isIncreaseByWithinUnlockedBalance(stackerWallet, this.increaseBy) && + isAmountWithinDelegationLimit( + stackerWallet, + this.increaseBy + stackerWallet.amountLocked, + ) && + isStackerLockedByOperator(operatorWallet, this.stacker) && + isUnlockedWithinCurrentRC(stackerWallet, model) ); } diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand.ts index c284975ae0..70f56fc191 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStackStxCommand.ts @@ -1,4 +1,12 @@ import { + isAmountAboveThreshold, + isAmountWithinBalance, + isAmountWithinDelegationLimit, + isStackerDelegatingToOperator, + isDelegating, + isStacking, + isStackingMinimumCalculated, + isUBHWithinDelegationLimit, logCommand, PoxCommand, Real, @@ -83,19 +91,17 @@ export class DelegateStackStxCommand implements PoxCommand { // - The Operator has to currently be delegated by the Stacker. // - The Period has to fit the last delegation burn block height. - const operatorWallet = model.stackers.get(this.operator.stxAddress)!; const stackerWallet = model.stackers.get(this.stacker.stxAddress)!; return ( - model.stackingMinimum > 0 && - !stackerWallet.isStacking && - stackerWallet.hasDelegated && - stackerWallet.delegatedMaxAmount >= Number(this.amountUstx) && - Number(this.amountUstx) <= stackerWallet.ustxBalance && - Number(this.amountUstx) >= model.stackingMinimum && - operatorWallet.poolMembers.includes(this.stacker.stxAddress) && - (stackerWallet.delegatedUntilBurnHt === undefined || - this.unlockBurnHt <= stackerWallet.delegatedUntilBurnHt) + isStackingMinimumCalculated(model) && + !isStacking(stackerWallet) && + isDelegating(stackerWallet) && + isAmountWithinDelegationLimit(stackerWallet, this.amountUstx) && + isAmountWithinBalance(stackerWallet, this.amountUstx) && + isAmountAboveThreshold(model, this.amountUstx) && + isStackerDelegatingToOperator(stackerWallet, this.operator) && + isUBHWithinDelegationLimit(stackerWallet, this.unlockBurnHt) ); } diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStxCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStxCommand.ts index 836b7d5162..cd14c39ce3 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStxCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DelegateStxCommand.ts @@ -1,4 +1,6 @@ import { + isDelegating, + isStackingMinimumCalculated, logCommand, PoxCommand, Real, @@ -55,10 +57,11 @@ export class DelegateStxCommand implements PoxCommand { check(model: Readonly): boolean { // Constraints for running this command include: // - The Stacker cannot currently be a delegator in another delegation. + const stackerWallet = model.stackers.get(this.wallet.stxAddress)!; return ( - model.stackingMinimum > 0 && - !model.stackers.get(this.wallet.stxAddress)?.hasDelegated + isStackingMinimumCalculated(model) && + !isDelegating(stackerWallet) ); } diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DisallowContractCallerCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DisallowContractCallerCommand.ts index 16b830b5fb..6108a5973f 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DisallowContractCallerCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_DisallowContractCallerCommand.ts @@ -1,4 +1,6 @@ import { + isAllowedContractCaller, + isCallerAllowedByStacker, logCommand, PoxCommand, Real, @@ -41,14 +43,10 @@ export class DisallowContractCallerCommand implements PoxCommand { const callerToDisallow = model.stackers.get( this.callerToDisallow.stxAddress, )!; + return ( - stacker.allowedContractCallers.includes( - this.callerToDisallow.stxAddress, - ) && - callerToDisallow.callerAllowedBy.includes( - this.stacker.stxAddress, - ) === - true + isAllowedContractCaller(stacker, this.callerToDisallow) && + isCallerAllowedByStacker(this.stacker, callerToDisallow) ); } diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand.ts index 2c3593f27d..98e2349a1f 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_RevokeDelegateStxCommand.ts @@ -1,4 +1,7 @@ import { + isDelegating, + isStackingMinimumCalculated, + isUBHWithinDelegationLimit, logCommand, PoxCommand, Real, @@ -34,11 +37,11 @@ export class RevokeDelegateStxCommand implements PoxCommand { // - The Stacker has to currently be delegating. // - The Stacker's delegation must not be expired. const stacker = model.stackers.get(this.wallet.stxAddress)!; + return ( - model.stackingMinimum > 0 && - stacker.hasDelegated === true && - (stacker.delegatedUntilBurnHt === undefined || - stacker.delegatedUntilBurnHt > model.burnBlockHeight) + isStackingMinimumCalculated(model) && + isDelegating(stacker) && + isUBHWithinDelegationLimit(stacker, model.burnBlockHeight) ); } @@ -80,7 +83,7 @@ export class RevokeDelegateStxCommand implements PoxCommand { // Update model so that we know this wallet is not delegating anymore. // This is important in order to prevent the test from revoking the // delegation multiple times with the same address. - // We update delegatedUntilBurnHt to 0, and not undefined. Undefined + // We update delegatedUntilBurnHt to 0, and not undefined. Undefined // stands for indefinite delegation. wallet.hasDelegated = false; wallet.delegatedTo = ""; diff --git a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand.ts b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand.ts index 999aa2f5b0..7145c673d4 100644 --- a/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand.ts +++ b/contrib/boot-contracts-stateful-prop-tests/tests/pox-4/pox_StackAggregationCommitAuthCommand.ts @@ -1,4 +1,6 @@ import { + hasLockedStackers, + isATCAboveThreshold, logCommand, PoxCommand, Real, @@ -31,7 +33,7 @@ export class StackAggregationCommitAuthCommand implements PoxCommand { readonly authId: number; /** - * Constructs a `StackAggregationCommitAuthCommand` to commit partially + * Constructs a `StackAggregationCommitAuthCommand` to commit partially * locked uSTX. * * @param operator - Represents the `Operator`'s wallet. @@ -52,8 +54,10 @@ export class StackAggregationCommitAuthCommand implements PoxCommand { // stackers has to be greater than the uSTX threshold. const operator = model.stackers.get(this.operator.stxAddress)!; - return operator.lockedAddresses.length > 0 && - operator.amountToCommit >= model.stackingMinimum; + return ( + hasLockedStackers(operator) && + isATCAboveThreshold(operator, model) + ); } run(model: Stub, real: Real): void { @@ -64,10 +68,10 @@ export class StackAggregationCommitAuthCommand implements PoxCommand { // Act - // Include the authorization and the `stack-aggregation-commit` transactions - // in a single block. This way we ensure both the authorization and the - // stack-aggregation-commit transactions are called during the same reward - // cycle, so the authorization currentRewCycle param is relevant for the + // Include the authorization and the `stack-aggregation-commit` transactions + // in a single block. This way we ensure both the authorization and the + // stack-aggregation-commit transactions are called during the same reward + // cycle, so the authorization currentRewCycle param is relevant for the // upcoming stack-aggregation-commit call. const block = real.network.mineBlock([ tx.callPublicFn(