From b904fd0481567f248422672b71011d917db5859d Mon Sep 17 00:00:00 2001 From: Michael Birch Date: Mon, 4 Oct 2021 17:34:23 +0200 Subject: [PATCH] Feat(engine): London hard fork support (#244) --- Cargo.lock | 8 +- engine-precompiles/src/lib.rs | 62 ++++- engine-tests/src/benches/nft_pagination.rs | 6 +- engine-tests/src/test_utils/erc20.rs | 32 +-- .../src/test_utils/exit_precompile.rs | 12 +- engine-tests/src/test_utils/mod.rs | 119 ++++++--- .../test_utils/one_inch/liquidity_protocol.rs | 4 +- engine-tests/src/test_utils/self_destruct.rs | 24 +- engine-tests/src/test_utils/solidity.rs | 18 +- .../src/test_utils/standard_precompiles.rs | 6 +- engine-tests/src/test_utils/uniswap.rs | 45 ++-- engine-tests/src/tests/access_lists.rs | 14 +- engine-tests/src/tests/eip1559.rs | 152 +++++++++++ engine-tests/src/tests/erc20.rs | 20 +- engine-tests/src/tests/erc20_connector.rs | 2 +- engine-tests/src/tests/mod.rs | 1 + engine-tests/src/tests/one_inch.rs | 4 +- engine-tests/src/tests/sanity.rs | 20 +- engine-tests/src/tests/uniswap.rs | 2 +- engine-types/src/types.rs | 3 + engine/src/engine.rs | 124 +++++---- engine/src/lib.rs | 81 +++--- engine/src/transaction/eip_1559.rs | 130 ++++++++++ .../{access_list.rs => eip_2930.rs} | 25 +- engine/src/transaction/legacy.rs | 44 ++-- engine/src/transaction/mod.rs | 240 +++++++++--------- etc/state-migration-test/Cargo.lock | 8 +- 27 files changed, 816 insertions(+), 390 deletions(-) create mode 100644 engine-tests/src/tests/eip1559.rs create mode 100644 engine/src/transaction/eip_1559.rs rename engine/src/transaction/{access_list.rs => eip_2930.rs} (84%) diff --git a/Cargo.lock b/Cargo.lock index 7c73da06e..cf7a737bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1319,7 +1319,7 @@ checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" [[package]] name = "evm" version = "0.31.1" -source = "git+https://github.com/aurora-is-near/sputnikvm.git#58895a8fa9dfef02bf9928cad975e3f03b05972c" +source = "git+https://github.com/aurora-is-near/sputnikvm.git#0fbde9fa7797308290f89111c6abe5cee55a5eac" dependencies = [ "environmental", "ethereum", @@ -1338,7 +1338,7 @@ dependencies = [ [[package]] name = "evm-core" version = "0.31.0" -source = "git+https://github.com/aurora-is-near/sputnikvm.git#58895a8fa9dfef02bf9928cad975e3f03b05972c" +source = "git+https://github.com/aurora-is-near/sputnikvm.git#0fbde9fa7797308290f89111c6abe5cee55a5eac" dependencies = [ "funty", "parity-scale-codec", @@ -1350,7 +1350,7 @@ dependencies = [ [[package]] name = "evm-gasometer" version = "0.31.0" -source = "git+https://github.com/aurora-is-near/sputnikvm.git#58895a8fa9dfef02bf9928cad975e3f03b05972c" +source = "git+https://github.com/aurora-is-near/sputnikvm.git#0fbde9fa7797308290f89111c6abe5cee55a5eac" dependencies = [ "environmental", "evm-core", @@ -1361,7 +1361,7 @@ dependencies = [ [[package]] name = "evm-runtime" version = "0.31.0" -source = "git+https://github.com/aurora-is-near/sputnikvm.git#58895a8fa9dfef02bf9928cad975e3f03b05972c" +source = "git+https://github.com/aurora-is-near/sputnikvm.git#0fbde9fa7797308290f89111c6abe5cee55a5eac" dependencies = [ "environmental", "evm-core", diff --git a/engine-precompiles/src/lib.rs b/engine-precompiles/src/lib.rs index f3eafcb2a..a03226a2b 100644 --- a/engine-precompiles/src/lib.rs +++ b/engine-precompiles/src/lib.rs @@ -23,6 +23,7 @@ use crate::modexp::ModExp; use crate::native::{ExitToEthereum, ExitToNear}; use crate::secp256k1::ECRecover; use evm::backend::Log; +use evm::executor; use evm::{Context, ExitError, ExitSucceed}; #[derive(Debug, Default)] @@ -96,6 +97,26 @@ type PrecompileFn = fn(&[u8], Option, &Context, bool) -> EvmPrecompileResul pub struct Precompiles(pub prelude::BTreeMap); +impl executor::PrecompileSet for Precompiles { + fn execute( + &self, + address: prelude::Address, + input: &[u8], + gas_limit: Option, + context: &Context, + is_static: bool, + ) -> Option> { + self.0.get(&address).map(|f| { + f(input, gas_limit, context, is_static) + .map_err(|exit_status| executor::PrecompileFailure::Error { exit_status }) + }) + } + + fn is_precompile(&self, address: prelude::Address) -> bool { + self.0.contains_key(&address) + } +} + impl Precompiles { #[allow(dead_code)] pub fn new_homestead() -> Self { @@ -187,9 +208,44 @@ impl Precompiles { Precompiles(map) } - #[allow(dead_code)] - fn new_berlin() -> Self { - Self::new_istanbul() + pub fn new_berlin() -> Self { + let addresses = prelude::vec![ + ECRecover::ADDRESS, + SHA256::ADDRESS, + RIPEMD160::ADDRESS, + Identity::ADDRESS, + ModExp::::ADDRESS, + Bn128Add::::ADDRESS, + Bn128Mul::::ADDRESS, + Bn128Pair::::ADDRESS, + Blake2F::ADDRESS, + ExitToNear::ADDRESS, + ExitToEthereum::ADDRESS, + ]; + let fun: prelude::Vec = prelude::vec![ + ECRecover::run, + SHA256::run, + RIPEMD160::run, + Identity::run, + ModExp::::run, + Bn128Add::::run, + Bn128Mul::::run, + Bn128Pair::::run, + Blake2F::run, + ExitToNear::run, + ExitToEthereum::run, + ]; + let mut map = prelude::BTreeMap::new(); + for (address, fun) in addresses.into_iter().zip(fun) { + map.insert(address, fun); + } + + Precompiles(map) + } + + pub fn new_london() -> Self { + // no precompile changes in London HF + Self::new_berlin() } } diff --git a/engine-tests/src/benches/nft_pagination.rs b/engine-tests/src/benches/nft_pagination.rs index 166b7ec1e..8d8e38d65 100644 --- a/engine-tests/src/benches/nft_pagination.rs +++ b/engine-tests/src/benches/nft_pagination.rs @@ -1,7 +1,7 @@ use crate::prelude::types::Wei; use crate::prelude::{Address, U256}; use crate::test_utils::{self, solidity}; -use aurora_engine::transaction::LegacyEthTransaction; +use aurora_engine::transaction::legacy::TransactionLegacy; use secp256k1::SecretKey; use std::path::{Path, PathBuf}; use std::process::Command; @@ -130,7 +130,7 @@ impl MarketPlace { data: String, price: Wei, nonce: U256, - ) -> LegacyEthTransaction { + ) -> TransactionLegacy { self.0.call_method_with_args( "minar", &[ @@ -147,7 +147,7 @@ impl MarketPlace { tokens_per_page: usize, page_index: usize, nonce: U256, - ) -> LegacyEthTransaction { + ) -> TransactionLegacy { self.0.call_method_with_args( "obtenerPaginav2", &[ diff --git a/engine-tests/src/test_utils/erc20.rs b/engine-tests/src/test_utils/erc20.rs index 7094a440a..57cc38f69 100644 --- a/engine-tests/src/test_utils/erc20.rs +++ b/engine-tests/src/test_utils/erc20.rs @@ -1,4 +1,4 @@ -use crate::prelude::{transaction::LegacyEthTransaction, Address, U256}; +use crate::prelude::{transaction::legacy::TransactionLegacy, Address, U256}; use crate::test_utils::solidity; use std::path::{Path, PathBuf}; use std::sync::Once; @@ -25,7 +25,7 @@ impl ERC20Constructor { )) } - pub fn deploy(&self, name: &str, symbol: &str, nonce: U256) -> LegacyEthTransaction { + pub fn deploy(&self, name: &str, symbol: &str, nonce: U256) -> TransactionLegacy { let data = self .0 .abi @@ -39,10 +39,10 @@ impl ERC20Constructor { ], ) .unwrap(); - LegacyEthTransaction { + TransactionLegacy { nonce, gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: None, value: Default::default(), data, @@ -71,7 +71,7 @@ impl ERC20Constructor { } impl ERC20 { - pub fn mint(&self, recipient: Address, amount: U256, nonce: U256) -> LegacyEthTransaction { + pub fn mint(&self, recipient: Address, amount: U256, nonce: U256) -> TransactionLegacy { let data = self .0 .abi @@ -83,17 +83,17 @@ impl ERC20 { ]) .unwrap(); - LegacyEthTransaction { + TransactionLegacy { nonce, gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: Some(self.0.address), value: Default::default(), data, } } - pub fn transfer(&self, recipient: Address, amount: U256, nonce: U256) -> LegacyEthTransaction { + pub fn transfer(&self, recipient: Address, amount: U256, nonce: U256) -> TransactionLegacy { let data = self .0 .abi @@ -104,17 +104,17 @@ impl ERC20 { ethabi::Token::Uint(amount), ]) .unwrap(); - LegacyEthTransaction { + TransactionLegacy { nonce, gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: Some(self.0.address), value: Default::default(), data, } } - pub fn approve(&self, spender: Address, amount: U256, nonce: U256) -> LegacyEthTransaction { + pub fn approve(&self, spender: Address, amount: U256, nonce: U256) -> TransactionLegacy { let data = self .0 .abi @@ -122,17 +122,17 @@ impl ERC20 { .unwrap() .encode_input(&[ethabi::Token::Address(spender), ethabi::Token::Uint(amount)]) .unwrap(); - LegacyEthTransaction { + TransactionLegacy { nonce, gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: Some(self.0.address), value: Default::default(), data, } } - pub fn balance_of(&self, address: Address, nonce: U256) -> LegacyEthTransaction { + pub fn balance_of(&self, address: Address, nonce: U256) -> TransactionLegacy { let data = self .0 .abi @@ -140,10 +140,10 @@ impl ERC20 { .unwrap() .encode_input(&[ethabi::Token::Address(address)]) .unwrap(); - LegacyEthTransaction { + TransactionLegacy { nonce, gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: Some(self.0.address), value: Default::default(), data, diff --git a/engine-tests/src/test_utils/exit_precompile.rs b/engine-tests/src/test_utils/exit_precompile.rs index 791c1c40d..6c69335ba 100644 --- a/engine-tests/src/test_utils/exit_precompile.rs +++ b/engine-tests/src/test_utils/exit_precompile.rs @@ -1,5 +1,5 @@ use crate::prelude::{ - parameters::SubmitResult, transaction::LegacyEthTransaction, Address, Wei, U256, + parameters::SubmitResult, transaction::legacy::TransactionLegacy, Address, Wei, U256, }; use crate::test_utils::{self, solidity, AuroraRunner, Signer}; @@ -17,7 +17,7 @@ impl TesterConstructor { )) } - pub fn deploy(&self, nonce: u64, token: Address) -> LegacyEthTransaction { + pub fn deploy(&self, nonce: u64, token: Address) -> TransactionLegacy { let data = self .0 .abi @@ -26,10 +26,10 @@ impl TesterConstructor { .encode_input(self.0.code.clone(), &[ethabi::Token::Address(token)]) .unwrap(); - LegacyEthTransaction { + TransactionLegacy { nonce: nonce.into(), gas_price: Default::default(), - gas: U256::from(DEPLOY_CONTRACT_GAS), + gas_limit: U256::from(DEPLOY_CONTRACT_GAS), to: None, value: Default::default(), data, @@ -70,10 +70,10 @@ impl Tester { .encode_input(params) .unwrap(); - let tx = LegacyEthTransaction { + let tx = TransactionLegacy { nonce: signer.use_nonce().into(), gas_price: Default::default(), - gas: U256::from(DEPLOY_CONTRACT_GAS), + gas_limit: U256::from(DEPLOY_CONTRACT_GAS), to: Some(self.contract.address), value, data, diff --git a/engine-tests/src/test_utils/mod.rs b/engine-tests/src/test_utils/mod.rs index d7cb21f5e..b4492c815 100644 --- a/engine-tests/src/test_utils/mod.rs +++ b/engine-tests/src/test_utils/mod.rs @@ -16,10 +16,11 @@ use crate::prelude::parameters::{ InitCallArgs, NewCallArgs, SubmitResult, TransactionStatus, ViewCallArgs, }; use crate::prelude::transaction::{ - access_list::{self, AccessListEthSignedTransaction, AccessListEthTransaction}, - LegacyEthSignedTransaction, LegacyEthTransaction, + eip_1559::{self, SignedTransaction1559, Transaction1559}, + eip_2930::{self, SignedTransaction2930, Transaction2930}, + legacy::{LegacyEthSignedTransaction, TransactionLegacy}, }; -use crate::prelude::{sdk, Address, Wei, U256}; +use crate::prelude::{sdk, Address, Wei, H256, U256}; use crate::test_utils::solidity::{ContractConstructor, DeployedContract}; // TODO(Copied from #84): Make sure that there is only one Signer after both PR are merged. @@ -197,6 +198,26 @@ impl AuroraRunner { address: Address, init_balance: crate::prelude::Wei, init_nonce: U256, + ) { + self.internal_create_address(address, init_balance, init_nonce, None) + } + + pub fn create_address_with_code( + &mut self, + address: Address, + init_balance: crate::prelude::Wei, + init_nonce: U256, + code: Vec, + ) { + self.internal_create_address(address, init_balance, init_nonce, Some(code)) + } + + fn internal_create_address( + &mut self, + address: Address, + init_balance: crate::prelude::Wei, + init_nonce: U256, + code: Option>, ) { let trie = &mut self.ext.fake_trie; @@ -212,6 +233,14 @@ impl AuroraRunner { ); let nonce_value = crate::prelude::u256_to_arr(&init_nonce); + if let Some(code) = code { + let code_key = crate::prelude::storage::address_to_key( + crate::prelude::storage::KeyPrefix::Code, + &address, + ); + trie.insert(code_key.to_vec(), code); + } + let ft_key = crate::prelude::storage::bytes_to_key( crate::prelude::storage::KeyPrefix::EthConnector, &[crate::prelude::storage::EthConnectorStorageId::FungibleToken as u8], @@ -230,7 +259,7 @@ impl AuroraRunner { trie.insert(ft_key, ft_value.try_to_vec().unwrap()); } - pub fn submit_with_signer LegacyEthTransaction>( + pub fn submit_with_signer TransactionLegacy>( &mut self, signer: &mut Signer, make_tx: F, @@ -239,7 +268,7 @@ impl AuroraRunner { .map(|(result, _)| result) } - pub fn submit_with_signer_profiled LegacyEthTransaction>( + pub fn submit_with_signer_profiled TransactionLegacy>( &mut self, signer: &mut Signer, make_tx: F, @@ -252,7 +281,7 @@ impl AuroraRunner { pub fn submit_transaction( &mut self, account: &SecretKey, - transaction: LegacyEthTransaction, + transaction: TransactionLegacy, ) -> Result { self.submit_transaction_profiled(account, transaction) .map(|(result, _)| result) @@ -261,7 +290,7 @@ impl AuroraRunner { pub fn submit_transaction_profiled( &mut self, account: &SecretKey, - transaction: LegacyEthTransaction, + transaction: TransactionLegacy, ) -> Result<(SubmitResult, ExecutionProfile), VMError> { let calling_account_id = "some-account.near"; let signed_tx = sign_transaction(transaction, Some(self.chain_id), account); @@ -280,7 +309,7 @@ impl AuroraRunner { } } - pub fn deploy_contract LegacyEthTransaction, T: Into>( + pub fn deploy_contract TransactionLegacy, T: Into>( &mut self, account: &SecretKey, constructor_tx: F, @@ -336,6 +365,21 @@ impl AuroraRunner { self.getter_method_call("get_code", address) } + pub fn get_storage(&self, address: Address, key: H256) -> H256 { + let input = aurora_engine::parameters::GetStorageAtArgs { + address: address.0, + key: key.0, + }; + let (outcome, maybe_error) = + self.one_shot() + .call("get_storage_at", "getter", input.try_to_vec().unwrap()); + assert!(maybe_error.is_none()); + let output = outcome.unwrap().return_data.as_value().unwrap(); + let mut result = [0u8; 32]; + result.copy_from_slice(&output); + H256(result) + } + fn u256_getter_method_call(&self, method_name: &str, address: Address) -> U256 { let bytes = self.getter_method_call(method_name, address); U256::from_big_endian(&bytes) @@ -464,25 +508,18 @@ pub(crate) fn deploy_evm() -> AuroraRunner { runner } -pub(crate) fn transfer( - to: Address, - amount: crate::prelude::Wei, - nonce: U256, -) -> LegacyEthTransaction { - LegacyEthTransaction { +pub(crate) fn transfer(to: Address, amount: Wei, nonce: U256) -> TransactionLegacy { + TransactionLegacy { nonce, gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: Some(to), value: amount, data: Vec::new(), } } -pub(crate) fn create_deploy_transaction( - contract_bytes: Vec, - nonce: U256, -) -> LegacyEthTransaction { +pub(crate) fn create_deploy_transaction(contract_bytes: Vec, nonce: U256) -> TransactionLegacy { let len = contract_bytes.len(); if len > u16::MAX as usize { panic!("Cannot deploy a contract with that many bytes!"); @@ -502,10 +539,10 @@ pub(crate) fn create_deploy_transaction( .chain(contract_bytes.into_iter()) .collect(); - LegacyEthTransaction { + TransactionLegacy { nonce, gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: None, value: Wei::zero(), data, @@ -520,10 +557,10 @@ pub(crate) fn create_eth_transaction( secret_key: &SecretKey, ) -> LegacyEthSignedTransaction { // nonce, gas_price and gas are not used by EVM contract currently - let tx = LegacyEthTransaction { + let tx = TransactionLegacy { nonce: Default::default(), gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to, value, data, @@ -531,7 +568,7 @@ pub(crate) fn create_eth_transaction( sign_transaction(tx, chain_id, secret_key) } -pub(crate) fn as_view_call(tx: LegacyEthTransaction, sender: Address) -> ViewCallArgs { +pub(crate) fn as_view_call(tx: TransactionLegacy, sender: Address) -> ViewCallArgs { ViewCallArgs { sender: sender.0, address: tx.to.unwrap().0, @@ -541,7 +578,7 @@ pub(crate) fn as_view_call(tx: LegacyEthTransaction, sender: Address) -> ViewCal } pub(crate) fn sign_transaction( - tx: LegacyEthTransaction, + tx: TransactionLegacy, chain_id: Option, secret_key: &SecretKey, ) -> LegacyEthSignedTransaction { @@ -566,11 +603,33 @@ pub(crate) fn sign_transaction( } pub(crate) fn sign_access_list_transaction( - tx: AccessListEthTransaction, + tx: Transaction2930, + secret_key: &SecretKey, +) -> SignedTransaction2930 { + let mut rlp_stream = RlpStream::new(); + rlp_stream.append(&eip_2930::TYPE_BYTE); + tx.rlp_append_unsigned(&mut rlp_stream); + let message_hash = sdk::keccak(rlp_stream.as_raw()); + let message = Message::parse_slice(message_hash.as_bytes()).unwrap(); + + let (signature, recovery_id) = secp256k1::sign(&message, secret_key); + let r = U256::from_big_endian(&signature.r.b32()); + let s = U256::from_big_endian(&signature.s.b32()); + + SignedTransaction2930 { + transaction: tx, + parity: recovery_id.serialize(), + r, + s, + } +} + +pub(crate) fn sign_eip_1559_transaction( + tx: Transaction1559, secret_key: &SecretKey, -) -> AccessListEthSignedTransaction { +) -> SignedTransaction1559 { let mut rlp_stream = RlpStream::new(); - rlp_stream.append(&access_list::TYPE_BYTE); + rlp_stream.append(&eip_1559::TYPE_BYTE); tx.rlp_append_unsigned(&mut rlp_stream); let message_hash = sdk::keccak(rlp_stream.as_raw()); let message = Message::parse_slice(message_hash.as_bytes()).unwrap(); @@ -579,8 +638,8 @@ pub(crate) fn sign_access_list_transaction( let r = U256::from_big_endian(&signature.r.b32()); let s = U256::from_big_endian(&signature.s.b32()); - AccessListEthSignedTransaction { - transaction_data: tx, + SignedTransaction1559 { + transaction: tx, parity: recovery_id.serialize(), r, s, diff --git a/engine-tests/src/test_utils/one_inch/liquidity_protocol.rs b/engine-tests/src/test_utils/one_inch/liquidity_protocol.rs index dd30b9215..4de97a372 100644 --- a/engine-tests/src/test_utils/one_inch/liquidity_protocol.rs +++ b/engine-tests/src/test_utils/one_inch/liquidity_protocol.rs @@ -27,10 +27,10 @@ impl<'a> Helper<'a> { let (result, profile) = self .runner .submit_with_signer_profiled(self.signer, |nonce| { - crate::prelude::transaction::LegacyEthTransaction { + crate::prelude::transaction::legacy::TransactionLegacy { nonce, gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: None, value: Default::default(), data, diff --git a/engine-tests/src/test_utils/self_destruct.rs b/engine-tests/src/test_utils/self_destruct.rs index 93afc4aac..ce7863516 100644 --- a/engine-tests/src/test_utils/self_destruct.rs +++ b/engine-tests/src/test_utils/self_destruct.rs @@ -1,5 +1,5 @@ use crate::prelude::{ - parameters::FunctionCallArgs, transaction::LegacyEthTransaction, Address, U256, + parameters::FunctionCallArgs, transaction::legacy::TransactionLegacy, Address, U256, }; use crate::test_utils::{self, solidity, AuroraRunner, Signer}; use borsh::BorshSerialize; @@ -16,7 +16,7 @@ impl SelfDestructFactoryConstructor { )) } - pub fn deploy(&self, nonce: u64) -> LegacyEthTransaction { + pub fn deploy(&self, nonce: u64) -> TransactionLegacy { let data = self .0 .abi @@ -25,10 +25,10 @@ impl SelfDestructFactoryConstructor { .encode_input(self.0.code.clone(), &[]) .unwrap(); - LegacyEthTransaction { + TransactionLegacy { nonce: nonce.into(), gas_price: Default::default(), - gas: U256::from(DEFAULT_GAS), + gas_limit: U256::from(DEFAULT_GAS), to: None, value: Default::default(), data, @@ -62,10 +62,10 @@ impl SelfDestructFactory { .encode_input(&[]) .unwrap(); - let tx = LegacyEthTransaction { + let tx = TransactionLegacy { nonce: signer.use_nonce().into(), gas_price: Default::default(), - gas: U256::from(DEFAULT_GAS), + gas_limit: U256::from(DEFAULT_GAS), to: Some(self.contract.address), value: Default::default(), data, @@ -102,10 +102,10 @@ impl SelfDestruct { .encode_input(&[]) .unwrap(); - let tx = LegacyEthTransaction { + let tx = TransactionLegacy { nonce: signer.use_nonce().into(), gas_price: Default::default(), - gas: U256::from(DEFAULT_GAS), + gas_limit: U256::from(DEFAULT_GAS), to: Some(self.contract.address), value: Default::default(), data, @@ -130,10 +130,10 @@ impl SelfDestruct { .encode_input(&[]) .unwrap(); - let tx = LegacyEthTransaction { + let tx = TransactionLegacy { nonce: signer.use_nonce().into(), gas_price: Default::default(), - gas: U256::from(DEFAULT_GAS), + gas_limit: U256::from(DEFAULT_GAS), to: Some(self.contract.address), value: Default::default(), data, @@ -151,10 +151,10 @@ impl SelfDestruct { .encode_input(&[]) .unwrap(); - let tx = LegacyEthTransaction { + let tx = TransactionLegacy { nonce: signer.use_nonce().into(), gas_price: Default::default(), - gas: U256::from(DEFAULT_GAS), + gas_limit: U256::from(DEFAULT_GAS), to: Some(self.contract.address), value: Default::default(), data, diff --git a/engine-tests/src/test_utils/solidity.rs b/engine-tests/src/test_utils/solidity.rs index 23c4dc6c4..e32d14862 100644 --- a/engine-tests/src/test_utils/solidity.rs +++ b/engine-tests/src/test_utils/solidity.rs @@ -1,4 +1,4 @@ -use crate::prelude::{transaction::LegacyEthTransaction, Address, U256}; +use crate::prelude::{transaction::legacy::TransactionLegacy, Address, U256}; use near_sdk::serde_json; use serde::Deserialize; use std::fs; @@ -74,21 +74,21 @@ impl ContractConstructor { } } - pub fn deploy_without_args(&self, nonce: U256) -> LegacyEthTransaction { + pub fn deploy_without_args(&self, nonce: U256) -> TransactionLegacy { self.deploy_with_args(nonce, &[]) } - pub fn deploy_with_args(&self, nonce: U256, args: &[ethabi::Token]) -> LegacyEthTransaction { + pub fn deploy_with_args(&self, nonce: U256, args: &[ethabi::Token]) -> TransactionLegacy { let data = self .abi .constructor() .unwrap() .encode_input(self.code.clone(), args) .unwrap(); - LegacyEthTransaction { + TransactionLegacy { nonce, gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: None, value: Default::default(), data, @@ -97,7 +97,7 @@ impl ContractConstructor { } impl DeployedContract { - pub fn call_method_without_args(&self, method_name: &str, nonce: U256) -> LegacyEthTransaction { + pub fn call_method_without_args(&self, method_name: &str, nonce: U256) -> TransactionLegacy { self.call_method_with_args(method_name, &[], nonce) } @@ -106,17 +106,17 @@ impl DeployedContract { method_name: &str, args: &[ethabi::Token], nonce: U256, - ) -> LegacyEthTransaction { + ) -> TransactionLegacy { let data = self .abi .function(method_name) .unwrap() .encode_input(args) .unwrap(); - LegacyEthTransaction { + TransactionLegacy { nonce, gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: Some(self.address), value: Default::default(), data, diff --git a/engine-tests/src/test_utils/standard_precompiles.rs b/engine-tests/src/test_utils/standard_precompiles.rs index 169596f7c..7404b4452 100644 --- a/engine-tests/src/test_utils/standard_precompiles.rs +++ b/engine-tests/src/test_utils/standard_precompiles.rs @@ -1,4 +1,4 @@ -use crate::prelude::{transaction::LegacyEthTransaction, U256}; +use crate::prelude::{transaction::legacy::TransactionLegacy, U256}; use crate::test_utils::solidity; use std::path::{Path, PathBuf}; @@ -22,7 +22,7 @@ impl PrecompilesConstructor { )) } - pub fn deploy(&self, nonce: U256) -> LegacyEthTransaction { + pub fn deploy(&self, nonce: U256) -> TransactionLegacy { self.0.deploy_without_args(nonce) } @@ -36,7 +36,7 @@ impl PrecompilesConstructor { } impl PrecompilesContract { - pub fn call_method(&self, method_name: &str, nonce: U256) -> LegacyEthTransaction { + pub fn call_method(&self, method_name: &str, nonce: U256) -> TransactionLegacy { self.0.call_method_without_args(method_name, nonce) } diff --git a/engine-tests/src/test_utils/uniswap.rs b/engine-tests/src/test_utils/uniswap.rs index 157a0236e..4d951ade8 100644 --- a/engine-tests/src/test_utils/uniswap.rs +++ b/engine-tests/src/test_utils/uniswap.rs @@ -1,6 +1,6 @@ use crate::prelude::{Address, U256}; use crate::test_utils::solidity; -use aurora_engine::transaction::LegacyEthTransaction; +use aurora_engine::transaction::legacy::TransactionLegacy; use std::ops::Not; use std::path::{Path, PathBuf}; use std::process::Command; @@ -73,7 +73,7 @@ impl FactoryConstructor { Self(load_constructor(artifact_path)) } - pub fn deploy(&self, nonce: U256) -> LegacyEthTransaction { + pub fn deploy(&self, nonce: U256) -> TransactionLegacy { self.0.deploy_without_args(nonce) } } @@ -103,7 +103,7 @@ impl PositionManagerConstructor { wrapped_eth: Address, token_descriptor: Address, nonce: U256, - ) -> LegacyEthTransaction { + ) -> TransactionLegacy { let data = self .0 .abi @@ -118,10 +118,10 @@ impl PositionManagerConstructor { ], ) .unwrap(); - LegacyEthTransaction { + TransactionLegacy { nonce, gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: None, value: Default::default(), data, @@ -136,7 +136,7 @@ impl Factory { token_b: Address, fee: U256, nonce: U256, - ) -> LegacyEthTransaction { + ) -> TransactionLegacy { let data = self .0 .abi @@ -149,10 +149,10 @@ impl Factory { ]) .unwrap(); - LegacyEthTransaction { + TransactionLegacy { nonce, gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: Some(self.0.address), value: Default::default(), data, @@ -183,7 +183,7 @@ impl Pool { }) } - pub fn initialize(&self, price: U256, nonce: U256) -> LegacyEthTransaction { + pub fn initialize(&self, price: U256, nonce: U256) -> TransactionLegacy { let data = self .0 .abi @@ -192,10 +192,10 @@ impl Pool { .encode_input(&[ethabi::Token::Uint(price)]) .unwrap(); - LegacyEthTransaction { + TransactionLegacy { nonce, gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: Some(self.0.address), value: Default::default(), data, @@ -204,7 +204,7 @@ impl Pool { } impl PositionManager { - pub fn mint(&self, params: MintParams, nonce: U256) -> LegacyEthTransaction { + pub fn mint(&self, params: MintParams, nonce: U256) -> TransactionLegacy { let tick_lower = Self::i64_to_u256(params.tick_lower); let tick_upper = Self::i64_to_u256(params.tick_upper); let data = self @@ -227,10 +227,10 @@ impl PositionManager { ])]) .unwrap(); - LegacyEthTransaction { + TransactionLegacy { nonce, gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: Some(self.0.address), value: Default::default(), data, @@ -267,12 +267,7 @@ impl SwapRouterConstructor { Self(load_constructor(artifact_path)) } - pub fn deploy( - &self, - factory: Address, - wrapped_eth: Address, - nonce: U256, - ) -> LegacyEthTransaction { + pub fn deploy(&self, factory: Address, wrapped_eth: Address, nonce: U256) -> TransactionLegacy { let data = self .0 .abi @@ -286,10 +281,10 @@ impl SwapRouterConstructor { ], ) .unwrap(); - LegacyEthTransaction { + TransactionLegacy { nonce, gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: None, value: Default::default(), data, @@ -313,7 +308,7 @@ impl SwapRouter { &self, params: ExactOutputSingleParams, nonce: U256, - ) -> LegacyEthTransaction { + ) -> TransactionLegacy { let data = self .0 .abi @@ -331,10 +326,10 @@ impl SwapRouter { ])]) .unwrap(); - LegacyEthTransaction { + TransactionLegacy { nonce, gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: Some(self.0.address), value: Default::default(), data, diff --git a/engine-tests/src/tests/access_lists.rs b/engine-tests/src/tests/access_lists.rs index 2b556f619..93ef7f779 100644 --- a/engine-tests/src/tests/access_lists.rs +++ b/engine-tests/src/tests/access_lists.rs @@ -1,8 +1,8 @@ use crate::prelude::Wei; use crate::prelude::{H256, U256}; use crate::test_utils; -use aurora_engine::transaction::access_list::{self, AccessListEthTransaction, AccessTuple}; -use aurora_engine::transaction::EthTransaction; +use aurora_engine::transaction::eip_2930::{self, AccessTuple, Transaction2930}; +use aurora_engine::transaction::EthTransactionKind; use std::convert::TryFrom; use std::iter; @@ -14,7 +14,7 @@ fn test_access_list_tx_encoding_decoding() { &hex::decode("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8").unwrap(), ) .unwrap(); - let transaction = AccessListEthTransaction { + let transaction = Transaction2930 { chain_id: 1, nonce: U256::zero(), gas_price: U256::from(0x0a), @@ -37,16 +37,16 @@ fn test_access_list_tx_encoding_decoding() { }; let signed_tx = test_utils::sign_access_list_transaction(transaction, &secret_key); - let bytes: Vec = iter::once(access_list::TYPE_BYTE) + let bytes: Vec = iter::once(eip_2930::TYPE_BYTE) .chain(rlp::encode(&signed_tx).into_iter()) .collect(); let expected_bytes = hex::decode("01f8f901800a83061a8094095e7baea6a6c7c4c2dfeb977efac326af552d87830186a000f893f85994095e7baea6a6c7c4c2dfeb977efac326af552d87f842a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001f794195e7baea6a6c7c4c2dfeb977efac326af552d87e1a0000000000000000000000000000000000000000000000000000000000000000080a011c97e0bb8a356fe4f49b37863d059c6fe8cd3214a6ac06a8387a2f6f0b75f60a0212368a1097da30806edfd13d9c35662e1baee939235eb25de867980bd0eda26").unwrap(); assert_eq!(bytes, expected_bytes); - let decoded_tx = match EthTransaction::try_from(expected_bytes.as_slice()) { - Ok(EthTransaction::AccessList(tx)) => tx, - Ok(EthTransaction::Legacy(_)) => panic!("Unexpected transaction type"), + let decoded_tx = match EthTransactionKind::try_from(expected_bytes.as_slice()) { + Ok(EthTransactionKind::Eip2930(tx)) => tx, + Ok(_) => panic!("Unexpected transaction type"), Err(_) => panic!("Transaction parsing failed"), }; diff --git a/engine-tests/src/tests/eip1559.rs b/engine-tests/src/tests/eip1559.rs new file mode 100644 index 000000000..239c5b33b --- /dev/null +++ b/engine-tests/src/tests/eip1559.rs @@ -0,0 +1,152 @@ +use crate::prelude::Wei; +use crate::prelude::{H256, U256}; +use crate::test_utils; +use aurora_engine::parameters::SubmitResult; +use aurora_engine::transaction::eip_1559::{self, SignedTransaction1559, Transaction1559}; +use aurora_engine::transaction::eip_2930::AccessTuple; +use aurora_engine::transaction::EthTransactionKind; +use borsh::BorshDeserialize; +use std::convert::TryFrom; +use std::iter; + +const SECRET_KEY: &str = "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"; +const INITIAL_NONCE: u64 = 1; +const INITIAL_BALANCE: Wei = Wei::new_u64(0x0de0b6b3a7640000); + +const CONTRACT_ADDRESS: &str = "0xcccccccccccccccccccccccccccccccccccccccc"; +const CONTRACT_NONCE: u64 = 1; +const CONTRACT_CODE: &str = "3a6000554860015500"; +const CONTRACT_BALANCE: Wei = Wei::new_u64(0x0de0b6b3a7640000); + +const EXAMPLE_TX_HEX: &str = "02f8c101010a8207d0833d090094cccccccccccccccccccccccccccccccccccccccc8000f85bf85994ccccccccccccccccccccccccccccccccccccccccf842a00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000180a0d671815898b8dd34321adbba4cb6a57baa7017323c26946f3719b00e70c755c2a03528b9efe3be57ea65a933d1e6bbf3b7d0c78830138883c1201e0c641fee6464"; + +// Test taken from https://github.com/ethereum/tests/blob/develop/GeneralStateTests/stExample/eip1559.json +// TODO(#170): generally support Ethereum tests +#[test] +fn test_eip_1559_tx_encoding_decoding() { + let secret_key = exmaple_signer().secret_key; + let transaction = example_transaction(); + + let signed_tx = test_utils::sign_eip_1559_transaction(transaction, &secret_key); + let bytes = encode_tx(&signed_tx); + let expected_bytes = hex::decode(EXAMPLE_TX_HEX).unwrap(); + + assert_eq!(bytes, expected_bytes); + + let decoded_tx = match EthTransactionKind::try_from(expected_bytes.as_slice()) { + Ok(EthTransactionKind::Eip1559(tx)) => tx, + Ok(_) => panic!("Unexpected transaction type"), + Err(_) => panic!("Transaction parsing failed"), + }; + + assert_eq!(signed_tx, decoded_tx); + + assert_eq!( + signed_tx.sender().unwrap(), + test_utils::address_from_secret_key(&secret_key) + ) +} + +// Test inspired by https://github.com/ethereum/tests/blob/develop/GeneralStateTests/stExample/eip1559.json +// but modified slightly because our BASEFEE is always 0. +#[test] +fn test_eip_1559_example() { + let mut runner = test_utils::deploy_evm(); + let mut signer = exmaple_signer(); + let signer_address = test_utils::address_from_secret_key(&signer.secret_key); + let contract_address = test_utils::address_from_hex(CONTRACT_ADDRESS); + let contract_code = hex::decode(CONTRACT_CODE).unwrap(); + + runner.create_address(signer_address, INITIAL_BALANCE, signer.nonce.into()); + runner.create_address_with_code( + contract_address, + CONTRACT_BALANCE, + CONTRACT_NONCE.into(), + contract_code.clone(), + ); + + // Check initial state + assert_eq!(runner.get_balance(signer_address), INITIAL_BALANCE); + assert_eq!(runner.get_nonce(signer_address), signer.nonce.into()); + assert_eq!(runner.get_balance(contract_address), CONTRACT_BALANCE); + assert_eq!(runner.get_nonce(contract_address), CONTRACT_NONCE.into()); + assert_eq!(runner.get_code(contract_address), contract_code); + + let mut transaction = example_transaction(); + transaction.chain_id = runner.chain_id; + signer.use_nonce(); + let signed_tx = test_utils::sign_eip_1559_transaction(transaction, &signer.secret_key); + + let sender = "relay.aurora"; + let (maybe_outcome, maybe_err) = runner.call(test_utils::SUBMIT, sender, encode_tx(&signed_tx)); + assert!(maybe_err.is_none()); + let result = + SubmitResult::try_from_slice(&maybe_outcome.unwrap().return_data.as_value().unwrap()) + .unwrap(); + assert_eq!(result.gas_used, 0xb8d2); + + // Check post state: + // signer spent some ETH on gas fees and incremented nonce for submitting transaction + assert_eq!( + runner.get_balance(signer_address), + Wei::new_u64(0x0de0b6b3a75cc7cc) + ); + assert_eq!(runner.get_nonce(signer_address), signer.nonce.into()); + // Contract balance, code, nonce all unchanged, but storage was written + assert_eq!(runner.get_balance(contract_address), CONTRACT_BALANCE); + assert_eq!(runner.get_nonce(contract_address), CONTRACT_NONCE.into()); + assert_eq!(runner.get_code(contract_address), contract_code); + assert_eq!( + runner.get_storage(contract_address, H256::zero()), + h256_from_hex("000000000000000000000000000000000000000000000000000000000000000a") + ); + assert_eq!(runner.get_storage(contract_address, one()), H256::zero()); + // Gas fees were awarded to the address derived from sending account + let coinbase = aurora_engine_sdk::types::near_account_to_evm_address(sender.as_bytes()); + assert_eq!(runner.get_balance(coinbase), Wei::new_u64(0x73834)); +} + +fn encode_tx(signed_tx: &SignedTransaction1559) -> Vec { + iter::once(eip_1559::TYPE_BYTE) + .chain(rlp::encode(signed_tx).into_iter()) + .collect() +} + +fn exmaple_signer() -> test_utils::Signer { + let secret_key = secp256k1::SecretKey::parse_slice(&hex::decode(SECRET_KEY).unwrap()).unwrap(); + + test_utils::Signer { + nonce: INITIAL_NONCE, + secret_key, + } +} + +fn example_transaction() -> Transaction1559 { + Transaction1559 { + chain_id: 1, + nonce: U256::from(INITIAL_NONCE), + gas_limit: U256::from(0x3d0900), + max_fee_per_gas: U256::from(0x07d0), + max_priority_fee_per_gas: U256::from(0x0a), + to: Some(test_utils::address_from_hex(CONTRACT_ADDRESS)), + value: Wei::zero(), + data: vec![0], + access_list: vec![AccessTuple { + address: test_utils::address_from_hex(CONTRACT_ADDRESS), + storage_keys: vec![H256::zero(), one()], + }], + } +} + +fn h256_from_hex(hex: &str) -> H256 { + let bytes = hex::decode(hex).unwrap(); + let mut result = [0u8; 32]; + result.copy_from_slice(&bytes); + H256(result) +} + +fn one() -> H256 { + let mut x = [0u8; 32]; + x[31] = 1; + H256(x) +} diff --git a/engine-tests/src/tests/erc20.rs b/engine-tests/src/tests/erc20.rs index a075c21ce..fe14d5d91 100644 --- a/engine-tests/src/tests/erc20.rs +++ b/engine-tests/src/tests/erc20.rs @@ -54,7 +54,12 @@ fn erc20_mint_out_of_gas() { let mut mint_tx = contract.mint(dest_address, mint_amount.into(), nonce.into()); // not enough gas to cover intrinsic cost - mint_tx.gas = (mint_tx.intrinsic_gas(&evm::Config::istanbul()).unwrap() - 1).into(); + let intrinsic_gas = mint_tx + .clone() + .normalize() + .intrinsic_gas(&evm::Config::istanbul()) + .unwrap(); + mint_tx.gas_limit = (intrinsic_gas - 1).into(); let outcome = runner.submit_transaction(&source_account.secret_key, mint_tx.clone()); let error = outcome.unwrap_err(); let error_message = format!("{:?}", error); @@ -63,7 +68,7 @@ fn erc20_mint_out_of_gas() { // not enough gas to complete transaction const GAS_LIMIT: u64 = 67_000; const GAS_PRICE: u64 = 10; - mint_tx.gas = U256::from(GAS_LIMIT); + mint_tx.gas_limit = U256::from(GAS_LIMIT); mint_tx.gas_price = U256::from(GAS_PRICE); // also set non-zero gas price to check gas still charged. let outcome = runner.submit_transaction(&source_account.secret_key, mint_tx); let error = outcome.unwrap(); @@ -208,18 +213,19 @@ fn deploy_erc_20_out_of_gas() { let mut deploy_transaction = constructor.deploy("OutOfGas", "OOG", INITIAL_NONCE.into()); // not enough gas to cover intrinsic cost - deploy_transaction.gas = (deploy_transaction + let intrinsic_gas = deploy_transaction + .clone() + .normalize() .intrinsic_gas(&evm::Config::istanbul()) - .unwrap() - - 1) - .into(); + .unwrap(); + deploy_transaction.gas_limit = (intrinsic_gas - 1).into(); let outcome = runner.submit_transaction(&source_account, deploy_transaction.clone()); let error = outcome.unwrap_err(); let error_message = format!("{:?}", error); assert!(error_message.contains("ERR_INTRINSIC_GAS")); // not enough gas to complete transaction - deploy_transaction.gas = U256::from(3_200_000); + deploy_transaction.gas_limit = U256::from(3_200_000); let outcome = runner.submit_transaction(&source_account, deploy_transaction); let error = outcome.unwrap(); assert_eq!(error.status, TransactionStatus::OutOfGas); diff --git a/engine-tests/src/tests/erc20_connector.rs b/engine-tests/src/tests/erc20_connector.rs index edb525a51..21240f0c5 100644 --- a/engine-tests/src/tests/erc20_connector.rs +++ b/engine-tests/src/tests/erc20_connector.rs @@ -2,7 +2,7 @@ use crate::prelude::{Address, Balance, RawAddress, TryInto, Wei, U256}; use crate::test_utils; use crate::test_utils::{create_eth_transaction, origin, AuroraRunner}; use aurora_engine::parameters::{FunctionCallArgs, SubmitResult}; -use aurora_engine::transaction::LegacyEthSignedTransaction; +use aurora_engine::transaction::legacy::LegacyEthSignedTransaction; use borsh::{BorshDeserialize, BorshSerialize}; use ethabi::Token; use near_vm_logic::VMOutcome; diff --git a/engine-tests/src/tests/mod.rs b/engine-tests/src/tests/mod.rs index 331a43b27..bf72ef66d 100644 --- a/engine-tests/src/tests/mod.rs +++ b/engine-tests/src/tests/mod.rs @@ -1,5 +1,6 @@ mod access_lists; mod contract_call; +mod eip1559; mod erc20; mod erc20_connector; mod eth_connector; diff --git a/engine-tests/src/tests/one_inch.rs b/engine-tests/src/tests/one_inch.rs index 9a2427fad..b2caebc98 100644 --- a/engine-tests/src/tests/one_inch.rs +++ b/engine-tests/src/tests/one_inch.rs @@ -125,10 +125,10 @@ fn deploy_1_inch_limit_order_contract( test_utils::solidity::ContractConstructor::compile_from_extended_json(contract_path); let nonce = signer.use_nonce(); - let deploy_tx = crate::prelude::transaction::LegacyEthTransaction { + let deploy_tx = crate::prelude::transaction::legacy::TransactionLegacy { nonce: nonce.into(), gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: None, value: Default::default(), data: constructor.code, diff --git a/engine-tests/src/tests/sanity.rs b/engine-tests/src/tests/sanity.rs index 0d08b764a..fe306057f 100644 --- a/engine-tests/src/tests/sanity.rs +++ b/engine-tests/src/tests/sanity.rs @@ -111,10 +111,10 @@ fn test_timestamp() { let nonce = signer.use_nonce(); let contract = runner.deploy_contract( &signer.secret_key, - |c| crate::prelude::transaction::LegacyEthTransaction { + |c| crate::prelude::transaction::legacy::TransactionLegacy { nonce: nonce.into(), gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: None, value: Default::default(), data: c.code.clone(), @@ -162,10 +162,10 @@ fn test_override_state() { // deploy contract let result = runner .submit_with_signer(&mut account1, |nonce| { - crate::prelude::transaction::LegacyEthTransaction { + crate::prelude::transaction::legacy::TransactionLegacy { nonce, gas_price: Default::default(), - gas: u64::MAX.into(), + gas_limit: u64::MAX.into(), to: None, value: Default::default(), data: contract.code.clone(), @@ -223,8 +223,8 @@ fn test_num_wasm_functions() { let module_info = artifact.info(); let num_functions = module_info.functions.len(); assert!( - num_functions <= 1420, - "{} is not less than 1420", + num_functions <= 1430, + "{} is not less than 1430", num_functions ); } @@ -344,7 +344,7 @@ fn test_eth_transfer_not_enough_gas() { let source_address = test_utils::address_from_secret_key(&source_account.secret_key); let transaction = |nonce| { let mut tx = test_utils::transfer(dest_address, TRANSFER_AMOUNT, nonce); - tx.gas = 10_000.into(); // this is not enough gas + tx.gas_limit = 10_000.into(); // this is not enough gas tx }; @@ -380,7 +380,7 @@ fn test_transfer_charging_gas_success() { let source_address = test_utils::address_from_secret_key(&source_account.secret_key); let transaction = |nonce| { let mut tx = test_utils::transfer(dest_address, TRANSFER_AMOUNT, nonce); - tx.gas = 30_000.into(); + tx.gas_limit = 30_000.into(); tx.gas_price = GAS_PRICE.into(); tx }; @@ -435,7 +435,7 @@ fn test_eth_transfer_charging_gas_not_enough_balance() { let mut tx = test_utils::transfer(dest_address, TRANSFER_AMOUNT, nonce); // With this gas limit and price the account does not // have enough balance to cover the gas cost - tx.gas = 3_000_000.into(); + tx.gas_limit = 3_000_000.into(); tx.gas_price = GAS_PRICE.into(); tx }; @@ -622,7 +622,7 @@ fn test_eth_transfer_charging_gas_not_enough_balance_sim() { // Run transaction which will fail (not enough balance to cover gas) let nonce = signer.use_nonce(); let mut tx = test_utils::transfer(Address([1; 20]), TRANSFER_AMOUNT, nonce.into()); - tx.gas = 3_000_000.into(); + tx.gas_limit = 3_000_000.into(); tx.gas_price = GAS_PRICE.into(); let signed_tx = test_utils::sign_transaction( tx, diff --git a/engine-tests/src/tests/uniswap.rs b/engine-tests/src/tests/uniswap.rs index f6e59340f..9803470fa 100644 --- a/engine-tests/src/tests/uniswap.rs +++ b/engine-tests/src/tests/uniswap.rs @@ -28,7 +28,7 @@ fn test_uniswap_exact_output() { let (_result, profile) = context.add_equal_liquidity(LIQUIDITY_AMOUNT.into(), &token_a, &token_b); - test_utils::assert_gas_bound(profile.all_gas(), 107); + test_utils::assert_gas_bound(profile.all_gas(), 108); let wasm_fraction = 100 * profile.wasm_gas() / profile.all_gas(); assert!(wasm_fraction >= 70, "{}% not more than 70%", wasm_fraction); diff --git a/engine-types/src/types.rs b/engine-types/src/types.rs index 983d0b9fb..48d243d92 100644 --- a/engine-types/src/types.rs +++ b/engine-types/src/types.rs @@ -45,6 +45,7 @@ pub fn validate_eth_address(address: String) -> Result Self { Self { - precompiles: Precompiles::new_istanbul(), + precompiles: Precompiles::new_london(), gas_limit, } } @@ -273,13 +273,20 @@ impl StackExecutorParams { fn make_executor<'a>( &'a self, engine: &'a Engine, - ) -> executor::StackExecutor<'static, 'a, executor::MemoryStackState> { + ) -> executor::StackExecutor<'static, 'a, executor::MemoryStackState, Precompiles> { let metadata = executor::StackSubstateMetadata::new(self.gas_limit, CONFIG); let state = executor::MemoryStackState::new(metadata, engine); - executor::StackExecutor::new_with_precompile(state, CONFIG, &self.precompiles.0) + executor::StackExecutor::new_with_precompiles(state, CONFIG, &self.precompiles) } } +#[derive(Debug, Default)] +pub struct GasPaymentResult { + pub prepaid_amount: Wei, + pub effective_gas_price: U256, + pub priority_fee_per_gas: U256, +} + /// Engine internal state, mostly configuration. /// Should not contain anything large or enumerable. #[derive(BorshSerialize, BorshDeserialize, Default)] @@ -313,10 +320,11 @@ impl From for EngineState { pub struct Engine { state: EngineState, origin: Address, + gas_price: U256, } // TODO: upgrade to Berlin HF -pub(crate) const CONFIG: &Config = &Config::istanbul(); +pub(crate) const CONFIG: &Config = &Config::london(); /// Key for storing the state of the engine. const STATE_KEY: &[u8; 5] = b"STATE"; @@ -327,7 +335,11 @@ impl Engine { } pub fn new_with_state(state: EngineState, origin: Address) -> Self { - Self { state, origin } + Self { + state, + origin, + gas_price: U256::zero(), + } } /// Saves state into the storage. @@ -373,54 +385,67 @@ impl Engine { sdk::sha256(&data) } - pub fn charge_gas_limit( + pub fn charge_gas( + &mut self, sender: &Address, - gas_limit: U256, - gas_price: U256, - ) -> Result { - // Early exit as performance optimization - if gas_price.is_zero() { - return Ok(Wei::zero()); + transaction: &NormalizedEthTransaction, + ) -> Result { + if transaction.max_fee_per_gas.is_zero() { + return Ok(GasPaymentResult::default()); } - let payment_for_gas = Wei::new( - gas_limit - .checked_mul(gas_price) - .ok_or(GasPaymentError::EthAmountOverflow)?, - ); - let account_balance = Self::get_balance(sender); - let remaining_balance = account_balance - .checked_sub(payment_for_gas) + let priority_fee_per_gas = transaction + .max_priority_fee_per_gas + .min(transaction.max_fee_per_gas - self.block_base_fee_per_gas()); + let effective_gas_price = priority_fee_per_gas + self.block_base_fee_per_gas(); + let gas_limit = transaction.gas_limit; + let prepaid_amount = gas_limit + .checked_mul(effective_gas_price) + .map(Wei::new) + .ok_or(GasPaymentError::EthAmountOverflow)?; + + let new_balance = Self::get_balance(sender) + .checked_sub(prepaid_amount) .ok_or(GasPaymentError::OutOfFund)?; - Self::set_balance(sender, &remaining_balance); + Self::set_balance(sender, &new_balance); - Ok(payment_for_gas) + self.gas_price = effective_gas_price; + + Ok(GasPaymentResult { + prepaid_amount, + effective_gas_price, + priority_fee_per_gas, + }) } pub fn refund_unused_gas( sender: &Address, + gas_used: u64, + gas_result: GasPaymentResult, relayer: &Address, - prepaid_amount: Wei, - used_gas: u64, - gas_price: U256, ) -> Result<(), GasPaymentError> { - // Early exit as performance optimization - if gas_price.is_zero() { + if gas_result.effective_gas_price.is_zero() { return Ok(()); } - let used_amount = Wei::new( - gas_price - .checked_mul(used_gas.into()) - .ok_or(GasPaymentError::EthAmountOverflow)?, - ); - // We cannot have used more than the gas_limit - debug_assert!(used_amount <= prepaid_amount); - let refund_amount = prepaid_amount - used_amount; + let gas_to_wei = |price: U256| { + U256::from(gas_used) + .checked_mul(price) + .map(Wei::new) + .ok_or(GasPaymentError::EthAmountOverflow) + }; - Self::add_balance(sender, refund_amount)?; - Self::add_balance(relayer, used_amount)?; + let spent_amount = gas_to_wei(gas_result.effective_gas_price)?; + let reward_amount = gas_to_wei(gas_result.priority_fee_per_gas)?; + + let refund = gas_result + .prepaid_amount + .checked_sub(spent_amount) + .ok_or(GasPaymentError::EthAmountOverflow)?; + + Self::add_balance(sender, refund)?; + Self::add_balance(relayer, reward_amount)?; Ok(()) } @@ -906,12 +931,9 @@ impl Engine { } impl evm::backend::Backend for Engine { - /// Returns the gas price. - /// - /// This is currently zero, but may be changed in the future. This is mainly - /// because there already is another cost for transactions. + /// Returns the "effective" gas price (as defined by EIP-1559) fn gas_price(&self) -> U256 { - U256::zero() + self.gas_price } /// Returns the origin address that created the contract. @@ -992,6 +1014,16 @@ impl evm::backend::Backend for Engine { U256::max_value() } + /// Returns the current base fee for the current block. + /// + /// Currently, this returns 0 as there is no concept of a base fee at this + /// time but this may change in the future. + /// + /// TODO: doc.aurora.dev link + fn block_base_fee_per_gas(&self) -> U256 { + U256::zero() + } + /// Returns the states chain ID. fn chain_id(&self) -> U256 { U256::from(self.state.chain_id) diff --git a/engine/src/lib.rs b/engine/src/lib.rs index daebe2eac..9728c30b5 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -84,7 +84,8 @@ mod contract { }; use crate::prelude::storage::{bytes_to_key, KeyPrefix}; use crate::prelude::types::{u256_to_arr, ERR_FAILED_PARSE}; - use crate::prelude::{sdk, Address, ToString, TryFrom, TryInto, H160, H256, U256}; + use crate::prelude::{sdk, vec, Address, ToString, TryFrom, TryInto, H160, H256, U256}; + use crate::transaction::{EthTransactionKind, NormalizedEthTransaction}; const CODE_KEY: &[u8; 4] = b"CODE"; const CODE_STAGE_KEY: &[u8; 10] = b"CODE_STAGE"; @@ -204,86 +205,83 @@ mod contract { /// Must match CHAIN_ID to make sure it's signed for given chain vs replayed from another chain. #[no_mangle] pub extern "C" fn submit() { - use crate::transaction::EthTransaction; - let input = sdk::read_input(); - let signed_transaction = EthTransaction::try_from(input.as_slice()).sdk_unwrap(); + let transaction: NormalizedEthTransaction = EthTransactionKind::try_from(input.as_slice()) + .sdk_unwrap() + .into(); let state = Engine::get_state().sdk_unwrap(); // Validate the chain ID, if provided inside the signature: - if let Some(chain_id) = signed_transaction.chain_id() { + if let Some(chain_id) = transaction.chain_id { if U256::from(chain_id) != U256::from(state.chain_id) { sdk::panic_utf8(b"ERR_INVALID_CHAIN_ID"); } } // Retrieve the signer of the transaction: - let sender = signed_transaction - .sender() + let sender = transaction + .address .sdk_expect("ERR_INVALID_ECDSA_SIGNATURE"); sdk::log!(crate::prelude::format!("signer_address {:?}", sender).as_str()); - Engine::check_nonce(&sender, signed_transaction.nonce()).sdk_unwrap(); + Engine::check_nonce(&sender, &transaction.nonce).sdk_unwrap(); // Check intrinsic gas is covered by transaction gas limit - match signed_transaction.intrinsic_gas(crate::engine::CONFIG) { + match transaction.intrinsic_gas(crate::engine::CONFIG) { None => sdk::panic_utf8(GAS_OVERFLOW.as_bytes()), Some(intrinsic_gas) => { - if signed_transaction.gas_limit() < intrinsic_gas.into() { + if transaction.gas_limit < intrinsic_gas.into() { sdk::panic_utf8(b"ERR_INTRINSIC_GAS") } } } - // Pay for gas - let gas_price = signed_transaction.gas_price(); - let prepaid_amount = - match Engine::charge_gas_limit(&sender, signed_transaction.gas_limit(), gas_price) { - Ok(amount) => amount, - // If the account does not have enough funds to cover the gas cost then we still - // must increment the nonce to prevent the transaction from being replayed in the - // future when the state may have changed such that it could pass. - Err(GasPaymentError::OutOfFund) => { - Engine::increment_nonce(&sender); - let result = SubmitResult::new( - TransactionStatus::OutOfFund, - 0, - crate::prelude::Vec::new(), - ); - sdk::return_output(&result.try_to_vec().unwrap()); - return; - } - // If an overflow happens then the transaction is statically invalid - // (i.e. validity does not depend on state), so we do not need to increment the nonce. - Err(err) => sdk::panic_utf8(err.as_ref()), - }; + if transaction.max_priority_fee_per_gas > transaction.max_fee_per_gas { + sdk::panic_utf8(b"ERR_MAX_PRIORITY_FEE_GREATER") + } // Figure out what kind of a transaction this is, and execute it: let mut engine = Engine::new_with_state(state, sender); - let (value, gas_limit, data, maybe_receiver, access_list) = - signed_transaction.destructure(); - let gas_limit = gas_limit.sdk_expect(GAS_OVERFLOW); - let access_list = access_list + let prepaid_amount = match engine.charge_gas(&sender, &transaction) { + Ok(gas_result) => gas_result, + Err(GasPaymentError::OutOfFund) => { + Engine::increment_nonce(&sender); + let result = SubmitResult::new(TransactionStatus::OutOfFund, 0, vec![]); + sdk::return_output(&result.try_to_vec().unwrap()); + return; + } + Err(err) => sdk::panic_utf8(err.as_ref()), + }; + let gas_limit: u64 = transaction.gas_limit.try_into().sdk_expect(GAS_OVERFLOW); + let access_list = transaction + .access_list .into_iter() .map(|a| (a.address, a.storage_keys)) .collect(); - let result = if let Some(receiver) = maybe_receiver { + let result = if let Some(receiver) = transaction.to { Engine::call( &mut engine, sender, receiver, - value, - data, + transaction.value, + transaction.data, gas_limit, access_list, ) // TODO: charge for storage } else { // Execute a contract deployment: - Engine::deploy_code(&mut engine, sender, value, data, gas_limit, access_list) + Engine::deploy_code( + &mut engine, + sender, + transaction.value, + transaction.data, + gas_limit, + access_list, + ) // TODO: charge for storage }; @@ -293,8 +291,7 @@ mod contract { Ok(submit_result) => submit_result.gas_used, Err(engine_err) => engine_err.gas_used, }; - Engine::refund_unused_gas(&sender, &relayer, prepaid_amount, gas_used, gas_price) - .sdk_unwrap(); + Engine::refund_unused_gas(&sender, gas_used, prepaid_amount, &relayer).sdk_unwrap(); // return result to user result diff --git a/engine/src/transaction/eip_1559.rs b/engine/src/transaction/eip_1559.rs new file mode 100644 index 000000000..0c63489c6 --- /dev/null +++ b/engine/src/transaction/eip_1559.rs @@ -0,0 +1,130 @@ +use crate::prelude::precompiles::secp256k1::ecrecover; +use crate::prelude::{Vec, U256}; +use crate::transaction::eip_2930::AccessTuple; +use aurora_engine_types::types::Wei; +use ethabi::Address; +use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; + +/// Type indicator (per EIP-1559) +pub const TYPE_BYTE: u8 = 0x02; + +/// A EIP-1559 transaction kind from the London hard fork. +/// +/// See [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) +/// for more details. +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct Transaction1559 { + pub chain_id: u64, + pub nonce: U256, + pub max_priority_fee_per_gas: U256, + pub max_fee_per_gas: U256, + pub gas_limit: U256, + pub to: Option
, + pub value: Wei, + pub data: Vec, + pub access_list: Vec, +} + +impl Transaction1559 { + /// RLP encoding of the data for an unsigned message (used to make signature) + pub fn rlp_append_unsigned(&self, s: &mut RlpStream) { + self.rlp_append(s, 9); + } + + /// RLP encoding for a signed message (used to encode the transaction for sending to tx pool) + pub fn rlp_append_signed(&self, s: &mut RlpStream) { + self.rlp_append(s, 12); + } + + fn rlp_append(&self, s: &mut RlpStream, list_len: usize) { + s.begin_list(list_len); + s.append(&self.chain_id); + s.append(&self.nonce); + s.append(&self.max_priority_fee_per_gas); + s.append(&self.max_fee_per_gas); + s.append(&self.gas_limit); + match self.to.as_ref() { + None => s.append(&""), + Some(address) => s.append(address), + }; + s.append(&self.value.raw()); + s.append(&self.data); + s.begin_list(self.access_list.len()); + for tuple in self.access_list.iter() { + s.begin_list(2); + s.append(&tuple.address); + s.begin_list(tuple.storage_keys.len()); + for key in tuple.storage_keys.iter() { + s.append(key); + } + } + } +} + +#[derive(Debug, Eq, PartialEq)] +pub struct SignedTransaction1559 { + pub transaction: Transaction1559, + /// The parity (0 for even, 1 for odd) of the y-value of a secp256k1 signature. + pub parity: u8, + pub r: U256, + pub s: U256, +} + +impl SignedTransaction1559 { + pub fn sender(&self) -> Option
{ + let mut rlp_stream = RlpStream::new(); + rlp_stream.append(&TYPE_BYTE); + self.transaction.rlp_append_unsigned(&mut rlp_stream); + let message_hash = aurora_engine_sdk::keccak(rlp_stream.as_raw()); + ecrecover( + message_hash, + &super::vrs_to_arr(self.parity, self.r, self.s), + ) + .ok() + } +} + +impl Encodable for SignedTransaction1559 { + fn rlp_append(&self, s: &mut RlpStream) { + self.transaction.rlp_append_signed(s); + s.append(&self.parity); + s.append(&self.r); + s.append(&self.s); + } +} + +impl Decodable for SignedTransaction1559 { + fn decode(rlp: &Rlp<'_>) -> Result { + if rlp.item_count() != Ok(12) { + return Err(rlp::DecoderError::RlpIncorrectListLen); + } + let chain_id = rlp.val_at(0)?; + let nonce = rlp.val_at(1)?; + let max_priority_fee_per_gas = rlp.val_at(2)?; + let max_fee_per_gas = rlp.val_at(3)?; + let gas_limit = rlp.val_at(4)?; + let to = super::rlp_extract_to(rlp, 5)?; + let value = Wei::new(rlp.val_at(6)?); + let data = rlp.val_at(7)?; + let access_list = rlp.list_at(8)?; + let parity = rlp.val_at(9)?; + let r = rlp.val_at(10)?; + let s = rlp.val_at(11)?; + Ok(Self { + transaction: Transaction1559 { + chain_id, + nonce, + max_priority_fee_per_gas, + max_fee_per_gas, + gas_limit, + to, + value, + data, + access_list, + }, + parity, + r, + s, + }) + } +} diff --git a/engine/src/transaction/access_list.rs b/engine/src/transaction/eip_2930.rs similarity index 84% rename from engine/src/transaction/access_list.rs rename to engine/src/transaction/eip_2930.rs index 86ff681d5..a4c8db8e9 100644 --- a/engine/src/transaction/access_list.rs +++ b/engine/src/transaction/eip_2930.rs @@ -25,7 +25,7 @@ impl Decodable for AccessTuple { /// See https://eips.ethereum.org/EIPS/eip-2930 #[derive(Debug, Eq, PartialEq, Clone)] -pub struct AccessListEthTransaction { +pub struct Transaction2930 { pub chain_id: u64, pub nonce: U256, pub gas_price: U256, @@ -36,7 +36,7 @@ pub struct AccessListEthTransaction { pub access_list: Vec, } -impl AccessListEthTransaction { +impl Transaction2930 { /// RLP encoding of the data for an unsigned message (used to make signature) pub fn rlp_append_unsigned(&self, s: &mut RlpStream) { self.rlp_append(s, 8); @@ -47,11 +47,6 @@ impl AccessListEthTransaction { self.rlp_append(s, 11); } - #[inline] - pub fn intrinsic_gas(&self, config: &evm::Config) -> Option { - super::intrinsic_gas(self.to.is_none(), &self.data, &self.access_list, config) - } - fn rlp_append(&self, s: &mut RlpStream, list_len: usize) { s.begin_list(list_len); s.append(&self.chain_id); @@ -77,19 +72,19 @@ impl AccessListEthTransaction { } #[derive(Debug, Eq, PartialEq)] -pub struct AccessListEthSignedTransaction { - pub transaction_data: AccessListEthTransaction, +pub struct SignedTransaction2930 { + pub transaction: Transaction2930, /// The parity (0 for even, 1 for odd) of the y-value of a secp256k1 signature. pub parity: u8, pub r: U256, pub s: U256, } -impl AccessListEthSignedTransaction { +impl SignedTransaction2930 { pub fn sender(&self) -> Option
{ let mut rlp_stream = RlpStream::new(); rlp_stream.append(&TYPE_BYTE); - self.transaction_data.rlp_append_unsigned(&mut rlp_stream); + self.transaction.rlp_append_unsigned(&mut rlp_stream); let message_hash = sdk::keccak(rlp_stream.as_raw()); ecrecover( message_hash, @@ -99,16 +94,16 @@ impl AccessListEthSignedTransaction { } } -impl Encodable for AccessListEthSignedTransaction { +impl Encodable for SignedTransaction2930 { fn rlp_append(&self, s: &mut RlpStream) { - self.transaction_data.rlp_append_signed(s); + self.transaction.rlp_append_signed(s); s.append(&self.parity); s.append(&self.r); s.append(&self.s); } } -impl Decodable for AccessListEthSignedTransaction { +impl Decodable for SignedTransaction2930 { fn decode(rlp: &Rlp<'_>) -> Result { if rlp.item_count() != Ok(11) { return Err(rlp::DecoderError::RlpIncorrectListLen); @@ -125,7 +120,7 @@ impl Decodable for AccessListEthSignedTransaction { let r = rlp.val_at(9)?; let s = rlp.val_at(10)?; Ok(Self { - transaction_data: AccessListEthTransaction { + transaction: Transaction2930 { chain_id, nonce, gas_price, diff --git a/engine/src/transaction/legacy.rs b/engine/src/transaction/legacy.rs index 93bc00a6b..7dac50ba4 100644 --- a/engine/src/transaction/legacy.rs +++ b/engine/src/transaction/legacy.rs @@ -3,13 +3,13 @@ use crate::prelude::{sdk, Address, Vec, Wei, U256}; use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; #[derive(Debug, Eq, PartialEq, Clone)] -pub struct LegacyEthTransaction { +pub struct TransactionLegacy { /// A monotonically increasing transaction counter for this sender pub nonce: U256, /// The fee the sender pays per unit of gas pub gas_price: U256, /// The maximum amount of gas units consumed by the transaction - pub gas: U256, + pub gas_limit: U256, /// The receiving address (`None` for the zero address) pub to: Option
, /// The amount of ETH to transfer @@ -18,12 +18,12 @@ pub struct LegacyEthTransaction { pub data: Vec, } -impl LegacyEthTransaction { +impl TransactionLegacy { pub fn rlp_append_unsigned(&self, s: &mut RlpStream, chain_id: Option) { s.begin_list(if chain_id.is_none() { 6 } else { 9 }); s.append(&self.nonce); s.append(&self.gas_price); - s.append(&self.gas); + s.append(&self.gas_limit); match self.to.as_ref() { None => s.append(&""), Some(address) => s.append(address), @@ -37,23 +37,33 @@ impl LegacyEthTransaction { } } - #[inline] - pub fn intrinsic_gas(&self, config: &evm::Config) -> Option { - super::intrinsic_gas(self.to.is_none(), &self.data, &[], config) - } - /// Returns self.gas as a u64, or None if self.gas > u64::MAX #[allow(unused)] pub fn get_gas_limit(&self) -> Option { use crate::prelude::TryInto; - self.gas.try_into().ok() + self.gas_limit.try_into().ok() + } + + pub fn normalize(self) -> super::NormalizedEthTransaction { + super::NormalizedEthTransaction { + address: None, + chain_id: None, + nonce: self.nonce, + gas_limit: self.gas_limit, + max_priority_fee_per_gas: self.gas_price, + max_fee_per_gas: self.gas_price, + to: self.to, + value: self.value, + data: self.data, + access_list: Vec::new(), + } } } #[derive(Debug, Eq, PartialEq)] pub struct LegacyEthSignedTransaction { /// The unsigned transaction data - pub transaction: LegacyEthTransaction, + pub transaction: TransactionLegacy, /// The ECDSA recovery ID pub v: u64, /// The first ECDSA signature output @@ -64,7 +74,6 @@ pub struct LegacyEthSignedTransaction { impl LegacyEthSignedTransaction { /// Returns sender of given signed transaction by doing ecrecover on the signature. - #[allow(dead_code)] pub fn sender(&self) -> Option
{ let mut rlp_stream = RlpStream::new(); // See details of CHAIN_ID computation here - https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md#specification @@ -81,7 +90,6 @@ impl LegacyEthSignedTransaction { } /// Returns chain id encoded in `v` parameter of the signature if that was done, otherwise None. - #[allow(dead_code)] pub fn chain_id(&self) -> Option { match self.v { 0..=34 => None, @@ -95,7 +103,7 @@ impl Encodable for LegacyEthSignedTransaction { s.begin_list(9); s.append(&self.transaction.nonce); s.append(&self.transaction.gas_price); - s.append(&self.transaction.gas); + s.append(&self.transaction.gas_limit); match self.transaction.to.as_ref() { None => s.append(&""), Some(address) => s.append(address), @@ -123,10 +131,10 @@ impl Decodable for LegacyEthSignedTransaction { let r = rlp.val_at(7)?; let s = rlp.val_at(8)?; Ok(Self { - transaction: LegacyEthTransaction { + transaction: TransactionLegacy { nonce, gas_price, - gas, + gas_limit: gas, to, value, data, @@ -162,10 +170,10 @@ mod tests { assert_eq!(tx.chain_id(), Some(1)); assert_eq!( tx.transaction, - LegacyEthTransaction { + TransactionLegacy { nonce: U256::zero(), gas_price: U256::from(234567897654321u128), - gas: U256::from(2000000u128), + gas_limit: U256::from(2000000u128), to: Some(address_from_arr( &hex::decode("F0109fC8DF283027b6285cc889F5aA624EaC1F55").unwrap() )), diff --git a/engine/src/transaction/mod.rs b/engine/src/transaction/mod.rs index a782f1373..23d18bafa 100644 --- a/engine/src/transaction/mod.rs +++ b/engine/src/transaction/mod.rs @@ -1,107 +1,138 @@ -use crate::prelude::{Address, TryFrom, Vec, U256}; +use crate::prelude::{vec, Address, TryFrom, Vec, U256}; use rlp::{Decodable, DecoderError, Rlp}; -pub mod access_list; -pub(crate) mod legacy; +pub mod eip_1559; +pub mod eip_2930; +pub mod legacy; -use access_list::AccessTuple; -pub use legacy::{LegacyEthSignedTransaction, LegacyEthTransaction}; +use aurora_engine_types::types::Wei; +use eip_2930::AccessTuple; /// Typed Transaction Envelope (see https://eips.ethereum.org/EIPS/eip-2718) #[derive(Eq, PartialEq)] -pub enum EthTransaction { - Legacy(LegacyEthSignedTransaction), - AccessList(access_list::AccessListEthSignedTransaction), +pub enum EthTransactionKind { + Legacy(legacy::LegacyEthSignedTransaction), + Eip2930(eip_2930::SignedTransaction2930), + Eip1559(eip_1559::SignedTransaction1559), } -impl EthTransaction { - pub fn chain_id(&self) -> Option { - match self { - Self::Legacy(tx) => tx.chain_id(), - Self::AccessList(tx) => Some(tx.transaction_data.chain_id), - } - } - - pub fn sender(&self) -> Option
{ - match self { - Self::Legacy(tx) => tx.sender(), - Self::AccessList(tx) => tx.sender(), - } - } - - pub fn nonce(&self) -> &U256 { - match self { - Self::Legacy(tx) => &tx.transaction.nonce, - Self::AccessList(tx) => &tx.transaction_data.nonce, - } - } - - pub fn intrinsic_gas(&self, config: &evm::Config) -> Option { - match self { - Self::Legacy(tx) => tx.transaction.intrinsic_gas(config), - Self::AccessList(tx) => tx.transaction_data.intrinsic_gas(config), - } - } +impl TryFrom<&[u8]> for EthTransactionKind { + type Error = ParseTransactionError; - pub fn gas_limit(&self) -> U256 { - match self { - Self::Legacy(tx) => tx.transaction.gas, - Self::AccessList(tx) => tx.transaction_data.gas_limit, + fn try_from(bytes: &[u8]) -> Result { + if bytes[0] == eip_2930::TYPE_BYTE { + Ok(Self::Eip2930(eip_2930::SignedTransaction2930::decode( + &Rlp::new(&bytes[1..]), + )?)) + } else if bytes[0] == eip_1559::TYPE_BYTE { + Ok(Self::Eip1559(eip_1559::SignedTransaction1559::decode( + &Rlp::new(&bytes[1..]), + )?)) + } else if bytes[0] <= 0x7f { + Err(ParseTransactionError::UnknownTransactionType) + } else if bytes[0] == 0xff { + Err(ParseTransactionError::ReservedSentinel) + } else { + let legacy = legacy::LegacyEthSignedTransaction::decode(&Rlp::new(bytes))?; + Ok(Self::Legacy(legacy)) } } +} - pub fn gas_price(&self) -> U256 { - match self { - Self::Legacy(tx) => tx.transaction.gas_price, - Self::AccessList(tx) => tx.transaction_data.gas_price, - } - } +/// A normalized Ethereum transaction which can be created from older +/// transactions. +pub struct NormalizedEthTransaction { + pub address: Option
, + pub chain_id: Option, + pub nonce: U256, + pub gas_limit: U256, + pub max_priority_fee_per_gas: U256, + pub max_fee_per_gas: U256, + pub to: Option
, + pub value: Wei, + pub data: Vec, + pub access_list: Vec, +} - pub fn destructure( - self, - ) -> ( - crate::prelude::Wei, - Option, - Vec, - Option
, - Vec, - ) { - use crate::prelude::TryInto; - match self { - Self::Legacy(tx) => { - let tx = tx.transaction; - (tx.value, tx.gas.try_into().ok(), tx.data, tx.to, Vec::new()) - } - Self::AccessList(tx) => { - let tx = tx.transaction_data; - ( - tx.value, - tx.gas_limit.try_into().ok(), - tx.data, - tx.to, - tx.access_list, - ) - } +impl From for NormalizedEthTransaction { + fn from(kind: EthTransactionKind) -> Self { + use EthTransactionKind::*; + match kind { + Legacy(tx) => Self { + address: tx.sender(), + chain_id: tx.chain_id(), + nonce: tx.transaction.nonce, + gas_limit: tx.transaction.gas_limit, + max_priority_fee_per_gas: tx.transaction.gas_price, + max_fee_per_gas: tx.transaction.gas_price, + to: tx.transaction.to, + value: tx.transaction.value, + data: tx.transaction.data, + access_list: vec![], + }, + Eip2930(tx) => Self { + address: tx.sender(), + chain_id: Some(tx.transaction.chain_id), + nonce: tx.transaction.nonce, + gas_limit: tx.transaction.gas_limit, + max_priority_fee_per_gas: tx.transaction.gas_price, + max_fee_per_gas: tx.transaction.gas_price, + to: tx.transaction.to, + value: tx.transaction.value, + data: tx.transaction.data, + access_list: tx.transaction.access_list, + }, + Eip1559(tx) => Self { + address: tx.sender(), + chain_id: Some(tx.transaction.chain_id), + nonce: tx.transaction.nonce, + gas_limit: tx.transaction.gas_limit, + max_priority_fee_per_gas: tx.transaction.max_priority_fee_per_gas, + max_fee_per_gas: tx.transaction.max_fee_per_gas, + to: tx.transaction.to, + value: tx.transaction.value, + data: tx.transaction.data, + access_list: tx.transaction.access_list, + }, } } } -impl TryFrom<&[u8]> for EthTransaction { - type Error = ParseTransactionError; +impl NormalizedEthTransaction { + pub fn intrinsic_gas(&self, config: &evm::Config) -> Option { + let is_contract_creation = self.to.is_none(); - fn try_from(bytes: &[u8]) -> Result { - if bytes[0] == access_list::TYPE_BYTE { - let access_list_tx = - access_list::AccessListEthSignedTransaction::decode(&Rlp::new(&bytes[1..]))?; - Ok(Self::AccessList(access_list_tx)) - } else if bytes[0] <= 0x7f { - Err(ParseTransactionError::UnknownTransactionType) - } else if bytes[0] == 0xff { - Err(ParseTransactionError::ReservedSentinel) + let base_gas = if is_contract_creation { + config.gas_transaction_create } else { - let legacy = LegacyEthSignedTransaction::decode(&Rlp::new(bytes))?; - Ok(Self::Legacy(legacy)) - } + config.gas_transaction_call + }; + + let num_zero_bytes = self.data.iter().filter(|b| **b == 0).count(); + let num_non_zero_bytes = self.data.len() - num_zero_bytes; + + let gas_zero_bytes = config + .gas_transaction_zero_data + .checked_mul(num_zero_bytes as u64)?; + let gas_non_zero_bytes = config + .gas_transaction_non_zero_data + .checked_mul(num_non_zero_bytes as u64)?; + + let gas_access_list_address = config + .gas_access_list_address + .checked_mul(self.access_list.len() as u64)?; + let gas_access_list_storage = config.gas_access_list_storage_key.checked_mul( + self.access_list + .iter() + .map(|a| a.storage_keys.len() as u64) + .sum(), + )?; + + base_gas + .checked_add(gas_zero_bytes) + .and_then(|gas| gas.checked_add(gas_non_zero_bytes)) + .and_then(|gas| gas.checked_add(gas_access_list_address)) + .and_then(|gas| gas.checked_add(gas_access_list_storage)) } } @@ -146,45 +177,6 @@ fn rlp_extract_to(rlp: &Rlp<'_>, index: usize) -> Result, Decode } } -fn intrinsic_gas( - is_contract_creation: bool, - data: &[u8], - access_list: &[access_list::AccessTuple], - config: &evm::Config, -) -> Option { - let base_gas = if is_contract_creation { - config.gas_transaction_create - } else { - config.gas_transaction_call - }; - - let num_zero_bytes = data.iter().filter(|b| **b == 0).count(); - let num_non_zero_bytes = data.len() - num_zero_bytes; - - let gas_zero_bytes = config - .gas_transaction_zero_data - .checked_mul(num_zero_bytes as u64)?; - let gas_non_zero_bytes = config - .gas_transaction_non_zero_data - .checked_mul(num_non_zero_bytes as u64)?; - - let gas_access_list_address = config - .gas_access_list_address - .checked_mul(access_list.len() as u64)?; - let gas_access_list_storage = config.gas_access_list_storage_key.checked_mul( - access_list - .iter() - .map(|a| a.storage_keys.len() as u64) - .sum(), - )?; - - base_gas - .checked_add(gas_zero_bytes) - .and_then(|gas| gas.checked_add(gas_non_zero_bytes)) - .and_then(|gas| gas.checked_add(gas_access_list_address)) - .and_then(|gas| gas.checked_add(gas_access_list_storage)) -} - fn vrs_to_arr(v: u8, r: U256, s: U256) -> [u8; 65] { let mut result = [0u8; 65]; // (r, s, v), typed (uint256, uint256, uint8) r.to_big_endian(&mut result[0..32]); diff --git a/etc/state-migration-test/Cargo.lock b/etc/state-migration-test/Cargo.lock index 7b1cd93dd..d5c746034 100644 --- a/etc/state-migration-test/Cargo.lock +++ b/etc/state-migration-test/Cargo.lock @@ -385,7 +385,7 @@ dependencies = [ [[package]] name = "evm" version = "0.31.1" -source = "git+https://github.com/aurora-is-near/sputnikvm.git#58895a8fa9dfef02bf9928cad975e3f03b05972c" +source = "git+https://github.com/aurora-is-near/sputnikvm.git#0fbde9fa7797308290f89111c6abe5cee55a5eac" dependencies = [ "ethereum", "evm-core", @@ -400,7 +400,7 @@ dependencies = [ [[package]] name = "evm-core" version = "0.31.0" -source = "git+https://github.com/aurora-is-near/sputnikvm.git#58895a8fa9dfef02bf9928cad975e3f03b05972c" +source = "git+https://github.com/aurora-is-near/sputnikvm.git#0fbde9fa7797308290f89111c6abe5cee55a5eac" dependencies = [ "funty", "primitive-types", @@ -409,7 +409,7 @@ dependencies = [ [[package]] name = "evm-gasometer" version = "0.31.0" -source = "git+https://github.com/aurora-is-near/sputnikvm.git#58895a8fa9dfef02bf9928cad975e3f03b05972c" +source = "git+https://github.com/aurora-is-near/sputnikvm.git#0fbde9fa7797308290f89111c6abe5cee55a5eac" dependencies = [ "evm-core", "evm-runtime", @@ -419,7 +419,7 @@ dependencies = [ [[package]] name = "evm-runtime" version = "0.31.0" -source = "git+https://github.com/aurora-is-near/sputnikvm.git#58895a8fa9dfef02bf9928cad975e3f03b05972c" +source = "git+https://github.com/aurora-is-near/sputnikvm.git#0fbde9fa7797308290f89111c6abe5cee55a5eac" dependencies = [ "evm-core", "primitive-types",