diff --git a/Cargo.lock b/Cargo.lock index 7ca379e96..c71b97c60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1059,12 +1059,14 @@ name = "engine-standalone-storage" version = "0.1.0" dependencies = [ "aurora-engine", + "aurora-engine-precompiles", "aurora-engine-sdk", "aurora-engine-transactions", "aurora-engine-types", "base64 0.13.0", "borsh", "evm-core", + "hex", "postgres", "rocksdb", "serde", diff --git a/engine-standalone-storage/Cargo.toml b/engine-standalone-storage/Cargo.toml index 16aefd3e5..8768f40c7 100644 --- a/engine-standalone-storage/Cargo.toml +++ b/engine-standalone-storage/Cargo.toml @@ -18,8 +18,10 @@ aurora-engine = { path = "../engine", default-features = false, features = ["std aurora-engine-types = { path = "../engine-types", default-features = false, features = ["std"] } aurora-engine-sdk = { path = "../engine-sdk", default-features = false, features = ["std"] } aurora-engine-transactions = { path = "../engine-transactions", default-features = false, features = ["std"] } +aurora-engine-precompiles = { path = "../engine-precompiles", default-features = false, features = ["std"] } borsh = { version = "0.9.3" } evm-core = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.36.0-aurora", default-features = false } +hex = "0.4.3" rocksdb = { version = "0.18.0", default-features = false } postgres = "0.19.2" serde = "1.0.130" diff --git a/engine-standalone-storage/src/sync/types.rs b/engine-standalone-storage/src/sync/types.rs index 4d732ab50..77a3b3866 100644 --- a/engine-standalone-storage/src/sync/types.rs +++ b/engine-standalone-storage/src/sync/types.rs @@ -1,8 +1,13 @@ +use crate::Storage; use aurora_engine::parameters; use aurora_engine::xcc::AddressVersionUpdateArgs; -use aurora_engine_transactions::EthTransactionKind; +use aurora_engine_transactions::{EthTransactionKind, NormalizedEthTransaction}; use aurora_engine_types::account_id::AccountId; -use aurora_engine_types::{types, H256}; +use aurora_engine_types::types::Address; +use aurora_engine_types::{ + types::{self, Wei}, + H256, U256, +}; use borsh::{BorshDeserialize, BorshSerialize}; use std::borrow::Cow; @@ -119,6 +124,252 @@ pub enum TransactionKind { Unknown, } +impl TransactionKind { + pub fn eth_repr( + self, + engine_account: &AccountId, + caller: &AccountId, + block_height: u64, + transaction_position: u16, + storage: &Storage, + ) -> NormalizedEthTransaction { + match self { + // In the case the submit arg fails to normalize, there is no EVM execution + Self::Submit(eth_tx_kind) => eth_tx_kind + .try_into() + .unwrap_or_else(|_| Self::no_evm_execution("submit")), + Self::Call(call_args) => { + let from = Self::get_implicit_address(caller); + let nonce = + Self::get_implicit_nonce(&from, block_height, transaction_position, storage); + let (to, data, value) = match call_args { + parameters::CallArgs::V1(args) => (args.contract, args.input, Wei::zero()), + parameters::CallArgs::V2(args) => ( + args.contract, + args.input, + Wei::new(U256::from_big_endian(&args.value)), + ), + }; + NormalizedEthTransaction { + address: from, + chain_id: None, + nonce, + gas_limit: U256::from(u64::MAX), + max_priority_fee_per_gas: U256::zero(), + max_fee_per_gas: U256::zero(), + to: Some(to), + value, + data, + access_list: Vec::new(), + } + } + Self::Deploy(data) => { + let from = Self::get_implicit_address(caller); + let nonce = + Self::get_implicit_nonce(&from, block_height, transaction_position, storage); + NormalizedEthTransaction { + address: from, + chain_id: None, + nonce, + gas_limit: U256::from(u64::MAX), + max_priority_fee_per_gas: U256::zero(), + max_fee_per_gas: U256::zero(), + to: None, + value: Wei::zero(), + data, + access_list: Vec::new(), + } + } + Self::DeployErc20(_) => { + let from = Self::get_implicit_address(caller); + let nonce = + Self::get_implicit_nonce(&from, block_height, transaction_position, storage); + let data = aurora_engine::engine::setup_deploy_erc20_input(engine_account); + NormalizedEthTransaction { + address: from, + chain_id: None, + nonce, + gas_limit: U256::from(u64::MAX), + max_priority_fee_per_gas: U256::zero(), + max_fee_per_gas: U256::zero(), + to: None, + value: Wei::zero(), + data, + access_list: Vec::new(), + } + } + Self::FtOnTransfer(args) => { + if engine_account == caller { + let recipient = aurora_engine::deposit_event::FtTransferMessageData::parse_on_transfer_message(&args.msg).map(|data| data.recipient).unwrap_or_default(); + let value = Wei::new(U256::from(args.amount.as_u128())); + // This transaction mints new ETH, so we'll say it comes from the zero address. + NormalizedEthTransaction { + address: types::Address::default(), + chain_id: None, + nonce: U256::zero(), + gas_limit: U256::from(u64::MAX), + max_priority_fee_per_gas: U256::zero(), + max_fee_per_gas: U256::zero(), + to: Some(recipient), + value, + data: Vec::new(), + access_list: Vec::new(), + } + } else { + let from = Self::get_implicit_address(engine_account); + let nonce = Self::get_implicit_nonce( + &from, + block_height, + transaction_position, + storage, + ); + let to = storage + .with_engine_access(block_height, transaction_position, &[], |io| { + aurora_engine::engine::get_erc20_from_nep141(&io, caller) + }) + .result + .ok() + .and_then(|bytes| types::Address::try_from_slice(&bytes).ok()) + .unwrap_or_default(); + let erc20_recipient = hex::decode(&args.msg.as_bytes()[0..40]) + .ok() + .and_then(|bytes| types::Address::try_from_slice(&bytes).ok()) + .unwrap_or_default(); + let data = aurora_engine::engine::setup_receive_erc20_tokens_input( + &args, + &erc20_recipient, + ); + NormalizedEthTransaction { + address: from, + chain_id: None, + nonce, + gas_limit: U256::from(u64::MAX), + max_priority_fee_per_gas: U256::zero(), + max_fee_per_gas: U256::zero(), + to: Some(to), + value: Wei::zero(), + data, + access_list: Vec::new(), + } + } + } + Self::RefundOnError(maybe_args) => { + match maybe_args { + Some(args) => match args.erc20_address { + Some(erc20_address) => { + // ERC-20 refund + let from = Self::get_implicit_address(engine_account); + let nonce = Self::get_implicit_nonce( + &from, + block_height, + transaction_position, + storage, + ); + let to = erc20_address; + let data = aurora_engine::engine::setup_refund_on_error_input( + U256::from_big_endian(&args.amount), + args.recipient_address, + ); + NormalizedEthTransaction { + address: from, + chain_id: None, + nonce, + gas_limit: U256::from(u64::MAX), + max_priority_fee_per_gas: U256::zero(), + max_fee_per_gas: U256::zero(), + to: Some(to), + value: Wei::zero(), + data, + access_list: Vec::new(), + } + } + None => { + // ETH refund + let value = Wei::new(U256::from_big_endian(&args.amount)); + let from = aurora_engine_precompiles::native::exit_to_near::ADDRESS; + let nonce = Self::get_implicit_nonce( + &from, + block_height, + transaction_position, + storage, + ); + NormalizedEthTransaction { + address: from, + chain_id: None, + nonce, + gas_limit: U256::from(u64::MAX), + max_priority_fee_per_gas: U256::zero(), + max_fee_per_gas: U256::zero(), + to: Some(args.recipient_address), + value, + data: Vec::new(), + access_list: Vec::new(), + } + } + }, + None => Self::no_evm_execution("refund_on_error"), + } + } + Self::Deposit(_) => Self::no_evm_execution("deposit"), + Self::FtTransferCall(_) => Self::no_evm_execution("ft_transfer_call"), + Self::FinishDeposit(_) => Self::no_evm_execution("finish_deposit"), + Self::ResolveTransfer(_, _) => Self::no_evm_execution("resolve_transfer"), + Self::FtTransfer(_) => Self::no_evm_execution("ft_transfer"), + TransactionKind::Withdraw(_) => Self::no_evm_execution("withdraw"), + TransactionKind::StorageDeposit(_) => Self::no_evm_execution("storage_deposit"), + TransactionKind::StorageUnregister(_) => Self::no_evm_execution("storage_unregister"), + TransactionKind::StorageWithdraw(_) => Self::no_evm_execution("storage_withdraw"), + TransactionKind::SetPausedFlags(_) => Self::no_evm_execution("set_paused_flags"), + TransactionKind::RegisterRelayer(_) => Self::no_evm_execution("register_relayer"), + TransactionKind::SetConnectorData(_) => Self::no_evm_execution("set_connector_data"), + TransactionKind::NewConnector(_) => Self::no_evm_execution("new_connector"), + TransactionKind::NewEngine(_) => Self::no_evm_execution("new_engine"), + TransactionKind::FactoryUpdate(_) => Self::no_evm_execution("factory_update"), + TransactionKind::FactoryUpdateAddressVersion(_) => { + Self::no_evm_execution("factory_update_address_version") + } + TransactionKind::FactorySetWNearAddress(_) => { + Self::no_evm_execution("factory_set_wnear_address") + } + TransactionKind::Unknown => Self::no_evm_execution("unknown"), + } + } + + /// There are many cases where a receipt on NEAR can change the Aurora contract state, but no EVM execution actually occurs. + /// In these cases we have a sentinel Ethereum transaction from the zero address to itself with input equal to the method name. + fn no_evm_execution(method_name: &str) -> NormalizedEthTransaction { + NormalizedEthTransaction { + address: Address::from_array([0; 20]), + chain_id: None, + nonce: U256::zero(), + gas_limit: U256::zero(), + max_priority_fee_per_gas: U256::zero(), + max_fee_per_gas: U256::zero(), + to: Some(Address::from_array([0; 20])), + value: Wei::zero(), + data: method_name.as_bytes().to_vec(), + access_list: Vec::new(), + } + } + + fn get_implicit_address(caller: &AccountId) -> types::Address { + aurora_engine_sdk::types::near_account_to_evm_address(caller.as_bytes()) + } + + fn get_implicit_nonce( + from: &types::Address, + block_height: u64, + transaction_position: u16, + storage: &Storage, + ) -> U256 { + storage + .with_engine_access(block_height, transaction_position, &[], |io| { + aurora_engine::engine::get_nonce(&io, from) + }) + .result + } +} + /// This data type represents `TransactionMessage` above in the way consistent with how it is /// stored on disk (in the DB). This type implements borsh (de)serialization. The purpose of /// having a private struct for borsh, which is separate from the main `TransactionMessage` diff --git a/engine-types/src/types/address.rs b/engine-types/src/types/address.rs index f006c3cd6..059b211f0 100755 --- a/engine-types/src/types/address.rs +++ b/engine-types/src/types/address.rs @@ -46,7 +46,7 @@ impl Address { Ok(Self::new(H160::from_slice(raw_addr))) } - pub fn from_array(array: [u8; 20]) -> Self { + pub const fn from_array(array: [u8; 20]) -> Self { Self(H160(array)) } diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 7172881fc..f879cf4b6 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -831,19 +831,13 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { ); } - let selector = ERC20_MINT_SELECTOR; - let tail = ethabi::encode(&[ - ethabi::Token::Address(recipient.raw()), - ethabi::Token::Uint(U256::from(args.amount.as_u128())), - ]); - let erc20_admin_address = current_address(current_account_id); unwrap_res_or_finish!( self.call( &erc20_admin_address, &erc20_token, Wei::zero(), - [selector, tail.as_slice()].concat(), + setup_receive_erc20_tokens_input(args, &recipient), u64::MAX, Vec::new(), // TODO: are there values we should put here? handler, @@ -1015,6 +1009,16 @@ pub fn submit( result } +pub fn setup_refund_on_error_input(amount: U256, refund_address: Address) -> Vec { + let selector = ERC20_MINT_SELECTOR; + let mint_args = ethabi::encode(&[ + ethabi::Token::Address(refund_address.raw()), + ethabi::Token::Uint(amount), + ]); + + [selector, mint_args.as_slice()].concat() +} + pub fn refund_on_error( io: I, env: &E, @@ -1032,18 +1036,13 @@ pub fn refund_on_error( let erc20_address = erc20_address; let refund_address = args.recipient_address; let amount = U256::from_big_endian(&args.amount); - - let selector = ERC20_MINT_SELECTOR; - let mint_args = ethabi::encode(&[ - ethabi::Token::Address(refund_address.raw()), - ethabi::Token::Uint(amount), - ]); + let input = setup_refund_on_error_input(amount, refund_address); engine.call( &erc20_admin_address, &erc20_address, Wei::zero(), - [selector, mint_args.as_slice()].concat(), + input, u64::MAX, Vec::new(), handler, @@ -1146,6 +1145,37 @@ pub fn refund_unused_gas( Ok(()) } +pub fn setup_receive_erc20_tokens_input( + args: &NEP141FtOnTransferArgs, + recipient: &Address, +) -> Vec { + let selector = ERC20_MINT_SELECTOR; + let tail = ethabi::encode(&[ + ethabi::Token::Address(recipient.raw()), + ethabi::Token::Uint(U256::from(args.amount.as_u128())), + ]); + + [selector, tail.as_slice()].concat() +} + +pub fn setup_deploy_erc20_input(current_account_id: &AccountId) -> Vec { + #[cfg(feature = "error_refund")] + let erc20_contract = include_bytes!("../../etc/eth-contracts/res/EvmErc20V2.bin"); + #[cfg(not(feature = "error_refund"))] + let erc20_contract = include_bytes!("../../etc/eth-contracts/res/EvmErc20.bin"); + + let erc20_admin_address = current_address(current_account_id); + + let deploy_args = ethabi::encode(&[ + ethabi::Token::String("Empty".to_string()), + ethabi::Token::String("EMPTY".to_string()), + ethabi::Token::Uint(ethabi::Uint::from(0)), + ethabi::Token::Address(erc20_admin_address.raw()), + ]); + + (&[erc20_contract, deploy_args.as_slice()].concat()).to_vec() +} + /// Used to bridge NEP-141 tokens from NEAR to Aurora. On Aurora the NEP-141 becomes an ERC-20. pub fn deploy_erc20_token( args: DeployErc20TokenArgs, @@ -1154,7 +1184,7 @@ pub fn deploy_erc20_token( handler: &mut P, ) -> Result { let current_account_id = env.current_account_id(); - let erc20_admin_address = current_address(¤t_account_id); + let input = setup_deploy_erc20_input(¤t_account_id); let mut engine = Engine::new( aurora_engine_sdk::types::near_account_to_evm_address( env.predecessor_account_id().as_bytes(), @@ -1165,23 +1195,7 @@ pub fn deploy_erc20_token( ) .map_err(DeployErc20Error::State)?; - #[cfg(feature = "error_refund")] - let erc20_contract = include_bytes!("../../etc/eth-contracts/res/EvmErc20V2.bin"); - #[cfg(not(feature = "error_refund"))] - let erc20_contract = include_bytes!("../../etc/eth-contracts/res/EvmErc20.bin"); - - let deploy_args = ethabi::encode(&[ - ethabi::Token::String("Empty".to_string()), - ethabi::Token::String("EMPTY".to_string()), - ethabi::Token::Uint(ethabi::Uint::from(0)), - ethabi::Token::Address(erc20_admin_address.raw()), - ]); - - let address = match Engine::deploy_code_with_input( - &mut engine, - (&[erc20_contract, deploy_args.as_slice()].concat()).to_vec(), - handler, - ) { + let address = match Engine::deploy_code_with_input(&mut engine, input, handler) { Ok(result) => match result.status { TransactionStatus::Succeed(ret) => { Address::new(H160(ret.as_slice().try_into().unwrap()))