Skip to content

Commit

Permalink
fix(pox-3): update stacking lib to always use the latest activated / …
Browse files Browse the repository at this point in the history
…current pox contract
  • Loading branch information
janniks committed May 10, 2023
1 parent c9e420e commit a2aac09
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 25 deletions.
4 changes: 4 additions & 0 deletions packages/stacking/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ export const SEGWIT_V1 = 1;
// mainnet P2TR: bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg3297
// testnet P2TR: tb1p6h5fuzmnvpdthf5shf0qqjzwy7wsqc5rhmgq2ks9xrak4ry6mtrscsqvzp

/**
* Transitional periods from the 2.1 launch
* @see SIP-015
*/
export enum PoxOperationPeriod {
/** Period 1: This is before the 2.1 fork. */
Period1 = 'Period1',
Expand Down
79 changes: 56 additions & 23 deletions packages/stacking/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
import { PoxOperationPeriod, StackingErrors } from './constants';
import {
ensureLegacyBtcAddressForPox1,
ensurePox2IsLive,
ensurePox2Activated,
poxAddressToTuple,
unwrap,
unwrapMap,
Expand Down Expand Up @@ -99,6 +99,14 @@ export type PoxOperationInfo =
period: PoxOperationPeriod;
pox1: { contract_id: string };
pox2: ContractVersion;
current: ContractVersion;
}
| {
period: PoxOperationPeriod.Period3;
pox1: { contract_id: string };
pox2: ContractVersion;
pox3: ContractVersion;
current: ContractVersion;
};

export interface AccountExtendedBalances {
Expand Down Expand Up @@ -506,7 +514,7 @@ export class StackingClient {
async getPoxOperationInfo(poxInfo?: PoxInfo): Promise<PoxOperationInfo> {
poxInfo = poxInfo ?? (await this.getPoxInfo());

// == Before 2.1 Fork ======================================================
// ++ Before 2.1 Fork ++++++++++++++++++++++++++++++++++++++++++++++++++++++
// => Period 1
if (
!poxInfo.current_burnchain_block_height ||
Expand All @@ -517,37 +525,54 @@ export class StackingClient {
return { period: PoxOperationPeriod.Period1, pox1: { contract_id: poxInfo.contract_id } };
}

const [pox1, pox2] = [...poxInfo.contract_versions].sort(
const poxContractVersions = [...poxInfo.contract_versions].sort(
(a, b) => a.activation_burnchain_block_height - b.activation_burnchain_block_height
); // by activation height ASC (earliest first)
const [pox1, pox2, pox3] = poxContractVersions;
const activatedPoxs = poxContractVersions.filter(
(c: ContractVersion) =>
(poxInfo?.current_burnchain_block_height as number) >= c.activation_burnchain_block_height
);
const [address, name] = pox2.contract_id.split('.');
const pox2ConfiguredUrl = this.network.getDataVarUrl(address, name, 'configured');
const isPox2NotYetConfigured =
(await this.network.fetchFn(pox2ConfiguredUrl).then(r => r.text())) !== '{"data":"0x03"}'; // PoX-2 is configured on fork

// => Period 1
if (isPox2NotYetConfigured) {
// Node hasn't forked yet (unclear if this case can happen)
return { period: PoxOperationPeriod.Period1, pox1, pox2 };
// Named pox contracts but also a more future-proof current pointer to latest activated PoX contract
const current = activatedPoxs[activatedPoxs.length - 1];

if (poxInfo.contract_versions.length == 2) {
const [address, name] = pox2.contract_id.split('.');
const pox2ConfiguredUrl = this.network.getDataVarUrl(address, name, 'configured');
const isPox2NotYetConfigured =
(await this.network.fetchFn(pox2ConfiguredUrl).then(r => r.text())) !== '{"data":"0x03"}'; // PoX-2 is configured on fork if data is 0x03

// => Period 1
if (isPox2NotYetConfigured) {
// Node hasn't forked yet (unclear if this case can happen on a non-mocknet/regtest node)
return { period: PoxOperationPeriod.Period1, pox1, pox2 };
}
}

// == In 2.1 Fork ==========================================================
// ++ >= 2.1 Fork ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// => Period 2a
if (poxInfo.contract_id === pox1.contract_id) {
// In 2.1 fork, but PoX-2 hasn't been activated yet
return { period: PoxOperationPeriod.Period2a, pox1, pox2 };
return { period: PoxOperationPeriod.Period2a, pox1, pox2, current };
}

// == PoX-2 is Live ========================================================
// ++ PoX-2 was activated ++++++++++++++++++++++++++++++++++++++++++++++++++
if (poxInfo.contract_id === pox2.contract_id) {
// => Period 2b
if (poxInfo.current_cycle.id < pox2.first_reward_cycle_id) {
// In 2.1 fork and PoX-2 is live
return { period: PoxOperationPeriod.Period2b, pox1, pox2 };
return { period: PoxOperationPeriod.Period2b, pox1, pox2, current };
}

// => Period 3
return { period: PoxOperationPeriod.Period3, pox1, pox2 };
return { period: PoxOperationPeriod.Period3, pox1, pox2, current };
}

// ++ Post PoX-2 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
if (activatedPoxs.length > 2) {
// More than two PoX contracts have been activated
// => (Still) Period 3
return { period: PoxOperationPeriod.Period3, pox1, pox2, pox3, current };
}

throw new Error('Could not determine PoX Operation Period');
Expand Down Expand Up @@ -666,7 +691,7 @@ export class StackingClient {
}: StackExtendOptions & BaseTxOptions): Promise<TxBroadcastResult> {
const poxInfo = await this.getPoxInfo();
const poxOperationInfo = await this.getPoxOperationInfo(poxInfo);
ensurePox2IsLive(poxOperationInfo);
ensurePox2Activated(poxOperationInfo);

const callOptions = this.getStackExtendOptions({
contract: poxInfo.contract_id,
Expand All @@ -693,7 +718,7 @@ export class StackingClient {
}: StackIncreaseOptions & BaseTxOptions): Promise<TxBroadcastResult> {
const poxInfo = await this.getPoxInfo();
const poxOperationInfo = await this.getPoxOperationInfo(poxInfo);
ensurePox2IsLive(poxOperationInfo);
ensurePox2Activated(poxOperationInfo);

const callOptions = this.getStackIncreaseOptions({
contract: poxInfo.contract_id,
Expand Down Expand Up @@ -824,7 +849,7 @@ export class StackingClient {
}: DelegateStackIncreaseOptions & BaseTxOptions): Promise<TxBroadcastResult> {
const poxInfo = await this.getPoxInfo();
const poxOperationInfo = await this.getPoxOperationInfo(poxInfo);
ensurePox2IsLive(poxOperationInfo);
ensurePox2Activated(poxOperationInfo);

const callOptions = this.getDelegateStackIncreaseOptions({
contract: poxInfo.contract_id,
Expand Down Expand Up @@ -1340,9 +1365,17 @@ export class StackingClient {
*/
async getStackingContract(poxOperationInfo?: PoxOperationInfo): Promise<string> {
poxOperationInfo = poxOperationInfo ?? (await this.getPoxOperationInfo());
return poxOperationInfo.period === PoxOperationPeriod.Period1
? poxOperationInfo.pox1.contract_id
: poxOperationInfo.pox2.contract_id; // in the 2.1 fork we can always stack to PoX-2
switch (poxOperationInfo.period) {
case PoxOperationPeriod.Period1:
return poxOperationInfo.pox1.contract_id;
case PoxOperationPeriod.Period2a:
case PoxOperationPeriod.Period2b:
// in the 2.1 fork we can always stack to PoX-2
return poxOperationInfo.pox2.contract_id;
case PoxOperationPeriod.Period3:
default:
return poxOperationInfo.current.contract_id;
}
}

/**
Expand Down
5 changes: 3 additions & 2 deletions packages/stacking/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,11 @@ export function unwrapMap<T extends ClarityValue, U>(optional: OptionalCV<T>, ma
throw new Error("Object is not an 'Optional'");
}

export function ensurePox2IsLive(operationInfo: PoxOperationInfo) {
/** @internal */
export function ensurePox2Activated(operationInfo: PoxOperationInfo) {
if (operationInfo.period === PoxOperationPeriod.Period1)
throw new Error(
`PoX-2 is not live yet (currently in period ${operationInfo.period} of PoX-2 operation)`
`PoX-2 has not activated yet (currently in period ${operationInfo.period} of PoX-2 operation)`
);
}

Expand Down

0 comments on commit a2aac09

Please sign in to comment.