From 64dbe25443b4d67c3e179946ce331710a816033a Mon Sep 17 00:00:00 2001 From: friedger Date: Wed, 20 Sep 2023 10:53:37 +0200 Subject: [PATCH 1/3] feat: add check-not-prepare-phase to pox-4 --- stackslib/src/chainstate/stacks/boot/pox-4.clar | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/stackslib/src/chainstate/stacks/boot/pox-4.clar b/stackslib/src/chainstate/stacks/boot/pox-4.clar index 6766e4022e..64a00e439a 100644 --- a/stackslib/src/chainstate/stacks/boot/pox-4.clar +++ b/stackslib/src/chainstate/stacks/boot/pox-4.clar @@ -27,6 +27,7 @@ (define-constant ERR_DELEGATION_WRONG_REWARD_SLOT 29) (define-constant ERR_STACKING_IS_DELEGATED 30) (define-constant ERR_STACKING_NOT_DELEGATED 31) +(define-constant ERR_STACKING_DURING_PREPARE_PHASE 32) ;; PoX disabling threshold (a percent) (define-constant POX_REJECTION_FRACTION u25) @@ -508,6 +509,12 @@ (and (>= lock-period MIN_POX_REWARD_CYCLES) (<= lock-period MAX_POX_REWARD_CYCLES))) +;; Is the current block height outside of the prepare phase? +(define-read-only (check-not-prepare-phase) + (let ((cycle-length (var-get pox-reward-cycle-length)))) + (< (mod (- burn-block-height (var-get first-burnchain-block-height)) cycle-length) + (- cycle-length (var-get pox-prepare-cycle-length)))) + ;; Evaluate if a participant can stack an amount of STX for a given period. ;; This method is designed as a read-only method so that it can be used as ;; a set of guard conditions and also as a read-only RPC call that can be @@ -545,6 +552,10 @@ (asserts! (check-pox-lock-period num-cycles) (err ERR_STACKING_INVALID_LOCK_PERIOD)) + ;; stacking must not happen during prepare phase + (asserts! (check-not-prepare-phase) + (err ERR_STACKING_DURING_PREPARE_PHASE)) + ;; address version must be valid (asserts! (check-pox-addr-version (get version pox-addr)) (err ERR_STACKING_INVALID_POX_ADDRESS)) @@ -1014,7 +1025,7 @@ stacker: tx-sender, add-amount: increase-by }))) (err ERR_STACKING_UNREACHABLE)) - ;; NOTE: stacking-state map is unchanged: it does not track amount-stacked in PoX-4 + ;; NOTE: stacking-state map is unchanged: it does not track amount-stacked in pox-4 (ok { stacker: tx-sender, total-locked: (+ amount-stacked increase-by)}))) ;; Extend an active Stacking lock. From 6ed2a25ee6a065406f9b4ef8e235464fc8e4b1a8 Mon Sep 17 00:00:00 2001 From: friedger Date: Tue, 12 Dec 2023 12:44:15 +0100 Subject: [PATCH 2/3] chore: add check for stacking during prepare phase --- contrib/core-contract-tests/Clarinet.toml | 12 +++++ .../contracts/pox/pox-helper.clar | 3 ++ .../tests/bns/name_register.test.ts | 8 +-- .../tests/bns/name_register_test.ts | 40 +++++++------- .../tests/pox/check_not_prepare_phase.test.ts | 54 +++++++++++++++++++ pox-locking/src/pox_3.rs | 27 ---------- stackslib/src/chainstate/stacks/boot/mod.rs | 6 +-- .../src/chainstate/stacks/boot/pox-4.clar | 40 +++++++++----- 8 files changed, 122 insertions(+), 68 deletions(-) create mode 100644 contrib/core-contract-tests/contracts/pox/pox-helper.clar create mode 100644 contrib/core-contract-tests/tests/pox/check_not_prepare_phase.test.ts diff --git a/contrib/core-contract-tests/Clarinet.toml b/contrib/core-contract-tests/Clarinet.toml index 5cdea76a4c..4cbafa9d0b 100644 --- a/contrib/core-contract-tests/Clarinet.toml +++ b/contrib/core-contract-tests/Clarinet.toml @@ -8,3 +8,15 @@ costs_version = 1 [contracts.bns] path = "../../stackslib/src/chainstate/stacks/boot/bns.clar" depends_on = [] + +[contracts.pox-4] +path = "../../stackslib/src/chainstate/stacks/boot/pox-4.clar" +depends_on = [] +clarity = 2 +epoch = 2.4 + +[contracts.pox-helper] +path = "./contracts/pox/pox-helper.clar" +depends_on = [] +clarity = 2 +epoch = 2.4 diff --git a/contrib/core-contract-tests/contracts/pox/pox-helper.clar b/contrib/core-contract-tests/contracts/pox/pox-helper.clar new file mode 100644 index 0000000000..426e225ba8 --- /dev/null +++ b/contrib/core-contract-tests/contracts/pox/pox-helper.clar @@ -0,0 +1,3 @@ + +(define-read-only (get-bbh) + burn-block-height) diff --git a/contrib/core-contract-tests/tests/bns/name_register.test.ts b/contrib/core-contract-tests/tests/bns/name_register.test.ts index 0647b0a9cc..5df577406e 100644 --- a/contrib/core-contract-tests/tests/bns/name_register.test.ts +++ b/contrib/core-contract-tests/tests/bns/name_register.test.ts @@ -366,8 +366,8 @@ describe("name revealing workflow", () => { Cl.tuple({ owner: Cl.standardPrincipal(bob), ["zonefile-hash"]: Cl.bufferFromUtf8(cases[0].zonefile), - ["lease-ending-at"]: Cl.some(Cl.uint(16)), - ["lease-started-at"]: Cl.uint(6), + ["lease-ending-at"]: Cl.some(Cl.uint(simnet.blockHeight + 10)), + ["lease-started-at"]: Cl.uint(simnet.blockHeight), }) ); }); @@ -589,8 +589,8 @@ describe("register a name again before and after expiration", () => { Cl.tuple({ owner: Cl.standardPrincipal(charlie), ["zonefile-hash"]: Cl.bufferFromAscii("CHARLIE"), - ["lease-ending-at"]: Cl.some(Cl.uint(5029)), - ["lease-started-at"]: Cl.uint(5019), + ["lease-ending-at"]: Cl.some(Cl.uint(simnet.blockHeight + 10)), + ["lease-started-at"]: Cl.uint(simnet.blockHeight), }) ); }); diff --git a/contrib/core-contract-tests/tests/bns/name_register_test.ts b/contrib/core-contract-tests/tests/bns/name_register_test.ts index b96e4576b3..8b2d486b1e 100644 --- a/contrib/core-contract-tests/tests/bns/name_register_test.ts +++ b/contrib/core-contract-tests/tests/bns/name_register_test.ts @@ -98,7 +98,7 @@ Clarinet.test({ ], cases[0].nameOwner.address), ]); - assertEquals(block.height, 2); + assertEquals(block.height, 3); block.receipts[0].result .expectErr() .expectInt(1005); @@ -119,7 +119,7 @@ Clarinet.test({ ], cases[1].namespaceOwner.address), ]); - assertEquals(block.height, 3); + assertEquals(block.height, 4); block.receipts[0].result .expectOk() .expectUint(144 + block.height - 1); @@ -136,7 +136,7 @@ Clarinet.test({ ], cases[1].namespaceOwner.address), ]); - assertEquals(block.height, 4); + assertEquals(block.height, 5); block.receipts[0].result .expectOk() .expectBool(true); @@ -158,7 +158,7 @@ Clarinet.test({ ], bob.address), ]); - assertEquals(block.height, 5); + assertEquals(block.height, 6); block.receipts[0].result .expectOk() .expectUint(144 + block.height - 1); @@ -174,7 +174,7 @@ Clarinet.test({ ], bob.address), ]); - assertEquals(block.height, 6); + assertEquals(block.height, 7); block.receipts[0].result .expectErr() .expectInt(2004); @@ -195,7 +195,7 @@ Clarinet.test({ ], cases[0].namespaceOwner.address), ]); - assertEquals(block.height, 7); + assertEquals(block.height, 8); block.receipts[0].result .expectOk() .expectUint(144 + block.height - 1); @@ -212,7 +212,7 @@ Clarinet.test({ ], cases[0].namespaceOwner.address), ]); - assertEquals(block.height, 8); + assertEquals(block.height, 9); block.receipts[0].result .expectOk() .expectBool(true); @@ -225,7 +225,7 @@ Clarinet.test({ ], cases[0].namespaceOwner.address), ]); - assertEquals(block.height, 9); + assertEquals(block.height, 10); block.receipts[0].result .expectOk() .expectBool(true); @@ -244,7 +244,7 @@ Clarinet.test({ ], bob.address), ]); - assertEquals(block.height, 10); + assertEquals(block.height, 11); block.receipts[0].result .expectErr() .expectInt(2001); @@ -266,7 +266,7 @@ Clarinet.test({ ], bob.address), ]); - assertEquals(block.height, 11); + assertEquals(block.height, 12); block.receipts[0].result .expectOk() .expectUint(144 + block.height - 1); @@ -282,7 +282,7 @@ Clarinet.test({ ], bob.address), ]); - assertEquals(block.height, 12); + assertEquals(block.height, 13); block.receipts[0].result .expectErr() .expectInt(2007); @@ -304,7 +304,7 @@ Clarinet.test({ ], bob.address), ]); - assertEquals(block.height, 13); + assertEquals(block.height, 14); block.receipts[0].result .expectOk() .expectUint(144 + block.height - 1); @@ -320,7 +320,7 @@ Clarinet.test({ ], bob.address), ]); - assertEquals(block.height, 14); + assertEquals(block.height, 15); block.receipts[0].result .expectErr() .expectInt(2022); @@ -342,7 +342,7 @@ Clarinet.test({ ], bob.address), ]); - assertEquals(block.height, 15); + assertEquals(block.height, 16); block.receipts[0].result .expectOk() .expectUint(144 + block.height - 1); @@ -358,7 +358,7 @@ Clarinet.test({ ], bob.address), ]); - assertEquals(block.height, 16); + assertEquals(block.height, 17); block.receipts[0].result .expectOk() .expectBool(true); @@ -388,7 +388,7 @@ Clarinet.test({ ], bob.address), ]); - assertEquals(block.height, 17); + assertEquals(block.height, 18); block.receipts[0].result .expectErr() .expectInt(2004); @@ -412,7 +412,7 @@ Clarinet.test({ ], charlie.address), ]); - assertEquals(block.height, 18); + assertEquals(block.height, 19); block.receipts[0].result .expectOk() .expectUint(144 + block.height - 1); @@ -428,7 +428,7 @@ Clarinet.test({ ], charlie.address), ]); - assertEquals(block.height, 19); + assertEquals(block.height, 20); block.receipts[0].result .expectErr() .expectInt(2004); @@ -452,7 +452,7 @@ Clarinet.test({ ], bob.address), ]); - assertEquals(block.height, 20); + assertEquals(block.height, 21); block.receipts[0].result .expectOk() .expectUint(144 + block.height - 1); @@ -468,7 +468,7 @@ Clarinet.test({ ], bob.address), ]); - assertEquals(block.height, 21); + assertEquals(block.height, 22); block.receipts[0].result .expectErr() .expectInt(3001); diff --git a/contrib/core-contract-tests/tests/pox/check_not_prepare_phase.test.ts b/contrib/core-contract-tests/tests/pox/check_not_prepare_phase.test.ts new file mode 100644 index 0000000000..f87d013707 --- /dev/null +++ b/contrib/core-contract-tests/tests/pox/check_not_prepare_phase.test.ts @@ -0,0 +1,54 @@ +import { Cl } from "@stacks/transactions"; +import { beforeEach, describe, expect, it } from "vitest"; +import { createHash } from "node:crypto"; + +const accounts = simnet.getAccounts(); +const alice = accounts.get("wallet_1")!; +const bob = accounts.get("wallet_2")!; +const charlie = accounts.get("wallet_3")!; + +function expectBurnBlockHeight(height: number) { + const { result: bbh } = simnet.callReadOnlyFn( + "pox-helper", + "get-bbh", + [], + alice + ); + expect(bbh).toBeUint(height); +} + +describe("test pox prepare phase check", () => { + it("should return true during prepare phase (1000 - 1049)", () => { + let { result } = simnet.callReadOnlyFn( + "pox-4", + "check-prepare-phase", + [Cl.uint(999)], + alice + ); + expect(result).toBeBool(false); + + ({ result } = simnet.callReadOnlyFn( + "pox-4", + "check-prepare-phase", + [Cl.uint(1000)], + alice + )); + expect(result).toBeBool(true); + + ({ result } = simnet.callReadOnlyFn( + "pox-4", + "check-prepare-phase", + [Cl.uint(1049)], + alice + )); + expect(result).toBeBool(true); + + ({ result } = simnet.callReadOnlyFn( + "pox-4", + "check-prepare-phase", + [Cl.uint(1050)], + alice + )); + expect(result).toBeBool(false); + }); +}); diff --git a/pox-locking/src/pox_3.rs b/pox-locking/src/pox_3.rs index cdfd0c740c..3323a27e34 100644 --- a/pox-locking/src/pox_3.rs +++ b/pox-locking/src/pox_3.rs @@ -33,33 +33,6 @@ use crate::{LockingError, POX_3_NAME}; /////////////////////// PoX-3 ///////////////////////////////// -/// is a PoX-3 function call read only? -pub(crate) fn is_read_only(func_name: &str) -> bool { - "get-pox-rejection" == func_name - || "is-pox-active" == func_name - || "burn-height-to-reward-cycle" == func_name - || "reward-cycle-to-burn-height" == func_name - || "current-pox-reward-cycle" == func_name - || "get-stacker-info" == func_name - || "get-check-delegation" == func_name - || "get-reward-set-size" == func_name - || "next-cycle-rejection-votes" == func_name - || "get-total-ustx-stacked" == func_name - || "get-reward-set-pox-address" == func_name - || "get-stacking-minimum" == func_name - || "check-pox-addr-version" == func_name - || "check-pox-addr-hashbytes" == func_name - || "check-pox-lock-period" == func_name - || "can-stack-stx" == func_name - || "minimal-can-stack-stx" == func_name - || "get-pox-info" == func_name - || "get-delegation-info" == func_name - || "get-allowance-contract-callers" == func_name - || "get-num-reward-set-pox-addresses" == func_name - || "get-partial-stacked-by-cycle" == func_name - || "get-total-pox-rejection" == func_name -} - /// Lock up STX for PoX for a time. Does NOT touch the account nonce. pub fn pox_lock_v3( db: &mut ClarityDatabase, diff --git a/stackslib/src/chainstate/stacks/boot/mod.rs b/stackslib/src/chainstate/stacks/boot/mod.rs index 887dd20900..ee26cea60d 100644 --- a/stackslib/src/chainstate/stacks/boot/mod.rs +++ b/stackslib/src/chainstate/stacks/boot/mod.rs @@ -97,10 +97,8 @@ lazy_static! { format!("{}\n{}", BOOT_CODE_POX_MAINNET_CONSTS, POX_3_BODY); pub static ref POX_3_TESTNET_CODE: String = format!("{}\n{}", BOOT_CODE_POX_TESTNET_CONSTS, POX_3_BODY); - pub static ref POX_4_MAINNET_CODE: String = - format!("{}\n{}", BOOT_CODE_POX_MAINNET_CONSTS, POX_4_BODY); - pub static ref POX_4_TESTNET_CODE: String = - format!("{}\n{}", BOOT_CODE_POX_TESTNET_CONSTS, POX_4_BODY); + pub static ref POX_4_MAINNET_CODE: String = format!("{}", POX_4_BODY); + pub static ref POX_4_TESTNET_CODE: String = format!("{}", POX_4_BODY); pub static ref BOOT_CODE_COST_VOTING_TESTNET: String = make_testnet_cost_voting(); pub static ref STACKS_BOOT_CODE_MAINNET: [(&'static str, &'static str); 6] = [ ("pox", &BOOT_CODE_POX_MAINNET), diff --git a/stackslib/src/chainstate/stacks/boot/pox-4.clar b/stackslib/src/chainstate/stacks/boot/pox-4.clar index 64a00e439a..5b59e4426c 100644 --- a/stackslib/src/chainstate/stacks/boot/pox-4.clar +++ b/stackslib/src/chainstate/stacks/boot/pox-4.clar @@ -33,16 +33,13 @@ (define-constant POX_REJECTION_FRACTION u25) ;; Valid values for burnchain address versions. -;; These first four correspond to address hash modes in Stacks 2.1, -;; and are defined in pox-mainnet.clar and pox-testnet.clar (so they -;; cannot be defined here again). ;; (define-constant ADDRESS_VERSION_P2PKH 0x00) ;; (define-constant ADDRESS_VERSION_P2SH 0x01) ;; (define-constant ADDRESS_VERSION_P2WPKH 0x02) ;; (define-constant ADDRESS_VERSION_P2WSH 0x03) -(define-constant ADDRESS_VERSION_NATIVE_P2WPKH 0x04) -(define-constant ADDRESS_VERSION_NATIVE_P2WSH 0x05) -(define-constant ADDRESS_VERSION_NATIVE_P2TR 0x06) +;; (define-constant ADDRESS_VERSION_NATIVE_P2WPKH 0x04) +;; (define-constant ADDRESS_VERSION_NATIVE_P2WSH 0x05) +;; (define-constant ADDRESS_VERSION_NATIVE_P2TR 0x06) ;; Keep these constants in lock-step with the address version buffs above ;; Maximum value of an address version as a uint (define-constant MAX_ADDRESS_VERSION u6) @@ -53,6 +50,21 @@ ;; (0x05 and 0x06 have 32-byte hashbytes) (define-constant MAX_ADDRESS_VERSION_BUFF_32 u6) +;; PoX mainnet constants +;; Min/max number of reward cycles uSTX can be locked for +(define-constant MIN_POX_REWARD_CYCLES u1) +(define-constant MAX_POX_REWARD_CYCLES u12) + +;; Default length of the PoX registration window, in burnchain blocks. +(define-constant PREPARE_CYCLE_LENGTH (if is-in-mainnet u100 u50)) + +;; Default length of the PoX reward cycle, in burnchain blocks. +(define-constant REWARD_CYCLE_LENGTH (if is-in-mainnet u2100 u1050)) + +;; Stacking thresholds +(define-constant STACKING_THRESHOLD_25 (if is-in-mainnet u20000 u8000)) +(define-constant STACKING_THRESHOLD_100 (if is-in-mainnet u5000 u2000)) + ;; Data vars that store a copy of the burnchain configuration. ;; Implemented as data-vars, so that different configurations can be ;; used in e.g. test harnesses. @@ -509,11 +521,13 @@ (and (>= lock-period MIN_POX_REWARD_CYCLES) (<= lock-period MAX_POX_REWARD_CYCLES))) -;; Is the current block height outside of the prepare phase? -(define-read-only (check-not-prepare-phase) - (let ((cycle-length (var-get pox-reward-cycle-length)))) - (< (mod (- burn-block-height (var-get first-burnchain-block-height)) cycle-length) - (- cycle-length (var-get pox-prepare-cycle-length)))) +;; Is the given burn block height in the prepare phase? +;; This computes `((height - first-burnchain-block-height) + pox-prepare-cycle-length) % pox-reward-cycle-length) < pox-prepare-cycle-length`. +(define-read-only (check-prepare-phase (height uint)) + (let ((prepare-cycle-length (var-get pox-prepare-cycle-length))) + (< (mod (+ (- height (var-get first-burnchain-block-height)) prepare-cycle-length) + (var-get pox-reward-cycle-length)) + prepare-cycle-length))) ;; Evaluate if a participant can stack an amount of STX for a given period. ;; This method is designed as a read-only method so that it can be used as @@ -553,7 +567,7 @@ (err ERR_STACKING_INVALID_LOCK_PERIOD)) ;; stacking must not happen during prepare phase - (asserts! (check-not-prepare-phase) + (asserts! (not (check-prepare-phase burn-block-height)) (err ERR_STACKING_DURING_PREPARE_PHASE)) ;; address version must be valid @@ -1025,7 +1039,7 @@ stacker: tx-sender, add-amount: increase-by }))) (err ERR_STACKING_UNREACHABLE)) - ;; NOTE: stacking-state map is unchanged: it does not track amount-stacked in pox-4 + ;; NOTE: stacking-state map is unchanged: it does not track amount-stacked in PoX-4 (ok { stacker: tx-sender, total-locked: (+ amount-stacked increase-by)}))) ;; Extend an active Stacking lock. From 70e8d32b22429c6859f80e591e2d017733943c8b Mon Sep 17 00:00:00 2001 From: friedger Date: Tue, 12 Dec 2023 12:47:30 +0100 Subject: [PATCH 3/3] chore: re-add deleted code --- pox-locking/src/pox_3.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pox-locking/src/pox_3.rs b/pox-locking/src/pox_3.rs index 3323a27e34..cdfd0c740c 100644 --- a/pox-locking/src/pox_3.rs +++ b/pox-locking/src/pox_3.rs @@ -33,6 +33,33 @@ use crate::{LockingError, POX_3_NAME}; /////////////////////// PoX-3 ///////////////////////////////// +/// is a PoX-3 function call read only? +pub(crate) fn is_read_only(func_name: &str) -> bool { + "get-pox-rejection" == func_name + || "is-pox-active" == func_name + || "burn-height-to-reward-cycle" == func_name + || "reward-cycle-to-burn-height" == func_name + || "current-pox-reward-cycle" == func_name + || "get-stacker-info" == func_name + || "get-check-delegation" == func_name + || "get-reward-set-size" == func_name + || "next-cycle-rejection-votes" == func_name + || "get-total-ustx-stacked" == func_name + || "get-reward-set-pox-address" == func_name + || "get-stacking-minimum" == func_name + || "check-pox-addr-version" == func_name + || "check-pox-addr-hashbytes" == func_name + || "check-pox-lock-period" == func_name + || "can-stack-stx" == func_name + || "minimal-can-stack-stx" == func_name + || "get-pox-info" == func_name + || "get-delegation-info" == func_name + || "get-allowance-contract-callers" == func_name + || "get-num-reward-set-pox-addresses" == func_name + || "get-partial-stacked-by-cycle" == func_name + || "get-total-pox-rejection" == func_name +} + /// Lock up STX for PoX for a time. Does NOT touch the account nonce. pub fn pox_lock_v3( db: &mut ClarityDatabase,