diff --git a/pox-locking/src/lib.rs b/pox-locking/src/lib.rs index 05303a4d9b..1127e6b667 100644 --- a/pox-locking/src/lib.rs +++ b/pox-locking/src/lib.rs @@ -107,6 +107,20 @@ pub fn handle_contract_call_special_cases( result, ); } else if *contract_id == boot_code_id(POX_3_NAME, global_context.mainnet) { + if !pox_3::is_read_only(function_name) && global_context.epoch_id >= StacksEpochId::Epoch25 + { + warn!("PoX-3 function call attempted on an account after Epoch 2.5"; + "v3_unlock_ht" => global_context.database.get_v3_unlock_height(), + "current_burn_ht" => global_context.database.get_current_burnchain_block_height(), + "function_name" => function_name, + "contract_id" => %contract_id + ); + return Err(ClarityError::Runtime( + RuntimeErrorType::DefunctPoxContract, + None, + )); + } + return pox_3::handle_contract_call( global_context, sender, 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, diff --git a/stackslib/src/chainstate/stacks/boot/pox_4_tests.rs b/stackslib/src/chainstate/stacks/boot/pox_4_tests.rs index 987ab35a4c..9193fb80f5 100644 --- a/stackslib/src/chainstate/stacks/boot/pox_4_tests.rs +++ b/stackslib/src/chainstate/stacks/boot/pox_4_tests.rs @@ -899,8 +899,14 @@ fn pox_3_defunct() { ); burnchain.pox_constants = pox_constants.clone(); - let (mut peer, keys) = - instantiate_pox_peer_with_epoch(&burnchain, function_name!(), Some(epochs.clone()), None); + let observer = TestEventObserver::new(); + + let (mut peer, keys) = instantiate_pox_peer_with_epoch( + &burnchain, + function_name!(), + Some(epochs.clone()), + Some(&observer), + ); assert_eq!(burnchain.pox_constants.reward_slots(), 6); let mut coinbase_nonce = 0; @@ -949,6 +955,28 @@ fn pox_3_defunct() { info!("Submitting stacking txs with pox3"); latest_block = peer.tenure_with_txs(&txs, &mut coinbase_nonce); + info!("Checking that stackers have no STX locked"); + let balances = balances_from_keys(&mut peer, &latest_block, &keys); + assert_eq!(balances[0].amount_locked(), 0); + assert_eq!(balances[1].amount_locked(), 0); + + info!("Checking tx receipts, all `pox3` calls should have returned `(err ...)`"); + let last_observer_block = observer + .get_blocks() + .last() + .unwrap() + .clone(); + + let receipts = last_observer_block.receipts + .iter() + .filter(|receipt| match &receipt.result { + Value::Response(r) => !r.committed, + _ => false, + }) + .collect::>(); + + assert_eq!(receipts.len(), 4); + // Advance to start of rewards cycle stackers are participating in let target_height = burnchain.pox_constants.pox_4_activation_height + 5; while get_tip(peer.sortdb.as_ref()).block_height < u64::from(target_height) { @@ -985,8 +1013,7 @@ fn pox_3_defunct() { } } -/// Test that we can lock STX for a couple cycles after pox4 starts, -/// and that it unlocks after the desired number of cycles +// Test that STX locked in pox3 automatically unlocks at `v3_unlock_height` #[test] fn pox_3_unlocks() { // Config for this test