From eb567cb32797a94714a902833289e2576f851b1f Mon Sep 17 00:00:00 2001 From: Septen Date: Fri, 14 May 2021 15:56:23 +0300 Subject: [PATCH 01/11] EthConnector: make AdminControlled and pausable. --- src/admin_controlled.rs | 26 ++++++++++++++++++++ src/connector.rs | 54 +++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 12 +++++++++ src/parameters.rs | 8 ++++++ src/storage.rs | 1 + 5 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 src/admin_controlled.rs diff --git a/src/admin_controlled.rs b/src/admin_controlled.rs new file mode 100644 index 000000000..b2c27b0af --- /dev/null +++ b/src/admin_controlled.rs @@ -0,0 +1,26 @@ +use crate::sdk; + +pub type PausedMask = u8; + +pub trait AdminControlled { + fn is_owner(&self) -> bool { + sdk::current_account_id() == sdk::predecessor_account_id() + } + + /// Return the current mask representing all paused events. + fn get_paused(&self) -> PausedMask; + + /// Update mask with all paused events. + /// Implementor is responsible for guaranteeing that this function can only be + /// called by owner of the contract. + fn set_paused(&mut self, paused: PausedMask); + + /// Return if the contract is paused for the current flag and user + fn is_paused(&self, flag: PausedMask) -> bool { + (self.get_paused() & flag) != 0 && !self.is_owner() + } + + fn check_not_paused(&self, flag: PausedMask) { + assert!(!self.is_paused(flag)); + } +} diff --git a/src/connector.rs b/src/connector.rs index 91b6b4830..f6517b125 100644 --- a/src/connector.rs +++ b/src/connector.rs @@ -8,6 +8,7 @@ use crate::engine::Engine; use crate::prelude::*; use crate::prover::validate_eth_address; use crate::storage::{self, EthConnectorStorageId, KeyPrefix}; +use crate::admin_controlled::{AdminControlled, PausedMask}; #[cfg(feature = "log")] use alloc::format; use borsh::{BorshDeserialize, BorshSerialize}; @@ -17,10 +18,15 @@ const GAS_FOR_FINISH_DEPOSIT: Gas = 50_000_000_000_000; const GAS_FOR_VERIFY_LOG_ENTRY: Gas = 40_000_000_000_000; const GAS_FOR_TRANSFER_CALL: Gas = 40_000_000_000_000; +const UNPAUSE_ALL: PausedMask = 0; +const PAUSE_DEPOSIT: PausedMask = 1 << 0; +const PAUSE_WITHDRAW: PausedMask = 1 << 1; + #[derive(BorshSerialize, BorshDeserialize)] pub struct EthConnectorContract { contract: EthConnector, ft: FungibleToken, + paused_mask: PausedMask, } /// eth-connector specific data @@ -49,6 +55,7 @@ impl EthConnectorContract { Self { contract: Self::get_contract_data(&EthConnectorStorageId::Contract), ft: Self::get_contract_data(&EthConnectorStorageId::FungibleToken), + paused_mask: Self::get_contract_data(&EthConnectorStorageId::PausedMask), } } @@ -82,14 +89,21 @@ impl EthConnectorContract { prover_account: args.prover_account, eth_custodian_address: validate_eth_address(args.eth_custodian_address), }; - // Save th-connector specific data + // Save eth-connector specific data sdk::save_contract( &Self::get_contract_key(&EthConnectorStorageId::Contract), &contract_data, ); + + let paused_mask = UNPAUSE_ALL; + sdk::save_contract( + &Self::get_contract_key(&EthConnectorStorageId::PausedMask), + &paused_mask, + ); Self { contract: contract_data, - ft, + ft: ft, + paused_mask: paused_mask, } .save_contract(); } @@ -139,6 +153,8 @@ impl EthConnectorContract { /// Deposit all types of tokens pub fn deposit(&self) { + self.check_not_paused(PAUSE_DEPOSIT); + use crate::prover::Proof; #[cfg(feature = "log")] sdk::log("[Deposit tokens]"); @@ -374,6 +390,8 @@ impl EthConnectorContract { /// Withdraw from NEAR accounts /// NOTE: it should be without any log data pub fn withdraw_near(&mut self) { + self.check_not_paused(PAUSE_WITHDRAW); + sdk::assert_one_yocto(); let args = WithdrawCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); let res = WithdrawResult { @@ -484,6 +502,8 @@ impl EthConnectorContract { /// We starting early checking for message data to avoid `ft_on_transfer` call panics /// But we don't check relayer exists. If relayer doesn't exist we simply not mint/burn the amount of the fee pub fn ft_transfer_call(&mut self) { + //TODO: perhaps need to add pausability functionality here as well? + sdk::assert_one_yocto(); let args = TransferCallCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); @@ -551,6 +571,8 @@ impl EthConnectorContract { /// ft_on_transfer callback function #[allow(clippy::unnecessary_unwrap)] pub fn ft_on_transfer(&mut self, engine: &Engine) { + //TODO: perhaps need to add pausability functionality here as well? + #[cfg(feature = "log")] sdk::log("Call ft_on_trasfer"); let args = FtOnTransfer::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); @@ -607,4 +629,32 @@ impl EthConnectorContract { fn check_used_event(&self, key: &str) -> bool { sdk::storage_has_key(&self.used_event_key(key)) } + + /// Get Eth connector paused flags + pub fn get_paused_flags(&self) { + let data = self.get_paused().try_to_vec().unwrap(); + sdk::return_output(&data[..]); + } + + /// Set Eth connector paused flags + pub fn set_paused_flags(&mut self) { + sdk::assert_private_call(); + + let args = PauseEthConnectorCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + self.set_paused(args.paused_mask); + } +} + +impl AdminControlled for EthConnectorContract { + fn get_paused(&self) -> PausedMask { + self.paused_mask + } + + fn set_paused(&mut self, paused_mask: PausedMask) { + self.paused_mask = paused_mask; + sdk::save_contract( + &Self::get_contract_key(&EthConnectorStorageId::PausedMask), + &self.paused_mask, + ); + } } diff --git a/src/lib.rs b/src/lib.rs index 08f8873fc..fa17ec15c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,8 @@ mod engine; mod fungible_token; #[cfg(feature = "contract")] mod log_entry; +#[cfg(feature = "contract")] +mod admin_controlled; mod precompiles; #[cfg(feature = "contract")] mod prover; @@ -434,6 +436,16 @@ mod contract { EthConnectorContract::get_instance().ft_on_transfer(&engine) } + #[no_mangle] + pub extern "C" fn get_eth_connector_paused_flags() { + EthConnectorContract::get_instance().get_paused_flags() + } + + #[no_mangle] + pub extern "C" fn set_eth_connector_paused_flags() { + EthConnectorContract::get_instance().set_paused_flags() + } + #[cfg(feature = "integration-test")] #[no_mangle] pub extern "C" fn verify_log_entry() { diff --git a/src/parameters.rs b/src/parameters.rs index 00ae65f3f..29cf8dc24 100644 --- a/src/parameters.rs +++ b/src/parameters.rs @@ -7,6 +7,8 @@ use crate::prover::Proof; use crate::types::Balance; #[cfg(feature = "contract")] use crate::types::EthAddress; +#[cfg(feature = "contract")] +use crate::admin_controlled::PausedMask; use crate::types::{AccountId, RawAddress, RawH256, RawU256}; use evm::backend::Log; @@ -287,6 +289,12 @@ pub struct RegisterRelayerCallArgs { pub address: EthAddress, } +#[cfg(feature = "contract")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct PauseEthConnectorCallArgs { + pub paused_mask: PausedMask, +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/storage.rs b/src/storage.rs index 10dab42bf..ce61f9314 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -25,6 +25,7 @@ pub enum EthConnectorStorageId { Contract = 0x0, FungibleToken = 0x1, UsedEvent = 0x2, + PausedMask = 0x3, } /// We can't use const generic over Enum, but we can do it over integral type From d5205d626bf59125b1f002e2ce6cbd157297cfb4 Mon Sep 17 00:00:00 2001 From: Septen Date: Fri, 14 May 2021 17:46:26 +0300 Subject: [PATCH 02/11] EthConnector: add AdminControlled&pausability tests. --- tests/test_connector.rs | 282 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 268 insertions(+), 14 deletions(-) diff --git a/tests/test_connector.rs b/tests/test_connector.rs index b3f005286..58f48fcbe 100644 --- a/tests/test_connector.rs +++ b/tests/test_connector.rs @@ -12,6 +12,11 @@ use byte_slice_cast::AsByteSlice; use near_sdk_sim::transaction::ExecutionStatus; use primitive_types::U256; +pub type PausedMask = u8; +const UNPAUSE_ALL: PausedMask = 0; +const PAUSE_DEPOSIT: PausedMask = 1 << 0; +const PAUSE_WITHDRAW: PausedMask = 1 << 1; + const CONTRACT_ACC: &'static str = "eth_connector.root"; const EXTERNAL_CONTRACT_ACC: &'static str = "eth_recipient.root"; const PROOF_DATA_NEAR: &'static str = r#"{"log_index":0,"log_entry_data":[248,251,148,208,69,247,225,155,36,136,146,75,151,249,193,69,181,229,29,13,137,90,101,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,137,27,39,73,35,139,39,255,88,233,81,8,142,85,176,77,231,29,195,116,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,54,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"receipt_index":0,"receipt_data":[249,2,6,1,130,98,214,185,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,128,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,208,69,247,225,155,36,136,146,75,151,249,193,69,181,229,29,13,137,90,101,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,137,27,39,73,35,139,39,255,88,233,81,8,142,85,176,77,231,29,195,116,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,54,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"header_data":[249,2,12,160,102,166,216,90,249,113,19,154,192,123,231,73,72,196,109,178,111,87,24,184,77,224,31,222,203,163,83,46,31,10,152,43,160,29,204,77,232,222,199,93,122,171,133,181,103,182,204,212,26,211,18,69,27,148,138,116,19,240,161,66,253,64,212,147,71,148,242,208,170,209,213,87,125,27,67,170,77,108,7,250,150,14,95,185,72,147,160,137,203,214,211,135,51,122,241,224,192,99,143,5,175,60,50,48,16,91,79,30,234,202,0,238,225,35,173,175,9,255,207,160,249,6,155,103,84,64,218,62,146,22,213,216,147,200,45,35,251,112,156,10,248,160,1,51,149,35,84,11,204,144,224,202,160,57,88,18,64,136,9,46,94,250,29,211,240,5,167,101,181,222,218,72,245,140,165,214,183,59,172,200,197,244,43,114,203,185,1,0,0,32,0,0,0,0,0,0,4,0,0,32,128,0,128,0,0,0,0,32,0,0,0,0,128,1,16,0,0,0,0,0,0,0,0,1,0,0,0,0,18,128,0,2,0,32,8,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,1,0,0,2,0,8,0,16,0,32,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,10,0,32,0,0,0,0,2,0,0,8,0,0,34,0,0,0,0,0,0,0,0,0,144,0,0,32,0,0,0,8,0,0,0,0,0,64,64,0,0,0,0,0,0,0,0,0,32,0,0,0,8,0,0,0,64,0,0,128,64,0,0,16,0,0,0,0,1,64,0,0,0,0,0,2,18,0,0,0,16,0,0,0,16,0,16,0,0,0,4,0,0,128,0,0,2,0,0,0,32,0,0,0,0,0,0,32,0,0,64,0,64,0,0,128,16,0,0,0,0,2,0,32,16,0,0,68,0,0,0,0,129,0,0,0,0,2,0,128,8,0,0,0,128,0,8,16,8,0,0,0,4,0,0,0,0,132,146,162,104,46,131,154,200,13,131,122,18,29,131,12,132,130,132,96,139,224,9,142,68,117,98,98,97,32,119,97,115,32,104,101,114,101,160,3,77,225,44,138,47,145,239,76,233,166,87,199,16,138,239,111,218,83,244,238,103,225,253,101,162,63,83,80,97,14,44,136,210,143,251,125,3,6,84,139],"proof":[[248,81,160,101,193,98,201,122,99,79,150,77,201,152,125,142,203,159,193,180,191,202,17,225,169,97,183,162,211,201,36,49,254,236,143,128,128,128,128,128,128,128,160,234,163,244,31,238,12,182,10,192,199,135,253,80,240,8,202,13,199,117,5,77,122,34,235,11,193,102,240,148,211,231,117,128,128,128,128,128,128,128,128],[249,2,13,48,185,2,9,249,2,6,1,130,98,214,185,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,128,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,253,248,251,148,208,69,247,225,155,36,136,146,75,151,249,193,69,181,229,29,13,137,90,101,248,66,160,209,66,67,156,39,142,37,218,217,165,7,102,241,83,208,227,210,215,191,43,209,111,194,120,28,75,212,148,178,177,90,157,160,0,0,0,0,0,0,0,0,0,0,0,0,137,27,39,73,35,139,39,255,88,233,81,8,142,85,176,77,231,29,195,116,184,160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,54,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,101,116,104,95,114,101,99,105,112,105,101,110,116,46,114,111,111,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]}"#; @@ -47,6 +52,20 @@ pub struct InitCallArgs { pub eth_custodian_address: String, } +#[derive(BorshSerialize, BorshDeserialize)] +pub struct WithdrawCallArgs { + pub recipient_address: EthAddress, + pub amount: Balance, +} + +#[derive(BorshDeserialize, Debug)] +pub struct WithdrawResult { + pub amount: Balance, + pub recipient_id: EthAddress, + pub eth_custodian_address: EthAddress, +} + + fn init(custodian_address: &str) -> (UserAccount, UserAccount) { let master_account = near_sdk_sim::init_simulator(None); let contract = init_contract(&master_account, CONTRACT_ACC, custodian_address); @@ -140,7 +159,7 @@ fn call_deposit_eth(master_account: &UserAccount, contract: &str) { 10, ); res.assert_success(); - println!("{:#?}", res.promise_results()); + //println!("{:#?}", res.promise_results()); } fn get_near_balance(master_account: &UserAccount, acc: &str, contract: &str) -> u128 { @@ -275,19 +294,6 @@ fn test_eth_deposit_balance_total_supply() { #[test] fn test_withdraw_near() { - #[derive(BorshSerialize, BorshDeserialize)] - pub struct WithdrawCallArgs { - pub recipient_address: EthAddress, - pub amount: Balance, - } - - #[derive(BorshDeserialize, Debug)] - pub struct WithdrawResult { - pub amount: Balance, - pub recipient_id: EthAddress, - pub eth_custodian_address: EthAddress, - } - let (master_account, contract) = init(CUSTODIAN_ADDRESS); call_deposit_near(&contract, CONTRACT_ACC); @@ -606,3 +612,251 @@ fn test_ft_transfer_call_fee_greater_than_amount() { let balance = total_supply_eth(&master_account, CONTRACT_ACC); assert_eq!(balance, 0); } + +fn call_deposit_with_proof(account: &UserAccount, contract: &str, proof: &str) -> Vec> { + let proof: Proof = serde_json::from_str(proof).unwrap(); + let res = account.call( + contract.to_string(), + "deposit", + &proof.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + res.promise_results() +} + +fn call_set_paused_flags(account: &UserAccount, contract: &str, paused_mask: PausedMask) -> ExecutionResult { + let res = account.call( + contract.to_string(), + "set_eth_connector_paused_flags", + &paused_mask.try_to_vec().unwrap(), + DEFAULT_GAS, + 0, + ); + res +} + +fn create_user_account(master_account: &UserAccount) -> UserAccount { + let user_account = master_account.create_user( + "eth_recipient.root".to_string(), + to_yocto("100") + ); + user_account +} + +#[test] +fn test_admin_controlled_only_admin_can_pause() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + let user_account = create_user_account(&master_account); + + // Try to pause from the user - should fail + let res = call_set_paused_flags(&user_account, CONTRACT_ACC, PAUSE_DEPOSIT); + let promises = res.promise_results(); + let p = promises[1].clone(); + match p.unwrap().status() { + ExecutionStatus::Failure(_) => {} + _ => panic!(), + } + + // Try to pause from the admin - should succeed + let res = call_set_paused_flags(&contract, CONTRACT_ACC, PAUSE_DEPOSIT); + res.assert_success(); +} + +#[test] +fn test_admin_controlled_admin_can_peform_actions_when_paused() { + let (_master_account, contract) = init(CUSTODIAN_ADDRESS); + + // 1st deposit call when unpaused - should succeed + let promises = call_deposit_with_proof(&contract, CONTRACT_ACC, PROOF_DATA_NEAR); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } + + let withdraw_amount = 100; + let recipient_addr = validate_eth_address(RECIPIENT_ETH_ADDRESS); + + // 1st withdraw call when unpaused - should succeed + let res = contract.call( + CONTRACT_ACC.to_string(), + "withdraw", + &WithdrawCallArgs { + recipient_address: recipient_addr, + amount: withdraw_amount, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + let promises = res.promise_results(); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } + + // Pause deposit + let res = call_set_paused_flags(&contract, CONTRACT_ACC, PAUSE_DEPOSIT); + res.assert_success(); + + // 2nd deposit call when paused, but the admin is calling it - should succeed + // NB: We can use `PROOF_DATA_ETH` this will be just a different proof but the same deposit + // method which should be paused + let promises = call_deposit_with_proof(&contract, CONTRACT_ACC, PROOF_DATA_ETH); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } + + // Pause withdraw + let res = call_set_paused_flags(&contract, CONTRACT_ACC, PAUSE_WITHDRAW); + res.assert_success(); + + // 2nd withdraw call when paused, but the admin is calling it - should succeed + let res = contract.call( + CONTRACT_ACC.to_string(), + "withdraw", + &WithdrawCallArgs { + recipient_address: recipient_addr, + amount: withdraw_amount, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + let promises = res.promise_results(); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } +} + +#[test] +fn test_deposit_pausability() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + let user_account = create_user_account(&master_account); + + // 1st deposit call - should succeed + let promises = call_deposit_with_proof(&user_account, CONTRACT_ACC, PROOF_DATA_NEAR); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } + + // Pause deposit + let res = call_set_paused_flags(&contract, CONTRACT_ACC, PAUSE_DEPOSIT); + res.assert_success(); + + // 2nd deposit call - should fail + // NB: We can use `PROOF_DATA_ETH` this will be just a different proof but the same deposit + // method which should be paused + let promises = call_deposit_with_proof(&user_account, CONTRACT_ACC, PROOF_DATA_ETH); + let num_promises = promises.len(); + let p = promises[num_promises - 2].clone(); + match p.unwrap().status() { + ExecutionStatus::Failure(_) => {} + _ => panic!(), + } + + // Unpause all + let res = call_set_paused_flags(&contract, CONTRACT_ACC, UNPAUSE_ALL); + res.assert_success(); + + // 3rd deposit call - should succeed + let promises = call_deposit_with_proof(&user_account, CONTRACT_ACC, PROOF_DATA_ETH); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } +} + +#[test] +fn test_withdraw_near_pausability() { + let (master_account, contract) = init(CUSTODIAN_ADDRESS); + let user_account = create_user_account(&master_account); + + call_deposit_near(&contract, CONTRACT_ACC); + + let withdraw_amount = 100; + let recipient_addr = validate_eth_address(RECIPIENT_ETH_ADDRESS); + // 1st withdraw - should succeed + let res = user_account.call( + CONTRACT_ACC.to_string(), + "withdraw", + &WithdrawCallArgs { + recipient_address: recipient_addr, + amount: withdraw_amount, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + let promises = res.promise_results(); + assert!(promises.len() > 1); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } + + // Pause withdraw + let res = call_set_paused_flags(&contract, CONTRACT_ACC, PAUSE_WITHDRAW); + res.assert_success(); + + // 2nd withdraw - should fail + let res = user_account.call( + CONTRACT_ACC.to_string(), + "withdraw", + &WithdrawCallArgs { + recipient_address: recipient_addr, + amount: withdraw_amount, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 1, + ); + let promises = res.promise_results(); + let p = promises[1].clone(); + match p.unwrap().status() { + ExecutionStatus::Failure(_) => {} + _ => panic!(), + } + + // Unpause all + let res = call_set_paused_flags(&contract, CONTRACT_ACC, UNPAUSE_ALL); + res.assert_success(); + + let res = user_account.call( + CONTRACT_ACC.to_string(), + "withdraw", + &WithdrawCallArgs { + recipient_address: recipient_addr, + amount: withdraw_amount, + } + .try_to_vec() + .unwrap(), + DEFAULT_GAS, + 1, + ); + res.assert_success(); + let promises = res.promise_results(); + assert!(promises.len() > 1); + for p in promises.iter() { + assert!(p.is_some()); + let p = p.as_ref().unwrap(); + p.assert_success() + } +} From ad78c9a9f3d111877c2f5bddbd30aa21149a3db2 Mon Sep 17 00:00:00 2001 From: Septen Date: Fri, 14 May 2021 17:55:00 +0300 Subject: [PATCH 03/11] Stylystic fixes. --- src/connector.rs | 9 +++++---- src/lib.rs | 4 ++-- src/parameters.rs | 4 ++-- tests/test_connector.rs | 15 +++++++++++---- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/connector.rs b/src/connector.rs index f6517b125..5e1ba9a93 100644 --- a/src/connector.rs +++ b/src/connector.rs @@ -3,12 +3,12 @@ use crate::parameters::*; use crate::sdk; use crate::types::*; +use crate::admin_controlled::{AdminControlled, PausedMask}; use crate::deposit_event::*; use crate::engine::Engine; use crate::prelude::*; use crate::prover::validate_eth_address; use crate::storage::{self, EthConnectorStorageId, KeyPrefix}; -use crate::admin_controlled::{AdminControlled, PausedMask}; #[cfg(feature = "log")] use alloc::format; use borsh::{BorshDeserialize, BorshSerialize}; @@ -102,8 +102,8 @@ impl EthConnectorContract { ); Self { contract: contract_data, - ft: ft, - paused_mask: paused_mask, + ft, + paused_mask, } .save_contract(); } @@ -640,7 +640,8 @@ impl EthConnectorContract { pub fn set_paused_flags(&mut self) { sdk::assert_private_call(); - let args = PauseEthConnectorCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + let args = + PauseEthConnectorCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); self.set_paused(args.paused_mask); } } diff --git a/src/lib.rs b/src/lib.rs index fa17ec15c..811b3846a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,8 @@ pub mod storage; pub mod transaction; pub mod types; +#[cfg(feature = "contract")] +mod admin_controlled; #[cfg(feature = "contract")] mod connector; #[cfg(feature = "contract")] @@ -26,8 +28,6 @@ mod engine; mod fungible_token; #[cfg(feature = "contract")] mod log_entry; -#[cfg(feature = "contract")] -mod admin_controlled; mod precompiles; #[cfg(feature = "contract")] mod prover; diff --git a/src/parameters.rs b/src/parameters.rs index 29cf8dc24..bc32a7a66 100644 --- a/src/parameters.rs +++ b/src/parameters.rs @@ -1,5 +1,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; +#[cfg(feature = "contract")] +use crate::admin_controlled::PausedMask; use crate::prelude::{String, Vec}; #[cfg(feature = "contract")] use crate::prover::Proof; @@ -7,8 +9,6 @@ use crate::prover::Proof; use crate::types::Balance; #[cfg(feature = "contract")] use crate::types::EthAddress; -#[cfg(feature = "contract")] -use crate::admin_controlled::PausedMask; use crate::types::{AccountId, RawAddress, RawH256, RawU256}; use evm::backend::Log; diff --git a/tests/test_connector.rs b/tests/test_connector.rs index 58f48fcbe..f19d47012 100644 --- a/tests/test_connector.rs +++ b/tests/test_connector.rs @@ -65,7 +65,6 @@ pub struct WithdrawResult { pub eth_custodian_address: EthAddress, } - fn init(custodian_address: &str) -> (UserAccount, UserAccount) { let master_account = near_sdk_sim::init_simulator(None); let contract = init_contract(&master_account, CONTRACT_ACC, custodian_address); @@ -613,7 +612,11 @@ fn test_ft_transfer_call_fee_greater_than_amount() { assert_eq!(balance, 0); } -fn call_deposit_with_proof(account: &UserAccount, contract: &str, proof: &str) -> Vec> { +fn call_deposit_with_proof( + account: &UserAccount, + contract: &str, + proof: &str, +) -> Vec> { let proof: Proof = serde_json::from_str(proof).unwrap(); let res = account.call( contract.to_string(), @@ -625,7 +628,11 @@ fn call_deposit_with_proof(account: &UserAccount, contract: &str, proof: &str) - res.promise_results() } -fn call_set_paused_flags(account: &UserAccount, contract: &str, paused_mask: PausedMask) -> ExecutionResult { +fn call_set_paused_flags( + account: &UserAccount, + contract: &str, + paused_mask: PausedMask, +) -> ExecutionResult { let res = account.call( contract.to_string(), "set_eth_connector_paused_flags", @@ -639,7 +646,7 @@ fn call_set_paused_flags(account: &UserAccount, contract: &str, paused_mask: Pau fn create_user_account(master_account: &UserAccount) -> UserAccount { let user_account = master_account.create_user( "eth_recipient.root".to_string(), - to_yocto("100") + to_yocto("100"), // initial balance ); user_account } From 14759d0bccc5237fac81dd3619be8d22b6e4054b Mon Sep 17 00:00:00 2001 From: Septen Date: Tue, 18 May 2021 18:45:11 +0300 Subject: [PATCH 04/11] AdminControlled: add panic message when paused. --- src/admin_controlled.rs | 2 +- tests/test_connector.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/admin_controlled.rs b/src/admin_controlled.rs index b2c27b0af..68f17d620 100644 --- a/src/admin_controlled.rs +++ b/src/admin_controlled.rs @@ -21,6 +21,6 @@ pub trait AdminControlled { } fn check_not_paused(&self, flag: PausedMask) { - assert!(!self.is_paused(flag)); + assert!(!self.is_paused(flag), "ERR_PAUSED"); } } diff --git a/tests/test_connector.rs b/tests/test_connector.rs index f19d47012..611552105 100644 --- a/tests/test_connector.rs +++ b/tests/test_connector.rs @@ -662,7 +662,7 @@ fn test_admin_controlled_only_admin_can_pause() { let p = promises[1].clone(); match p.unwrap().status() { ExecutionStatus::Failure(_) => {} - _ => panic!(), + _ => panic!("Expected failure as only admin can pause, but user successfully paused"), } // Try to pause from the admin - should succeed @@ -771,7 +771,7 @@ fn test_deposit_pausability() { let p = promises[num_promises - 2].clone(); match p.unwrap().status() { ExecutionStatus::Failure(_) => {} - _ => panic!(), + _ => panic!("Expected failure due to pause, but deposit succeeded"), } // Unpause all @@ -839,7 +839,7 @@ fn test_withdraw_near_pausability() { let p = promises[1].clone(); match p.unwrap().status() { ExecutionStatus::Failure(_) => {} - _ => panic!(), + _ => panic!("Expected failure due to pause, but withdraw succeeded"), } // Unpause all From df199d32dd164339629dd92d48bf99bbc22c4a73 Mon Sep 17 00:00:00 2001 From: Septen Date: Tue, 18 May 2021 18:48:05 +0300 Subject: [PATCH 05/11] AdminControlled: minor refactoring. --- src/admin_controlled.rs | 3 ++- src/connector.rs | 8 ++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/admin_controlled.rs b/src/admin_controlled.rs index 68f17d620..24bd9264e 100644 --- a/src/admin_controlled.rs +++ b/src/admin_controlled.rs @@ -3,6 +3,7 @@ use crate::sdk; pub type PausedMask = u8; pub trait AdminControlled { + /// Returns true if the current account is owner fn is_owner(&self) -> bool { sdk::current_account_id() == sdk::predecessor_account_id() } @@ -20,7 +21,7 @@ pub trait AdminControlled { (self.get_paused() & flag) != 0 && !self.is_owner() } - fn check_not_paused(&self, flag: PausedMask) { + fn assert_not_paused(&self, flag: PausedMask) { assert!(!self.is_paused(flag), "ERR_PAUSED"); } } diff --git a/src/connector.rs b/src/connector.rs index 5e1ba9a93..31b37e795 100644 --- a/src/connector.rs +++ b/src/connector.rs @@ -153,7 +153,7 @@ impl EthConnectorContract { /// Deposit all types of tokens pub fn deposit(&self) { - self.check_not_paused(PAUSE_DEPOSIT); + self.assert_not_paused(PAUSE_DEPOSIT); use crate::prover::Proof; #[cfg(feature = "log")] @@ -390,7 +390,7 @@ impl EthConnectorContract { /// Withdraw from NEAR accounts /// NOTE: it should be without any log data pub fn withdraw_near(&mut self) { - self.check_not_paused(PAUSE_WITHDRAW); + self.assert_not_paused(PAUSE_WITHDRAW); sdk::assert_one_yocto(); let args = WithdrawCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); @@ -502,8 +502,6 @@ impl EthConnectorContract { /// We starting early checking for message data to avoid `ft_on_transfer` call panics /// But we don't check relayer exists. If relayer doesn't exist we simply not mint/burn the amount of the fee pub fn ft_transfer_call(&mut self) { - //TODO: perhaps need to add pausability functionality here as well? - sdk::assert_one_yocto(); let args = TransferCallCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); @@ -571,8 +569,6 @@ impl EthConnectorContract { /// ft_on_transfer callback function #[allow(clippy::unnecessary_unwrap)] pub fn ft_on_transfer(&mut self, engine: &Engine) { - //TODO: perhaps need to add pausability functionality here as well? - #[cfg(feature = "log")] sdk::log("Call ft_on_trasfer"); let args = FtOnTransfer::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); From 5d1e0a29264ec8307c0bdbc6bd822e0e6cf494f4 Mon Sep 17 00:00:00 2001 From: Septen Date: Thu, 20 May 2021 16:23:19 +0300 Subject: [PATCH 06/11] AdminControlled: add errors. Naming improvements. --- src/connector.rs | 2 +- src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/connector.rs b/src/connector.rs index 31b37e795..dc1b0341d 100644 --- a/src/connector.rs +++ b/src/connector.rs @@ -628,7 +628,7 @@ impl EthConnectorContract { /// Get Eth connector paused flags pub fn get_paused_flags(&self) { - let data = self.get_paused().try_to_vec().unwrap(); + let data = self.get_paused().try_to_vec().expect(ERR_FAILED_PARSE); sdk::return_output(&data[..]); } diff --git a/src/lib.rs b/src/lib.rs index 811b3846a..c7d38eb26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -437,12 +437,12 @@ mod contract { } #[no_mangle] - pub extern "C" fn get_eth_connector_paused_flags() { + pub extern "C" fn get_paused_flags() { EthConnectorContract::get_instance().get_paused_flags() } #[no_mangle] - pub extern "C" fn set_eth_connector_paused_flags() { + pub extern "C" fn set_paused_flags() { EthConnectorContract::get_instance().set_paused_flags() } From 46d64b445f5bb8756eb1fe55fbed416c6688106d Mon Sep 17 00:00:00 2001 From: Septen Date: Thu, 20 May 2021 16:26:49 +0300 Subject: [PATCH 07/11] AdminControlled: doc improvements. --- src/admin_controlled.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/admin_controlled.rs b/src/admin_controlled.rs index 24bd9264e..c96c55ccf 100644 --- a/src/admin_controlled.rs +++ b/src/admin_controlled.rs @@ -21,6 +21,7 @@ pub trait AdminControlled { (self.get_paused() & flag) != 0 && !self.is_owner() } + /// Asserts the passed paused flag is not set. Panics with "ERR_PAUSED" if the flag is set. fn assert_not_paused(&self, flag: PausedMask) { assert!(!self.is_paused(flag), "ERR_PAUSED"); } From 3b8fb3aa9e769ea405c2f78e191c9393367e63e2 Mon Sep 17 00:00:00 2001 From: Septen Date: Fri, 21 May 2021 02:09:33 +0300 Subject: [PATCH 08/11] Test fixes. --- tests/test_connector.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_connector.rs b/tests/test_connector.rs index 611552105..721378d87 100644 --- a/tests/test_connector.rs +++ b/tests/test_connector.rs @@ -635,7 +635,7 @@ fn call_set_paused_flags( ) -> ExecutionResult { let res = account.call( contract.to_string(), - "set_eth_connector_paused_flags", + "set_paused_flags", &paused_mask.try_to_vec().unwrap(), DEFAULT_GAS, 0, From 7851046b5ded5f19d705a8187171190a4354bfb9 Mon Sep 17 00:00:00 2001 From: Septen Date: Thu, 27 May 2021 17:37:56 +0300 Subject: [PATCH 09/11] Fix fmt --- tests/test_connector.rs | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/tests/test_connector.rs b/tests/test_connector.rs index 9b734b86e..abd68f870 100644 --- a/tests/test_connector.rs +++ b/tests/test_connector.rs @@ -92,8 +92,8 @@ fn init_contract( bridge_prover_id: accounts(0).to_string(), upgrade_delay_blocks: 1, } - .try_to_vec() - .unwrap(), + .try_to_vec() + .unwrap(), DEFAULT_GAS, STORAGE_AMOUNT, ) @@ -106,8 +106,8 @@ fn init_contract( prover_account: PROVER_ACCOUNT.into(), eth_custodian_address: custodian_address.into(), } - .try_to_vec() - .unwrap(), + .try_to_vec() + .unwrap(), DEFAULT_GAS, 0, ) @@ -255,8 +255,8 @@ fn test_eth_deposit_balance_total_supply() { &RegisterRelayerCallArgs { address: validate_eth_address(CUSTODIAN_ADDRESS), } - .try_to_vec() - .unwrap(), + .try_to_vec() + .unwrap(), DEFAULT_GAS, 0, ); @@ -302,8 +302,8 @@ fn test_withdraw_near() { recipient_address: recipient_addr, amount: withdraw_amount, } - .try_to_vec() - .unwrap(), + .try_to_vec() + .unwrap(), DEFAULT_GAS, 1, ); @@ -346,8 +346,8 @@ fn test_ft_transfer() { "amount": transfer_amount, "memo": "transfer memo" }) - .to_string() - .as_bytes(), + .to_string() + .as_bytes(), DEFAULT_GAS, 1, ); @@ -395,8 +395,8 @@ fn test_ft_transfer_call_eth() { &RegisterRelayerCallArgs { address: validate_eth_address(CUSTODIAN_ADDRESS), } - .try_to_vec() - .unwrap(), + .try_to_vec() + .unwrap(), DEFAULT_GAS, 0, ); @@ -415,8 +415,8 @@ fn test_ft_transfer_call_eth() { "amount": transfer_amount as u64, "msg": message, }) - .to_string() - .as_bytes(), + .to_string() + .as_bytes(), DEFAULT_GAS, 1, ); @@ -495,8 +495,8 @@ fn test_ft_transfer_call_without_relayer() { "amount": transfer_amount as u64, "msg": message, }) - .to_string() - .as_bytes(), + .to_string() + .as_bytes(), DEFAULT_GAS, 1, ); @@ -551,8 +551,8 @@ fn test_ft_transfer_call_fee_greater_than_amount() { "amount": transfer_amount as u64, "msg": message, }) - .to_string() - .as_bytes(), + .to_string() + .as_bytes(), DEFAULT_GAS, 1, ); @@ -847,7 +847,6 @@ fn test_withdraw_near_pausability() { } } - #[test] fn test_get_accounts_counter() { let (master_account, contract) = init(CUSTODIAN_ADDRESS); @@ -878,8 +877,8 @@ fn test_get_accounts_counter_and_transfer() { "amount": transfer_amount, "memo": "transfer memo" }) - .to_string() - .as_bytes(), + .to_string() + .as_bytes(), DEFAULT_GAS, 1, ); From 5f14cc1d7aaa7e6f65ba2855f2723b9bf1a1a43a Mon Sep 17 00:00:00 2001 From: Septen Date: Thu, 27 May 2021 20:17:58 +0300 Subject: [PATCH 10/11] AdminControlled: make IO in main contract module. --- src/connector.rs | 11 +++-------- src/lib.rs | 14 ++++++++++---- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/connector.rs b/src/connector.rs index 906a6e922..7e5dc58ee 100644 --- a/src/connector.rs +++ b/src/connector.rs @@ -632,17 +632,12 @@ impl EthConnectorContract { } /// Get Eth connector paused flags - pub fn get_paused_flags(&self) { - let data = self.get_paused().try_to_vec().expect(ERR_FAILED_PARSE); - sdk::return_output(&data[..]); + pub fn get_paused_flags(&self) -> PausedMask { + self.get_paused() } /// Set Eth connector paused flags - pub fn set_paused_flags(&mut self) { - sdk::assert_private_call(); - - let args = - PauseEthConnectorCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + pub fn set_paused_flags(&mut self, args: PauseEthConnectorCallArgs) { self.set_paused(args.paused_mask); } } diff --git a/src/lib.rs b/src/lib.rs index b3403920e..2d106e881 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,8 +52,8 @@ mod contract { #[cfg(feature = "evm_bully")] use crate::parameters::{BeginBlockArgs, BeginChainArgs}; use crate::parameters::{ - ExpectUtf8, FunctionCallArgs, GetStorageAtArgs, NewCallArgs, TransferCallCallArgs, - ViewCallArgs, + ExpectUtf8, FunctionCallArgs, GetStorageAtArgs, NewCallArgs, PauseEthConnectorCallArgs, + TransferCallCallArgs, ViewCallArgs, }; use crate::prelude::{Address, TryInto, H256, U256}; use crate::sdk; @@ -451,12 +451,18 @@ mod contract { #[no_mangle] pub extern "C" fn get_paused_flags() { - EthConnectorContract::get_instance().get_paused_flags() + let paused_flags = EthConnectorContract::get_instance().get_paused_flags(); + let data = paused_flags.try_to_vec().expect(ERR_FAILED_PARSE); + sdk::return_output(&data[..]); } #[no_mangle] pub extern "C" fn set_paused_flags() { - EthConnectorContract::get_instance().set_paused_flags() + sdk::assert_private_call(); + + let args = + PauseEthConnectorCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + EthConnectorContract::get_instance().set_paused_flags(args); } #[no_mangle] From 63ab023f5de6ee94d92a4f5133a5df8e90dc0fcd Mon Sep 17 00:00:00 2001 From: Septen Date: Fri, 28 May 2021 01:57:25 +0300 Subject: [PATCH 11/11] AdminControlled:explicitly check panic msg in tests. --- tests/test_connector.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/tests/test_connector.rs b/tests/test_connector.rs index abf6ffaad..ded7797b6 100644 --- a/tests/test_connector.rs +++ b/tests/test_connector.rs @@ -32,6 +32,7 @@ const EVM_CUSTODIAN_ADDRESS: &'static str = "096DE9C2B8A5B8c22cEe3289B101f6960d6 const DEPOSITED_EVM_AMOUNT: u128 = 10200; const DEPOSITED_EVM_FEE: u128 = 200; const ERR_NOT_ENOUGH_BALANCE_FOR_FEE: &'static str = "ERR_NOT_ENOUGH_BALANCE_FOR_FEE"; +const ERR_PAUSED: &'static str = "ERR_PAUSED"; near_sdk_sim::lazy_static_include::lazy_static_include_bytes! { EVM_WASM_BYTES => "release.wasm" @@ -562,7 +563,7 @@ fn test_ft_transfer_call_fee_greater_than_amount() { DEFAULT_GAS, 1, ); - match res.outcome().status { + match res.outcome().clone().status { ExecutionStatus::Failure(_) => {} _ => panic!(), } @@ -645,7 +646,7 @@ fn test_admin_controlled_only_admin_can_pause() { let res = call_set_paused_flags(&user_account, CONTRACT_ACC, PAUSE_DEPOSIT); let promises = res.promise_results(); let p = promises[1].clone(); - match p.unwrap().status() { + match p.unwrap().outcome().clone().status { ExecutionStatus::Failure(_) => {} _ => panic!("Expected failure as only admin can pause, but user successfully paused"), } @@ -754,10 +755,7 @@ fn test_deposit_pausability() { let promises = call_deposit_with_proof(&user_account, CONTRACT_ACC, PROOF_DATA_ETH); let num_promises = promises.len(); let p = promises[num_promises - 2].clone(); - match p.unwrap().status() { - ExecutionStatus::Failure(_) => {} - _ => panic!("Expected failure due to pause, but deposit succeeded"), - } + check_execution_status_failure(p.unwrap().outcome().clone().status, ERR_PAUSED); // Unpause all let res = call_set_paused_flags(&contract, CONTRACT_ACC, UNPAUSE_ALL); @@ -822,10 +820,7 @@ fn test_withdraw_near_pausability() { ); let promises = res.promise_results(); let p = promises[1].clone(); - match p.unwrap().status() { - ExecutionStatus::Failure(_) => {} - _ => panic!("Expected failure due to pause, but withdraw succeeded"), - } + check_execution_status_failure(p.unwrap().outcome().clone().status, ERR_PAUSED); // Unpause all let res = call_set_paused_flags(&contract, CONTRACT_ACC, UNPAUSE_ALL);