diff --git a/engine-standalone-storage/src/relayer_db/mod.rs b/engine-standalone-storage/src/relayer_db/mod.rs index 09baa4a73..fc0bb02df 100644 --- a/engine-standalone-storage/src/relayer_db/mod.rs +++ b/engine-standalone-storage/src/relayer_db/mod.rs @@ -219,6 +219,7 @@ mod test { chain_id: aurora_engine_types::types::u256_to_arr(&1_313_161_555.into()), owner_id: "aurora".parse().unwrap(), upgrade_delay_blocks: 0, + is_paused: false, }; // Initialize engine and connector states in storage. diff --git a/engine-standalone-storage/src/sync/mod.rs b/engine-standalone-storage/src/sync/mod.rs index af69ed29b..1670e970b 100644 --- a/engine-standalone-storage/src/sync/mod.rs +++ b/engine-standalone-storage/src/sync/mod.rs @@ -453,6 +453,22 @@ fn non_submit_execute<'db, M: ModExpAlgorithm + 'static>( prev.upgrade_delay_blocks = args.upgrade_delay_blocks; state::set_state(&mut io, &prev)?; + None + } + TransactionKind::PauseContract => { + let mut prev = state::get_state(&io)?; + + prev.is_paused = true; + state::set_state(&mut io, &prev)?; + + None + } + TransactionKind::ResumeContract => { + let mut prev = state::get_state(&io)?; + + prev.is_paused = false; + state::set_state(&mut io, &prev)?; + None } }; diff --git a/engine-standalone-storage/src/sync/types.rs b/engine-standalone-storage/src/sync/types.rs index 1b4073f0d..d401f4df0 100644 --- a/engine-standalone-storage/src/sync/types.rs +++ b/engine-standalone-storage/src/sync/types.rs @@ -131,6 +131,10 @@ pub enum TransactionKind { FactoryUpdateAddressVersion(AddressVersionUpdateArgs), FactorySetWNearAddress(Address), FundXccSubAccound(FundXccArgs), + /// Pause the contract + PauseContract, + /// Resume the contract + ResumeContract, /// Sentinel kind for cases where a NEAR receipt caused a /// change in Aurora state, but we failed to parse the Action. Unknown, @@ -354,6 +358,8 @@ impl TransactionKind { Self::SetOwner(_) => Self::no_evm_execution("set_owner"), Self::SetUpgradeDelayBlocks(_) => Self::no_evm_execution("set_upgrade_delay_blocks"), Self::FundXccSubAccound(_) => Self::no_evm_execution("fund_xcc_sub_account"), + Self::PauseContract => Self::no_evm_execution("pause_contract"), + Self::ResumeContract => Self::no_evm_execution("resume_contract"), } } @@ -523,6 +529,8 @@ enum BorshableTransactionKind<'a> { SubmitWithArgs(Cow<'a, parameters::SubmitArgs>), FundXccSubAccound(Cow<'a, FundXccArgs>), SetUpgradeDelayBlocks(Cow<'a, parameters::SetUpgradeDelayBlocksArgs>), + PauseContract, + ResumeContract, } impl<'a> From<&'a TransactionKind> for BorshableTransactionKind<'a> { @@ -569,6 +577,8 @@ impl<'a> From<&'a TransactionKind> for BorshableTransactionKind<'a> { TransactionKind::SetUpgradeDelayBlocks(x) => { Self::SetUpgradeDelayBlocks(Cow::Borrowed(x)) } + TransactionKind::PauseContract => Self::PauseContract, + TransactionKind::ResumeContract => Self::ResumeContract, } } } @@ -634,6 +644,8 @@ impl<'a> TryFrom> for TransactionKind { BorshableTransactionKind::SetUpgradeDelayBlocks(x) => { Ok(Self::SetUpgradeDelayBlocks(x.into_owned())) } + BorshableTransactionKind::PauseContract => Ok(Self::PauseContract), + BorshableTransactionKind::ResumeContract => Ok(Self::ResumeContract), } } } diff --git a/engine-tests/src/test_utils/mod.rs b/engine-tests/src/test_utils/mod.rs index 54e261fe2..56db405f3 100644 --- a/engine-tests/src/test_utils/mod.rs +++ b/engine-tests/src/test_utils/mod.rs @@ -42,6 +42,8 @@ pub const PAUSED_PRECOMPILES: &str = "paused_precompiles"; pub const RESUME_PRECOMPILES: &str = "resume_precompiles"; pub const SET_OWNER: &str = "set_owner"; pub const SET_UPGRADE_DELAY_BLOCKS: &str = "set_upgrade_delay_blocks"; +pub const PAUSE_CONTRACT: &str = "pause_contract"; +pub const RESUME_CONTRACT: &str = "resume_contract"; const CALLER_ACCOUNT_ID: &str = "some-account.near"; @@ -245,6 +247,8 @@ impl AuroraRunner { || method_name == RESUME_PRECOMPILES || method_name == SET_OWNER || method_name == SET_UPGRADE_DELAY_BLOCKS + || method_name == PAUSE_CONTRACT + || method_name == RESUME_CONTRACT { standalone_runner.submit_raw(method_name, &self.context, &self.promise_results)?; self.validate_standalone(); diff --git a/engine-tests/src/test_utils/standalone/mod.rs b/engine-tests/src/test_utils/standalone/mod.rs index 886709312..43776db5e 100644 --- a/engine-tests/src/test_utils/standalone/mod.rs +++ b/engine-tests/src/test_utils/standalone/mod.rs @@ -332,6 +332,38 @@ impl StandaloneRunner { self.cumulative_diff.append(outcome.diff.clone()); storage::commit(storage, &outcome); + Ok(SubmitResult::new( + TransactionStatus::Succeed(Vec::new()), + 0, + Vec::new(), + )) + } else if method_name == test_utils::PAUSE_CONTRACT { + let transaction_hash = aurora_engine_sdk::keccak(&ctx.input); + let mut tx_msg = + Self::template_tx_msg(storage, &env, 0, transaction_hash, promise_results); + tx_msg.transaction = TransactionKind::PauseContract; + + let outcome = + sync::execute_transaction_message::(storage, tx_msg).unwrap(); + self.cumulative_diff.append(outcome.diff.clone()); + storage::commit(storage, &outcome); + + Ok(SubmitResult::new( + TransactionStatus::Succeed(Vec::new()), + 0, + Vec::new(), + )) + } else if method_name == test_utils::RESUME_CONTRACT { + let transaction_hash = aurora_engine_sdk::keccak(&ctx.input); + let mut tx_msg = + Self::template_tx_msg(storage, &env, 0, transaction_hash, promise_results); + tx_msg.transaction = TransactionKind::ResumeContract; + + let outcome = + sync::execute_transaction_message::(storage, tx_msg).unwrap(); + self.cumulative_diff.append(outcome.diff.clone()); + storage::commit(storage, &outcome); + Ok(SubmitResult::new( TransactionStatus::Succeed(Vec::new()), 0, diff --git a/engine-tests/src/tests/mod.rs b/engine-tests/src/tests/mod.rs index a7fbc4a66..4b4c4fe06 100644 --- a/engine-tests/src/tests/mod.rs +++ b/engine-tests/src/tests/mod.rs @@ -13,6 +13,7 @@ pub mod modexp; mod multisender; mod one_inch; mod pausable_precompiles; +mod pause_contract; mod prepaid_gas_precompile; mod promise_results_precompile; mod random; diff --git a/engine-tests/src/tests/pause_contract.rs b/engine-tests/src/tests/pause_contract.rs new file mode 100644 index 000000000..b06698b45 --- /dev/null +++ b/engine-tests/src/tests/pause_contract.rs @@ -0,0 +1,118 @@ +use crate::test_utils; +use aurora_engine::parameters::SetUpgradeDelayBlocksArgs; +use borsh::BorshSerialize; + +#[test] +fn test_pause_contract_require_owner() { + let mut runner = test_utils::deploy_evm(); + let aurora_account_id = runner.aurora_account_id.clone(); + + let result = runner.call("pause_contract", &aurora_account_id, vec![]); + assert!(result.is_ok()); + + let result = runner.call("resume_contract", &aurora_account_id, vec![]); + assert!(result.is_ok()); + + let result = runner.call("pause_contract", "new_owner.near", vec![]); + assert!(result.is_err()); +} + +#[test] +fn test_resume_contract_require_owner() { + let mut runner = test_utils::deploy_evm(); + let aurora_account_id = runner.aurora_account_id.clone(); + + let result = runner.call("pause_contract", &aurora_account_id, vec![]); + assert!(result.is_ok()); + + let result = runner.call("resume_contract", &aurora_account_id, vec![]); + assert!(result.is_ok()); + + let result = runner.call("pause_contract", &aurora_account_id, vec![]); + assert!(result.is_ok()); + + let result = runner.call("resume_contract", "new_owner.near", vec![]); + assert!(result.is_err()); +} + +#[test] +fn test_pause_contract_require_running() { + let mut runner = test_utils::deploy_evm(); + let aurora_account_id = runner.aurora_account_id.clone(); + + let result = runner.call("pause_contract", &aurora_account_id, vec![]); + assert!(result.is_ok()); + + let result = runner.call("pause_contract", &aurora_account_id, vec![]); + assert!(result.is_err()); +} + +#[test] +fn test_resume_contract_require_paused() { + let mut runner = test_utils::deploy_evm(); + let aurora_account_id = runner.aurora_account_id.clone(); + + let result = runner.call("resume_contract", &aurora_account_id, vec![]); + assert!(result.is_err()); + + let result = runner.call("pause_contract", &aurora_account_id, vec![]); + assert!(result.is_ok()); + + let result = runner.call("resume_contract", &aurora_account_id, vec![]); + assert!(result.is_ok()); +} + +#[test] +fn test_pause_contract() { + let mut runner = test_utils::deploy_evm(); + let aurora_account_id = runner.aurora_account_id.clone(); + let set = SetUpgradeDelayBlocksArgs { + upgrade_delay_blocks: 2, + } + .try_to_vec() + .unwrap(); + + // contract is running by default, gets and sets should work + let result = runner.call("get_upgrade_delay_blocks", &aurora_account_id, vec![]); + assert!(result.is_ok()); + + let result = runner.call("set_upgrade_delay_blocks", &aurora_account_id, set.clone()); + assert!(result.is_ok()); + + // pause contract + let result = runner.call("pause_contract", &aurora_account_id, vec![]); + assert!(result.is_ok()); + + // contract is paused, gets should still work but sets should fail + let result = runner.call("get_upgrade_delay_blocks", &aurora_account_id, vec![]); + assert!(result.is_ok()); + + let result = runner.call("set_upgrade_delay_blocks", &aurora_account_id, set); + assert!(result.is_err()); +} + +#[test] +fn test_resume_contract() { + let mut runner = test_utils::deploy_evm(); + let aurora_account_id = runner.aurora_account_id.clone(); + let set = SetUpgradeDelayBlocksArgs { + upgrade_delay_blocks: 2, + } + .try_to_vec() + .unwrap(); + + // pause contract + let result = runner.call("pause_contract", &aurora_account_id, vec![]); + assert!(result.is_ok()); + + // resume contract + let result = runner.call("resume_contract", &aurora_account_id, vec![]); + assert!(result.is_ok()); + + // contract is running again, gets and sets should work + let result = runner.call("get_upgrade_delay_blocks", &aurora_account_id, vec![]); + assert!(result.is_ok()); + + let result = runner.call("set_upgrade_delay_blocks", &aurora_account_id, set); + assert!(result.is_ok()); +} diff --git a/engine-tests/src/tests/sanity.rs b/engine-tests/src/tests/sanity.rs index 4834f9813..8ae1dbd89 100644 --- a/engine-tests/src/tests/sanity.rs +++ b/engine-tests/src/tests/sanity.rs @@ -147,9 +147,9 @@ fn test_state_format() { }; let state: aurora_engine::state::EngineState = args.into(); let expected_hex: String = [ - "01000000000000000000000000000000000000000000000000000000000000029a", + "02000000000000000000000000000000000000000000000000000000000000029a", "04000000626f7373", - "0300000000000000", + "030000000000000000", ] .concat(); assert_eq!(hex::encode(state.borsh_serialize().unwrap()), expected_hex); diff --git a/engine-tests/src/tests/standalone/sanity.rs b/engine-tests/src/tests/standalone/sanity.rs index 55d93b504..1ec721c2e 100644 --- a/engine-tests/src/tests/standalone/sanity.rs +++ b/engine-tests/src/tests/standalone/sanity.rs @@ -19,6 +19,7 @@ fn test_deploy_code() { chain_id, owner_id: owner_id.clone(), upgrade_delay_blocks: 0, + is_paused: false, }; let origin = Address::new(H160([0u8; 20])); let storage = RefCell::new(Storage::default()); diff --git a/engine/src/errors.rs b/engine/src/errors.rs index 5387f23c9..0d2c48ea1 100644 --- a/engine/src/errors.rs +++ b/engine/src/errors.rs @@ -22,6 +22,8 @@ pub const ERR_VERIFY_PROOF: &[u8; 16] = b"ERR_VERIFY_PROOF"; pub const ERR_INVALID_UPGRADE: &[u8; 19] = b"ERR_INVALID_UPGRADE"; pub const ERR_NO_UPGRADE: &[u8; 14] = b"ERR_NO_UPGRADE"; pub const ERR_NOT_ALLOWED: &[u8; 15] = b"ERR_NOT_ALLOWED"; +pub const ERR_PAUSED: &[u8; 10] = b"ERR_PAUSED"; +pub const ERR_RUNNING: &[u8; 11] = b"ERR_RUNNING"; pub const ERR_SERIALIZE: &str = "ERR_SERIALIZE"; pub const ERR_PROMISE_ENCODING: &str = "ERR_PROMISE_ENCODING"; diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 38ee1fda6..cc2d2a6fc 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -156,6 +156,7 @@ mod contract { pub extern "C" fn set_owner() { let mut io = Runtime; let mut state = state::get_state(&io).sdk_unwrap(); + require_running(&state); require_owner_only(&state, &io.predecessor_account_id()); let args: SetOwnerArgs = io.read_input_borsh().sdk_unwrap(); if state.owner_id == args.new_owner { @@ -192,6 +193,7 @@ mod contract { pub extern "C" fn set_upgrade_delay_blocks() { let mut io = Runtime; let mut state = state::get_state(&io).sdk_unwrap(); + require_running(&state); require_owner_only(&state, &io.predecessor_account_id()); let args: SetUpgradeDelayBlocksArgs = io.read_input_borsh().sdk_unwrap(); state.upgrade_delay_blocks = args.upgrade_delay_blocks; @@ -210,6 +212,7 @@ mod contract { pub extern "C" fn stage_upgrade() { let mut io = Runtime; let state = state::get_state(&io).sdk_unwrap(); + require_running(&state); let delay_block_height = io.block_height() + state.upgrade_delay_blocks; require_owner_only(&state, &io.predecessor_account_id()); io.read_input_and_store(&bytes_to_key(KeyPrefix::Config, CODE_KEY)); @@ -223,6 +226,8 @@ mod contract { #[no_mangle] pub extern "C" fn deploy_upgrade() { let mut io = Runtime; + let state = state::get_state(&io).sdk_unwrap(); + require_running(&state); let index = internal_get_upgrade_index(); if io.block_height() <= index { @@ -248,6 +253,7 @@ mod contract { pub extern "C" fn resume_precompiles() { let io = Runtime; let state = state::get_state(&io).sdk_unwrap(); + require_running(&state); let predecessor_account_id = io.predecessor_account_id(); require_owner_only(&state, &predecessor_account_id); @@ -262,6 +268,7 @@ mod contract { #[no_mangle] pub extern "C" fn pause_precompiles() { let io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); let authorizer: pausables::EngineAuthorizer = engine::get_authorizer(&io); if !authorizer.is_authorized(&io.predecessor_account_id()) { @@ -284,6 +291,32 @@ mod contract { io.return_output(&data[..]); } + /// Sets the flag to pause the contract. + #[no_mangle] + pub extern "C" fn pause_contract() { + let mut io = Runtime; + let mut state = state::get_state(&io).sdk_unwrap(); + require_owner_only(&state, &io.predecessor_account_id()); + if state.is_paused { + sdk::panic_utf8(errors::ERR_PAUSED); + } + state.is_paused = true; + state::set_state(&mut io, &state).sdk_unwrap(); + } + + /// Sets the flag to resume the contract. + #[no_mangle] + pub extern "C" fn resume_contract() { + let mut io = Runtime; + let mut state = state::get_state(&io).sdk_unwrap(); + require_owner_only(&state, &io.predecessor_account_id()); + if !state.is_paused { + sdk::panic_utf8(errors::ERR_RUNNING); + } + state.is_paused = false; + state::set_state(&mut io, &state).sdk_unwrap(); + } + /// /// MUTATIVE METHODS /// @@ -292,6 +325,7 @@ mod contract { #[no_mangle] pub extern "C" fn deploy_code() { let io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); let input = io.read_input().to_vec(); let current_account_id = io.current_account_id(); let mut engine: Engine<_, _> = Engine::new( @@ -311,6 +345,7 @@ mod contract { #[no_mangle] pub extern "C" fn call() { let io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); let bytes = io.read_input().to_vec(); let args = CallArgs::deserialize(&bytes).sdk_expect(errors::ERR_BORSH_DESERIALIZE); let current_account_id = io.current_account_id(); @@ -345,9 +380,10 @@ mod contract { #[no_mangle] pub extern "C" fn submit() { let io = Runtime; + let state = state::get_state(&io).sdk_unwrap(); + require_running(&state); let tx_data = io.read_input().to_vec(); let current_account_id = io.current_account_id(); - let state = state::get_state(&io).sdk_unwrap(); let relayer_address = predecessor_address(&io.predecessor_account_id()); let args = SubmitArgs { tx_data, @@ -373,9 +409,10 @@ mod contract { #[no_mangle] pub extern "C" fn submit_with_args() { let io = Runtime; + let state = state::get_state(&io).sdk_unwrap(); + require_running(&state); let args: SubmitArgs = io.read_input_borsh().sdk_unwrap(); let current_account_id = io.current_account_id(); - let state = state::get_state(&io).sdk_unwrap(); let relayer_address = predecessor_address(&io.predecessor_account_id()); let result = engine::submit( io, @@ -395,6 +432,7 @@ mod contract { #[no_mangle] pub extern "C" fn register_relayer() { let io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); let relayer_address = io.read_input_arr20().sdk_unwrap(); let current_account_id = io.current_account_id(); @@ -419,6 +457,7 @@ mod contract { pub extern "C" fn factory_update() { let mut io = Runtime; let state = state::get_state(&io).sdk_unwrap(); + require_running(&state); require_owner_only(&state, &io.predecessor_account_id()); let bytes = io.read_input().to_vec(); let router_bytecode = crate::xcc::RouterCode::new(bytes); @@ -430,6 +469,7 @@ mod contract { #[no_mangle] pub extern "C" fn factory_update_address_version() { let mut io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); // The function is only set to be private, otherwise callback error will happen. io.assert_private_call().sdk_unwrap(); let check_deploy: Result<(), &[u8]> = match io.promise_result_check() { @@ -448,6 +488,7 @@ mod contract { pub extern "C" fn factory_set_wnear_address() { let mut io = Runtime; let state = state::get_state(&io).sdk_unwrap(); + require_running(&state); require_owner_only(&state, &io.predecessor_account_id()); let address = io.read_input_arr20().sdk_unwrap(); crate::xcc::set_wnear_address(&mut io, &Address::from_array(address)); @@ -460,6 +501,7 @@ mod contract { pub extern "C" fn fund_xcc_sub_account() { let io = Runtime; let state = state::get_state(&io).sdk_unwrap(); + require_running(&state); // This method can only be called by the owner because it allows specifying the // account ID of the wNEAR account. This information must be accurate for the // sub-account to work properly, therefore this method can only be called by @@ -477,6 +519,7 @@ mod contract { #[no_mangle] pub extern "C" fn ft_on_transfer() { let io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); let current_account_id = io.current_account_id(); let predecessor_account_id = io.predecessor_account_id(); let mut engine: Engine<_, _> = Engine::new( @@ -510,6 +553,7 @@ mod contract { #[no_mangle] pub extern "C" fn deploy_erc20_token() { let mut io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); // Id of the NEP141 token in Near let args: DeployErc20TokenArgs = io.read_input_borsh().sdk_unwrap(); @@ -530,6 +574,8 @@ mod contract { #[no_mangle] pub extern "C" fn refund_on_error() { let io = Runtime; + let state = state::get_state(&io).sdk_unwrap(); + require_running(&state); io.assert_private_call().sdk_unwrap(); // This function should only be called as the callback of @@ -543,7 +589,6 @@ mod contract { } else { // Exit call failed; need to refund tokens let args: RefundCallArgs = io.read_input_borsh().sdk_unwrap(); - let state = state::get_state(&io).sdk_unwrap(); let refund_result = engine::refund_on_error(io, &io, state, &args, &mut Runtime).sdk_unwrap(); @@ -653,10 +698,11 @@ mod contract { #[no_mangle] pub extern "C" fn new_eth_connector() { let io = Runtime; + let state = state::get_state(&io).sdk_unwrap(); + require_running(&state); // Only the owner can initialize the EthConnector let is_private = io.assert_private_call(); if is_private.is_err() { - let state = state::get_state(&io).sdk_unwrap(); require_owner_only(&state, &io.predecessor_account_id()); } @@ -669,10 +715,11 @@ mod contract { #[no_mangle] pub extern "C" fn set_eth_connector_contract_data() { let mut io = Runtime; + let state = state::get_state(&io).sdk_unwrap(); + require_running(&state); // Only the owner can set the EthConnector contract data let is_private = io.assert_private_call(); if is_private.is_err() { - let state = state::get_state(&io).sdk_unwrap(); require_owner_only(&state, &io.predecessor_account_id()); } @@ -683,6 +730,7 @@ mod contract { #[no_mangle] pub extern "C" fn withdraw() { let io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); io.assert_one_yocto().sdk_unwrap(); let args = io.read_input_borsh().sdk_unwrap(); let current_account_id = io.current_account_id(); @@ -706,6 +754,7 @@ mod contract { #[no_mangle] pub extern "C" fn deposit() { let mut io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); let raw_proof = io.read_input().to_vec(); let current_account_id = io.current_account_id(); let predecessor_account_id = io.predecessor_account_id(); @@ -723,6 +772,7 @@ mod contract { #[no_mangle] pub extern "C" fn finish_deposit() { let mut io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); io.assert_private_call().sdk_unwrap(); // Check result from proof verification call @@ -821,6 +871,7 @@ mod contract { #[no_mangle] pub extern "C" fn ft_transfer() { let io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); io.assert_one_yocto().sdk_unwrap(); let predecessor_account_id = io.predecessor_account_id(); let args: parameters::TransferCallArgs = serde_json::from_slice(&io.read_input().to_vec()) @@ -835,6 +886,7 @@ mod contract { #[no_mangle] pub extern "C" fn ft_resolve_transfer() { let io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); io.assert_private_call().sdk_unwrap(); if io.promise_results_count() != 1 { @@ -852,6 +904,7 @@ mod contract { #[no_mangle] pub extern "C" fn ft_transfer_call() { let mut io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); // Check is payable io.assert_one_yocto().sdk_unwrap(); @@ -878,6 +931,7 @@ mod contract { #[no_mangle] pub extern "C" fn storage_deposit() { let mut io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); let args: StorageDepositCallArgs = serde_json::from_slice(&io.read_input().to_vec()) .map_err(Into::::into) .sdk_unwrap(); @@ -897,6 +951,7 @@ mod contract { #[no_mangle] pub extern "C" fn storage_unregister() { let mut io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); io.assert_one_yocto().sdk_unwrap(); let predecessor_account_id = io.predecessor_account_id(); let force = serde_json::from_slice::(&io.read_input().to_vec()) @@ -915,6 +970,7 @@ mod contract { #[no_mangle] pub extern "C" fn storage_withdraw() { let io = Runtime; + require_running(&state::get_state(&io).sdk_unwrap()); io.assert_one_yocto().sdk_unwrap(); let args: StorageWithdrawCallArgs = serde_json::from_slice(&io.read_input().to_vec()) .map_err(Into::::into) @@ -951,9 +1007,10 @@ mod contract { #[no_mangle] pub extern "C" fn set_paused_flags() { let io = Runtime; + let state = state::get_state(&io).sdk_unwrap(); + require_running(&state); let is_private = io.assert_private_call(); if is_private.is_err() { - let state = state::get_state(&io).sdk_unwrap(); require_owner_only(&state, &io.predecessor_account_id()); } let args: PauseEthConnectorCallArgs = io.read_input_borsh().sdk_unwrap(); @@ -1099,6 +1156,12 @@ mod contract { } } + fn require_running(state: &state::EngineState) { + if state.is_paused { + sdk::panic_utf8(errors::ERR_PAUSED); + } + } + fn predecessor_address(predecessor_account_id: &AccountId) -> Address { near_account_to_evm_address(predecessor_account_id.as_bytes()) } diff --git a/engine/src/state.rs b/engine/src/state.rs index 248b2027d..23316bbdf 100644 --- a/engine/src/state.rs +++ b/engine/src/state.rs @@ -21,6 +21,8 @@ pub struct EngineState { pub owner_id: AccountId, /// How many blocks after staging upgrade can deploy it. pub upgrade_delay_blocks: u64, + /// Flag to pause and unpause the engine. + pub is_paused: bool, } impl EngineState { @@ -54,6 +56,7 @@ impl EngineState { pub enum BorshableEngineState<'a> { V1(BorshableEngineStateV1<'a>), V2(BorshableEngineStateV2<'a>), + V3(BorshableEngineStateV3<'a>), } #[derive(BorshSerialize, BorshDeserialize, Default, Clone, PartialEq, Eq, Debug)] @@ -71,12 +74,21 @@ pub struct BorshableEngineStateV2<'a> { pub upgrade_delay_blocks: u64, } +#[derive(BorshSerialize, BorshDeserialize, Default, Clone, PartialEq, Eq, Debug)] +pub struct BorshableEngineStateV3<'a> { + pub chain_id: [u8; 32], + pub owner_id: Cow<'a, AccountId>, + pub upgrade_delay_blocks: u64, + pub is_paused: bool, +} + impl<'a> From<&'a EngineState> for BorshableEngineState<'a> { fn from(state: &'a EngineState) -> Self { - Self::V2(BorshableEngineStateV2 { + Self::V3(BorshableEngineStateV3 { chain_id: state.chain_id, owner_id: Cow::Borrowed(&state.owner_id), upgrade_delay_blocks: state.upgrade_delay_blocks, + is_paused: state.is_paused, }) } } @@ -86,6 +98,7 @@ impl<'a> From> for EngineState { match state { BorshableEngineState::V1(state) => state.into(), BorshableEngineState::V2(state) => state.into(), + BorshableEngineState::V3(state) => state.into(), } } } @@ -96,6 +109,7 @@ impl<'a> From> for EngineState { chain_id: state.chain_id, owner_id: state.owner_id.into_owned(), upgrade_delay_blocks: state.upgrade_delay_blocks, + is_paused: false, } } } @@ -106,6 +120,18 @@ impl<'a> From> for EngineState { chain_id: state.chain_id, owner_id: state.owner_id.into_owned(), upgrade_delay_blocks: state.upgrade_delay_blocks, + is_paused: false, + } + } +} + +impl<'a> From> for EngineState { + fn from(state: BorshableEngineStateV3<'a>) -> Self { + Self { + chain_id: state.chain_id, + owner_id: state.owner_id.into_owned(), + upgrade_delay_blocks: state.upgrade_delay_blocks, + is_paused: state.is_paused, } } } @@ -116,6 +142,7 @@ impl From for EngineState { chain_id: args.chain_id, owner_id: args.owner_id, upgrade_delay_blocks: args.upgrade_delay_blocks, + is_paused: false, } } } @@ -126,6 +153,7 @@ impl From for EngineState { chain_id: args.chain_id, owner_id: args.owner_id, upgrade_delay_blocks: args.upgrade_delay_blocks, + is_paused: false, } } }