From dbdd2f47bf859d20a168a7519d32b9c745999c16 Mon Sep 17 00:00:00 2001 From: Michael Birch Date: Mon, 28 Mar 2022 07:15:20 -0400 Subject: [PATCH] Fix(precompiles): Allow native precompiles to work in the standalone engine (#473) --- engine-precompiles/src/lib.rs | 103 ++++++++++++++++----------- engine-precompiles/src/native.rs | 115 ++++++++++++------------------- engine/src/engine.rs | 21 +++--- engine/src/lib.rs | 2 +- 4 files changed, 121 insertions(+), 120 deletions(-) diff --git a/engine-precompiles/src/lib.rs b/engine-precompiles/src/lib.rs index a7bfc88e9..d19b93f64 100644 --- a/engine-precompiles/src/lib.rs +++ b/engine-precompiles/src/lib.rs @@ -21,11 +21,12 @@ use crate::bn128::{Bn128Add, Bn128Mul, Bn128Pair}; use crate::hash::{RIPEMD160, SHA256}; use crate::identity::Identity; use crate::modexp::ModExp; -use crate::native::{ExitToEthereum, ExitToNear}; +use crate::native::{exit_to_ethereum, exit_to_near, ExitToEthereum, ExitToNear}; use crate::prelude::types::EthGas; use crate::prelude::{Vec, H160, H256}; use crate::random::RandomSeed; use crate::secp256k1::ECRecover; +use aurora_engine_sdk::io::IO; use aurora_engine_types::{account_id::AccountId, types::Address, vec, BTreeMap, Box}; use evm::backend::Log; use evm::executor; @@ -101,9 +102,15 @@ impl HardFork for Istanbul {} impl HardFork for Berlin {} -pub struct Precompiles(pub prelude::BTreeMap>); +pub struct Precompiles { + pub generic_precompiles: prelude::BTreeMap>, + // Cannot be part of the generic precompiles because the `dyn` type-erasure messes with + // with the lifetime requirements on the type parameter `I`. + pub near_exit: ExitToNear, + pub ethereum_exit: ExitToEthereum, +} -impl executor::stack::PrecompileSet for Precompiles { +impl executor::stack::PrecompileSet for Precompiles { fn execute( &self, address: prelude::H160, @@ -112,32 +119,32 @@ impl executor::stack::PrecompileSet for Precompiles { context: &Context, is_static: bool, ) -> Option> { - self.0.get(&Address::new(address)).map(|p| { + self.precompile_action(Address::new(address), |p| { p.run(input, gas_limit.map(EthGas::new), context, is_static) .map_err(|exit_status| executor::stack::PrecompileFailure::Error { exit_status }) }) } fn is_precompile(&self, address: prelude::H160) -> bool { - self.0.contains_key(&Address::new(address)) + self.precompile_action(Address::new(address), |_| true) + .unwrap_or(false) } } -pub struct PrecompileConstructorContext { +pub struct PrecompileConstructorContext { pub current_account_id: AccountId, pub random_seed: H256, pub predecessor_account_id: AccountId, + pub io: I, } -impl Precompiles { +impl Precompiles { #[allow(dead_code)] - pub fn new_homestead(ctx: PrecompileConstructorContext) -> Self { + pub fn new_homestead(ctx: PrecompileConstructorContext) -> Self { let addresses = vec![ ECRecover::ADDRESS, SHA256::ADDRESS, RIPEMD160::ADDRESS, - ExitToNear::ADDRESS, - ExitToEthereum::ADDRESS, RandomSeed::ADDRESS, CurrentAccount::ADDRESS, PredecessorAccount::ADDRESS, @@ -146,19 +153,16 @@ impl Precompiles { Box::new(ECRecover), Box::new(SHA256), Box::new(RIPEMD160), - Box::new(ExitToNear::new(ctx.current_account_id.clone())), - Box::new(ExitToEthereum::new(ctx.current_account_id.clone())), Box::new(RandomSeed::new(ctx.random_seed)), - Box::new(CurrentAccount::new(ctx.current_account_id)), - Box::new(PredecessorAccount::new(ctx.predecessor_account_id)), + Box::new(CurrentAccount::new(ctx.current_account_id.clone())), + Box::new(PredecessorAccount::new(ctx.predecessor_account_id.clone())), ]; let map: BTreeMap> = addresses.into_iter().zip(fun).collect(); - - Precompiles(map) + Self::with_generic_precompiles(map, ctx) } #[allow(dead_code)] - pub fn new_byzantium(ctx: PrecompileConstructorContext) -> Self { + pub fn new_byzantium(ctx: PrecompileConstructorContext) -> Self { let addresses = vec![ ECRecover::ADDRESS, SHA256::ADDRESS, @@ -168,8 +172,6 @@ impl Precompiles { Bn128Add::::ADDRESS, Bn128Mul::::ADDRESS, Bn128Pair::::ADDRESS, - ExitToNear::ADDRESS, - ExitToEthereum::ADDRESS, RandomSeed::ADDRESS, CurrentAccount::ADDRESS, PredecessorAccount::ADDRESS, @@ -183,18 +185,16 @@ impl Precompiles { Box::new(Bn128Add::::new()), Box::new(Bn128Mul::::new()), Box::new(Bn128Pair::::new()), - Box::new(ExitToNear::new(ctx.current_account_id.clone())), - Box::new(ExitToEthereum::new(ctx.current_account_id.clone())), Box::new(RandomSeed::new(ctx.random_seed)), - Box::new(CurrentAccount::new(ctx.current_account_id)), - Box::new(PredecessorAccount::new(ctx.predecessor_account_id)), + Box::new(CurrentAccount::new(ctx.current_account_id.clone())), + Box::new(PredecessorAccount::new(ctx.predecessor_account_id.clone())), ]; let map: BTreeMap> = addresses.into_iter().zip(fun).collect(); - Precompiles(map) + Self::with_generic_precompiles(map, ctx) } - pub fn new_istanbul(ctx: PrecompileConstructorContext) -> Self { + pub fn new_istanbul(ctx: PrecompileConstructorContext) -> Self { let addresses = vec![ ECRecover::ADDRESS, SHA256::ADDRESS, @@ -205,8 +205,6 @@ impl Precompiles { Bn128Mul::::ADDRESS, Bn128Pair::::ADDRESS, Blake2F::ADDRESS, - ExitToNear::ADDRESS, - ExitToEthereum::ADDRESS, RandomSeed::ADDRESS, CurrentAccount::ADDRESS, PredecessorAccount::ADDRESS, @@ -221,18 +219,16 @@ impl Precompiles { Box::new(Bn128Mul::::new()), Box::new(Bn128Pair::::new()), Box::new(Blake2F), - Box::new(ExitToNear::new(ctx.current_account_id.clone())), - Box::new(ExitToEthereum::new(ctx.current_account_id.clone())), Box::new(RandomSeed::new(ctx.random_seed)), - Box::new(CurrentAccount::new(ctx.current_account_id)), - Box::new(PredecessorAccount::new(ctx.predecessor_account_id)), + Box::new(CurrentAccount::new(ctx.current_account_id.clone())), + Box::new(PredecessorAccount::new(ctx.predecessor_account_id.clone())), ]; let map: BTreeMap> = addresses.into_iter().zip(fun).collect(); - Precompiles(map) + Self::with_generic_precompiles(map, ctx) } - pub fn new_berlin(ctx: PrecompileConstructorContext) -> Self { + pub fn new_berlin(ctx: PrecompileConstructorContext) -> Self { let addresses = vec![ ECRecover::ADDRESS, SHA256::ADDRESS, @@ -243,8 +239,6 @@ impl Precompiles { Bn128Mul::::ADDRESS, Bn128Pair::::ADDRESS, Blake2F::ADDRESS, - ExitToNear::ADDRESS, - ExitToEthereum::ADDRESS, RandomSeed::ADDRESS, CurrentAccount::ADDRESS, PredecessorAccount::ADDRESS, @@ -259,21 +253,48 @@ impl Precompiles { Box::new(Bn128Mul::::new()), Box::new(Bn128Pair::::new()), Box::new(Blake2F), - Box::new(ExitToNear::new(ctx.current_account_id.clone())), - Box::new(ExitToEthereum::new(ctx.current_account_id.clone())), Box::new(RandomSeed::new(ctx.random_seed)), - Box::new(CurrentAccount::new(ctx.current_account_id)), - Box::new(PredecessorAccount::new(ctx.predecessor_account_id)), + Box::new(CurrentAccount::new(ctx.current_account_id.clone())), + Box::new(PredecessorAccount::new(ctx.predecessor_account_id.clone())), ]; let map: BTreeMap> = addresses.into_iter().zip(fun).collect(); - Precompiles(map) + Self::with_generic_precompiles(map, ctx) } - pub fn new_london(ctx: PrecompileConstructorContext) -> Self { + pub fn new_london(ctx: PrecompileConstructorContext) -> Self { // no precompile changes in London HF Self::new_berlin(ctx) } + + fn with_generic_precompiles( + generic_precompiles: BTreeMap>, + ctx: PrecompileConstructorContext, + ) -> Self { + let near_exit = ExitToNear::new(ctx.current_account_id.clone(), ctx.io); + let ethereum_exit = ExitToEthereum::new(ctx.current_account_id, ctx.io); + + Self { + generic_precompiles, + near_exit, + ethereum_exit, + } + } + + fn precompile_action U>( + &self, + address: Address, + f: F, + ) -> Option { + if address == exit_to_near::ADDRESS { + return Some(f(&self.near_exit)); + } else if address == exit_to_ethereum::ADDRESS { + return Some(f(&self.ethereum_exit)); + } + self.generic_precompiles + .get(&address) + .map(|p| f(p.as_ref())) + } } /// fn for making an address by concatenating the bytes from two given numbers, diff --git a/engine-precompiles/src/native.rs b/engine-precompiles/src/native.rs index 8cea60da4..5ae6de0c2 100644 --- a/engine-precompiles/src/native.rs +++ b/engine-precompiles/src/native.rs @@ -1,23 +1,21 @@ use super::{EvmPrecompileResult, Precompile}; -#[cfg(feature = "contract")] use crate::prelude::{ format, parameters::{PromiseArgs, PromiseCreateArgs, WithdrawCallArgs}, - sdk, + sdk::io::{StorageIntermediate, IO}, storage::{bytes_to_key, KeyPrefix}, - types::Yocto, + types::{Address, Yocto}, vec, BorshSerialize, Cow, String, ToString, Vec, U256, }; -#[cfg(all(feature = "error_refund", feature = "contract"))] +#[cfg(feature = "error_refund")] use crate::prelude::{ parameters::{PromiseWithCallbackArgs, RefundCallArgs}, types, }; -use crate::prelude::types::{Address, EthGas}; +use crate::prelude::types::EthGas; use crate::PrecompileOutput; use aurora_engine_types::account_id::AccountId; -#[cfg(feature = "contract")] use evm::backend::Log; use evm::{Context, ExitError}; @@ -191,58 +189,45 @@ pub mod events { } //TransferEthToNear -pub struct ExitToNear { +pub struct ExitToNear { current_account_id: AccountId, + io: I, } -impl ExitToNear { +pub mod exit_to_near { + use aurora_engine_types::types::Address; + /// Exit to NEAR precompile address /// /// Address: `0xe9217bc70b7ed1f598ddd3199e80b093fa71124f` /// This address is computed as: `&keccak("exitToNear")[12..]` pub const ADDRESS: Address = - super::make_address(0xe9217bc7, 0x0b7ed1f598ddd3199e80b093fa71124f); + crate::make_address(0xe9217bc7, 0x0b7ed1f598ddd3199e80b093fa71124f); +} - pub fn new(current_account_id: AccountId) -> Self { - Self { current_account_id } +impl ExitToNear { + pub fn new(current_account_id: AccountId, io: I) -> Self { + Self { + current_account_id, + io, + } } } -#[cfg(feature = "contract")] -fn get_nep141_from_erc20(erc20_token: &[u8]) -> AccountId { - use sdk::io::{StorageIntermediate, IO}; +fn get_nep141_from_erc20(erc20_token: &[u8], io: &I) -> Result { AccountId::try_from( - sdk::near_runtime::Runtime - .read_storage(bytes_to_key(KeyPrefix::Erc20Nep141Map, erc20_token).as_slice()) + io.read_storage(bytes_to_key(KeyPrefix::Erc20Nep141Map, erc20_token).as_slice()) .map(|s| s.to_vec()) - .expect(ERR_TARGET_TOKEN_NOT_FOUND), + .ok_or(ExitError::Other(Cow::Borrowed(ERR_TARGET_TOKEN_NOT_FOUND)))?, ) - .unwrap() + .map_err(|_| ExitError::Other(Cow::Borrowed("ERR_INVALID_NEP141_ACCOUNT"))) } -impl Precompile for ExitToNear { +impl Precompile for ExitToNear { fn required_gas(_input: &[u8]) -> Result { Ok(costs::EXIT_TO_NEAR_GAS) } - #[cfg(not(feature = "contract"))] - fn run( - &self, - input: &[u8], - target_gas: Option, - _context: &Context, - _is_static: bool, - ) -> EvmPrecompileResult { - if let Some(target_gas) = target_gas { - if Self::required_gas(input)? > target_gas { - return Err(ExitError::OutOfGas); - } - } - - Ok(PrecompileOutput::default().into()) - } - - #[cfg(feature = "contract")] fn run( &self, input: &[u8], @@ -329,7 +314,7 @@ impl Precompile for ExitToNear { } let erc20_address = context.caller; - let nep141_address = get_nep141_from_erc20(erc20_address.as_bytes()); + let nep141_address = get_nep141_from_erc20(erc20_address.as_bytes(), &self.io)?; let amount = U256::from_big_endian(&input[..32]); input = &input[32..]; @@ -397,13 +382,13 @@ impl Precompile for ExitToNear { let promise = PromiseArgs::Create(transfer_promise); let promise_log = Log { - address: Self::ADDRESS.raw(), + address: exit_to_near::ADDRESS.raw(), topics: Vec::new(), data: promise.try_to_vec().unwrap(), }; let exit_event_log = exit_event.encode(); let exit_event_log = Log { - address: Self::ADDRESS.raw(), + address: exit_to_near::ADDRESS.raw(), topics: exit_event_log.topics, data: exit_event_log.data, }; @@ -416,46 +401,36 @@ impl Precompile for ExitToNear { } } -pub struct ExitToEthereum { +pub struct ExitToEthereum { current_account_id: AccountId, + io: I, } -impl ExitToEthereum { +pub mod exit_to_ethereum { + use aurora_engine_types::types::Address; + /// Exit to Ethereum precompile address /// /// Address: `0xb0bd02f6a392af548bdf1cfaee5dfa0eefcc8eab` /// This address is computed as: `&keccak("exitToEthereum")[12..]` pub const ADDRESS: Address = - super::make_address(0xb0bd02f6, 0xa392af548bdf1cfaee5dfa0eefcc8eab); + crate::make_address(0xb0bd02f6, 0xa392af548bdf1cfaee5dfa0eefcc8eab); +} - pub fn new(current_account_id: AccountId) -> Self { - Self { current_account_id } +impl ExitToEthereum { + pub fn new(current_account_id: AccountId, io: I) -> Self { + Self { + current_account_id, + io, + } } } -impl Precompile for ExitToEthereum { +impl Precompile for ExitToEthereum { fn required_gas(_input: &[u8]) -> Result { Ok(costs::EXIT_TO_ETHEREUM_GAS) } - #[cfg(not(feature = "contract"))] - fn run( - &self, - input: &[u8], - target_gas: Option, - _context: &Context, - _is_static: bool, - ) -> EvmPrecompileResult { - if let Some(target_gas) = target_gas { - if Self::required_gas(input)? > target_gas { - return Err(ExitError::OutOfGas); - } - } - - Ok(PrecompileOutput::default().into()) - } - - #[cfg(feature = "contract")] fn run( &self, input: &[u8], @@ -526,7 +501,7 @@ impl Precompile for ExitToEthereum { } let erc20_address = context.caller; - let nep141_address = get_nep141_from_erc20(erc20_address.as_bytes()); + let nep141_address = get_nep141_from_erc20(erc20_address.as_bytes(), &self.io)?; let amount = U256::from_big_endian(&input[..32]); input = &input[32..]; @@ -577,13 +552,13 @@ impl Precompile for ExitToEthereum { let promise = PromiseArgs::Create(withdraw_promise).try_to_vec().unwrap(); let promise_log = Log { - address: Self::ADDRESS.raw(), + address: exit_to_ethereum::ADDRESS.raw(), topics: Vec::new(), data: promise, }; let exit_event_log = exit_event.encode(); let exit_event_log = Log { - address: Self::ADDRESS.raw(), + address: exit_to_ethereum::ADDRESS.raw(), topics: exit_event_log.topics, data: exit_event_log.data, }; @@ -598,17 +573,17 @@ impl Precompile for ExitToEthereum { #[cfg(test)] mod tests { - use super::{ExitToEthereum, ExitToNear}; + use super::{exit_to_ethereum, exit_to_near}; use crate::prelude::sdk::types::near_account_to_evm_address; #[test] fn test_precompile_id() { assert_eq!( - ExitToEthereum::ADDRESS, + exit_to_ethereum::ADDRESS, near_account_to_evm_address("exitToEthereum".as_bytes()) ); assert_eq!( - ExitToNear::ADDRESS, + exit_to_near::ADDRESS, near_account_to_evm_address("exitToNear".as_bytes()) ); } diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 115284685..064e8435a 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -12,7 +12,7 @@ use aurora_engine_sdk::io::{StorageIntermediate, IO}; use aurora_engine_sdk::promise::{PromiseHandler, PromiseId}; use crate::parameters::{DeployErc20TokenArgs, NewCallArgs, TransactionStatus}; -use crate::prelude::precompiles::native::{ExitToEthereum, ExitToNear}; +use crate::prelude::precompiles::native::{exit_to_ethereum, exit_to_near}; use crate::prelude::precompiles::Precompiles; use crate::prelude::transactions::{EthTransactionKind, NormalizedEthTransaction}; use crate::prelude::{ @@ -340,36 +340,38 @@ impl AsRef<[u8]> for EngineStateError { } } -struct StackExecutorParams { - precompiles: Precompiles, +struct StackExecutorParams { + precompiles: Precompiles, gas_limit: u64, } -impl StackExecutorParams { +impl StackExecutorParams { fn new( gas_limit: u64, current_account_id: AccountId, predecessor_account_id: AccountId, random_seed: H256, + io: I, ) -> Self { Self { precompiles: Precompiles::new_london(PrecompileConstructorContext { current_account_id, random_seed, predecessor_account_id, + io, }), gas_limit, } } - fn make_executor<'a, 'env, I: IO + Copy, E: Env>( + fn make_executor<'a, 'env, E: Env>( &'a self, engine: &'a Engine<'env, I, E>, ) -> executor::stack::StackExecutor< 'static, 'a, executor::stack::MemoryStackState>, - Precompiles, + Precompiles, > { let metadata = executor::stack::StackSubstateMetadata::new(self.gas_limit, CONFIG); let state = executor::stack::MemoryStackState::new(metadata, engine); @@ -518,6 +520,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { self.current_account_id.clone(), self.env.predecessor_account_id(), self.env.random_seed(), + self.io, ); let mut executor = executor_params.make_executor(self); let address = executor.create_address(CreateScheme::Legacy { @@ -603,6 +606,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { self.current_account_id.clone(), self.env.predecessor_account_id(), self.env.random_seed(), + self.io, ); let mut executor = executor_params.make_executor(self); let (exit_reason, result) = executor.transact_call( @@ -653,6 +657,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { self.current_account_id.clone(), self.env.predecessor_account_id(), self.env.random_seed(), + self.io, ); let mut executor = executor_params.make_executor(self); let (status, result) = executor.transact_call( @@ -1306,8 +1311,8 @@ where { logs.into_iter() .filter_map(|log| { - if log.address == ExitToNear::ADDRESS.raw() - || log.address == ExitToEthereum::ADDRESS.raw() + if log.address == exit_to_near::ADDRESS.raw() + || log.address == exit_to_ethereum::ADDRESS.raw() { if log.topics.is_empty() { if let Ok(promise) = PromiseArgs::try_from_slice(&log.data) { diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 78a51c0ff..560b6e386 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -418,7 +418,7 @@ mod contract { } // ETH exit; transfer ETH back from precompile address None => { - let exit_address = aurora_engine_precompiles::native::ExitToNear::ADDRESS; + let exit_address = aurora_engine_precompiles::native::exit_to_near::ADDRESS; let mut engine = Engine::new(exit_address, current_account_id, io, &io).sdk_unwrap(); let refund_address = args.recipient_address;