Skip to content

Commit d72629e

Browse files
committed
Add StackIncreaseSigCommand
1 parent 4aa7732 commit d72629e

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-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
@@ -21,6 +21,7 @@ import { DisallowContractCallerCommand } from "./pox_DisallowContractCallerComma
2121
import { StackExtendAuthCommand } from "./pox_StackExtendAuthCommand";
2222
import { StackExtendSigCommand } from "./pox_StackExtendSigCommand";
2323
import { StackIncreaseAuthCommand } from "./pox_StackIncreaseAuthCommand";
24+
import { StackIncreaseSigCommand } from "./pox_StackIncreaseSigCommand";
2425

2526
export function PoxCommands(
2627
wallets: Map<StxAddress, Wallet>,
@@ -139,6 +140,19 @@ export function PoxCommands(
139140
r.authId,
140141
);
141142
}),
143+
// StackIncreaseSigCommand
144+
fc.record({
145+
operator: fc.constantFrom(...wallets.values()),
146+
increaseBy: fc.nat(),
147+
authId: fc.nat(),
148+
})
149+
.map((r) => {
150+
return new StackIncreaseSigCommand(
151+
r.operator,
152+
r.increaseBy,
153+
r.authId,
154+
);
155+
}),
142156
// GetStackingMinimumCommand
143157
fc
144158
.record({
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { Pox4SignatureTopic } from "@stacks/stacking";
2+
import { logCommand, PoxCommand, Real, Stub, Wallet } from "./pox_CommandModel";
3+
import {
4+
Cl,
5+
ClarityType,
6+
ClarityValue,
7+
cvToJSON,
8+
cvToValue,
9+
isClarityType,
10+
} from "@stacks/transactions";
11+
import { assert, expect } from "vitest";
12+
13+
/**
14+
* The `StackIncreaseSigCommand` locks up an additional amount
15+
* of STX from `tx-sender`'s, indicated by `increase-by`.
16+
*
17+
* This command calls `stack-increase` using a `signature`.
18+
*
19+
* Constraints for running this command include:
20+
* - The Stacker must have locked uSTX.
21+
* - The Stacker must be stacking solo.
22+
* - The Stacker must not have delegated to a pool.
23+
* - The increase amount must be less than or equal to the
24+
* Stacker's unlocked uSTX amount.
25+
* - The increase amount must be equal or greater than 1.
26+
*/
27+
export class StackIncreaseSigCommand implements PoxCommand {
28+
readonly wallet: Wallet;
29+
readonly increaseBy: number;
30+
readonly authId: number;
31+
32+
/**
33+
* Constructs a `StackIncreaseSigCommand` to lock uSTX for stacking.
34+
*
35+
* @param wallet - Represents the Stacker's wallet.
36+
* @param increaseBy - Represents the locked amount to be increased by.
37+
* @param authId - Unique auth-id for the authorization.
38+
*/
39+
constructor(wallet: Wallet, increaseBy: number, authId: number) {
40+
this.wallet = wallet;
41+
this.increaseBy = increaseBy;
42+
this.authId = authId;
43+
}
44+
45+
check(model: Readonly<Stub>): boolean {
46+
// Constraints for running this command include:
47+
// - The Stacker must have locked uSTX.
48+
// - The Stacker must be stacking solo.
49+
// - The Stacker must not have delegated to a pool.
50+
// - The increse amount must be less than or equal to the
51+
// Stacker's unlocked uSTX amount.
52+
// - The increase amount must be equal or greater than 1.
53+
const stacker = model.stackers.get(this.wallet.stxAddress)!;
54+
55+
return (
56+
model.stackingMinimum > 0 &&
57+
stacker.isStacking &&
58+
stacker.isStackingSolo &&
59+
!stacker.hasDelegated &&
60+
stacker.amountLocked > 0 &&
61+
this.increaseBy <= stacker.amountUnlocked &&
62+
this.increaseBy >= 1
63+
);
64+
}
65+
66+
run(model: Stub, real: Real): void {
67+
model.trackCommandRun(this.constructor.name);
68+
69+
const stacker = model.stackers.get(this.wallet.stxAddress)!;
70+
71+
const maxAmount = stacker.amountLocked + this.increaseBy;
72+
73+
const burnBlockHeightCV = real.network.runSnippet("burn-block-height");
74+
const burnBlockHeight = Number(
75+
cvToValue(burnBlockHeightCV as ClarityValue),
76+
);
77+
78+
const { result: rewardCycleNextBlockCV } = real.network.callReadOnlyFn(
79+
"ST000000000000000000002AMW42H.pox-4",
80+
"burn-height-to-reward-cycle",
81+
[Cl.uint(burnBlockHeight + 1)],
82+
this.wallet.stxAddress,
83+
);
84+
assert(isClarityType(rewardCycleNextBlockCV, ClarityType.UInt));
85+
86+
const rewardCycleNextBlock = cvToValue(rewardCycleNextBlockCV);
87+
88+
// Get the lock period from the stacking state. This will be used for correctly
89+
// issuing the authorization.
90+
const stackingStateCV = real.network.getMapEntry(
91+
"ST000000000000000000002AMW42H.pox-4",
92+
"stacking-state",
93+
Cl.tuple({ stacker: Cl.principal(this.wallet.stxAddress) }),
94+
);
95+
const period = cvToJSON(stackingStateCV).value.value["lock-period"].value;
96+
97+
const signerSig = this.wallet.stackingClient.signPoxSignature({
98+
// The signer key being authorized.
99+
signerPrivateKey: this.wallet.signerPrvKey,
100+
// The reward cycle for which the authorization is valid.
101+
// For `stack-stx` and `stack-extend`, this refers to the reward cycle
102+
// where the transaction is confirmed. For `stack-aggregation-commit`,
103+
// this refers to the reward cycle argument in that function.
104+
rewardCycle: rewardCycleNextBlock,
105+
// For `stack-stx`, this refers to `lock-period`. For `stack-extend`,
106+
// this refers to `extend-count`. For `stack-aggregation-commit`, this is
107+
// `u1`.
108+
period: period,
109+
// A string representing the function where this authorization is valid.
110+
// Either `stack-stx`, `stack-extend`, `stack-increase` or `agg-commit`.
111+
topic: Pox4SignatureTopic.StackIncrease,
112+
// The PoX address that can be used with this signer key.
113+
poxAddress: this.wallet.btcAddress,
114+
// The unique auth-id for this authorization.
115+
authId: this.authId,
116+
// The maximum amount of uSTX that can be used (per tx) with this signer
117+
// key.
118+
maxAmount: maxAmount,
119+
});
120+
121+
const stackIncrease = real.network.callPublicFn(
122+
"ST000000000000000000002AMW42H.pox-4",
123+
"stack-increase",
124+
[
125+
// (increase-by uint)
126+
Cl.uint(this.increaseBy),
127+
// (signer-sig (optional (buff 65)))
128+
Cl.some(Cl.bufferFromHex(signerSig)),
129+
// (signer-key (buff 33))
130+
Cl.bufferFromHex(this.wallet.signerPubKey),
131+
// (max-amount uint)
132+
Cl.uint(maxAmount),
133+
// (auth-id uint)
134+
Cl.uint(this.authId),
135+
],
136+
this.wallet.stxAddress,
137+
);
138+
139+
expect(stackIncrease.result).toBeOk(
140+
Cl.tuple({
141+
stacker: Cl.principal(this.wallet.stxAddress),
142+
"total-locked": Cl.uint(stacker.amountLocked + this.increaseBy),
143+
}),
144+
);
145+
146+
// Get the wallet from the model and update it with the new state.
147+
const wallet = model.stackers.get(this.wallet.stxAddress)!;
148+
// Update model so that we know this wallet's locked amount and unlocked amount was extended.
149+
wallet.amountLocked += this.increaseBy;
150+
wallet.amountUnlocked -= this.increaseBy;
151+
152+
// Log to console for debugging purposes. This is not necessary for the
153+
// test to pass but it is useful for debugging and eyeballing the test.
154+
logCommand(
155+
`₿ ${model.burnBlockHeight}`,
156+
`✓ ${this.wallet.label}`,
157+
"stack-increase-sig",
158+
"increase-by",
159+
this.increaseBy.toString(),
160+
);
161+
162+
// Refresh the model's state if the network gets to the next reward cycle.
163+
model.refreshStateForNextRewardCycle(real);
164+
}
165+
166+
toString() {
167+
// fast-check will call toString() in case of errors, e.g. property failed.
168+
// It will then make a minimal counterexample, a process called 'shrinking'
169+
// https://github.com/dubzzz/fast-check/issues/2864#issuecomment-1098002642
170+
return `${this.wallet.label} stack-increase sig increase-by ${this.increaseBy}`;
171+
}
172+
}

0 commit comments

Comments
 (0)