From fa1be8ec75b966838289094ea0d547ed79c52758 Mon Sep 17 00:00:00 2001 From: "Joshua J. Bouw" Date: Fri, 21 Jul 2023 08:00:40 -0600 Subject: [PATCH] Pause Contract and Resume Contract (#779) Feature to pause and resume the engine contract. We added two public methods to pause and resume the engine contract. Only DAO can use these functionalities. We use an Engine State field as a flag to indicate paused state. All existing contract public methods check this flat before proceeding, and fail if contract is paused. All public methods would need to read the Engine State now, but no impact on performance was visible. Added new tests to cover the DAO requirement and the feature effect. Please, take a look at the public methods on the contract: -All set methods should be affected by pausing. -All get methods should NOT be affected by pausing. This feature came as a necessity for the Aurora Hashchain integration. The decided strategy is to: 1. upgrade the engine with the Hashchain inactive. 2. pause the contract. 3. call a special contract method added by the Hashchain upgrade, to pass the seed computed Hashchain value for the block, and to activate the Hashchain. 4. resume the contract. 5. upgrade the engine to remove unnecessary code and the special method. There are more details about this but this is the general strategy. --- .../src/relayer_db/mod.rs | 1 + engine-standalone-storage/src/sync/mod.rs | 16 +++ engine-standalone-storage/src/sync/types.rs | 12 ++ engine-tests/src/test_utils/mod.rs | 4 + engine-tests/src/test_utils/standalone/mod.rs | 32 +++++ engine-tests/src/tests/mod.rs | 1 + engine-tests/src/tests/pause_contract.rs | 118 ++++++++++++++++++ engine-tests/src/tests/sanity.rs | 4 +- engine-tests/src/tests/standalone/sanity.rs | 1 + engine/src/errors.rs | 2 + engine/src/lib.rs | 75 ++++++++++- engine/src/state.rs | 30 ++++- 12 files changed, 287 insertions(+), 9 deletions(-) create mode 100644 engine-tests/src/tests/pause_contract.rs 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, } } }