diff --git a/pox-locking/src/lib.rs b/pox-locking/src/lib.rs
index b195f4cc9b7..05303a4d9b5 100644
--- a/pox-locking/src/lib.rs
+++ b/pox-locking/src/lib.rs
@@ -38,6 +38,7 @@ mod events;
mod pox_1;
mod pox_2;
mod pox_3;
+mod pox_4;
#[derive(Debug)]
pub enum LockingError {
@@ -52,6 +53,7 @@ pub enum LockingError {
pub const POX_1_NAME: &str = "pox";
pub const POX_2_NAME: &str = "pox-2";
pub const POX_3_NAME: &str = "pox-3";
+pub const POX_4_NAME: &str = "pox-4";
/// Handle special cases of contract-calls -- namely, those into PoX that should lock up STX
pub fn handle_contract_call_special_cases(
@@ -113,6 +115,15 @@ pub fn handle_contract_call_special_cases(
args,
result,
);
+ } else if *contract_id == boot_code_id(POX_4_NAME, global_context.mainnet) {
+ return pox_4::handle_contract_call(
+ global_context,
+ sender,
+ contract_id,
+ function_name,
+ args,
+ result,
+ );
}
Ok(())
diff --git a/pox-locking/src/pox_4.rs b/pox-locking/src/pox_4.rs
new file mode 100644
index 00000000000..82bf38cdb1a
--- /dev/null
+++ b/pox-locking/src/pox_4.rs
@@ -0,0 +1,395 @@
+// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
+// Copyright (C) 2020-2023 Stacks Open Internet Foundation
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use clarity::boot_util::boot_code_id;
+use clarity::vm::contexts::GlobalContext;
+use clarity::vm::costs::cost_functions::ClarityCostFunction;
+use clarity::vm::costs::runtime_cost;
+use clarity::vm::database::{ClarityDatabase, STXBalance};
+use clarity::vm::errors::{Error as ClarityError, RuntimeErrorType};
+use clarity::vm::events::{STXEventType, STXLockEventData, StacksTransactionEvent};
+use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier};
+use clarity::vm::{Environment, Value};
+use slog::{slog_debug, slog_error};
+use stacks_common::{debug, error};
+
+use crate::events::synthesize_pox_2_or_3_event_info;
+// Note: PoX-4 uses the same contract-call result parsing routines as PoX-2
+use crate::pox_2::{parse_pox_extend_result, parse_pox_increase, parse_pox_stacking_result};
+use crate::{LockingError, POX_4_NAME};
+
+/////////////////////// PoX-4 /////////////////////////////////
+
+/// Lock up STX for PoX for a time. Does NOT touch the account nonce.
+pub fn pox_lock_v4(
+ db: &mut ClarityDatabase,
+ principal: &PrincipalData,
+ lock_amount: u128,
+ unlock_burn_height: u64,
+) -> Result<(), LockingError> {
+ assert!(unlock_burn_height > 0);
+ assert!(lock_amount > 0);
+
+ let mut snapshot = db.get_stx_balance_snapshot(principal);
+
+ if snapshot.has_locked_tokens() {
+ return Err(LockingError::PoxAlreadyLocked);
+ }
+ if !snapshot.can_transfer(lock_amount) {
+ return Err(LockingError::PoxInsufficientBalance);
+ }
+ snapshot.lock_tokens_v4(lock_amount, unlock_burn_height);
+
+ debug!(
+ "PoX v4 lock applied";
+ "pox_locked_ustx" => snapshot.balance().amount_locked(),
+ "available_ustx" => snapshot.balance().amount_unlocked(),
+ "unlock_burn_height" => unlock_burn_height,
+ "account" => %principal,
+ );
+
+ snapshot.save();
+ Ok(())
+}
+
+/// Extend a STX lock up for PoX for a time. Does NOT touch the account nonce.
+/// Returns Ok(lock_amount) when successful
+///
+/// # Errors
+/// - Returns Error::PoxExtendNotLocked if this function was called on an account
+/// which isn't locked. This *should* have been checked by the PoX v4 contract,
+/// so this should surface in a panic.
+pub fn pox_lock_extend_v4(
+ db: &mut ClarityDatabase,
+ principal: &PrincipalData,
+ unlock_burn_height: u64,
+) -> Result {
+ assert!(unlock_burn_height > 0);
+
+ let mut snapshot = db.get_stx_balance_snapshot(principal);
+
+ if !snapshot.has_locked_tokens() {
+ return Err(LockingError::PoxExtendNotLocked);
+ }
+
+ snapshot.extend_lock_v4(unlock_burn_height);
+
+ let amount_locked = snapshot.balance().amount_locked();
+
+ debug!(
+ "PoX v4 lock applied";
+ "pox_locked_ustx" => amount_locked,
+ "available_ustx" => snapshot.balance().amount_unlocked(),
+ "unlock_burn_height" => unlock_burn_height,
+ "account" => %principal,
+ );
+
+ snapshot.save();
+ Ok(amount_locked)
+}
+
+/// Increase a STX lock up for PoX-4. Does NOT touch the account nonce.
+/// Returns Ok( account snapshot ) when successful
+///
+/// # Errors
+/// - Returns Error::PoxExtendNotLocked if this function was called on an account
+/// which isn't locked. This *should* have been checked by the PoX v4 contract,
+/// so this should surface in a panic.
+pub fn pox_lock_increase_v4(
+ db: &mut ClarityDatabase,
+ principal: &PrincipalData,
+ new_total_locked: u128,
+) -> Result {
+ assert!(new_total_locked > 0);
+
+ let mut snapshot = db.get_stx_balance_snapshot(principal);
+
+ if !snapshot.has_locked_tokens() {
+ return Err(LockingError::PoxExtendNotLocked);
+ }
+
+ let bal = snapshot.canonical_balance_repr();
+ let total_amount = bal
+ .amount_unlocked()
+ .checked_add(bal.amount_locked())
+ .expect("STX balance overflowed u128");
+ if total_amount < new_total_locked {
+ return Err(LockingError::PoxInsufficientBalance);
+ }
+
+ if bal.amount_locked() > new_total_locked {
+ return Err(LockingError::PoxInvalidIncrease);
+ }
+
+ snapshot.increase_lock_v4(new_total_locked);
+
+ let out_balance = snapshot.canonical_balance_repr();
+
+ debug!(
+ "PoX v4 lock increased";
+ "pox_locked_ustx" => out_balance.amount_locked(),
+ "available_ustx" => out_balance.amount_unlocked(),
+ "unlock_burn_height" => out_balance.unlock_height(),
+ "account" => %principal,
+ );
+
+ snapshot.save();
+ Ok(out_balance)
+}
+
+/////////////// PoX-4 //////////////////////////////////////////
+
+/// Handle responses from stack-stx and delegate-stack-stx in pox-4 -- functions that *lock up* STX
+fn handle_stack_lockup_pox_v4(
+ global_context: &mut GlobalContext,
+ function_name: &str,
+ value: &Value,
+) -> Result