Skip to content

Commit 43a801d

Browse files
committed
Add StackIncreaseAuthCommand
1 parent ebf7808 commit 43a801d

File tree

2 files changed

+180
-0
lines changed

2 files changed

+180
-0
lines changed

contrib/core-contract-tests/tests/pox-4/pox_Commands.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { StackAggregationIncreaseCommand } from "./pox_StackAggregationIncreaseC
2020
import { DisallowContractCallerCommand } from "./pox_DisallowContractCallerCommand";
2121
import { StackExtendAuthCommand } from "./pox_StackExtendAuthCommand";
2222
import { StackExtendSigCommand } from "./pox_StackExtendSigCommand";
23+
import { StackIncreaseAuthCommand } from "./pox_StackIncreaseAuthCommand";
2324

2425
export function PoxCommands(
2526
wallets: Map<StxAddress, Wallet>,
@@ -125,6 +126,19 @@ export function PoxCommands(
125126
r.currentCycle,
126127
),
127128
),
129+
// StackIncreaseAuthCommand
130+
fc.record({
131+
operator: fc.constantFrom(...wallets.values()),
132+
increaseBy: fc.nat(),
133+
authId: fc.nat(),
134+
})
135+
.map((r) => {
136+
return new StackIncreaseAuthCommand(
137+
r.operator,
138+
r.increaseBy,
139+
r.authId,
140+
);
141+
}),
128142
// GetStackingMinimumCommand
129143
fc
130144
.record({
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { Pox4SignatureTopic, poxAddressToTuple } from "@stacks/stacking";
2+
import { logCommand, PoxCommand, Real, Stub, Wallet } from "./pox_CommandModel";
3+
import {
4+
currentCycle,
5+
FIRST_BURNCHAIN_BLOCK_HEIGHT,
6+
REWARD_CYCLE_LENGTH,
7+
} from "./pox_Commands";
8+
import { Cl } from "@stacks/transactions";
9+
import { expect } from "vitest";
10+
import { tx } from "@hirosystems/clarinet-sdk";
11+
12+
/**
13+
* The `StackIncreaseAuthCommand` locks up an additional amount
14+
* of STX from `tx-sender`'s, indicated by `increase-by`.
15+
*
16+
* This command calls `stack-increase` using an `authorization`.
17+
*
18+
* Constraints for running this command include:
19+
* - The Stacker must have locked uSTX.
20+
* - The Stacker must be stacking solo.
21+
* - The Stacker must not have delegated to a pool.
22+
* - The increase amount must be less than or equal to the
23+
* Stacker's unlocked uSTX amount.
24+
*/
25+
26+
export class StackIncreaseAuthCommand implements PoxCommand {
27+
readonly wallet: Wallet;
28+
readonly increaseBy: number;
29+
readonly authId: number;
30+
31+
/**
32+
* Constructs a `StackIncreaseAuthCommand` to increase lock uSTX for stacking.
33+
*
34+
* @param wallet - Represents the Stacker's wallet.
35+
* @param increaseBy - Represents the locked amount to be increased by.
36+
* @param authId - Unique auth-id for the authorization.
37+
*/
38+
constructor(wallet: Wallet, increaseBy: number, authId: number) {
39+
this.wallet = wallet;
40+
this.increaseBy = increaseBy;
41+
this.authId = authId;
42+
}
43+
44+
check(model: Readonly<Stub>): boolean {
45+
// Constraints for running this command include:
46+
// - The Stacker must have locked uSTX.
47+
// - The Stacker must be stacking solo.
48+
// - The Stacker must not have delegated to a pool.
49+
// - The increse amount must be less or equal to the
50+
// Stacker's unlocked uSTX amount.
51+
const stacker = model.stackers.get(this.wallet.stxAddress)!;
52+
53+
return (
54+
model.stackingMinimum > 0 &&
55+
stacker.isStacking &&
56+
stacker.isStackingSolo &&
57+
!stacker.hasDelegated &&
58+
stacker.amountLocked > 0 &&
59+
this.increaseBy <= stacker.amountUnlocked &&
60+
this.increaseBy >= 1
61+
);
62+
}
63+
64+
run(model: Stub, real: Real): void {
65+
model.trackCommandRun(this.constructor.name);
66+
67+
const currentRewCycle = currentCycle(real.network);
68+
const stacker = model.stackers.get(this.wallet.stxAddress)!;
69+
70+
const firstRewardCycle = stacker.firstLockedRewardCycle;
71+
72+
const unlockCycle = Math.floor(
73+
(stacker.unlockHeight - FIRST_BURNCHAIN_BLOCK_HEIGHT) /
74+
REWARD_CYCLE_LENGTH,
75+
);
76+
77+
const period = unlockCycle - firstRewardCycle;
78+
79+
const maxAmount = stacker.amountLocked + this.increaseBy;
80+
81+
// Act
82+
83+
// Include the authorization and the `stack-increase` transactions in a single
84+
// block. This way we ensure both the authorization and the stack-increase
85+
// transactions are called during the same reward cycle and avoid the clarity
86+
// error `ERR_INVALID_REWARD_CYCLE`.
87+
const block = real.network.mineBlock([
88+
tx.callPublicFn(
89+
"ST000000000000000000002AMW42H.pox-4",
90+
"set-signer-key-authorization",
91+
[
92+
// (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32))))
93+
poxAddressToTuple(this.wallet.btcAddress),
94+
// (period uint)
95+
Cl.uint(period),
96+
// (reward-cycle uint)
97+
Cl.uint(currentRewCycle),
98+
// (topic (string-ascii 14))
99+
Cl.stringAscii(Pox4SignatureTopic.StackIncrease),
100+
// (signer-key (buff 33))
101+
Cl.bufferFromHex(this.wallet.signerPubKey),
102+
// (allowed bool)
103+
Cl.bool(true),
104+
// (max-amount uint)
105+
Cl.uint(maxAmount),
106+
// (auth-id uint)
107+
Cl.uint(this.authId),
108+
],
109+
this.wallet.stxAddress,
110+
),
111+
tx.callPublicFn(
112+
"ST000000000000000000002AMW42H.pox-4",
113+
"stack-increase",
114+
[
115+
// (increase-by uint)
116+
Cl.uint(this.increaseBy),
117+
// (signer-sig (optional (buff 65)))
118+
Cl.none(),
119+
// (signer-key (buff 33))
120+
Cl.bufferFromHex(this.wallet.signerPubKey),
121+
// (max-amount uint)
122+
Cl.uint(maxAmount),
123+
// (auth-id uint)
124+
Cl.uint(this.authId),
125+
],
126+
this.wallet.stxAddress,
127+
),
128+
]);
129+
130+
// Assert
131+
expect(block[0].result).toBeOk(Cl.bool(true));
132+
expect(block[1].result).toBeOk(
133+
Cl.tuple({
134+
stacker: Cl.principal(this.wallet.stxAddress),
135+
"total-locked": Cl.uint(stacker.amountLocked + this.increaseBy),
136+
}),
137+
);
138+
139+
// Get the wallet from the model and update it with the new state.
140+
const wallet = model.stackers.get(this.wallet.stxAddress)!;
141+
// Update model so that we know this wallet's locked amount and unlocked
142+
// amount was extended.
143+
wallet.amountLocked += this.increaseBy;
144+
wallet.amountUnlocked -= this.increaseBy;
145+
146+
// Log to console for debugging purposes. This is not necessary for the
147+
// test to pass but it is useful for debugging and eyeballing the test.
148+
logCommand(
149+
`₿ ${model.burnBlockHeight}`,
150+
`✓ ${this.wallet.label}`,
151+
"stack-increase-auth",
152+
"increase-by",
153+
this.increaseBy.toString(),
154+
);
155+
156+
// Refresh the model's state if the network gets to the next reward cycle.
157+
model.refreshStateForNextRewardCycle(real);
158+
}
159+
160+
toString() {
161+
// fast-check will call toString() in case of errors, e.g. property failed.
162+
// It will then make a minimal counterexample, a process called 'shrinking'
163+
// https://github.com/dubzzz/fast-check/issues/2864#issuecomment-1098002642
164+
return `${this.wallet.label} stack-increase auth increase-by ${this.increaseBy}`;
165+
}
166+
}

0 commit comments

Comments
 (0)