From ce08d8db68bd676d106ce1822be8fc4ca3350358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Sun, 10 Jul 2022 16:04:07 +0200 Subject: [PATCH] Feat(Engine): Introduce pausability for precompiles and the concept of permissions for such action. Permissions are hardcoded for a single NEAR account. --- Cargo.lock | 25 ++ engine-precompiles/src/alt_bn256.rs | 15 +- engine-precompiles/src/blake2.rs | 4 +- engine-precompiles/src/hash.rs | 4 +- engine-precompiles/src/identity.rs | 2 +- engine-precompiles/src/lib.rs | 19 +- engine-precompiles/src/modexp.rs | 5 +- engine-precompiles/src/secp256k1.rs | 2 +- engine-standalone-storage/src/sync/mod.rs | 19 ++ engine-standalone-storage/src/sync/types.rs | 17 ++ engine-tests/Cargo.toml | 3 +- engine-tests/src/test_utils/mod.rs | 11 +- engine-tests/src/test_utils/standalone/mod.rs | 42 +++- engine-tests/src/tests/mod.rs | 1 + .../src/tests/pausable_precompiles.rs | 168 +++++++++++++ engine-tests/src/tests/repro.rs | 2 +- .../src/tests/standard_precompiles.rs | 2 +- engine-types/src/lib.rs | 1 + engine/Cargo.toml | 2 + engine/src/engine.rs | 148 ++++++----- engine/src/lib.rs | 54 ++++- engine/src/parameters.rs | 5 + engine/src/pausables.rs | 229 ++++++++++++++++++ etc/state-migration-test/Cargo.lock | 7 + 24 files changed, 707 insertions(+), 80 deletions(-) create mode 100644 engine-tests/src/tests/pausable_precompiles.rs create mode 100644 engine/src/pausables.rs diff --git a/Cargo.lock b/Cargo.lock index c71b97c60..e91e95439 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,6 +107,7 @@ dependencies = [ "aurora-engine-transactions", "aurora-engine-types", "base64 0.13.0", + "bitflags", "borsh", "byte-slice-cast", "ethabi", @@ -117,6 +118,7 @@ dependencies = [ "rlp", "serde", "serde_json", + "test-case", "wee_alloc", ] @@ -179,6 +181,7 @@ dependencies = [ "near-primitives-core", "near-sdk", "near-sdk-sim", + "near-vm-errors", "near-vm-logic", "near-vm-runner", "rand 0.8.5", @@ -3857,6 +3860,28 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-case" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07aea929e9488998b64adc414c29fe5620398f01c2e3f58164122b17e567a6d5" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-macros" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c95968eedc6fc4f5c21920e0f4264f78ec5e4c56bb394f319becc1a5830b3e54" +dependencies = [ + "cfg-if 1.0.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "textwrap" version = "0.11.0" diff --git a/engine-precompiles/src/alt_bn256.rs b/engine-precompiles/src/alt_bn256.rs index dbb99f528..df462b047 100644 --- a/engine-precompiles/src/alt_bn256.rs +++ b/engine-precompiles/src/alt_bn256.rs @@ -167,10 +167,11 @@ fn read_point(input: &[u8], pos: usize) -> Result { }) } -pub(super) struct Bn256Add(PhantomData); +#[derive(Default)] +pub struct Bn256Add(PhantomData); impl Bn256Add { - pub(super) const ADDRESS: Address = super::make_address(0, 6); + pub const ADDRESS: Address = super::make_address(0, 6); pub fn new() -> Self { Self(Default::default()) @@ -269,10 +270,11 @@ impl Precompile for Bn256Add { } } -pub(super) struct Bn256Mul(PhantomData); +#[derive(Default)] +pub struct Bn256Mul(PhantomData); impl Bn256Mul { - pub(super) const ADDRESS: Address = super::make_address(0, 7); + pub const ADDRESS: Address = super::make_address(0, 7); pub fn new() -> Self { Self(Default::default()) @@ -374,10 +376,11 @@ impl Precompile for Bn256Mul { } } -pub(super) struct Bn256Pair(PhantomData); +#[derive(Default)] +pub struct Bn256Pair(PhantomData); impl Bn256Pair { - pub(super) const ADDRESS: Address = super::make_address(0, 8); + pub const ADDRESS: Address = super::make_address(0, 8); pub fn new() -> Self { Self(Default::default()) diff --git a/engine-precompiles/src/blake2.rs b/engine-precompiles/src/blake2.rs index 5d841e668..8c6b6d7fc 100644 --- a/engine-precompiles/src/blake2.rs +++ b/engine-precompiles/src/blake2.rs @@ -128,10 +128,10 @@ fn f(mut h: [u64; 8], m: [u64; 16], t: [u64; 2], f: bool, rounds: u32) -> Vec [u8; 20] { diff --git a/engine-precompiles/src/identity.rs b/engine-precompiles/src/identity.rs index 142dcd874..f898ee137 100644 --- a/engine-precompiles/src/identity.rs +++ b/engine-precompiles/src/identity.rs @@ -21,7 +21,7 @@ mod consts { pub struct Identity; impl Identity { - pub(super) const ADDRESS: Address = super::make_address(0, 4); + pub const ADDRESS: Address = super::make_address(0, 4); } impl Precompile for Identity { diff --git a/engine-precompiles/src/lib.rs b/engine-precompiles/src/lib.rs index 78a956e05..1bf9184d8 100644 --- a/engine-precompiles/src/lib.rs +++ b/engine-precompiles/src/lib.rs @@ -34,13 +34,13 @@ use crate::xcc::CrossContractCall; use aurora_engine_sdk::env::Env; use aurora_engine_sdk::io::IO; use aurora_engine_sdk::promise::ReadOnlyPromiseHandler; -use aurora_engine_types::{account_id::AccountId, types::Address, vec, BTreeMap, Box}; +use aurora_engine_types::{account_id::AccountId, types::Address, vec, BTreeMap, BTreeSet, Box}; use evm::backend::Log; use evm::executor::{ self, stack::{PrecompileFailure, PrecompileHandle}, }; -use evm::{Context, ExitError, ExitSucceed}; +use evm::{Context, ExitError, ExitFatal, ExitSucceed}; use promise_result::PromiseResult; use xcc::cross_contract_call; @@ -112,6 +112,13 @@ impl HardFork for Berlin {} pub struct Precompiles<'a, I, E, H> { pub all_precompiles: prelude::BTreeMap>, + pub paused_precompiles: prelude::BTreeSet
, +} + +impl<'a, I, E, H> Precompiles<'a, I, E, H> { + fn is_paused(&self, address: prelude::H160) -> bool { + self.paused_precompiles.contains(&Address::new(address)) + } } impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> executor::stack::PrecompileSet @@ -123,6 +130,12 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> executor::stack::Preco ) -> Option> { let address = handle.code_address(); + if self.is_paused(address) { + return Some(Err(PrecompileFailure::Fatal { + exit_status: ExitFatal::Other(prelude::Cow::Borrowed("ERR_PAUSED")), + })); + } + let result = match self.all_precompiles.get(&Address::new(address))? { AllPrecompiles::ExitToNear(p) => process_precompile(p, handle), AllPrecompiles::ExitToEthereum(p) => process_precompile(p, handle), @@ -132,6 +145,7 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> executor::stack::Preco AllPrecompiles::CrossContractCall(p) => process_handle_based_precompile(p, handle), AllPrecompiles::Generic(p) => process_precompile(p.as_ref(), handle), }; + Some(result.and_then(|output| post_process(output, handle))) } @@ -354,6 +368,7 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> Precompiles<'a, I, E, Self { all_precompiles: generic_precompiles, + paused_precompiles: BTreeSet::new(), } } } diff --git a/engine-precompiles/src/modexp.rs b/engine-precompiles/src/modexp.rs index bf56d5bf8..7bc5972df 100644 --- a/engine-precompiles/src/modexp.rs +++ b/engine-precompiles/src/modexp.rs @@ -6,10 +6,11 @@ use crate::{ use evm::{Context, ExitError}; use num::{BigUint, Integer}; -pub(super) struct ModExp(PhantomData); +#[derive(Default)] +pub struct ModExp(PhantomData); impl ModExp { - pub(super) const ADDRESS: Address = super::make_address(0, 5); + pub const ADDRESS: Address = super::make_address(0, 5); pub fn new() -> Self { Self(Default::default()) diff --git a/engine-precompiles/src/secp256k1.rs b/engine-precompiles/src/secp256k1.rs index f66a06a5e..8b4ea088e 100644 --- a/engine-precompiles/src/secp256k1.rs +++ b/engine-precompiles/src/secp256k1.rs @@ -56,7 +56,7 @@ fn internal_impl(hash: H256, signature: &[u8]) -> Result { pub struct ECRecover; impl ECRecover { - pub(super) const ADDRESS: Address = super::make_address(0, 1); + pub const ADDRESS: Address = super::make_address(0, 1); } impl Precompile for ECRecover { diff --git a/engine-standalone-storage/src/sync/mod.rs b/engine-standalone-storage/src/sync/mod.rs index 407526e2f..965f73ef3 100644 --- a/engine-standalone-storage/src/sync/mod.rs +++ b/engine-standalone-storage/src/sync/mod.rs @@ -1,3 +1,4 @@ +use aurora_engine::pausables::PausedPrecompilesKeeper; use aurora_engine::{connector, engine, parameters::SubmitResult, xcc}; use aurora_engine_sdk::env::{self, Env, DEFAULT_PREPAID_GAS}; use aurora_engine_types::{ @@ -388,6 +389,24 @@ fn non_submit_execute<'db>( TransactionKind::Unknown => None, // Not handled in this function; is handled by the general `execute_transaction` function TransactionKind::Submit(_) => unreachable!(), + TransactionKind::PausePrecompiles(args) => { + let precompiles_to_pause = args.paused_mask; + + let mut pauser = engine::get_pauser(&io).expect("Unable to read precompiles pauser"); + pauser.pause_precompiles(precompiles_to_pause); + engine::set_pauser(&mut io, pauser); + + None + } + TransactionKind::ResumePrecompiles(args) => { + let precompiles_to_pause = args.paused_mask; + + let mut pauser = engine::get_pauser(&io).expect("Unable to read precompiles pauser"); + pauser.resume_precompiles(precompiles_to_pause); + engine::set_pauser(&mut io, pauser); + + None + } }; Ok(result) diff --git a/engine-standalone-storage/src/sync/types.rs b/engine-standalone-storage/src/sync/types.rs index 77a3b3866..c3eb652ca 100644 --- a/engine-standalone-storage/src/sync/types.rs +++ b/engine-standalone-storage/src/sync/types.rs @@ -1,5 +1,6 @@ use crate::Storage; use aurora_engine::parameters; +use aurora_engine::parameters::PausePrecompilesCallArgs; use aurora_engine::xcc::AddressVersionUpdateArgs; use aurora_engine_transactions::{EthTransactionKind, NormalizedEthTransaction}; use aurora_engine_types::account_id::AccountId; @@ -77,6 +78,10 @@ pub enum TransactionKind { Submit(EthTransactionKind), /// Ethereum transaction triggered by a NEAR account Call(parameters::CallArgs), + /// Administrative method that makes a subset of precompiles paused + PausePrecompiles(PausePrecompilesCallArgs), + /// Administrative method that resumes previously paused subset of precompiles + ResumePrecompiles(PausePrecompilesCallArgs), /// Input here represents the EVM code used to create the new contract Deploy(Vec), /// New bridged token @@ -332,6 +337,8 @@ impl TransactionKind { Self::no_evm_execution("factory_set_wnear_address") } TransactionKind::Unknown => Self::no_evm_execution("unknown"), + Self::PausePrecompiles(_) => Self::no_evm_execution("pause_precompiles"), + Self::ResumePrecompiles(_) => Self::no_evm_execution("resume_precompiles"), } } @@ -494,6 +501,8 @@ enum BorshableTransactionKind<'a> { FactoryUpdate(Cow<'a, Vec>), FactoryUpdateAddressVersion(Cow<'a, AddressVersionUpdateArgs>), FactorySetWNearAddress(types::Address), + PausePrecompiles(Cow<'a, parameters::PausePrecompilesCallArgs>), + ResumePrecompiles(Cow<'a, parameters::PausePrecompilesCallArgs>), Unknown, } @@ -533,6 +542,8 @@ impl<'a> From<&'a TransactionKind> for BorshableTransactionKind<'a> { Self::FactorySetWNearAddress(*address) } TransactionKind::Unknown => Self::Unknown, + TransactionKind::PausePrecompiles(x) => Self::PausePrecompiles(Cow::Borrowed(x)), + TransactionKind::ResumePrecompiles(x) => Self::ResumePrecompiles(Cow::Borrowed(x)), } } } @@ -584,6 +595,12 @@ impl<'a> TryFrom> for TransactionKind { Ok(Self::FactorySetWNearAddress(address)) } BorshableTransactionKind::Unknown => Ok(Self::Unknown), + BorshableTransactionKind::PausePrecompiles(x) => { + Ok(Self::PausePrecompiles(x.into_owned())) + } + BorshableTransactionKind::ResumePrecompiles(x) => { + Ok(Self::ResumePrecompiles(x.into_owned())) + } } } } diff --git a/engine-tests/Cargo.toml b/engine-tests/Cargo.toml index b74330172..416cf9927 100644 --- a/engine-tests/Cargo.toml +++ b/engine-tests/Cargo.toml @@ -31,13 +31,14 @@ rlp = { version = "0.5.0", default-features = false } base64 = "0.13.0" bstr = "0.2" byte-slice-cast = { version = "1.0", default-features = false } -ethabi = "17.1" +ethabi = "17.1" serde = { version = "1", features = ["derive"] } serde_json = "1" hex = { version = "0.4.3", default-features = false } near-sdk = { git = "https://github.com/aurora-is-near/near-sdk-rs.git", rev = "7a3fa3fbff84b712050370d840297df38c925d2d" } near-sdk-sim = { git = "https://github.com/aurora-is-near/near-sdk-rs.git", rev = "7a3fa3fbff84b712050370d840297df38c925d2d" } near-crypto = { git = "https://github.com/birchmd/nearcore.git", rev = "980bc48dc02878fea1e0dbc5812ae7de49f12dda" } +near-vm-errors = { git = "https://github.com/birchmd/nearcore.git", rev = "980bc48dc02878fea1e0dbc5812ae7de49f12dda" } near-vm-runner = { git = "https://github.com/birchmd/nearcore.git", rev = "980bc48dc02878fea1e0dbc5812ae7de49f12dda", default-features = false, features = [ "wasmer2_vm", "protocol_feature_alt_bn128" ] } near-vm-logic = { git = "https://github.com/birchmd/nearcore.git", rev = "980bc48dc02878fea1e0dbc5812ae7de49f12dda", default-features = false, features = [ "protocol_feature_alt_bn128" ] } near-primitives-core = { git = "https://github.com/birchmd/nearcore.git", rev = "980bc48dc02878fea1e0dbc5812ae7de49f12dda", features = [ "protocol_feature_alt_bn128" ] } diff --git a/engine-tests/src/test_utils/mod.rs b/engine-tests/src/test_utils/mod.rs index 6ed7188a4..8cbf0623e 100644 --- a/engine-tests/src/test_utils/mod.rs +++ b/engine-tests/src/test_utils/mod.rs @@ -33,6 +33,9 @@ pub fn origin() -> String { pub(crate) const SUBMIT: &str = "submit"; pub(crate) const CALL: &str = "call"; pub(crate) const DEPLOY_ERC20: &str = "deploy_erc20_token"; +pub(crate) const PAUSE_PRECOMPILES: &str = "pause_precompiles"; +pub(crate) const PAUSED_PRECOMPILES: &str = "paused_precompiles"; +pub(crate) const RESUME_PRECOMPILES: &str = "resume_precompiles"; pub(crate) mod erc20; pub(crate) mod exit_precompile; @@ -223,7 +226,11 @@ impl AuroraRunner { if let Some(standalone_runner) = &mut self.standalone_runner { if maybe_error.is_none() - && (method_name == SUBMIT || method_name == CALL || method_name == DEPLOY_ERC20) + && (method_name == SUBMIT + || method_name == CALL + || method_name == DEPLOY_ERC20 + || method_name == PAUSE_PRECOMPILES + || method_name == RESUME_PRECOMPILES) { standalone_runner .submit_raw(method_name, &self.context, &self.promise_results) @@ -582,7 +589,7 @@ impl Default for AuroraRunner { /// Wrapper around `ProfileData` to still include the wasm gas usage /// (which was removed in https://github.com/near/nearcore/pull/4438). -#[derive(Default, Clone)] +#[derive(Debug, Default, Clone)] pub(crate) struct ExecutionProfile { pub host_breakdown: ProfileData, wasm_gas: u64, diff --git a/engine-tests/src/test_utils/standalone/mod.rs b/engine-tests/src/test_utils/standalone/mod.rs index 85e9baeac..4dc9ea025 100644 --- a/engine-tests/src/test_utils/standalone/mod.rs +++ b/engine-tests/src/test_utils/standalone/mod.rs @@ -1,5 +1,7 @@ use aurora_engine::engine; -use aurora_engine::parameters::{CallArgs, DeployErc20TokenArgs, SubmitResult, TransactionStatus}; +use aurora_engine::parameters::{ + CallArgs, DeployErc20TokenArgs, PausePrecompilesCallArgs, SubmitResult, TransactionStatus, +}; use aurora_engine_sdk::env::{self, Env}; use aurora_engine_transactions::legacy::{LegacyEthSignedTransaction, TransactionLegacy}; use aurora_engine_types::types::{Address, NearGas, PromiseResult, Wei}; @@ -238,6 +240,44 @@ impl StandaloneRunner { 0, Vec::new(), )) + } else if method_name == test_utils::RESUME_PRECOMPILES { + let input = &ctx.input[..]; + let call_args = PausePrecompilesCallArgs::try_from_slice(input) + .expect("Unable to parse input as PausePrecompilesCallArgs"); + + 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::ResumePrecompiles(call_args); + + 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::PAUSE_PRECOMPILES { + let input = &ctx.input[..]; + let call_args = PausePrecompilesCallArgs::try_from_slice(input) + .expect("Unable to parse input as PausePrecompilesCallArgs"); + + 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::PausePrecompiles(call_args); + + 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 { panic!("Unsupported standalone method {}", method_name); } diff --git a/engine-tests/src/tests/mod.rs b/engine-tests/src/tests/mod.rs index 720c72019..30f18d30e 100644 --- a/engine-tests/src/tests/mod.rs +++ b/engine-tests/src/tests/mod.rs @@ -11,6 +11,7 @@ mod ghsa_3p69_m8gg_fwmf; mod meta_parsing; mod multisender; mod one_inch; +mod pausable_precompiles; mod prepaid_gas_precompile; mod promise_results_precompile; mod random; diff --git a/engine-tests/src/tests/pausable_precompiles.rs b/engine-tests/src/tests/pausable_precompiles.rs new file mode 100644 index 000000000..939702282 --- /dev/null +++ b/engine-tests/src/tests/pausable_precompiles.rs @@ -0,0 +1,168 @@ +use crate::prelude::U256; +use crate::test_utils::standard_precompiles::{PrecompilesConstructor, PrecompilesContract}; +use crate::test_utils::{ + self, AuroraRunner, Signer, PAUSED_PRECOMPILES, PAUSE_PRECOMPILES, RESUME_PRECOMPILES, +}; +use aurora_engine::parameters::{PausePrecompilesCallArgs, TransactionStatus}; +use aurora_engine_types::types::Wei; +use borsh::BorshSerialize; +use near_vm_errors::{FunctionCallError, HostError}; +use near_vm_runner::VMError; + +#[test] +fn test_paused_precompile_is_shown_when_viewing() { + const MODEXP_FLAG: u32 = 0b000000000010000; + const CALLED_ACCOUNT_ID: &str = "aurora"; + + let mut runner = test_utils::deploy_evm(); + + let call_args = PausePrecompilesCallArgs { + paused_mask: MODEXP_FLAG, + }; + + let mut input: Vec = Vec::new(); + call_args.serialize(&mut input).unwrap(); + + let _ = runner.call(PAUSE_PRECOMPILES, CALLED_ACCOUNT_ID, input.clone()); + let (outcome, error) = runner.call(PAUSED_PRECOMPILES, CALLED_ACCOUNT_ID, Vec::new()); + + assert!(error.is_none(), "{:?}", error); + + let input: Vec = outcome + .as_ref() + .unwrap() + .return_data + .clone() + .as_value() + .unwrap(); + + let actual_paused_precompiles = u32::from_le_bytes(input.as_slice().try_into().unwrap()); + let expected_paused_precompiles = MODEXP_FLAG; + + assert_eq!(expected_paused_precompiles, actual_paused_precompiles); +} + +#[test] +fn test_executing_paused_precompile_throws_error() { + const MODEXP_FLAG: u32 = 0b000000000010000; + const TEST_MODEXP: &str = "test_modexp"; + const CALLED_ACCOUNT_ID: &str = "aurora"; + + let (mut runner, mut signer, contract) = initialize_precompiles_contract(); + + let call_args = PausePrecompilesCallArgs { + paused_mask: MODEXP_FLAG, + }; + + let mut input: Vec = Vec::new(); + call_args.serialize(&mut input).unwrap(); + + let _ = runner.call(PAUSE_PRECOMPILES, CALLED_ACCOUNT_ID, input.clone()); + let result = runner.submit_with_signer_profiled(&mut signer, |nonce| { + contract.call_method(TEST_MODEXP, nonce) + }); + + assert!(result.is_err(), "{:?}", result); + + let error = result.unwrap_err(); + let msg = match &error { + VMError::FunctionCallError(fn_error) => match fn_error { + FunctionCallError::HostError(err) => match err { + HostError::GuestPanic { panic_msg } => Some(panic_msg), + _ => None, + }, + _ => None, + }, + _ => None, + }; + + assert!(msg.is_some(), "{:?}", error); + assert_eq!(msg.unwrap(), "ERR_PAUSED"); +} + +#[test] +fn test_executing_paused_and_then_resumed_precompile_succeeds() { + const MODEXP_FLAG: u32 = 0b000000000010000; + const TEST_MODEXP: &str = "test_modexp"; + const CALLED_ACCOUNT_ID: &str = "aurora"; + + let (mut runner, mut signer, contract) = initialize_precompiles_contract(); + + let call_args = PausePrecompilesCallArgs { + paused_mask: MODEXP_FLAG, + }; + + let mut input: Vec = Vec::new(); + call_args.serialize(&mut input).unwrap(); + + let _ = runner.call(PAUSE_PRECOMPILES, CALLED_ACCOUNT_ID, input.clone()); + let _ = runner.call(RESUME_PRECOMPILES, CALLED_ACCOUNT_ID, input); + let (result, _) = runner + .submit_with_signer_profiled(&mut signer, |nonce| { + contract.call_method(TEST_MODEXP, nonce) + }) + .unwrap(); + + let number = match result.status { + TransactionStatus::Succeed(number) => U256::from(number.as_slice()), + _ => panic!("Unexpected status {:?}", result), + }; + + assert_eq!(number, U256::one()); +} + +#[test] +fn test_resuming_precompile_does_not_throw_error() { + const CALLED_ACCOUNT_ID: &str = "aurora"; + + let mut runner = test_utils::deploy_evm(); + + let call_args = PausePrecompilesCallArgs { paused_mask: 0b1 }; + + let mut input: Vec = Vec::new(); + call_args.serialize(&mut input).unwrap(); + + let (_, error) = runner.call(RESUME_PRECOMPILES, CALLED_ACCOUNT_ID, input); + + assert!(error.is_none(), "{:?}", error); +} + +#[test] +fn test_pausing_precompile_does_not_throw_error() { + const CALLED_ACCOUNT_ID: &str = "aurora"; + + let mut runner = test_utils::deploy_evm(); + + let call_args = PausePrecompilesCallArgs { paused_mask: 0b1 }; + + let mut input: Vec = Vec::new(); + call_args.serialize(&mut input).unwrap(); + + let (_, error) = runner.call(PAUSE_PRECOMPILES, CALLED_ACCOUNT_ID, input); + + assert!(error.is_none(), "{:?}", error); +} + +fn initialize_precompiles_contract() -> (AuroraRunner, Signer, PrecompilesContract) { + const INITIAL_BALANCE: Wei = Wei::new_u64(1000); + const INITIAL_NONCE: u64 = 0; + + let mut runner = test_utils::deploy_evm(); + let mut signer = Signer::random(); + signer.nonce = INITIAL_NONCE; + runner.create_address( + test_utils::address_from_secret_key(&signer.secret_key), + INITIAL_BALANCE, + INITIAL_NONCE.into(), + ); + + let constructor = PrecompilesConstructor::load(); + let nonce = signer.use_nonce(); + let contract = PrecompilesContract(runner.deploy_contract( + &signer.secret_key, + |c| c.deploy(nonce.into()), + constructor, + )); + + (runner, signer, contract) +} diff --git a/engine-tests/src/tests/repro.rs b/engine-tests/src/tests/repro.rs index d8d628b8a..143bfa1f3 100644 --- a/engine-tests/src/tests/repro.rs +++ b/engine-tests/src/tests/repro.rs @@ -52,7 +52,7 @@ fn repro_8ru7VEA() { block_timestamp: 1648829935343349589, input_path: "src/tests/res/input_8ru7VEA.hex", evm_gas_used: 1732181, - near_gas_used: 240, + near_gas_used: 241, }); } diff --git a/engine-tests/src/tests/standard_precompiles.rs b/engine-tests/src/tests/standard_precompiles.rs index 94454ba63..9d16dfc3c 100644 --- a/engine-tests/src/tests/standard_precompiles.rs +++ b/engine-tests/src/tests/standard_precompiles.rs @@ -54,7 +54,7 @@ fn profile_identity() { #[test] fn profile_modexp() { let profile = precompile_execution_profile("test_modexp"); - test_utils::assert_gas_bound(profile.all_gas(), 7); + test_utils::assert_gas_bound(profile.all_gas(), 8); } #[test] diff --git a/engine-types/src/lib.rs b/engine-types/src/lib.rs index f2cf3590b..da10bbe53 100644 --- a/engine-types/src/lib.rs +++ b/engine-types/src/lib.rs @@ -18,6 +18,7 @@ mod v0 { boxed::Box, collections::BTreeMap as HashMap, collections::BTreeMap, + collections::BTreeSet, fmt, format, str, string::String, string::ToString, diff --git a/engine/Cargo.toml b/engine/Cargo.toml index f9fa20cd3..4e1a18b05 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -21,6 +21,7 @@ aurora-engine-sdk = { path = "../engine-sdk", default-features = false } aurora-engine-precompiles = { path = "../engine-precompiles", default-features = false } aurora-engine-transactions = { path = "../engine-transactions", default-features = false } base64 = { version = "0.13.0", default-features = false, features = ["alloc"] } +bitflags = { version = "1.3", default-features = false } borsh = { version = "0.9.3", default-features = false } byte-slice-cast = { version = "1.0", default-features = false } ethabi = { version = "17.1", default-features = false } @@ -36,6 +37,7 @@ wee_alloc = { version = "0.4.5", default-features = false } [dev-dependencies] serde_json = "1" rand = "0.7.3" +test-case = "2.1" [features] default = ["std"] diff --git a/engine/src/engine.rs b/engine/src/engine.rs index f879cf4b6..2a3a4e170 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -14,6 +14,7 @@ use aurora_engine_sdk::promise::{PromiseHandler, PromiseId, ReadOnlyPromiseHandl use crate::accounting; use crate::parameters::{DeployErc20TokenArgs, NewCallArgs, TransactionStatus}; +use crate::pausables::{EngineAuthorizer, EnginePrecompilesPauser, PrecompileFlags}; use crate::prelude::parameters::RefundCallArgs; use crate::prelude::precompiles::native::{exit_to_ethereum, exit_to_near}; use crate::prelude::precompiles::xcc::cross_contract_call; @@ -26,6 +27,7 @@ use crate::prelude::{ }; use aurora_engine_precompiles::PrecompileConstructorContext; use core::cell::RefCell; +use core::iter::once; /// Used as the first byte in the concatenation of data used to compute the blockhash. /// Could be useful in the future as a version byte, or to distinguish different types of blocks. @@ -354,36 +356,7 @@ struct StackExecutorParams<'a, I, E, H> { } impl<'env, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> StackExecutorParams<'env, I, E, H> { - fn new( - gas_limit: u64, - current_account_id: AccountId, - random_seed: H256, - io: I, - env: &'env E, - ro_promise_handler: H, - ) -> Self { - let precompiles = if cfg!(all(feature = "mainnet", not(feature = "integration-test"))) { - let mut tmp = Precompiles::new_london(PrecompileConstructorContext { - current_account_id, - random_seed, - io, - env, - promise_handler: ro_promise_handler, - }); - // Cross contract calls are not enabled on mainnet yet. - tmp.all_precompiles - .remove(&aurora_engine_precompiles::xcc::cross_contract_call::ADDRESS); - tmp - } else { - Precompiles::new_london(PrecompileConstructorContext { - current_account_id, - random_seed, - io, - env, - promise_handler: ro_promise_handler, - }) - }; - + fn new(gas_limit: u64, precompiles: Precompiles<'env, I, E, H>) -> Self { Self { precompiles, gas_limit, @@ -456,6 +429,8 @@ pub(crate) const CONFIG: &Config = &Config::london(); /// Key for storing the state of the engine. const STATE_KEY: &[u8; 5] = b"STATE"; +/// Key for storing [PrecompileFlags]. +const PAUSE_FLAGS_KEY: &[u8; 11] = b"PAUSE_FLAGS"; impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { pub fn new( @@ -541,14 +516,10 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { access_list: Vec<(H160, Vec)>, // See EIP-2930 handler: &mut P, ) -> EngineResult { - let executor_params = StackExecutorParams::new( - gas_limit, - self.current_account_id.clone(), - self.env.random_seed(), - self.io, - self.env, - handler.read_only(), - ); + let pause_flags = get_pauser(&self.io).unwrap().precompiles_pause_flags; + let precompiles = self.create_precompiles(pause_flags, handler); + + let executor_params = StackExecutorParams::new(gas_limit, precompiles); let mut executor = executor_params.make_executor(self); let address = executor.create_address(CreateScheme::Legacy { caller: origin.raw(), @@ -628,14 +599,10 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { access_list: Vec<(H160, Vec)>, // See EIP-2930 handler: &mut P, ) -> EngineResult { - let executor_params = StackExecutorParams::new( - gas_limit, - self.current_account_id.clone(), - self.env.random_seed(), - self.io, - self.env, - handler.read_only(), - ); + let pause_flags = get_pauser(&self.io).unwrap().precompiles_pause_flags; + let precompiles = self.create_precompiles(pause_flags, handler); + + let executor_params = StackExecutorParams::new(gas_limit, precompiles); let mut executor = executor_params.make_executor(self); let (exit_reason, result) = executor.transact_call( origin.raw(), @@ -680,15 +647,12 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { input: Vec, gas_limit: u64, ) -> Result { - let executor_params = StackExecutorParams::new( - gas_limit, - self.current_account_id.clone(), - self.env.random_seed(), - self.io, - self.env, - // View calls cannot interact with promises - aurora_engine_sdk::promise::Noop, - ); + // View calls cannot interact with promises + let mut handler = aurora_engine_sdk::promise::Noop; + let pause_flags = get_pauser(&self.io).unwrap().precompiles_pause_flags; + let precompiles = self.create_precompiles(pause_flags, &mut handler); + + let executor_params = StackExecutorParams::new(gas_limit, precompiles); let mut executor = executor_params.make_executor(self); let (status, result) = executor.transact_call( origin.raw(), @@ -883,6 +847,57 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { // Everything succeed so return "0" self.io.return_output(b"\"0\""); } + + fn create_precompiles( + &self, + pause_flags: PrecompileFlags, + handler: &mut P, + ) -> Precompiles<'env, I, E, P::ReadOnly> { + let current_account_id = self.current_account_id.clone(); + let random_seed = self.env.random_seed(); + let io = self.io; + let env = self.env; + let ro_promise_handler = handler.read_only(); + + let precompiles = if cfg!(all(feature = "mainnet", not(feature = "integration-test"))) { + let mut tmp = Precompiles::new_london(PrecompileConstructorContext { + current_account_id, + random_seed, + io, + env, + promise_handler: ro_promise_handler, + }); + // Cross contract calls are not enabled on mainnet yet. + tmp.all_precompiles + .remove(&aurora_engine_precompiles::xcc::cross_contract_call::ADDRESS); + tmp + } else { + Precompiles::new_london(PrecompileConstructorContext { + current_account_id, + random_seed, + io, + env, + promise_handler: ro_promise_handler, + }) + }; + + Self::apply_pause_flags_to_precompiles(precompiles, pause_flags) + } + + fn apply_pause_flags_to_precompiles( + precompiles: Precompiles<'env, I, E, H>, + pause_flags: PrecompileFlags, + ) -> Precompiles<'env, I, E, H> { + Precompiles { + paused_precompiles: precompiles + .all_precompiles + .iter() + .filter(|(address, _)| pause_flags.is_paused_by_address(address)) + .map(|(k, _)| *k) + .collect(), + all_precompiles: precompiles.all_precompiles, + } + } } pub fn submit( @@ -1113,6 +1128,29 @@ pub fn set_state(io: &mut I, state: EngineState) { ); } +pub fn get_authorizer() -> EngineAuthorizer { + // TODO: a temporary account until the engine adapts std with near-plugins + let account = Address::decode("4444588443c3a91288c5002483449aba1054192b") + .expect("Valid address failed to parse from string"); + + EngineAuthorizer::from_addresses(once(account)) +} + +pub fn get_pauser(io: &I) -> Result { + match io.read_storage(&bytes_to_key(KeyPrefix::Config, PAUSE_FLAGS_KEY)) { + None => Ok(EnginePrecompilesPauser::default()), + Some(bytes) => EnginePrecompilesPauser::try_from_slice(&bytes.to_vec()) + .map_err(|_| EngineStateError::DeserializationFailed), + } +} + +pub fn set_pauser(io: &mut I, state: EnginePrecompilesPauser) { + io.write_storage( + &bytes_to_key(KeyPrefix::Config, PAUSE_FLAGS_KEY), + &state.try_to_vec().expect("ERR_SER"), + ); +} + pub fn refund_unused_gas( io: &mut I, sender: &Address, diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 96be012ff..d9255a498 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -27,6 +27,7 @@ pub mod errors; pub mod fungible_token; pub mod json; pub mod log_entry; +pub mod pausables; mod prelude; pub mod xcc; @@ -71,17 +72,18 @@ mod contract { use crate::connector::{self, EthConnectorContract}; use crate::engine::{self, Engine, EngineState}; - use crate::errors; use crate::fungible_token::FungibleTokenMetadata; use crate::json::parse_json; use crate::parameters::{ self, CallArgs, DeployErc20TokenArgs, GetErc20FromNep141CallArgs, GetStorageAtArgs, InitCallArgs, IsUsedProofCallArgs, NEP141FtOnTransferArgs, NewCallArgs, - PauseEthConnectorCallArgs, ResolveTransferCallArgs, SetContractDataCallArgs, - StorageDepositCallArgs, StorageWithdrawCallArgs, TransferCallCallArgs, ViewCallArgs, + PauseEthConnectorCallArgs, PausePrecompilesCallArgs, ResolveTransferCallArgs, + SetContractDataCallArgs, StorageDepositCallArgs, StorageWithdrawCallArgs, + TransferCallCallArgs, ViewCallArgs, }; #[cfg(feature = "evm_bully")] use crate::parameters::{BeginBlockArgs, BeginChainArgs}; + use crate::pausables::{Authorizer, PausedPrecompilesKeeper}; use crate::prelude::account_id::AccountId; use crate::prelude::parameters::RefundCallArgs; use crate::prelude::sdk::types::{ @@ -91,6 +93,7 @@ mod contract { use crate::prelude::{ sdk, u256_to_arr, Address, PromiseResult, ToString, Yocto, ERR_FAILED_PARSE, H256, }; + use crate::{errors, pausables}; use aurora_engine_sdk::env::Env; use aurora_engine_sdk::io::{StorageIntermediate, IO}; use aurora_engine_sdk::near_runtime::{Runtime, ViewEnv}; @@ -196,6 +199,51 @@ mod contract { // TODO: currently we don't have migrations } + /// Resumes previously [`paused`] precompiles. + /// + /// [`paused`]: crate::contract::pause_precompiles + #[no_mangle] + pub extern "C" fn resume_precompiles() { + let mut io = Runtime; + let state = engine::get_state(&io).sdk_unwrap(); + let predecessor_account_id = io.predecessor_account_id(); + + require_owner_only(&state, &predecessor_account_id); + + let args: PausePrecompilesCallArgs = io.read_input_borsh().sdk_unwrap(); + let mut pauser = engine::get_pauser(&io).sdk_unwrap(); + pauser.resume_precompiles(args.paused_mask); + engine::set_pauser(&mut io, pauser); + } + + /// Pauses a precompile. + #[no_mangle] + pub extern "C" fn pause_precompiles() { + let mut io = Runtime; + let authorizer: pausables::EngineAuthorizer = engine::get_authorizer(); + let predecessor_account_id = io.predecessor_account_id(); + let predecessor_address = predecessor_address(&predecessor_account_id); + + if !authorizer.is_authorized(&predecessor_address) { + sdk::panic_utf8("ERR_UNAUTHORIZED".as_bytes()); + } + + let args: PausePrecompilesCallArgs = io.read_input_borsh().sdk_unwrap(); + let mut pauser = engine::get_pauser(&io).sdk_unwrap(); + pauser.pause_precompiles(args.paused_mask); + engine::set_pauser(&mut io, pauser); + } + + /// Returns an unsigned integer where each 1-bit means that a precompile corresponding to that bit is paused and + /// 0-bit means not paused. + #[no_mangle] + pub extern "C" fn paused_precompiles() { + let mut io = Runtime; + let pauser = engine::get_pauser(&io).sdk_unwrap(); + let data = pauser.precompiles_pause_flags.bits().to_le_bytes(); + io.return_output(&data[..]); + } + /// /// MUTATIVE METHODS /// diff --git a/engine/src/parameters.rs b/engine/src/parameters.rs index caa9c5e08..dbd14231d 100644 --- a/engine/src/parameters.rs +++ b/engine/src/parameters.rs @@ -473,6 +473,11 @@ pub struct PauseEthConnectorCallArgs { pub paused_mask: PausedMask, } +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +pub struct PausePrecompilesCallArgs { + pub paused_mask: u32, +} + impl TryFrom for ResolveTransferCallArgs { type Error = error::ParseTypeFromJsonError; diff --git a/engine/src/pausables.rs b/engine/src/pausables.rs new file mode 100644 index 000000000..98ad08367 --- /dev/null +++ b/engine/src/pausables.rs @@ -0,0 +1,229 @@ +use aurora_engine_precompiles::account_ids::{predecessor_account, CurrentAccount}; +use aurora_engine_precompiles::alt_bn256::{Bn256Add, Bn256Mul, Bn256Pair}; +use aurora_engine_precompiles::blake2::Blake2F; +use aurora_engine_precompiles::hash::{RIPEMD160, SHA256}; +use aurora_engine_precompiles::identity::Identity; +use aurora_engine_precompiles::modexp::ModExp; +use aurora_engine_precompiles::native::{exit_to_ethereum, exit_to_near}; +use aurora_engine_precompiles::random::RandomSeed; +use aurora_engine_precompiles::secp256k1::ECRecover; +use aurora_engine_precompiles::{prepaid_gas, Berlin}; +use aurora_engine_types::types::Address; +use aurora_engine_types::BTreeSet; +use bitflags::bitflags; +use borsh::{BorshDeserialize, BorshSerialize}; + +bitflags! { + /// Wraps unsigned integer where each bit identifies a different precompile. + #[derive(BorshSerialize, BorshDeserialize, Default)] + pub struct PrecompileFlags: u32 { + const ECRECOVER = 0b000000000000001; + const SHA256 = 0b000000000000010; + const RIPEMD160 = 0b000000000000100; + const IDENTITY = 0b000000000001000; + const MODEXP = 0b000000000010000; + const BN256_ADD = 0b000000000100000; + const BN256_MUL = 0b000000001000000; + const BN256_PAIR = 0b000000010000000; + const BLAKE2F = 0b000000100000000; + const RANDOM_SEED = 0b000001000000000; + const CURRENT_ACCOUNT = 0b000010000000000; + const PREDECESSOR_ACCOUNT = 0b000100000000000; + const EXIT_TO_ETHEREUM = 0b001000000000000; + const EXIT_TO_NEAR = 0b010000000000000; + const PREPAID_GAS = 0b100000000000000; + } +} + +impl PrecompileFlags { + /// Checks if the precompile belonging to the `address` is marked as paused. + pub fn is_paused_by_address(&self, address: &Address) -> bool { + let precompile_flag = if address == &ECRecover::ADDRESS { + PrecompileFlags::ECRECOVER + } else if address == &SHA256::ADDRESS { + PrecompileFlags::SHA256 + } else if address == &RIPEMD160::ADDRESS { + PrecompileFlags::RIPEMD160 + } else if address == &Identity::ADDRESS { + PrecompileFlags::IDENTITY + } else if address == &ModExp::::ADDRESS { + PrecompileFlags::MODEXP + } else if address == &Bn256Add::::ADDRESS { + PrecompileFlags::BN256_ADD + } else if address == &Bn256Mul::::ADDRESS { + PrecompileFlags::BN256_MUL + } else if address == &Bn256Pair::::ADDRESS { + PrecompileFlags::BN256_PAIR + } else if address == &Blake2F::ADDRESS { + PrecompileFlags::BLAKE2F + } else if address == &RandomSeed::ADDRESS { + PrecompileFlags::RANDOM_SEED + } else if address == &CurrentAccount::ADDRESS { + PrecompileFlags::CURRENT_ACCOUNT + } else if address == &predecessor_account::ADDRESS { + PrecompileFlags::PREDECESSOR_ACCOUNT + } else if address == &exit_to_ethereum::ADDRESS { + PrecompileFlags::EXIT_TO_ETHEREUM + } else if address == &exit_to_near::ADDRESS { + PrecompileFlags::EXIT_TO_NEAR + } else if address == &prepaid_gas::ADDRESS { + PrecompileFlags::PREPAID_GAS + } else { + return false; + }; + + self.contains(precompile_flag) + } +} + +/// Can check if given account has a permission to pause precompiles. +pub trait Authorizer { + /// Checks if the `address` has the permission to pause precompiles. + fn is_authorized(&self, address: &Address) -> bool; +} + +/// Can check if a subset of precompiles is currently paused or not. +pub trait Pauser { + /// Checks if all of the `precompiles` are paused. + /// + /// The `precompiles` mask can be a subset and every 1 bit is meant to be checked and every 0 bit is ignored. + fn is_paused(&self, precompiles: PrecompileFlags) -> bool; +} + +/// Responsible for resuming and pausing of precompiles. +pub trait PausedPrecompilesKeeper { + /// Resumes all the given `precompiles_to_resume`. + /// + /// The `precompiles_to_resume` mask can be a subset and every 1 bit is meant to be resumed and every 0 bit is + /// ignored. + fn resume_precompiles(&mut self, precompiles_to_resume: u32); + + /// Pauses all the given precompiles. + /// + /// The `precompiles_to_pause` mask can be a subset and every 1 bit is meant to be paused and every 0 bit is + /// ignored. + fn pause_precompiles(&mut self, precompiles_to_pause: u32); +} + +#[derive(BorshSerialize, BorshDeserialize, Debug, Default, Clone)] +pub struct EngineAuthorizer { + /// List of [AccountId]s with the permissions to pause precompiles are authenticated. + pub acl: BTreeSet
, +} + +impl EngineAuthorizer { + /// Creates new [EngineAuthorizer] and grants permission to pause precompiles for all given `accounts`. + pub fn from_addresses(addresses: impl Iterator) -> Self { + Self { + acl: addresses.collect(), + } + } +} + +#[derive(BorshSerialize, BorshDeserialize, Debug, Default, Clone)] +pub struct EnginePrecompilesPauser { + /// Determines which precompiled are currently paused, where off bit means running and on bit means paused. + pub precompiles_pause_flags: PrecompileFlags, +} + +impl Authorizer for EngineAuthorizer { + fn is_authorized(&self, address: &Address) -> bool { + self.acl.get(address).is_some() + } +} + +impl Pauser for EnginePrecompilesPauser { + fn is_paused(&self, precompiles: PrecompileFlags) -> bool { + self.precompiles_pause_flags.contains(precompiles) + } +} + +impl PausedPrecompilesKeeper for EnginePrecompilesPauser { + fn resume_precompiles(&mut self, precompiles_to_resume: u32) { + self.precompiles_pause_flags + .remove(PrecompileFlags::from_bits_truncate(precompiles_to_resume)); + } + + fn pause_precompiles(&mut self, precompiles_to_pause: u32) { + self.precompiles_pause_flags + .insert(PrecompileFlags::from_bits_truncate(precompiles_to_pause)); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::iter::once; + use test_case::test_case; + + #[test_case(PrecompileFlags::ECRECOVER, ECRecover::ADDRESS)] + #[test_case(PrecompileFlags::SHA256, SHA256::ADDRESS)] + #[test_case(PrecompileFlags::RIPEMD160, RIPEMD160::ADDRESS)] + #[test_case(PrecompileFlags::IDENTITY, Identity::ADDRESS)] + #[test_case(PrecompileFlags::MODEXP, ModExp::::ADDRESS)] + #[test_case(PrecompileFlags::BN256_ADD, Bn256Add::::ADDRESS)] + #[test_case(PrecompileFlags::BN256_MUL, Bn256Mul::::ADDRESS)] + #[test_case(PrecompileFlags::BN256_PAIR, Bn256Pair::::ADDRESS)] + #[test_case(PrecompileFlags::BLAKE2F, Blake2F::ADDRESS)] + #[test_case(PrecompileFlags::RANDOM_SEED, RandomSeed::ADDRESS)] + #[test_case(PrecompileFlags::CURRENT_ACCOUNT, CurrentAccount::ADDRESS)] + #[test_case(PrecompileFlags::PREDECESSOR_ACCOUNT, predecessor_account::ADDRESS)] + #[test_case(PrecompileFlags::EXIT_TO_ETHEREUM, exit_to_ethereum::ADDRESS)] + #[test_case(PrecompileFlags::EXIT_TO_NEAR, exit_to_near::ADDRESS)] + #[test_case(PrecompileFlags::PREPAID_GAS, prepaid_gas::ADDRESS)] + fn test_paused_flag_marks_precompiles_address_as_paused( + flags: PrecompileFlags, + address: Address, + ) { + assert!(flags.is_paused_by_address(&address)); + } + + #[test] + fn test_unknown_precompile_address_is_not_marked_as_paused() { + let flags = PrecompileFlags::all(); + let address = Address::zero(); + + assert!(!flags.is_paused_by_address(&address)); + } + + #[test] + fn test_pausing_precompile_marks_it_as_paused() { + let flags = PrecompileFlags::EXIT_TO_NEAR; + let mut pauser = EnginePrecompilesPauser { + precompiles_pause_flags: PrecompileFlags::empty(), + }; + + assert!(!pauser.is_paused(flags)); + pauser.pause_precompiles(flags.bits); + assert!(pauser.is_paused(flags)); + } + + #[test] + fn test_resume_precompile_removes_its_mark_as_paused() { + let flags = PrecompileFlags::EXIT_TO_NEAR; + let mut pauser = EnginePrecompilesPauser { + precompiles_pause_flags: PrecompileFlags::empty(), + }; + pauser.pause_precompiles(flags.bits); + + assert!(pauser.is_paused(flags)); + pauser.resume_precompiles(flags.bits); + assert!(!pauser.is_paused(flags)); + } + + #[test] + fn test_granting_permission_to_account_authorizes_it() { + let address = Address::default(); + let authorizer = EngineAuthorizer::from_addresses(once(address)); + + assert!(authorizer.is_authorized(&address)); + } + + #[test] + fn test_revoking_permission_from_account_unauthorizes_it() { + let address = Address::default(); + let authorizer = EngineAuthorizer::default(); + + assert!(!authorizer.is_authorized(&address)); + } +} diff --git a/etc/state-migration-test/Cargo.lock b/etc/state-migration-test/Cargo.lock index 43f8a1762..56b2b9927 100644 --- a/etc/state-migration-test/Cargo.lock +++ b/etc/state-migration-test/Cargo.lock @@ -39,6 +39,7 @@ dependencies = [ "aurora-engine-transactions", "aurora-engine-types", "base64", + "bitflags", "borsh", "byte-slice-cast", "ethabi", @@ -150,6 +151,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6736e2428df2ca2848d846c43e88745121a6654696e349ce0054a420815a7409" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "block-buffer" version = "0.9.0"