From 3a8a0c968909d7c6bc7de62630c82b70387cb1cb Mon Sep 17 00:00:00 2001 From: devwums Date: Sat, 30 May 2026 09:22:03 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20add=20token=20pause=20=E2=80=94=20admin?= =?UTF-8?q?=20emergency=20halt=20for=20all=20transfers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- veritixpay/contract/token/src/lib.rs | 4 +++ veritixpay/contract/token/src/pause.rs | 29 +++++++++++++++ veritixpay/contract/token/src/pause_test.rs | 35 +++++++++++++++++++ .../contract/token/src/storage_types.rs | 3 ++ 4 files changed, 71 insertions(+) create mode 100644 veritixpay/contract/token/src/pause.rs create mode 100644 veritixpay/contract/token/src/pause_test.rs diff --git a/veritixpay/contract/token/src/lib.rs b/veritixpay/contract/token/src/lib.rs index d688869..806ed47 100644 --- a/veritixpay/contract/token/src/lib.rs +++ b/veritixpay/contract/token/src/lib.rs @@ -11,6 +11,7 @@ pub mod dispute; pub mod escrow; pub mod freeze; pub mod metadata; +pub mod pause; pub mod recurring; pub mod splitter; pub mod storage_types; @@ -42,6 +43,9 @@ mod splitter_test; #[cfg(test)] mod dispute_test; +#[cfg(test)] +mod pause_test; + #[cfg(test)] mod recurring_test; diff --git a/veritixpay/contract/token/src/pause.rs b/veritixpay/contract/token/src/pause.rs new file mode 100644 index 0000000..1df633c --- /dev/null +++ b/veritixpay/contract/token/src/pause.rs @@ -0,0 +1,29 @@ +use crate::admin::check_admin; +use crate::storage_types::DataKey; +use soroban_sdk::{symbol_short, Address, Env}; + +/// Returns `true` if the contract is currently paused. +pub fn is_paused(e: &Env) -> bool { + e.storage().instance().get(&DataKey::Paused).unwrap_or(false) +} + +/// Panics if the contract is paused; call at the top of transfer/mint/burn. +pub fn require_not_paused(e: &Env) { + if is_paused(e) { + panic!("ContractPaused: all transfers are currently paused"); + } +} + +/// Admin pauses all token transfers globally. +pub fn pause(e: &Env, admin: Address) { + check_admin(e, &admin); + e.storage().instance().set(&DataKey::Paused, &true); + e.events().publish((symbol_short!("paused"), admin), ()); +} + +/// Admin unpauses the contract, restoring normal operation. +pub fn unpause(e: &Env, admin: Address) { + check_admin(e, &admin); + e.storage().instance().remove(&DataKey::Paused); + e.events().publish((symbol_short!("unpaused"), admin), ()); +} diff --git a/veritixpay/contract/token/src/pause_test.rs b/veritixpay/contract/token/src/pause_test.rs new file mode 100644 index 0000000..3e8fe29 --- /dev/null +++ b/veritixpay/contract/token/src/pause_test.rs @@ -0,0 +1,35 @@ +use soroban_sdk::{testutils::Address as _, Address, Env}; +use crate::contract::VeritixToken; +use crate::pause::{is_paused, pause, require_not_paused, unpause}; + +fn setup_env() -> Env { let e = Env::default(); e.mock_all_auths(); e } + +#[test] +fn test_pause_and_unpause_toggles_state() { + let e = setup_env(); + let cid = e.register_contract(None, VeritixToken); + let admin = Address::generate(&e); + + e.as_contract(&cid, || { + crate::admin::write_admin(&e, &admin); + assert!(!is_paused(&e)); + pause(&e, admin.clone()); + assert!(is_paused(&e)); + unpause(&e, admin.clone()); + assert!(!is_paused(&e)); + }); +} + +#[test] +#[should_panic(expected = "ContractPaused")] +fn test_require_not_paused_panics_when_paused() { + let e = setup_env(); + let cid = e.register_contract(None, VeritixToken); + let admin = Address::generate(&e); + + e.as_contract(&cid, || { + crate::admin::write_admin(&e, &admin); + pause(&e, admin.clone()); + require_not_paused(&e); + }); +} diff --git a/veritixpay/contract/token/src/storage_types.rs b/veritixpay/contract/token/src/storage_types.rs index b4d22c1..2eed9ab 100644 --- a/veritixpay/contract/token/src/storage_types.rs +++ b/veritixpay/contract/token/src/storage_types.rs @@ -45,6 +45,9 @@ pub enum DataKey { // Stores per-address freeze status. Freeze(Address), + + // Global emergency pause flag. + Paused, } pub fn read_persistent_record(e: &Env, key: &DataKey, missing_message: &'static str) -> T