diff --git a/src/admin_controlled.rs b/src/admin_controlled.rs new file mode 100644 index 000000000..c96c55ccf --- /dev/null +++ b/src/admin_controlled.rs @@ -0,0 +1,28 @@ +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() + } + + /// 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() + } + + /// 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"); + } +} diff --git a/src/connector.rs b/src/connector.rs index a675fb8f7..7e5dc58ee 100644 --- a/src/connector.rs +++ b/src/connector.rs @@ -3,6 +3,7 @@ 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::json::parse_json; @@ -18,10 +19,15 @@ const GAS_FOR_FINISH_DEPOSIT: Gas = 50_000_000_000_000; // Note: Is 40Tgas always enough? const GAS_FOR_VERIFY_LOG_ENTRY: 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 @@ -51,6 +57,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), } } @@ -84,14 +91,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, + paused_mask, } .save_ft_contract(); } @@ -153,6 +167,8 @@ impl EthConnectorContract { /// Deposit all types of tokens pub fn deposit(&self) { + self.assert_not_paused(PAUSE_DEPOSIT); + use crate::prover::Proof; #[cfg(feature = "log")] sdk::log("[Deposit tokens]"); @@ -381,6 +397,8 @@ impl EthConnectorContract { /// Withdraw from NEAR accounts /// NOTE: it should be without any log data pub fn withdraw_near(&mut self) { + self.assert_not_paused(PAUSE_WITHDRAW); + sdk::assert_one_yocto(); let args = WithdrawCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); let res = WithdrawResult { @@ -612,4 +630,28 @@ 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) -> PausedMask { + self.get_paused() + } + + /// Set Eth connector paused flags + pub fn set_paused_flags(&mut self, args: PauseEthConnectorCallArgs) { + 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 01a24dfc0..2d106e881 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")] @@ -50,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; @@ -447,6 +449,22 @@ mod contract { EthConnectorContract::get_instance().ft_on_transfer(&engine) } + #[no_mangle] + pub extern "C" fn 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() { + 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] pub extern "C" fn get_accounts_counter() { EthConnectorContract::get_instance().get_accounts_counter() diff --git a/src/parameters.rs b/src/parameters.rs index 774511b1c..3ad9e36a1 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; #[cfg(feature = "contract")] use crate::json; #[cfg(feature = "contract")] @@ -375,6 +377,12 @@ pub struct RegisterRelayerCallArgs { pub address: EthAddress, } +#[cfg(feature = "contract")] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct PauseEthConnectorCallArgs { + pub paused_mask: PausedMask, +} + #[cfg(feature = "contract")] pub trait ExpectUtf8 { fn expect_utf8(self, message: &[u8]) -> T; diff --git a/src/storage.rs b/src/storage.rs index a0e53c89b..a4eb3286e 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -25,7 +25,8 @@ pub enum EthConnectorStorageId { Contract = 0x0, FungibleToken = 0x1, UsedEvent = 0x2, - StatisticsAuroraAccountsCounter = 0x3, + PausedMask = 0x3, + StatisticsAuroraAccountsCounter = 0x4, } /// We can't use const generic over Enum, but we can do it over integral type diff --git a/tests/test_connector.rs b/tests/test_connector.rs index b28d12a40..ded7797b6 100644 --- a/tests/test_connector.rs +++ b/tests/test_connector.rs @@ -13,6 +13,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,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,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,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,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,107,17,185,1,0,0,0,8,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,128,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,128,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,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,0,0,0,0,0,0,0,0,0,0,8,0,0,0,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,0,0,0,0,0,0,0,0,0,0,0,0,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,0,248,253,248,251,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,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,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,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,10,160,177,33,112,26,26,176,12,12,163,2,249,133,245,12,51,201,55,50,148,156,122,67,27,26,101,178,36,153,54,100,53,137,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,124,28,230,160,8,239,64,193,62,78,177,68,166,204,116,240,224,174,172,126,160,197,65,5,202,188,134,5,164,246,19,133,35,57,28,114,241,186,81,123,163,166,161,24,32,157,168,170,13,108,58,61,46,160,6,199,163,13,91,119,225,39,168,255,213,10,107,252,143,246,138,241,108,139,59,35,187,185,162,223,53,108,222,73,181,109,160,27,154,49,63,26,170,15,177,97,255,6,204,84,221,234,197,159,172,114,47,148,126,32,199,241,127,101,120,182,51,52,100,185,1,0,0,0,8,0,0,0,0,0,0,0,32,0,0,0,0,0,2,0,8,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,8,32,0,32,0,0,128,0,2,0,0,0,1,0,32,0,0,0,2,0,0,0,0,32,0,0,0,0,0,4,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,128,64,0,0,0,0,1,32,0,0,0,0,0,0,96,32,0,64,0,0,0,128,1,0,0,0,0,1,0,0,0,8,0,0,0,18,32,0,0,64,145,1,8,0,4,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,33,16,0,128,0,0,0,0,0,0,128,0,2,0,0,0,0,0,0,0,0,0,0,2,0,80,0,0,0,0,0,0,0,0,1,128,0,8,0,0,0,0,4,0,0,0,128,2,0,32,0,128,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,16,0,8,0,0,0,0,0,0,0,0,0,0,128,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132,25,1,227,23,131,157,85,14,131,122,18,0,131,75,91,132,132,96,174,58,224,140,115,112,105,100,101,114,49,48,1,2,8,230,160,188,212,199,183,154,22,223,85,103,215,24,122,240,235,79,129,44,93,184,88,161,218,79,5,44,226,106,100,50,40,163,97,136,155,158,202,3,149,91,200,78],"proof":[[248,113,160,46,156,31,85,241,226,241,13,5,56,73,146,176,67,195,109,6,189,172,104,44,103,44,88,32,15,181,152,136,29,121,252,160,191,48,87,174,71,151,208,114,164,150,51,200,171,90,90,106,46,200,79,77,222,145,95,89,141,137,138,149,67,73,8,87,128,128,128,128,128,128,160,175,9,219,77,174,13,247,133,55,172,92,185,202,7,160,10,204,112,44,133,36,96,30,234,235,134,30,209,205,166,212,255,128,128,128,128,128,128,128,128],[249,2,13,48,185,2,9,249,2,6,1,130,107,17,185,1,0,0,0,8,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,128,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,128,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,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,0,0,0,0,0,0,0,0,0,0,8,0,0,0,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,0,0,0,0,0,0,0,0,0,0,0,0,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,0,248,253,248,251,148,9,109,233,194,184,165,184,194,44,238,50,137,177,1,246,150,13,104,229,30,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,121,24,63,219,216,14,45,138,234,26,202,162,246,123,251,138,54,212,10,141,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]]}"#; @@ -27,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" @@ -49,6 +55,19 @@ 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); @@ -142,7 +161,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 { @@ -273,19 +292,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); @@ -557,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!(), } @@ -592,6 +598,256 @@ 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> { + 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_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"), // initial balance + ); + 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().outcome().clone().status { + ExecutionStatus::Failure(_) => {} + _ => panic!("Expected failure as only admin can pause, but user successfully paused"), + } + + // 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(); + check_execution_status_failure(p.unwrap().outcome().clone().status, ERR_PAUSED); + + // 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(); + check_execution_status_failure(p.unwrap().outcome().clone().status, ERR_PAUSED); + + // 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() + } +} + #[test] fn test_get_accounts_counter() { let (master_account, contract) = init(CUSTODIAN_ADDRESS);