diff --git a/src/components/config/src/abci/mod.rs b/src/components/config/src/abci/mod.rs index b97a41c6b..f20193426 100644 --- a/src/components/config/src/abci/mod.rs +++ b/src/components/config/src/abci/mod.rs @@ -88,6 +88,7 @@ pub struct CheckPointConfig { // Fix the amount in the delegators that staking did not modify when it punished the validator. pub fix_delegators_am_height: u64, pub validators_limit_v2_height: u64, + pub enable_eip1559_height: u64, } impl CheckPointConfig { @@ -127,6 +128,7 @@ impl CheckPointConfig { proper_gas_set_height: 0, fix_delegators_am_height: 0, validators_limit_v2_height: 0, + enable_eip1559_height: 0, }; #[cfg(not(feature = "debug_env"))] let config = CheckPointConfig { @@ -156,6 +158,7 @@ impl CheckPointConfig { fix_undelegation_missing_reward_height: 3000000, fix_delegators_am_height: 30000000, validators_limit_v2_height: 30000000, + enable_eip1559_height: 40000000, }; let content = toml::to_string(&config).unwrap(); file.write_all(content.as_bytes()).unwrap(); diff --git a/src/components/contracts/baseapp/src/lib.rs b/src/components/contracts/baseapp/src/lib.rs index 02de696a1..eeffde329 100644 --- a/src/components/contracts/baseapp/src/lib.rs +++ b/src/components/contracts/baseapp/src/lib.rs @@ -13,7 +13,7 @@ pub mod tm_events; use crate::modules::ModuleManager; use abci::Header; -use ethereum::BlockV0 as Block; +use ethereum::BlockV2 as Block; use evm_precompile::{self, FindoraPrecompiles}; use fin_db::{FinDB, RocksDB}; use fp_core::context::Context as Context2; @@ -24,6 +24,7 @@ use fp_core::{ transaction::{ActionResult, Executable, ValidateUnsigned}, }; use fp_evm::BlockId; +use fp_traits::evm::FeeCalculator as FeeCalculator2; use fp_traits::{ account::{AccountAsset, FeeCalculator}, base::BaseProvider, @@ -54,6 +55,23 @@ const CHAIN_STATE_PATH: &str = "state.db"; const CHAIN_HISTORY_DATA_PATH: &str = "history.db"; const BLOCKS_IN_DAY: u64 = 4 * 60 * 24; +const INITIAL_BASE_FEE: u64 = 1000000000; +const ELASTICITY_MULTIPLIER: u64 = 2; +const BASE_FEE_MAX_CHANGE_DENOMINATOR: u64 = 8; + +#[inline(always)] +pub fn get_initial_base_fee() -> U256 { + U256::from(INITIAL_BASE_FEE) +} +#[inline(always)] +pub fn get_elasticity_multiplier() -> U256 { + U256::from(ELASTICITY_MULTIPLIER) +} +#[inline(always)] +pub fn get_base_fee_max_change_denominator() -> U256 { + U256::from(BASE_FEE_MAX_CHANGE_DENOMINATOR) +} + #[derive(Clone)] pub struct BaseApp { /// application name from abci.Info @@ -467,4 +485,15 @@ impl BaseProvider for BaseApp { None } } + + /// Return the base fee at the given height. + #[allow(clippy::comparison_chain, clippy::question_mark)] + fn base_fee(&self, id: Option) -> Option { + let _ = id; + Some( + ::FeeCalculator::min_gas_price( + self.current_block_number()?.as_u64(), + ), + ) + } } diff --git a/src/components/contracts/modules/account/src/impls.rs b/src/components/contracts/modules/account/src/impls.rs index c0ff983cf..e3cbba4af 100644 --- a/src/components/contracts/modules/account/src/impls.rs +++ b/src/components/contracts/modules/account/src/impls.rs @@ -242,4 +242,14 @@ impl AccountAsset
for App { ) -> Result<()> { Allowances::insert(ctx.state.write().borrow_mut(), owner, spender, &amount) } + + fn income(ctx: &Context, who: &Address, value: U256) -> Result<()> { + if value.is_zero() { + return Ok(()); + } + + let mut sa = Self::account_of(ctx, who, None).c(d!("account does not exist"))?; + sa.balance = sa.balance.checked_add(value).c(d!("balance overflow"))?; + AccountStore::insert(ctx.state.write().borrow_mut(), who, &sa) + } } diff --git a/src/components/contracts/modules/ethereum/src/impls.rs b/src/components/contracts/modules/ethereum/src/impls.rs index 61654416b..9281217a1 100644 --- a/src/components/contracts/modules/ethereum/src/impls.rs +++ b/src/components/contracts/modules/ethereum/src/impls.rs @@ -1,10 +1,7 @@ use crate::storage::*; use crate::{App, Config, ContractLog, TransactionExecuted}; use config::abci::global_cfg::CFG; -use ethereum::{ - BlockV0 as Block, LegacyTransactionMessage, ReceiptV0 as Receipt, - TransactionV0 as Transaction, -}; +use ethereum::{BlockV2 as Block, ReceiptV0 as Receipt, TransactionV2 as Transaction}; use ethereum_types::{Bloom, BloomInput, H160, H256, H64, U256}; use evm::{ExitFatal, ExitReason}; use fp_core::{ @@ -16,10 +13,8 @@ use fp_core::{ use fp_events::Event; use fp_evm::{BlockId, CallOrCreateInfo, Runner, TransactionStatus}; use fp_storage::{Borrow, BorrowMut}; -use fp_types::{ - actions::evm as EvmAction, - crypto::{secp256k1_ecdsa_recover, HA256}, -}; +// use fp_types::crypto::secp256k1_ecdsa_recover; +use fp_types::{actions::evm as EvmAction, crypto::HA256}; use fp_utils::{proposer_converter, timestamp_converter}; use ruc::*; use sha3::{Digest, Keccak256}; @@ -63,14 +58,28 @@ impl App { pub fn recover_signer(transaction: &Transaction) -> Option { let mut sig = [0u8; 65]; let mut msg = [0u8; 32]; - sig[0..32].copy_from_slice(&transaction.signature.r()[..]); - sig[32..64].copy_from_slice(&transaction.signature.s()[..]); - sig[64] = transaction.signature.standard_v(); - msg.copy_from_slice( - &LegacyTransactionMessage::from(transaction.clone()).hash()[..], - ); - - let pubkey = secp256k1_ecdsa_recover(&sig, &msg).ok()?; + match transaction { + Transaction::Legacy(t) => { + sig[0..32].copy_from_slice(&t.signature.r()[..]); + sig[32..64].copy_from_slice(&t.signature.s()[..]); + sig[64] = t.signature.standard_v(); + msg.copy_from_slice( + ðereum::LegacyTransactionMessage::from(t.clone()).hash()[..], + ); + } + Transaction::EIP1559(t) => { + sig[0..32].copy_from_slice(&t.r[..]); + sig[32..64].copy_from_slice(&t.s[..]); + sig[64] = t.odd_y_parity as u8; + msg.copy_from_slice( + ðereum::EIP1559TransactionMessage::from(t.clone()).hash()[..], + ); + } + _ => { + return None; + } + } + let pubkey = fp_types::crypto::secp256k1_ecdsa_recover(&sig, &msg).ok()?; Some(H160::from(H256::from_slice( Keccak256::digest(pubkey).as_slice(), ))) @@ -225,8 +234,7 @@ impl App { let source = Self::recover_signer_fast(ctx, &transaction) .ok_or_else(|| eg!("ExecuteTransaction: InvalidSignature"))?; - let transaction_hash = - H256::from_slice(Keccak256::digest(&rlp::encode(&transaction)).as_slice()); + let transaction_hash = transaction.hash(); let transaction_index = if just_check { 0 @@ -236,22 +244,74 @@ impl App { txns.len() as u32 }; - let gas_limit = transaction.gas_limit; + let nonce; + let gas_price; + let gas_limit; + let action; + let value; + let input; + let max_priority_fee_per_gas; + let max_fee_per_gas; + let _chain_id; + let mut _access_list = vec![]; + + let is_eip1559 = match &transaction { + Transaction::Legacy(legacy_transaction_transaction) => { + let transaction = legacy_transaction_transaction; + nonce = transaction.nonce; + gas_price = transaction.gas_price; + max_fee_per_gas = transaction.gas_price; + max_priority_fee_per_gas = U256::from(0); + gas_limit = transaction.gas_limit; + action = transaction.action; + value = transaction.value; + input = transaction.input.clone(); + _chain_id = match transaction.signature.chain_id() { + Some(chain_id) => chain_id, + None => return Err(eg!("Must provide chainId")), + }; + + false + } + Transaction::EIP1559(eip1559_transaction_transaction) => { + let transaction = eip1559_transaction_transaction; + nonce = transaction.nonce; + gas_limit = transaction.gas_limit; + action = transaction.action; + value = transaction.value; + input = transaction.input.clone(); + max_fee_per_gas = transaction.max_fee_per_gas; + max_priority_fee_per_gas = transaction.max_priority_fee_per_gas; + gas_price = transaction.max_fee_per_gas; + _chain_id = transaction.chain_id; + _access_list = transaction.access_list.clone(); + + true + } + _ => { + return Err(eg!("Transaction Type Error")); + } + }; + + // let gas_limit = transaction.gas_limit; let execute_ret = Self::execute_transaction( ctx, source, - transaction.input.clone(), - transaction.value, - transaction.gas_limit, - Some(transaction.gas_price), - Some(transaction.nonce), - transaction.action, + input, + value, + gas_limit, + Some(gas_price), + Some(max_fee_per_gas), + Some(max_priority_fee_per_gas), + Some(nonce), + action, + is_eip1559, ); if let Err(e) = execute_ret { let mut to = Default::default(); - if let ethereum::TransactionAction::Call(target) = transaction.action { + if let ethereum::TransactionAction::Call(target) = action { to = target; } events.push(Event::emit_event( @@ -410,42 +470,85 @@ impl App { value: U256, gas_limit: U256, gas_price: Option, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, nonce: Option, action: ethereum::TransactionAction, + is_eip1559: bool, ) -> Result<(Option, Option, CallOrCreateInfo)> { - match action { - ethereum::TransactionAction::Call(target) => { - let res = C::Runner::call( - ctx, - EvmAction::Call { - source: from, - target, - input, - value, - gas_limit: gas_limit.low_u64(), - gas_price, - nonce, - }, - C::config(), - )?; - - Ok((Some(target), None, CallOrCreateInfo::Call(res))) + if is_eip1559 { + match action { + ethereum::TransactionAction::Call(target) => { + let res = C::Runner::call_eip1559( + ctx, + EvmAction::CallEip1559 { + source: from, + target, + input, + value, + gas_limit: gas_limit.low_u64(), + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + }, + C::config(), + )?; + + Ok((Some(target), None, CallOrCreateInfo::Call(res))) + } + ethereum::TransactionAction::Create => { + let res = C::Runner::create_eip1559( + ctx, + EvmAction::CreateEip1559 { + source: from, + init: input, + value, + gas_limit: gas_limit.low_u64(), + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + }, + C::config(), + )?; + + Ok((None, Some(res.value), CallOrCreateInfo::Create(res))) + } } - ethereum::TransactionAction::Create => { - let res = C::Runner::create( - ctx, - EvmAction::Create { - source: from, - init: input, - value, - gas_limit: gas_limit.low_u64(), - gas_price, - nonce, - }, - C::config(), - )?; - - Ok((None, Some(res.value), CallOrCreateInfo::Create(res))) + } else { + match action { + ethereum::TransactionAction::Call(target) => { + let res = C::Runner::call( + ctx, + EvmAction::Call { + source: from, + target, + input, + value, + gas_limit: gas_limit.low_u64(), + gas_price, + nonce, + }, + C::config(), + )?; + + Ok((Some(target), None, CallOrCreateInfo::Call(res))) + } + ethereum::TransactionAction::Create => { + let res = C::Runner::create( + ctx, + EvmAction::Create { + source: from, + init: input, + value, + gas_limit: gas_limit.low_u64(), + gas_price, + nonce, + }, + C::config(), + )?; + + Ok((None, Some(res.value), CallOrCreateInfo::Create(res))) + } } } } diff --git a/src/components/contracts/modules/ethereum/src/lib.rs b/src/components/contracts/modules/ethereum/src/lib.rs index 07c180354..1575b1257 100644 --- a/src/components/contracts/modules/ethereum/src/lib.rs +++ b/src/components/contracts/modules/ethereum/src/lib.rs @@ -5,6 +5,7 @@ mod basic; mod impls; use config::abci::global_cfg::CFG; +use ethereum::TransactionV2 as Transaction; use ethereum_types::{H160, H256, U256}; use evm::Config as EvmConfig; use fp_core::context::RunTxMode; @@ -54,7 +55,7 @@ pub trait Config { pub mod storage { use ethereum::{ - BlockV0 as Block, ReceiptV0 as Receipt, TransactionV0 as Transaction, + BlockV2 as Block, ReceiptV0 as Receipt, TransactionV2 as Transaction, }; use ethereum_types::U256; use fp_evm::TransactionStatus; @@ -176,60 +177,112 @@ impl ValidateUnsigned for App { fn validate_unsigned(ctx: &Context, call: &Self::Call) -> Result<()> { let Action::Transact(transaction) = call; - if let Some(chain_id) = transaction.signature.chain_id() { - if chain_id != C::ChainId::get() { - return Err(eg!(format!( - "InvalidChainId, got {}, but expected {}", - chain_id, - C::ChainId::get() - ))); + + let ( + nonce, + gas_price, + gas_limit, + value, + chain_id, + max_fee_per_gas, + max_priority_fee_per_gas, + is_eip1559, + ); + + match transaction { + Transaction::Legacy(t) => { + nonce = t.nonce; + gas_price = t.gas_price; + gas_limit = t.gas_limit; + value = t.value; + chain_id = match t.signature.chain_id() { + Some(chain_id) => chain_id, + None => return Err(eg!("Must provide chainId")), + }; + is_eip1559 = false; + + max_fee_per_gas = U256::zero(); + max_priority_fee_per_gas = U256::zero(); } - } else { - return Err(eg!("Must provide chainId".to_string())); + Transaction::EIP1559(t) => { + nonce = t.nonce; + gas_limit = t.gas_limit; + max_fee_per_gas = t.max_fee_per_gas; + max_priority_fee_per_gas = t.max_priority_fee_per_gas; + value = t.value; + chain_id = t.chain_id; + is_eip1559 = true; + + gas_price = U256::zero(); + } + _ => { + return Err(eg!("Transaction Type Error")); + } + } + + if chain_id != C::ChainId::get() { + return Err(eg!(format!( + "InvalidChainId, got {}, but expected {}", + chain_id, + C::ChainId::get() + ))); } let origin = Self::recover_signer_fast(ctx, transaction) .ok_or_else(|| eg!("ExecuteTransaction: InvalidSignature"))?; // Same as go ethereum, Min gas limit is 21000. - if transaction.gas_limit < U256::from(21000) - || transaction.gas_limit > C::BlockGasLimit::get() - { + if gas_limit < U256::from(21000) || gas_limit > C::BlockGasLimit::get() { return Err(eg!(format!( "InvalidGasLimit: got {}, the gas limit must be in range [21000, {}]", - transaction.gas_limit, + gas_limit, C::BlockGasLimit::get() ))); } - let min_gas_price = C::FeeCalculator::min_gas_price(ctx.header.height as u64); + if !is_eip1559 { + let min_gas_price = + C::FeeCalculator::min_gas_price(ctx.header.height as u64); - if transaction.gas_price < min_gas_price { - return Err(eg!(format!( - "InvalidGasPrice: got {}, but the minimum gas price is {}", - transaction.gas_price, min_gas_price - ))); + if gas_price < min_gas_price { + return Err(eg!(format!( + "InvalidGasPrice: got {}, but the minimum gas price is {}", + gas_price, min_gas_price + ))); + } } let account_id = C::AddressMapping::convert_to_account_id(origin); let account = C::AccountAsset::account_of(ctx, &account_id, None).unwrap_or_default(); - let nonce = account.nonce; - let balance = account.balance; - if transaction.nonce < nonce { + if nonce < account.nonce { return Err(eg!(format!( "InvalidNonce: origin: {:?}, got {}, but expected {}", - origin, transaction.nonce, nonce + origin, nonce, account.nonce ))); } - let fee = transaction.gas_price.saturating_mul(transaction.gas_limit); - let total_payment = transaction.value.saturating_add(fee); - if balance < total_payment { + let fee = if !is_eip1559 { + gas_price.saturating_mul(gas_limit) + } else { + let max_base_fee = max_fee_per_gas + .checked_mul(gas_limit) + .ok_or(eg!("FeeOverflow"))?; + let max_priority_fee = max_priority_fee_per_gas + .checked_mul(gas_limit) + .ok_or(eg!("FeeOverflow"))?; + max_base_fee + .checked_add(max_priority_fee) + .ok_or(eg!("FeeOverflow"))? + }; + + let total_payment = value.saturating_add(fee); + + if account.balance < total_payment { return Err(eg!(format!( "InsufficientBalance, origin: {:?}, actual balance {}, but expected payment {}", - origin, balance, total_payment + origin, account.balance, total_payment ))); } diff --git a/src/components/contracts/modules/evm/Cargo.toml b/src/components/contracts/modules/evm/Cargo.toml index 6bac465ee..1b01e3e68 100644 --- a/src/components/contracts/modules/evm/Cargo.toml +++ b/src/components/contracts/modules/evm/Cargo.toml @@ -16,6 +16,7 @@ evm-runtime = { version = "0.35.0", default-features = false } evm-gasometer = { version = "0.30.0", default-features = false } ethereum = { version = "0.12.0", default-features = false, features = ["with-serde"] } impl-trait-for-tuples = "0.2" +log = "0.4" tracing = "0.1" rlp = { version = "0.5", default-features = false } ruc = "1.0" diff --git a/src/components/contracts/modules/evm/precompile/utils/Cargo.toml b/src/components/contracts/modules/evm/precompile/utils/Cargo.toml index 5a2091980..8deb7e314 100644 --- a/src/components/contracts/modules/evm/precompile/utils/Cargo.toml +++ b/src/components/contracts/modules/evm/precompile/utils/Cargo.toml @@ -13,3 +13,4 @@ tracing = "0.1" num_enum = { version = "0.5.3", default-features = false } precompile-utils-macro = { path = "macro" } sha3 = { version = "0.10", default-features = false } +module-evm = { path = "../../../../modules/evm"} diff --git a/src/components/contracts/modules/evm/src/impls.rs b/src/components/contracts/modules/evm/src/impls.rs index bf405846c..071d2a3d8 100644 --- a/src/components/contracts/modules/evm/src/impls.rs +++ b/src/components/contracts/modules/evm/src/impls.rs @@ -108,4 +108,11 @@ impl OnChargeEVMTransaction for App { C::AccountAsset::refund(ctx, &account_id, already_withdrawn)?; C::AccountAsset::burn(ctx, &account_id, corrected_fee) } + + fn pay_priority_fee(ctx: &Context, tip: U256) -> Result<()> { + let author = App::::find_proposer(ctx); + let account_id = C::AddressMapping::convert_to_account_id(author); + + C::AccountAsset::income(ctx, &account_id, tip) + } } diff --git a/src/components/contracts/modules/evm/src/lib.rs b/src/components/contracts/modules/evm/src/lib.rs index 3839482c3..23e11d9c0 100644 --- a/src/components/contracts/modules/evm/src/lib.rs +++ b/src/components/contracts/modules/evm/src/lib.rs @@ -33,7 +33,7 @@ use fp_evm::TransactionStatus; use evm::executor::stack::PrecompileSet as EvmPrecompileSet; use ethereum::{ - Log, ReceiptV0 as Receipt, TransactionAction, TransactionSignature, TransactionV0, + Log, ReceiptV0 as Receipt, TransactionAction, TransactionSignature, TransactionV2, }; use fp_types::{ @@ -110,7 +110,7 @@ impl App { _lowlevel: Vec, transaction_index: u32, transaction_hash: H256, - ) -> Result<(TransactionV0, TransactionStatus, Receipt)> { + ) -> Result<(TransactionV2, TransactionStatus, Receipt)> { let function = self.contracts.bridge.function("withdrawAsset").c(d!())?; let asset = Token::FixedBytes(Vec::from(_asset)); @@ -167,7 +167,7 @@ impl App { _lowlevel: Vec, transaction_index: u32, transaction_hash: H256, - ) -> Result<(TransactionV0, TransactionStatus, Receipt)> { + ) -> Result<(TransactionV2, TransactionStatus, Receipt)> { let function = self.contracts.bridge.function("withdrawFRA").c(d!())?; let from = Token::Bytes(from.noah_to_bytes()); @@ -251,13 +251,13 @@ impl App { from: H160, to: H160, logs: Vec, - ) -> (TransactionV0, TransactionStatus, Receipt) { + ) -> (TransactionV2, TransactionStatus, Receipt) { let signature_fake = H256([ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, ]); - let tx = TransactionV0 { + let tx = TransactionV2::Legacy(ethereum::LegacyTransaction { nonce: U256::zero(), gas_price, gas_limit, @@ -266,7 +266,7 @@ impl App { .unwrap(), input, action, - }; + }); let mut logs_bloom = Bloom::default(); Self::logs_bloom(&logs, &mut logs_bloom); diff --git a/src/components/contracts/modules/evm/src/runtime/runner.rs b/src/components/contracts/modules/evm/src/runtime/runner.rs index 7525ac9b1..e9414ed97 100644 --- a/src/components/contracts/modules/evm/src/runtime/runner.rs +++ b/src/components/contracts/modules/evm/src/runtime/runner.rs @@ -143,6 +143,170 @@ impl ActionRunner { }) } + // eip1559 support + pub fn execute_eip1559<'config, 'precompiles, F, R>( + ctx: &Context, + source: H160, + value: U256, + gas_limit: u64, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, + nonce: Option, + config: &'config evm::Config, + precompiles: &'precompiles C::PrecompilesType, + f: F, + ) -> Result> + where + F: FnOnce( + &mut StackExecutor< + 'config, + 'precompiles, + FindoraStackState<'_, '_, 'config, C>, + C::PrecompilesType, + >, + ) -> (ExitReason, R), + { + let base_fee = C::FeeCalculator::min_gas_price(ctx.header.height as u64); + // Gas price check is skipped when performing a gas estimation. + let max_fee_per_gas = match max_fee_per_gas { + Some(max_fee_per_gas) => { + ensure!(max_fee_per_gas >= base_fee, "GasPriceTooLow"); + max_fee_per_gas + } + None => Default::default(), + }; + + let vicinity = Vicinity { + gas_price: max_fee_per_gas, + origin: source, + }; + + let metadata = StackSubstateMetadata::new(gas_limit, config); + let state = FindoraStackState::::new(ctx, &vicinity, metadata); + let mut executor = + StackExecutor::new_with_precompiles(state, config, precompiles); + + // After eip-1559 we make sure the account can pay both the evm execution and priority fees. + let max_base_fee = max_fee_per_gas + .checked_mul(U256::from(gas_limit)) + .ok_or(eg!("FeeOverflow"))?; + let max_priority_fee = if let Some(max_priority_fee) = max_priority_fee_per_gas { + max_priority_fee + .checked_mul(U256::from(gas_limit)) + .ok_or(eg!("FeeOverflow"))? + } else { + U256::zero() + }; + + let total_fee = max_base_fee + .checked_add(max_priority_fee) + .ok_or(eg!("FeeOverflow"))?; + + let total_payment = + value.checked_add(total_fee).ok_or(eg!("PaymentOverflow"))?; + let source_account = App::::account_basic(ctx, &source); + + if let Some(nonce) = nonce { + ensure!( + source_account.nonce == nonce, + format!( + "InvalidNonce, expected: {}, actual: {}", + source_account.nonce, nonce + ) + ); + } + if !config.estimate { + ensure!(source_account.balance >= total_payment, "BalanceLow"); + + // Deduct fee from the `source` account. + App::::withdraw_fee(ctx, &source, total_fee)?; + } + + // Execute the EVM call. + let (reason, retv) = f(&mut executor); + + let used_gas = U256::from(executor.used_gas()); + let (actual_fee, actual_priority_fee) = + if let Some(max_priority_fee) = max_priority_fee_per_gas { + let actual_priority_fee = max_priority_fee + .checked_mul(used_gas) + .ok_or(eg!("FeeOverflow"))?; + let actual_fee = executor + .fee(base_fee) + .checked_add(actual_priority_fee) + .unwrap_or_else(U256::max_value); + (actual_fee, Some(actual_priority_fee)) + } else { + (executor.fee(base_fee), None) + }; + tracing::trace!( + target: "evm", + "Execution {:?} [source: {:?}, value: {}, gas_limit: {}, actual_fee: {}]", + reason, + source, + value, + gas_limit, + actual_fee + ); + // The difference between initially withdrawn and the actual cost is refunded. + // + // Considered the following request: + // +-----------+---------+--------------+ + // | Gas_limit | Max_Fee | Max_Priority | + // +-----------+---------+--------------+ + // | 20 | 10 | 6 | + // +-----------+---------+--------------+ + // + // And execution: + // +----------+----------+ + // | Gas_used | Base_Fee | + // +----------+----------+ + // | 5 | 2 | + // +----------+----------+ + // + // Initially withdrawn (10 + 6) * 20 = 320. + // Actual cost (2 + 6) * 5 = 40. + // Refunded 320 - 40 = 280. + // Tip 5 * 6 = 30. + // Burned 320 - (280 + 30) = 10. Which is equivalent to gas_used * base_fee. + if !config.estimate { + App::::correct_and_deposit_fee(ctx, &source, actual_fee, total_fee)?; + if let Some(actual_priority_fee) = actual_priority_fee { + App::::pay_priority_fee(ctx, actual_priority_fee)?; + } + } + + let state = executor.into_state(); + + for address in state.substate.deletes { + tracing::trace!( + target: "evm", + "Deleting account at {:?}", + address + ); + App::::remove_account(ctx, &address.into()) + } + + for log in &state.substate.logs { + tracing::trace!( + target: "evm", + "Inserting log for {:?}, topics ({}) {:?}, data ({}): {:?}]", + log.address, + log.topics.len(), + log.topics, + log.data.len(), + log.data + ); + } + + Ok(ExecutionInfo { + value: retv, + exit_reason: reason, + used_gas, + logs: state.substate.logs, + }) + } + pub fn inital_system_contract( ctx: &Context, bytecode: Vec, @@ -366,4 +530,112 @@ impl Runner for ActionRunner { }, ) } + + fn call_eip1559( + ctx: &Context, + args: CallEip1559, + config: &evm::Config, + ) -> Result { + let precompiles = C::PrecompilesValue::get(ctx.clone()); + let access_list = Vec::new(); + Self::execute_eip1559( + ctx, + args.source, + args.value, + args.gas_limit, + args.max_fee_per_gas, + args.max_priority_fee_per_gas, + args.nonce, + config, + &precompiles, + |executor| { + executor.transact_call( + args.source, + args.target, + args.value, + args.input, + args.gas_limit, + access_list, + ) + }, + ) + } + + fn create_eip1559( + ctx: &Context, + args: CreateEip1559, + config: &evm::Config, + ) -> Result { + let precompiles = C::PrecompilesValue::get(ctx.clone()); + let access_list = Vec::new(); + Self::execute_eip1559( + ctx, + args.source, + args.value, + args.gas_limit, + args.max_fee_per_gas, + args.max_priority_fee_per_gas, + args.nonce, + config, + &precompiles, + |executor| { + let address = executor.create_address(evm::CreateScheme::Legacy { + caller: args.source, + }); + ( + executor + .transact_create( + args.source, + args.value, + args.init, + args.gas_limit, + access_list, + ) + .0, + address, + ) + }, + ) + } + + fn create2_eip1559( + ctx: &Context, + args: Create2Eip1559, + config: &evm::Config, + ) -> Result { + let code_hash = H256::from_slice(Keccak256::digest(&args.init).as_slice()); + let precompiles = C::PrecompilesValue::get(ctx.clone()); + let access_list = Vec::new(); + Self::execute_eip1559( + ctx, + args.source, + args.value, + args.gas_limit, + args.max_fee_per_gas, + args.max_priority_fee_per_gas, + args.nonce, + config, + &precompiles, + |executor| { + let address = executor.create_address(evm::CreateScheme::Create2 { + caller: args.source, + code_hash, + salt: args.salt, + }); + ( + executor + .transact_create2( + args.source, + args.value, + args.init, + args.salt, + args.gas_limit, + access_list, + ) + .0, + address, + ) + }, + ) + } } diff --git a/src/components/contracts/primitives/evm/src/lib.rs b/src/components/contracts/primitives/evm/src/lib.rs index 2b972ea60..06cbcc9c3 100644 --- a/src/components/contracts/primitives/evm/src/lib.rs +++ b/src/components/contracts/primitives/evm/src/lib.rs @@ -4,7 +4,9 @@ use ethereum_types::{Bloom, H160, H256, U256}; use evm::ExitReason; use fp_core::context::Context; -use fp_types::actions::evm::{Call, Create, Create2}; +use fp_types::actions::evm::{ + Call, CallEip1559, Create, Create2, Create2Eip1559, CreateEip1559, +}; use ruc::*; use serde::{Deserialize, Serialize}; @@ -55,6 +57,24 @@ pub trait Runner { fn create2(ctx: &Context, args: Create2, config: &evm::Config) -> Result; + + fn call_eip1559( + ctx: &Context, + args: CallEip1559, + config: &evm::Config, + ) -> Result; + + fn create_eip1559( + ctx: &Context, + args: CreateEip1559, + config: &evm::Config, + ) -> Result; + + fn create2_eip1559( + ctx: &Context, + args: Create2Eip1559, + config: &evm::Config, + ) -> Result; } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/components/contracts/primitives/mocks/src/lib.rs b/src/components/contracts/primitives/mocks/src/lib.rs index f88cac008..4eaed9676 100644 --- a/src/components/contracts/primitives/mocks/src/lib.rs +++ b/src/components/contracts/primitives/mocks/src/lib.rs @@ -7,7 +7,7 @@ pub use baseapp::{ }; pub use fp_types::{actions::Action, assemble::UncheckedTransaction}; -use ethereum::{TransactionAction, TransactionSignature, TransactionV0 as Transaction}; +use ethereum::{TransactionAction, TransactionSignature, TransactionV2 as Transaction}; use fp_traits::account::AccountAsset; use fp_traits::evm::{AddressMapping, EthereumAddressMapping}; use fp_types::crypto::{Address, MultiSignature}; @@ -148,7 +148,7 @@ impl UnsignedTransaction { ) .unwrap(); - Transaction { + Transaction::Legacy(ethereum::LegacyTransaction { nonce: self.nonce, gas_price: self.gas_price, gas_limit: self.gas_limit, @@ -156,6 +156,6 @@ impl UnsignedTransaction { value: self.value, input: self.input.clone(), signature: sig, - } + }) } } diff --git a/src/components/contracts/primitives/rpc-core/Cargo.toml b/src/components/contracts/primitives/rpc-core/Cargo.toml index 81617b82f..87ff9c686 100644 --- a/src/components/contracts/primitives/rpc-core/Cargo.toml +++ b/src/components/contracts/primitives/rpc-core/Cargo.toml @@ -9,6 +9,8 @@ description = "RPC traits of Ethereum." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" [dependencies] +rlp = "0.5" +ethereum = { version = "0.12.0", default-features = false, features = ["with-serde"] } ethereum-types = "0.13.1" futures = "0.3.16" jsonrpc-core = { git = "https://github.com/FindoraNetwork/jsonrpc.git", package = "jsonrpc-core" } diff --git a/src/components/contracts/primitives/rpc-core/src/types/block.rs b/src/components/contracts/primitives/rpc-core/src/types/block.rs index 5786136da..855ffa17f 100644 --- a/src/components/contracts/primitives/rpc-core/src/types/block.rs +++ b/src/components/contracts/primitives/rpc-core/src/types/block.rs @@ -90,6 +90,8 @@ pub struct Block { pub transactions: BlockTransactions, /// Size in bytes pub size: Option, + /// Base Fee for post-EIP1559 blocks. + pub base_fee_per_gas: Option, } /// Block header representation. diff --git a/src/components/contracts/primitives/rpc-core/src/types/call_request.rs b/src/components/contracts/primitives/rpc-core/src/types/call_request.rs index 0451dbaf6..2e965b9c9 100644 --- a/src/components/contracts/primitives/rpc-core/src/types/call_request.rs +++ b/src/components/contracts/primitives/rpc-core/src/types/call_request.rs @@ -31,6 +31,10 @@ pub struct CallRequest { pub to: Option, /// Gas Price pub gas_price: Option, + /// EIP-1559 Max base fee the caller is willing to pay + pub max_fee_per_gas: Option, + /// EIP-1559 Priority fee the caller is paying to the block author + pub max_priority_fee_per_gas: Option, /// Gas pub gas: Option, /// Value diff --git a/src/components/contracts/primitives/rpc-core/src/types/mod.rs b/src/components/contracts/primitives/rpc-core/src/types/mod.rs index daa204156..422f78580 100644 --- a/src/components/contracts/primitives/rpc-core/src/types/mod.rs +++ b/src/components/contracts/primitives/rpc-core/src/types/mod.rs @@ -56,5 +56,5 @@ pub use self::transaction::{ LocalTransactionStatus, PendingTransaction, PendingTransactions, RichRawTransaction, Transaction, }; -pub use self::transaction_request::TransactionRequest; +pub use self::transaction_request::{TransactionMessage, TransactionRequest}; pub use self::work::Work; diff --git a/src/components/contracts/primitives/rpc-core/src/types/receipt.rs b/src/components/contracts/primitives/rpc-core/src/types/receipt.rs index 5d1ed929e..846609176 100644 --- a/src/components/contracts/primitives/rpc-core/src/types/receipt.rs +++ b/src/components/contracts/primitives/rpc-core/src/types/receipt.rs @@ -54,4 +54,7 @@ pub struct Receipt { // NOTE(niklasad1): Unknown after EIP98 rules, if it's missing then skip serializing it #[serde(skip_serializing_if = "Option::is_none", rename = "status")] pub status_code: Option, + + /// Effective gas price. Pre-eip1559 this is just the gasprice. Post-eip1559 this is base fee + priority fee. + pub effective_gas_price: U256, } diff --git a/src/components/contracts/primitives/rpc-core/src/types/transaction.rs b/src/components/contracts/primitives/rpc-core/src/types/transaction.rs index 6d767f40c..d57e20f55 100644 --- a/src/components/contracts/primitives/rpc-core/src/types/transaction.rs +++ b/src/components/contracts/primitives/rpc-core/src/types/transaction.rs @@ -17,6 +17,7 @@ // along with this program. If not, see . use crate::types::Bytes; +use ethereum::{AccessListItem, TransactionV2}; use ethereum_types::{H160, H256, H512, U256, U64}; use serde::ser::SerializeStruct; use serde::{Serialize, Serializer}; @@ -46,7 +47,11 @@ pub struct Transaction { /// Transfered value pub value: U256, /// Gas Price - pub gas_price: U256, + pub gas_price: Option, + /// eip1559. Max BaseFeePerGas the user is willing to pay. + pub max_fee_per_gas: Option, + /// eip1559.The miner's tip. + pub max_priority_fee_per_gas: Option, /// Gas pub gas: U256, /// Data @@ -67,6 +72,67 @@ pub struct Transaction { pub r: U256, /// The S field of the signature. pub s: U256, + /// eip1559. Pre-pay to warm storage access. + pub access_list: Option>, +} + +impl From for Transaction { + fn from(transaction: TransactionV2) -> Self { + let serialized = rlp::encode(&transaction); + let hash = transaction.hash(); + let raw = Bytes(serialized.to_vec()); + match transaction { + TransactionV2::Legacy(t) => Transaction { + hash, + nonce: t.nonce, + block_hash: None, + block_number: None, + transaction_index: None, + from: H160::default(), + to: None, + value: t.value, + gas_price: Some(t.gas_price), + max_fee_per_gas: Some(t.gas_price), + max_priority_fee_per_gas: Some(t.gas_price), + gas: t.gas_limit, + input: Bytes(t.clone().input), + creates: None, + raw, + public_key: None, + chain_id: t.signature.chain_id().map(U64::from), + standard_v: U256::from(t.signature.standard_v()), + v: U256::from(t.signature.v()), + r: U256::from(t.signature.r().as_bytes()), + s: U256::from(t.signature.s().as_bytes()), + access_list: None, + }, + TransactionV2::EIP1559(t) => Transaction { + hash, + nonce: t.nonce, + block_hash: None, + block_number: None, + transaction_index: None, + from: H160::default(), + to: None, + value: t.value, + gas_price: None, + max_fee_per_gas: Some(t.max_fee_per_gas), + max_priority_fee_per_gas: Some(t.max_priority_fee_per_gas), + gas: t.gas_limit, + input: Bytes(t.clone().input), + creates: None, + raw, + public_key: None, + chain_id: Some(U64::from(t.chain_id)), + standard_v: U256::from(t.odd_y_parity as u8), + v: U256::from(t.odd_y_parity as u8), + r: U256::from(t.r.as_bytes()), + s: U256::from(t.s.as_bytes()), + access_list: Some(t.access_list), + }, + _ => Transaction::default(), + } + } } /// Local Transaction Status diff --git a/src/components/contracts/primitives/rpc-core/src/types/transaction_request.rs b/src/components/contracts/primitives/rpc-core/src/types/transaction_request.rs index bbddf8d5f..dab2d6a50 100644 --- a/src/components/contracts/primitives/rpc-core/src/types/transaction_request.rs +++ b/src/components/contracts/primitives/rpc-core/src/types/transaction_request.rs @@ -22,8 +22,15 @@ use crate::types::Bytes; use ethereum_types::{H160, U256}; use serde::{Deserialize, Serialize}; +use ethereum::{AccessListItem, EIP1559TransactionMessage, LegacyTransactionMessage}; + +pub enum TransactionMessage { + Legacy(LegacyTransactionMessage), + EIP1559(EIP1559TransactionMessage), +} + /// Transaction request coming from RPC -#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] #[serde(rename_all = "camelCase")] pub struct TransactionRequest { @@ -33,6 +40,10 @@ pub struct TransactionRequest { pub to: Option, /// Gas Price pub gas_price: Option, + /// Max BaseFeePerGas the user is willing to pay. + pub max_fee_per_gas: Option, + /// The miner's tip. + pub max_priority_fee_per_gas: Option, /// Gas pub gas: Option, /// Value of transaction in wei @@ -41,4 +52,57 @@ pub struct TransactionRequest { pub data: Option, /// Transaction's nonce pub nonce: Option, + /// Pre-pay to warm storage access. + pub access_list: Option>, +} + +impl Into> for TransactionRequest { + fn into(self) -> Option { + match ( + self.gas_price, + self.max_fee_per_gas, + self.access_list.clone(), + ) { + // Legacy + (Some(_), None, None) => { + Some(TransactionMessage::Legacy(LegacyTransactionMessage { + nonce: U256::zero(), + gas_price: self.gas_price.unwrap_or_default(), + gas_limit: self.gas.unwrap_or_default(), + value: self.value.unwrap_or(U256::zero()), + input: self.data.map(|s| s.into_vec()).unwrap_or_default(), + action: match self.to { + Some(to) => ethereum::TransactionAction::Call(to), + None => ethereum::TransactionAction::Create, + }, + chain_id: None, + })) + } + // EIP1559 + (None, Some(_), _) | (None, None, None) => { + // Empty fields fall back to the canonical transaction schema. + Some(TransactionMessage::EIP1559(EIP1559TransactionMessage { + nonce: U256::zero(), + max_fee_per_gas: self.max_fee_per_gas.unwrap_or_default(), + max_priority_fee_per_gas: self + .max_priority_fee_per_gas + .unwrap_or_else(|| U256::from(0)), + gas_limit: self.gas.unwrap_or_default(), + value: self.value.unwrap_or(U256::zero()), + input: self.data.map(|s| s.into_vec()).unwrap_or_default(), + action: match self.to { + Some(to) => ethereum::TransactionAction::Call(to), + None => ethereum::TransactionAction::Create, + }, + chain_id: 0, + access_list: self + .access_list + .unwrap_or_default() + .into_iter() + .collect(), + })) + } + _ => None, + } + } } diff --git a/src/components/contracts/primitives/traits/src/account.rs b/src/components/contracts/primitives/traits/src/account.rs index 65bb14fd1..0227483d1 100644 --- a/src/components/contracts/primitives/traits/src/account.rs +++ b/src/components/contracts/primitives/traits/src/account.rs @@ -56,6 +56,8 @@ pub trait AccountAsset
{ spender: &Address, amount: U256, ) -> Result<()>; + + fn income(ctx: &Context, who: &Address, value: U256) -> Result<()>; } /// Outputs the current transaction fee. diff --git a/src/components/contracts/primitives/traits/src/base.rs b/src/components/contracts/primitives/traits/src/base.rs index b25b35e35..fd379e283 100644 --- a/src/components/contracts/primitives/traits/src/base.rs +++ b/src/components/contracts/primitives/traits/src/base.rs @@ -1,4 +1,4 @@ -use ethereum::{BlockV0 as Block, ReceiptV0 as Receipt}; +use ethereum::{BlockV2 as Block, ReceiptV0 as Receipt}; use fp_core::account::SmartAccount; use fp_evm::BlockId; use fp_types::crypto::Address; @@ -32,4 +32,7 @@ pub trait BaseProvider { index: H256, height: Option, ) -> Option; + + /// Return the base fee at the given height. + fn base_fee(&self, id: Option) -> Option; } diff --git a/src/components/contracts/primitives/traits/src/evm.rs b/src/components/contracts/primitives/traits/src/evm.rs index 7e6d000fd..4260c8991 100644 --- a/src/components/contracts/primitives/traits/src/evm.rs +++ b/src/components/contracts/primitives/traits/src/evm.rs @@ -81,4 +81,7 @@ pub trait OnChargeEVMTransaction { corrected_fee: U256, already_withdrawn: U256, ) -> Result<()>; + + /// Introduced in EIP1559 to handle the priority tip payment to the block Author. + fn pay_priority_fee(ctx: &Context, tip: U256) -> Result<()>; } diff --git a/src/components/contracts/primitives/types/src/actions/ethereum.rs b/src/components/contracts/primitives/types/src/actions/ethereum.rs index 5d2403a08..4efd37397 100644 --- a/src/components/contracts/primitives/types/src/actions/ethereum.rs +++ b/src/components/contracts/primitives/types/src/actions/ethereum.rs @@ -1,7 +1,13 @@ -use ethereum::TransactionV0 as Transaction; +use ethereum::TransactionV0 as LegacyTransaction; +use ethereum::TransactionV2 as Transaction; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum Action { Transact(Transaction), } + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum LegacyAction { + Transact(LegacyTransaction), +} diff --git a/src/components/contracts/primitives/types/src/actions/evm.rs b/src/components/contracts/primitives/types/src/actions/evm.rs index 378d06a6d..9510a461c 100644 --- a/src/components/contracts/primitives/types/src/actions/evm.rs +++ b/src/components/contracts/primitives/types/src/actions/evm.rs @@ -6,6 +6,9 @@ pub enum Action { Call(Call), Create(Create), Create2(Create2), + CallEip1559(CallEip1559), + CreateEip1559(CreateEip1559), + Create2Eip1559(Create2Eip1559), } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -19,6 +22,18 @@ pub struct Call { pub nonce: Option, } +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct CallEip1559 { + pub source: H160, + pub target: H160, + pub input: Vec, + pub value: U256, + pub gas_limit: u64, + pub max_fee_per_gas: Option, + pub max_priority_fee_per_gas: Option, + pub nonce: Option, +} + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Create { pub source: H160, @@ -29,6 +44,17 @@ pub struct Create { pub nonce: Option, } +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct CreateEip1559 { + pub source: H160, + pub init: Vec, + pub value: U256, + pub gas_limit: u64, + pub max_fee_per_gas: Option, + pub max_priority_fee_per_gas: Option, + pub nonce: Option, +} + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Create2 { pub source: H160, @@ -39,3 +65,15 @@ pub struct Create2 { pub gas_price: Option, pub nonce: Option, } + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Create2Eip1559 { + pub source: H160, + pub init: Vec, + pub salt: H256, + pub value: U256, + pub gas_limit: u64, + pub max_fee_per_gas: Option, + pub max_priority_fee_per_gas: Option, + pub nonce: Option, +} diff --git a/src/components/contracts/primitives/types/src/actions/mod.rs b/src/components/contracts/primitives/types/src/actions/mod.rs index b049ae7d2..0f006107e 100644 --- a/src/components/contracts/primitives/types/src/actions/mod.rs +++ b/src/components/contracts/primitives/types/src/actions/mod.rs @@ -12,3 +12,11 @@ pub enum Action { XHub(xhub::Action), Template(template::Action), } + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum LegacyAction { + Ethereum(ethereum::LegacyAction), + Evm(evm::Action), + XHub(xhub::Action), + Template(template::Action), +} diff --git a/src/components/contracts/primitives/types/src/assemble.rs b/src/components/contracts/primitives/types/src/assemble.rs index 41cdb5a16..a2c709a7a 100644 --- a/src/components/contracts/primitives/types/src/assemble.rs +++ b/src/components/contracts/primitives/types/src/assemble.rs @@ -1,8 +1,8 @@ use crate::actions::ethereum::Action as EtherAction; -use crate::actions::Action; +use crate::actions::{Action, LegacyAction}; use crate::crypto::{Address, Signature}; use crate::transaction; -use ethereum::TransactionV0 as Transaction; +use ethereum::TransactionV2 as Transaction; use primitive_types::U256; use ruc::*; use serde::{Deserialize, Serialize}; @@ -28,6 +28,9 @@ impl CheckFee { } } +pub type LegacyUncheckedTransaction = + transaction::UncheckedTransaction; + /// Unchecked transaction type as expected by this application. pub type UncheckedTransaction = transaction::UncheckedTransaction; diff --git a/src/components/contracts/primitives/utils/Cargo.toml b/src/components/contracts/primitives/utils/Cargo.toml index 672f13389..8ad50470c 100644 --- a/src/components/contracts/primitives/utils/Cargo.toml +++ b/src/components/contracts/primitives/utils/Cargo.toml @@ -9,6 +9,8 @@ description = "Findora Primitive utility functions" readme = "README.md" [dependencies] +ethereum = { version = "0.12.0", default-features = false, features = ["with-serde"] } +ethereum-types = { version = "0.13.1", default-features = false } futures = "0.3.16" base64 = "0.13" bip0039 = "0.8.0" diff --git a/src/components/contracts/primitives/wasm/Cargo.toml b/src/components/contracts/primitives/wasm/Cargo.toml index 7a35e137b..e4318191c 100644 --- a/src/components/contracts/primitives/wasm/Cargo.toml +++ b/src/components/contracts/primitives/wasm/Cargo.toml @@ -22,7 +22,7 @@ rlp = "0.5" ruc = "1.0" sha3 = "0.10" serde_json = "1.0" -wasm-bindgen = { version = "=0.2.73", features = ["serde-serialize"] } +wasm-bindgen = { version = "0.2.83", features = ["serde-serialize"] } # Must enable the "js"-feature, # OR the compiling will fail. diff --git a/src/components/contracts/primitives/wasm/src/wasm.rs b/src/components/contracts/primitives/wasm/src/wasm.rs index f587e244c..05b0f5bf4 100644 --- a/src/components/contracts/primitives/wasm/src/wasm.rs +++ b/src/components/contracts/primitives/wasm/src/wasm.rs @@ -1,7 +1,10 @@ #![allow(clippy::unused_unit)] use core::fmt::Display; -use ethereum::{LegacyTransactionMessage, TransactionV0 as Transaction}; +use ethereum::{ + LegacyTransactionMessage, TransactionV0 as LegacyTransaction, + TransactionV2 as Transaction, +}; use ethereum_types::{H160, H256}; use fp_types::{ actions::{ethereum::Action as EthAction, Action}, @@ -19,7 +22,7 @@ pub(crate) fn error_to_jsvalue(e: T) -> JsValue { } #[inline(always)] -pub fn recover_signer(transaction: &Transaction) -> Option { +pub fn legacy_recover_signer(transaction: &LegacyTransaction) -> Option { let mut sig = [0u8; 65]; let mut msg = [0u8; 32]; sig[0..32].copy_from_slice(&transaction.signature.r()[..]); @@ -33,6 +36,37 @@ pub fn recover_signer(transaction: &Transaction) -> Option { ))) } +#[inline(always)] +pub fn recover_signer(transaction: &Transaction) -> Option { + let mut sig = [0u8; 65]; + let mut msg = [0u8; 32]; + match transaction { + Transaction::Legacy(t) => { + sig[0..32].copy_from_slice(&t.signature.r()[..]); + sig[32..64].copy_from_slice(&t.signature.s()[..]); + sig[64] = t.signature.standard_v(); + msg.copy_from_slice( + ðereum::LegacyTransactionMessage::from(t.clone()).hash()[..], + ); + } + Transaction::EIP1559(t) => { + sig[0..32].copy_from_slice(&t.r[..]); + sig[32..64].copy_from_slice(&t.s[..]); + sig[64] = t.odd_y_parity as u8; + msg.copy_from_slice( + ðereum::EIP1559TransactionMessage::from(t.clone()).hash()[..], + ); + } + _ => { + return None; + } + } + let pubkey = fp_types::crypto::secp256k1_ecdsa_recover(&sig, &msg).ok()?; + Some(H160::from(H256::from_slice( + Keccak256::digest(&pubkey).as_slice(), + ))) +} + #[wasm_bindgen] pub fn recover_tx_signer(raw_tx: String) -> Result { let tx_bytes = base64::decode_config(raw_tx, base64::URL_SAFE) @@ -77,17 +111,25 @@ pub fn evm_tx_hash(raw_tx: String) -> Result { #[allow(missing_docs)] mod test { use super::*; - use fp_types::actions::Action; + use fp_types::{ + actions::{ + ethereum::Action as EthAction, ethereum::LegacyAction as LegacyEthAction, + Action, LegacyAction, + }, + assemble::LegacyUncheckedTransaction, + }; #[test] fn recover_signer_works() { let raw_tx = String::from("ZXZtOnsic2lnbmF0dXJlIjpudWxsLCJmdW5jdGlvbiI6eyJFdGhlcmV1bSI6eyJUcmFuc2FjdCI6eyJub25jZSI6IjB4MSIsImdhc19wcmljZSI6IjB4MTc0ODc2ZTgwMCIsImdhc19saW1pdCI6IjB4NTIwOCIsImFjdGlvbiI6eyJDYWxsIjoiMHgyYWQzMjg0NmM2ZGQyZmZkM2VkYWRiZTUxY2Q1YWUwNGFhNWU1NzVlIn0sInZhbHVlIjoiMHg1NmJjNzVlMmQ2MzEwMDAwMCIsImlucHV0IjpbXSwic2lnbmF0dXJlIjp7InYiOjEwODIsInIiOiIweGY4YWVmN2Y4MDUzZDg5ZmVlMzk1MGM0ZDcwMjA4MGJmM2E4MDcyYmVkNWQ4NGEzYWYxOWEzNjAwODFiNjM2YTIiLCJzIjoiMHgyOTYyOTlhOGYyNDMwYjg2ZmQzZWI5NzZlYWJjNzMwYWMxY2ZiYmJlMzZlYjY5ZWFlMzM4Y2ZmMzNjNGE5OGMxIn19fX19"); let tx_bytes = base64::decode_config(raw_tx, base64::URL_SAFE).unwrap(); let evm_tx = EvmRawTxWrapper::unwrap(&tx_bytes).unwrap(); - let unchecked_tx: UncheckedTransaction<()> = + let unchecked_tx: LegacyUncheckedTransaction<()> = serde_json::from_slice(evm_tx).unwrap(); - if let Action::Ethereum(EthAction::Transact(tx)) = unchecked_tx.function { - let signer = recover_signer(&tx).unwrap(); + if let LegacyAction::Ethereum(LegacyEthAction::Transact(tx)) = + unchecked_tx.function + { + let signer = legacy_recover_signer(&tx).unwrap(); assert_eq!( format!("{:?}", signer), "0xa5225cbee5052100ec2d2d94aa6d258558073757" @@ -95,15 +137,31 @@ mod test { } else { panic!() } + + let raw_tx = String::from("ZXZtOnsiRXRoZXJldW0iOnsiVHJhbnNhY3QiOnsiRUlQMTU1OSI6eyJjaGFpbl9pZCI6MjE1Miwibm9uY2UiOiIweDEiLCJtYXhfcHJpb3JpdHlfZmVlX3Blcl9nYXMiOiIweDI3MTAiLCJtYXhfZmVlX3Blcl9nYXMiOiIweDI3MTAiLCJnYXNfbGltaXQiOiIweDI3MTAiLCJhY3Rpb24iOnsiQ2FsbCI6IjB4MmFkMzI4NDZjNmRkMmZmZDNlZGFkYmU1MWNkNWFlMDRhYTVlNTc1ZSJ9LCJ2YWx1ZSI6IjB4MjcxMCIsImlucHV0IjpbXSwiYWNjZXNzX2xpc3QiOltdLCJvZGRfeV9wYXJpdHkiOmZhbHNlLCJyIjoiMHhmOGFlZjdmODA1M2Q4OWZlZTM5NTBjNGQ3MDIwODBiZjNhODA3MmJlZDVkODRhM2FmMTlhMzYwMDgxYjYzNmEyIiwicyI6IjB4Mjk2Mjk5YThmMjQzMGI4NmZkM2ViOTc2ZWFiYzczMGFjMWNmYmJiZTM2ZWI2OWVhZTMzOGNmZjMzYzRhOThjMSJ9fX19"); + let tx_bytes = base64::decode_config(&raw_tx, base64::URL_SAFE).unwrap(); + let evm_tx = EvmRawTxWrapper::unwrap(&tx_bytes).unwrap(); + let action: Action = serde_json::from_slice(evm_tx).unwrap(); + if let Action::Ethereum(EthAction::Transact(tx)) = action { + let signer = recover_signer(&tx).unwrap(); + assert_eq!( + format!("{:?}", signer), + "0x7bc371bb41545d62117591e5051be3d2c6296f3e" + ); + } else { + panic!() + } } #[test] fn evm_tx_hash_works() { let raw_tx = String::from("eyJzaWduYXR1cmUiOm51bGwsImZ1bmN0aW9uIjp7IkV0aGVyZXVtIjp7IlRyYW5zYWN0Ijp7Im5vbmNlIjoiMHg5IiwiZ2FzX3ByaWNlIjoiMHhlOGQ0YTUxMDAwIiwiZ2FzX2xpbWl0IjoiMHg1MjA4IiwiYWN0aW9uIjp7IkNhbGwiOiIweGE1MjI1Y2JlZTUwNTIxMDBlYzJkMmQ5NGFhNmQyNTg1NTgwNzM3NTcifSwidmFsdWUiOiIweDk4YTdkOWI4MzE0YzAwMDAiLCJpbnB1dCI6W10sInNpZ25hdHVyZSI6eyJ2IjoxMDgyLCJyIjoiMHg4MDBjZjQ5ZTAzMmJhYzY4MjY3MzdhZGJhZDEzN2Y0MTk5OTRjNjgxZWE1ZDUyYjliMGJhZDJmNDAyYjMwMTI0IiwicyI6IjB4Mjk1Mjc3ZWY2NTYzNDAwY2VkNjFiODhkM2ZiNGM3YjMyY2NkNTcwYThiOWJiOGNiYmUyNTkyMTRhYjdkZTI1YSJ9fX19fQ=="); let tx_bytes = base64::decode_config(raw_tx, base64::URL_SAFE).unwrap(); - let unchecked_tx: UncheckedTransaction<()> = + let unchecked_tx: LegacyUncheckedTransaction<()> = serde_json::from_slice(tx_bytes.as_slice()).unwrap(); - if let Action::Ethereum(EthAction::Transact(tx)) = unchecked_tx.function { + if let LegacyAction::Ethereum(LegacyEthAction::Transact(tx)) = + unchecked_tx.function + { let hash = H256::from_slice(Keccak256::digest(&rlp::encode(&tx)).as_slice()); assert_eq!( format!("{:?}", hash), diff --git a/src/components/contracts/rpc/src/eth.rs b/src/components/contracts/rpc/src/eth.rs index 5f572cb1b..88eec89c9 100644 --- a/src/components/contracts/rpc/src/eth.rs +++ b/src/components/contracts/rpc/src/eth.rs @@ -7,16 +7,17 @@ use crate::{error_on_execution_failure, internal_err}; use baseapp::{extensions::SignedExtra, BaseApp}; use config::abci::global_cfg::CFG; use ethereum::{ - BlockV0 as EthereumBlock, LegacyTransactionMessage as EthereumTransactionMessage, - TransactionV0 as EthereumTransaction, + BlockV2 as EthereumBlock, + // LegacyTransactionMessage as EthereumTransactionMessage, + TransactionV2 as EthereumTransaction, }; use ethereum_types::{BigEndianHash, Bloom, H160, H256, H512, H64, U256, U64}; use evm::{ExitError, ExitReason}; use fp_evm::{BlockId, Runner, TransactionStatus}; use fp_rpc_core::types::{ Block, BlockNumber, BlockTransactions, Bytes, CallRequest, Filter, FilteredParams, - Index, Log, Receipt, Rich, RichBlock, SyncStatus, Transaction, TransactionRequest, - Work, + Index, Log, Receipt, Rich, RichBlock, SyncStatus, Transaction, TransactionMessage, + TransactionRequest, Work, }; use fp_rpc_core::EthApi; use fp_traits::{ @@ -25,7 +26,7 @@ use fp_traits::{ }; use fp_types::{ actions, - actions::evm::{Call, Create}, + actions::evm::{Call, CallEip1559, Create, CreateEip1559}, assemble::UncheckedTransaction, }; use fp_utils::ecdsa::SecpPair; @@ -256,30 +257,63 @@ impl EthApi for EthApiImpl { }; let chain_id = match self.chain_id() { - Ok(chain_id) => chain_id, + Ok(Some(chain_id)) => chain_id.as_u64(), + Ok(None) => { + return Box::pin(future::err(internal_err("chain id not available"))) + } Err(e) => return Box::pin(future::err(e)), }; + // let hash = self.client.info().best_hash; let curr_height = match self.block_number() { Ok(h) => h.as_u64(), Err(e) => return Box::pin(future::err(e)), }; - let message = EthereumTransactionMessage { - nonce, - gas_price: request.gas_price.unwrap_or_else(|| { - ::FeeCalculator::min_gas_price( - curr_height, - ) - }), - gas_limit: request.gas.unwrap_or_else(U256::max_value), - value: request.value.unwrap_or_else(U256::zero), - input: request.data.map(|s| s.into_vec()).unwrap_or_default(), - action: match request.to { - Some(to) => ethereum::TransactionAction::Call(to), - None => ethereum::TransactionAction::Create, - }, - chain_id: chain_id.map(|s| s.as_u64()), + let gas_price = request.gas_price.unwrap_or_else(|| { + ::FeeCalculator::min_gas_price(curr_height) + }); + let block = self.account_base_app.read().current_block(None); + let gas_limit = match request.gas { + Some(gas_limit) => gas_limit, + None => { + if let Some(block) = block { + block.header.gas_limit + } else { + ::BlockGasLimit::get() + } + } + }; + + let max_fee_per_gas = request.max_fee_per_gas; + + let message: Option = request.into(); + let message = match message { + Some(TransactionMessage::Legacy(mut m)) => { + m.nonce = nonce; + m.chain_id = Some(chain_id); + m.gas_limit = gas_limit; + m.gas_price = gas_price; + TransactionMessage::Legacy(m) + } + Some(TransactionMessage::EIP1559(mut m)) => { + if CFG.checkpoint.enable_eip1559_height > curr_height { + return Box::pin(future::err(internal_err(format!( + "eip1559 not enabled at height: {:?}", + curr_height + )))); + } + m.nonce = nonce; + m.chain_id = chain_id; + m.gas_limit = gas_limit; + if max_fee_per_gas.is_none() { + m.max_fee_per_gas = self.gas_price().unwrap_or_default(); + } + TransactionMessage::EIP1559(m) + } + _ => { + return Box::pin(future::err(internal_err("Transaction Type Error"))); + } }; let mut transaction = None; @@ -301,8 +335,8 @@ impl EthApi for EthApiImpl { return Box::pin(future::err(internal_err("no signer available"))); } }; - let transaction_hash = - H256::from_slice(Keccak256::digest(&rlp::encode(&transaction)).as_slice()); + let transaction_hash = transaction.hash(); + let function = actions::Action::Ethereum(actions::ethereum::Action::Transact(transaction)); let txn = serde_json::to_vec( @@ -352,12 +386,29 @@ impl EthApi for EthApiImpl { from, to, gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, gas, value, data, nonce, } = request; + let is_eip1559 = max_fee_per_gas.is_some() + && max_priority_fee_per_gas.is_some() + && max_fee_per_gas.unwrap() > U256::from(0) + && max_priority_fee_per_gas.unwrap() > U256::from(0); + + let (gas_price, max_fee_per_gas, max_priority_fee_per_gas) = { + let details = + fee_details(gas_price, max_fee_per_gas, max_priority_fee_per_gas)?; + ( + details.gas_price, + details.max_fee_per_gas, + details.max_priority_fee_per_gas, + ) + }; + let block = account_base_app.read().current_block(None); // use given gas limit or query current block's limit let gas_limit = match gas { @@ -390,51 +441,105 @@ impl EthApi for EthApiImpl { Vec::from(block.header.beneficiary.as_bytes()) } - match to { - Some(to) => { - let call = Call { - source: from.unwrap_or_default(), - target: to, - input: data, - value: value.unwrap_or_default(), - gas_limit: gas_limit.as_u64(), - gas_price, - nonce, - }; + if !is_eip1559 { + match to { + Some(to) => { + let call = Call { + source: from.unwrap_or_default(), + target: to, + input: data, + value: value.unwrap_or_default(), + gas_limit: gas_limit.as_u64(), + gas_price, + nonce, + }; - let info = ::Runner::call( - &ctx, call, &config, - ) - .map_err(|err| { - internal_err(format!("evm runner call error: {:?}", err)) - })?; - debug!(target: "eth_rpc", "evm runner call result: {:?}", info); + let info = ::Runner::call( + &ctx, call, &config, + ) + .map_err(|err| { + internal_err(format!("evm runner call error: {:?}", err)) + })?; + debug!(target: "eth_rpc", "evm runner call result: {:?}", info); + + error_on_execution_failure(&info.exit_reason, &info.value)?; + + Ok(Bytes(info.value)) + } + None => { + let create = Create { + source: from.unwrap_or_default(), + init: data, + value: value.unwrap_or_default(), + gas_limit: gas_limit.as_u64(), + gas_price, + nonce, + }; + + let info = ::Runner::create( + &ctx, create, &config, + ) + .map_err(|err| { + internal_err(format!("evm runner create error: {:?}", err)) + })?; + debug!(target: "eth_rpc", "evm runner create result: {:?}", info); - error_on_execution_failure(&info.exit_reason, &info.value)?; + error_on_execution_failure(&info.exit_reason, &[])?; - Ok(Bytes(info.value)) + Ok(Bytes(info.value[..].to_vec())) + } } - None => { - let create = Create { - source: from.unwrap_or_default(), - init: data, - value: value.unwrap_or_default(), - gas_limit: gas_limit.as_u64(), - gas_price, - nonce, - }; + } else { + match to { + Some(to) => { + let call = CallEip1559 { + source: from.unwrap_or_default(), + target: to, + input: data, + value: value.unwrap_or_default(), + gas_limit: gas_limit.as_u64(), + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + }; - let info = ::Runner::create( - &ctx, create, &config, - ) - .map_err(|err| { - internal_err(format!("evm runner create error: {:?}", err)) - })?; - debug!(target: "eth_rpc", "evm runner create result: {:?}", info); + let info = + ::Runner::call_eip1559( + &ctx, call, &config, + ) + .map_err(|err| { + internal_err(format!("evm runner call error: {:?}", err)) + })?; + debug!(target: "eth_rpc", "evm runner call result: {:?}", info); + + error_on_execution_failure(&info.exit_reason, &info.value)?; + + Ok(Bytes(info.value)) + } + None => { + let create = CreateEip1559 { + source: from.unwrap_or_default(), + init: data, + value: value.unwrap_or_default(), + gas_limit: gas_limit.as_u64(), + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + }; + + let info = + ::Runner::create_eip1559( + &ctx, create, &config, + ) + .map_err(|err| { + internal_err(format!("evm runner create error: {:?}", err)) + })?; + debug!(target: "eth_rpc", "evm runner create result: {:?}", info); - error_on_execution_failure(&info.exit_reason, &[])?; + error_on_execution_failure(&info.exit_reason, &[])?; - Ok(Bytes(info.value[..].to_vec())) + Ok(Bytes(info.value[..].to_vec())) + } } } }); @@ -538,12 +643,15 @@ impl EthApi for EthApiImpl { .read() .current_transaction_statuses(Some(BlockId::Hash(hash))); + let base_fee = account_base_app.read().base_fee(Some(BlockId::Hash(hash))); + match (block, statuses) { (Some(block), Some(statuses)) => Ok(Some(rich_block_build( block, statuses.into_iter().map(Some).collect(), Some(hash), full, + base_fee, ))), _ => Ok(None), } @@ -580,7 +688,11 @@ impl EthApi for EthApiImpl { let id = native_block_id(Some(number)); let block = account_base_app.read().current_block(id.clone()); - let statuses = account_base_app.read().current_transaction_statuses(id); + let statuses = account_base_app + .read() + .current_transaction_statuses(id.clone()); + + let base_fee = account_base_app.read().base_fee(id); match (block, statuses) { (Some(block), Some(statuses)) => { @@ -591,9 +703,18 @@ impl EthApi for EthApiImpl { statuses.into_iter().map(Some).collect(), Some(hash), full, + base_fee, ))) } - _ => Ok(None), + _ => Ok(if let Some(h) = height { + if 0 < h && h < *EVM_FIRST_BLOCK_HEIGHT { + Some(dummy_block(h, full)) + } else { + None + } + } else { + None + }), } }); @@ -717,16 +838,54 @@ impl EthApi for EthApiImpl { } fn send_raw_transaction(&self, bytes: Bytes) -> BoxFuture> { - let transaction = match rlp::decode::(&bytes.0[..]) { - Ok(transaction) => transaction, - Err(_) => { - return Box::pin(future::err(internal_err("decode transaction failed"))); + let slice = &bytes.0[..]; + if slice.is_empty() { + return Box::pin(future::err(internal_err("transaction data is empty"))); + } + // let first = slice.get(0).unwrap(); + let first = slice.first().unwrap(); + let transaction = if first > &0x7f { + // Legacy transaction. Decode and wrap in envelope. + match rlp::decode::(slice) { + Ok(transaction) => ethereum::TransactionV2::Legacy(transaction), + Err(_) => { + return Box::pin(future::err(internal_err( + "decode transaction failed", + ))); + } + } + } else { + let curr_height = match self.block_number() { + Ok(h) => h.as_u64(), + Err(e) => return Box::pin(future::err(e)), + }; + + if CFG.checkpoint.enable_eip1559_height > curr_height { + return Box::pin(future::err(internal_err(format!( + "eip1559 not enabled at height: {:?}", + curr_height + )))); + } + // Typed Transaction. + // `ethereum` crate decode implementation for `TransactionV2` expects a valid rlp input, + // and EIP-1559 breaks that assumption by prepending a version byte. + // We re-encode the payload input to get a valid rlp, and the decode implementation will strip + // them to check the transaction version byte. + let extend = rlp::encode(&slice); + match rlp::decode::(&extend[..]) { + Ok(transaction) => transaction, + Err(_) => { + return Box::pin(future::err(internal_err( + "decode transaction failed", + ))) + } } }; + debug!(target: "eth_rpc", "send_raw_transaction :{:?}", transaction); - let transaction_hash = - H256::from_slice(Keccak256::digest(&rlp::encode(&transaction)).as_slice()); + let transaction_hash = transaction.hash(); + let function = actions::Action::Ethereum(actions::ethereum::Action::Transact(transaction)); let txn = serde_json::to_vec( @@ -764,6 +923,29 @@ impl EthApi for EthApiImpl { ) -> BoxFuture> { debug!(target: "eth_rpc", "estimate_gas, block number {:?} request:{:?}", number, request); + let is_eip1559 = request.max_fee_per_gas.is_some() + && request.max_priority_fee_per_gas.is_some() + && request.max_fee_per_gas.unwrap() > U256::from(0) + && request.max_priority_fee_per_gas.unwrap() > U256::from(0); + + let (gas_price, max_fee_per_gas, max_priority_fee_per_gas) = { + if let Ok(details) = fee_details( + request.gas_price, + request.max_fee_per_gas, + request.max_priority_fee_per_gas, + ) { + ( + details.gas_price, + details.max_fee_per_gas, + details.max_priority_fee_per_gas, + ) + } else { + return Box::pin(async move { + Err(internal_err("estimate_gas fee_details err!!!".to_string())) + }); + } + }; + let account_base_app = self.account_base_app.clone(); let task = spawn_blocking(move || { @@ -860,7 +1042,9 @@ impl EthApi for EthApiImpl { let CallRequest { from, to, - gas_price, + gas_price: _, + max_fee_per_gas: _, + max_priority_fee_per_gas: _, gas, value, data, @@ -875,55 +1059,123 @@ impl EthApi for EthApiImpl { let mut config = ::config().clone(); config.estimate = true; - match to { - Some(to) => { - let call = Call { - source: from.unwrap_or_default(), - target: to, - input: data.map(|d| d.0).unwrap_or_default(), - value: value.unwrap_or_default(), - gas_limit, - gas_price, - nonce, - }; + if !is_eip1559 { + match to { + Some(to) => { + let call = Call { + source: from.unwrap_or_default(), + target: to, + input: data.map(|d| d.0).unwrap_or_default(), + value: value.unwrap_or_default(), + gas_limit, + gas_price, + nonce, + }; + + let info = + ::Runner::call( + &ctx, call, &config, + ) + .map_err(|err| { + internal_err(format!( + "evm runner call error: {:?}", + err + )) + })?; + debug!(target: "eth_rpc", "evm runner call result: {:?}", info); - let info = ::Runner::call( - &ctx, call, &config, - ) - .map_err(|err| { - internal_err(format!("evm runner call error: {:?}", err)) - })?; - debug!(target: "eth_rpc", "evm runner call result: {:?}", info); + Ok(ExecuteResult { + data: info.value, + exit_reason: info.exit_reason, + used_gas: info.used_gas, + }) + } + None => { + let create = Create { + source: from.unwrap_or_default(), + init: data.map(|d| d.0).unwrap_or_default(), + value: value.unwrap_or_default(), + gas_limit, + gas_price, + nonce, + }; + + let info = + ::Runner::create( + &ctx, create, &config, + ) + .map_err(|err| { + internal_err(format!( + "evm runner create error: {:?}", + err + )) + })?; + debug!(target: "eth_rpc", "evm runner create result: {:?}", info); - Ok(ExecuteResult { - data: info.value, - exit_reason: info.exit_reason, - used_gas: info.used_gas, - }) + Ok(ExecuteResult { + data: vec![], + exit_reason: info.exit_reason, + used_gas: info.used_gas, + }) + } } - None => { - let create = Create { - source: from.unwrap_or_default(), - init: data.map(|d| d.0).unwrap_or_default(), - value: value.unwrap_or_default(), - gas_limit, - gas_price, - nonce, - }; - - let info = ::Runner::create( - &ctx, create, &config, - ) - .map_err(|err| { - internal_err(format!("evm runner create error: {:?}", err)) - })?; - debug!(target: "eth_rpc", "evm runner create result: {:?}", info); - - Ok(ExecuteResult { - data: vec![], - exit_reason: info.exit_reason, - used_gas: info.used_gas, - }) + } else { + match to { + Some(to) => { + let call = CallEip1559 { + source: from.unwrap_or_default(), + target: to, + input: data.map(|d| d.0).unwrap_or_default(), + value: value.unwrap_or_default(), + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + }; + + let info = + ::Runner::call_eip1559( + &ctx, call, &config, + ) + .map_err(|err| { + internal_err(format!("evm runner call error: {:?}", err)) + })?; + debug!(target: "eth_rpc", "evm runner call result: {:?}", info); + + Ok(ExecuteResult { + data: info.value, + exit_reason: info.exit_reason, + used_gas: info.used_gas, + }) + } + None => { + let create = CreateEip1559 { + source: from.unwrap_or_default(), + init: data.map(|d| d.0).unwrap_or_default(), + value: value.unwrap_or_default(), + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + }; + + let info = ::Runner::create_eip1559( + &ctx, create, &config, + ) + .map_err(|err| { + internal_err(format!( + "evm runner create error: {:?}", + err + )) + })?; + debug!(target: "eth_rpc", "evm runner create result: {:?}", info); + + Ok(ExecuteResult { + data: vec![], + exit_reason: info.exit_reason, + used_gas: info.used_gas, + }) + } } } }; @@ -1005,10 +1257,23 @@ impl EthApi for EthApiImpl { } } + let is_eip1559 = !matches!( + &block.transactions[index], + EthereumTransaction::Legacy(_) + ); + // match &block.transactions[index] { + // EthereumTransaction::Legacy(_) => false, + // _ => true, + // }; + + let base_fee = account_base_app.read().base_fee(id); + Ok(Some(transaction_build( block.transactions[index].clone(), Some(block), Some(statuses[index].clone()), + is_eip1559, + base_fee, ))) } _ => Ok(None), @@ -1040,16 +1305,30 @@ impl EthApi for EthApiImpl { .read() .current_transaction_statuses(Some(BlockId::Hash(hash))); + // match &block.transactions[index] { + // EthereumTransaction::Legacy(_) => false, + // _ => true, + // }; + + let base_fee = account_base_app.read().base_fee(Some(BlockId::Hash(hash))); + match (block, statuses) { (Some(block), Some(statuses)) => { if index >= block.transactions.len() { return Ok(None); } + let is_eip1559 = !matches!( + &block.transactions[index], + EthereumTransaction::Legacy(_) + ); + Ok(Some(transaction_build( block.transactions[index].clone(), Some(block), Some(statuses[index].clone()), + is_eip1559, + base_fee, ))) } _ => Ok(None), @@ -1076,7 +1355,9 @@ impl EthApi for EthApiImpl { let id = native_block_id(Some(number)); let index = index.value(); let block = account_base_app.read().current_block(id.clone()); - let statuses = account_base_app.read().current_transaction_statuses(id); + let statuses = account_base_app + .read() + .current_transaction_statuses(id.clone()); match (block, statuses) { (Some(block), Some(statuses)) => { @@ -1084,10 +1365,23 @@ impl EthApi for EthApiImpl { return Ok(None); } + let is_eip1559 = !matches!( + &block.transactions[index], + EthereumTransaction::Legacy(_) + ); + // match &block.transactions[index] { + // EthereumTransaction::Legacy(_) => true, + // _ => false, + // }; + + let base_fee = account_base_app.read().base_fee(id); + Ok(Some(transaction_build( block.transactions[index].clone(), Some(block), Some(statuses[index].clone()), + is_eip1559, + base_fee, ))) } _ => Ok(None), @@ -1121,8 +1415,10 @@ impl EthApi for EthApiImpl { .current_transaction_statuses(id.clone()); let receipts = account_base_app.read().current_receipts(id.clone()); - match (block, statuses, receipts) { - (Some(block), Some(statuses), Some(receipts)) => { + let base_fee = account_base_app.read().base_fee(id.clone()); + + match (block, statuses, receipts, base_fee) { + (Some(block), Some(statuses), Some(receipts), Some(base_fee)) => { if id.is_none() { if let Some(idx) = statuses.iter().position(|t| t.transaction_hash == hash) @@ -1143,6 +1439,15 @@ impl EthApi for EthApiImpl { cumulative_receipts .truncate((status.transaction_index + 1) as usize); + let transaction = block.transactions[index].clone(); + let effective_gas_price = match transaction { + EthereumTransaction::Legacy(t) => t.gas_price, + EthereumTransaction::EIP1559(t) => base_fee + .checked_add(t.max_priority_fee_per_gas) + .unwrap_or_else(U256::max_value), + EthereumTransaction::EIP2930(t) => t.gas_price, + }; + return Ok(Some(Receipt { transaction_hash: Some(status.transaction_hash), transaction_index: Some(status.transaction_index.into()), @@ -1196,6 +1501,7 @@ impl EthApi for EthApiImpl { status_code: Some(U64::from(receipt.state_root.to_low_u64_be())), logs_bloom: receipt.logs_bloom, state_root: None, + effective_gas_price, })); } _ => Ok(None), @@ -1307,33 +1613,62 @@ impl EthApi for EthApiImpl { } pub fn sign_transaction_message( - message: EthereumTransactionMessage, + message: TransactionMessage, private_key: &H256, ) -> ruc::Result { - let signing_message = libsecp256k1::Message::parse_slice(&message.hash()[..]) - .map_err(|_| eg!("invalid signing message"))?; - let secret = &libsecp256k1::SecretKey::parse_slice(&private_key[..]) - .map_err(|_| eg!("invalid secret"))?; - let (signature, recid) = libsecp256k1::sign(&signing_message, secret); - - let v = match message.chain_id { - None => 27 + recid.serialize() as u64, - Some(chain_id) => 2 * chain_id + 35 + recid.serialize() as u64, - }; - let rs = signature.serialize(); - let r = H256::from_slice(&rs[0..32]); - let s = H256::from_slice(&rs[32..64]); - - Ok(EthereumTransaction { - nonce: message.nonce, - gas_price: message.gas_price, - gas_limit: message.gas_limit, - action: message.action, - value: message.value, - input: message.input, - signature: ethereum::TransactionSignature::new(v, r, s) - .ok_or(eg!("signer generated invalid signature"))?, - }) + match message { + TransactionMessage::Legacy(m) => { + let signing_message = libsecp256k1::Message::parse_slice(&m.hash()[..]) + .map_err(|_| eg!("invalid signing message"))?; + let secret = &libsecp256k1::SecretKey::parse_slice(&private_key[..]) + .map_err(|_| eg!("invalid secret"))?; + let (signature, recid) = libsecp256k1::sign(&signing_message, secret); + + let v = match m.chain_id { + None => 27 + recid.serialize() as u64, + Some(chain_id) => 2 * chain_id + 35 + recid.serialize() as u64, + }; + let rs = signature.serialize(); + let r = H256::from_slice(&rs[0..32]); + let s = H256::from_slice(&rs[32..64]); + + Ok(EthereumTransaction::Legacy(ethereum::LegacyTransaction { + nonce: m.nonce, + gas_price: m.gas_price, + gas_limit: m.gas_limit, + action: m.action, + value: m.value, + input: m.input, + signature: ethereum::TransactionSignature::new(v, r, s) + .ok_or(eg!("signer generated invalid signature"))?, + })) + } + TransactionMessage::EIP1559(m) => { + let signing_message = libsecp256k1::Message::parse_slice(&m.hash()[..]) + .map_err(|_| eg!("invalid signing message"))?; + let secret = &libsecp256k1::SecretKey::parse_slice(&private_key[..]) + .map_err(|_| eg!("invalid secret"))?; + let (signature, recid) = libsecp256k1::sign(&signing_message, secret); + let rs = signature.serialize(); + let r = H256::from_slice(&rs[0..32]); + let s = H256::from_slice(&rs[32..64]); + + Ok(EthereumTransaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: m.chain_id, + nonce: m.nonce, + max_priority_fee_per_gas: m.max_priority_fee_per_gas, + max_fee_per_gas: m.max_fee_per_gas, + gas_limit: m.gas_limit, + action: m.action, + value: m.value, + input: m.input.clone(), + access_list: m.access_list, + odd_y_parity: recid.serialize() != 0, + r, + s, + })) + } + } } fn rich_block_build( @@ -1341,6 +1676,7 @@ fn rich_block_build( statuses: Vec>, hash: Option, full_transactions: bool, + base_fee: Option, ) -> RichBlock { Rich { inner: Block { @@ -1377,10 +1713,21 @@ fn rich_block_build( .iter() .enumerate() .map(|(index, transaction)| { + let is_eip1559 = !matches!( + &block.transactions[index], + EthereumTransaction::Legacy(_) + ); + // match &transaction { + // EthereumTransaction::Legacy(_) => true, + // _ => false, + // }; + transaction_build( transaction.clone(), Some(block.clone()), Some(statuses[index].clone().unwrap_or_default()), + is_eip1559, + base_fee, ) }) .collect(), @@ -1403,82 +1750,166 @@ fn rich_block_build( } }, size: Some(U256::from(rlp::encode(&block).len() as u32)), + base_fee_per_gas: base_fee, }, extra_info: BTreeMap::new(), } } fn transaction_build( - transaction: EthereumTransaction, - block: Option, + ethereum_transaction: EthereumTransaction, + block: Option>, status: Option, + is_eip1559: bool, + base_fee: Option, ) -> Transaction { - let pubkey = match public_key(&transaction) { + let mut transaction: Transaction = ethereum_transaction.clone().into(); + + if let EthereumTransaction::EIP1559(_) = ethereum_transaction { + if block.is_none() && status.is_none() { + transaction.gas_price = transaction.max_fee_per_gas; + } else { + let base_fee = base_fee.unwrap_or(U256::zero()); + let max_priority_fee_per_gas = + transaction.max_priority_fee_per_gas.unwrap_or(U256::zero()); + transaction.gas_price = Some( + base_fee + .checked_add(max_priority_fee_per_gas) + .unwrap_or_else(U256::max_value), + ); + } + } else if !is_eip1559 { + transaction.max_fee_per_gas = None; + transaction.max_priority_fee_per_gas = None; + } + + let pubkey = match public_key(ðereum_transaction) { Ok(p) => Some(p), Err(_e) => None, }; - let hash = if let Some(status) = &status { + // Block hash. + // transaction.block_hash = block.as_ref().map_or(None, |block| { + // Some(H256::from_slice( + // Keccak256::digest(&rlp::encode(&block.header)).as_slice(), + // )) + // }); + transaction.block_hash = block.as_ref().map(|block| { + H256::from_slice(Keccak256::digest(&rlp::encode(&block.header)).as_slice()) + }); + + // Block number. + transaction.block_number = block.as_ref().map(|block| block.header.number); + // Transaction index. + + transaction.transaction_index = status + .as_ref() + .map(|status| U256::from(status.transaction_index)); + + transaction.from = status.as_ref().map_or( + { + match pubkey { + Some(pk) => { + H160::from(H256::from_slice(Keccak256::digest(pk).as_slice())) + } + _ => H160::default(), + } + }, + |status| status.from, + ); + // To. + transaction.to = status.as_ref().map_or( + { + let action = match ethereum_transaction.clone() { + EthereumTransaction::Legacy(t) => t.action, + EthereumTransaction::EIP1559(t) => t.action, + EthereumTransaction::EIP2930(t) => t.action, + }; + match action { + ethereum::TransactionAction::Call(to) => Some(to), + _ => None, + } + }, + |status| status.to, + ); + // Creates. + // transaction.creates = status + // .as_ref() + // .map_or(None, |status| status.contract_address); + transaction.creates = status.as_ref().and_then(|status| status.contract_address); + + // Public key. + transaction.public_key = pubkey.as_ref().map(H512::from); + + transaction.hash = if let Some(status) = &status { status.transaction_hash } else { - H256::from_slice(Keccak256::digest(&rlp::encode(&transaction)).as_slice()) + match ethereum_transaction { + EthereumTransaction::Legacy(t) => { + H256::from_slice(Keccak256::digest(&rlp::encode(&t)).as_slice()) + } + EthereumTransaction::EIP1559(t) => { + H256::from_slice(Keccak256::digest(&rlp::encode(&t)).as_slice()) + } + EthereumTransaction::EIP2930(t) => { + H256::from_slice(Keccak256::digest(&rlp::encode(&t)).as_slice()) + } + } }; - Transaction { - hash, - nonce: transaction.nonce, - block_hash: block.as_ref().map(|block| { - H256::from_slice(Keccak256::digest(&rlp::encode(&block.header)).as_slice()) - }), - block_number: block.as_ref().map(|block| block.header.number), - transaction_index: status - .as_ref() - .map(|status| U256::from(status.transaction_index)), - from: status.as_ref().map_or( - { - match pubkey { - Some(pk) => { - H160::from(H256::from_slice(Keccak256::digest(pk).as_slice())) - } - _ => H160::default(), - } - }, - |status| status.from, - ), - to: status.as_ref().map_or( - { - match transaction.action { - ethereum::TransactionAction::Call(to) => Some(to), - _ => None, - } - }, - |status| status.to, - ), - value: transaction.value, - gas_price: transaction.gas_price, - gas: transaction.gas_limit, - input: Bytes(transaction.clone().input), - creates: status.as_ref().and_then(|status| status.contract_address), - raw: Bytes(rlp::encode(&transaction).to_vec()), - public_key: pubkey.as_ref().map(H512::from), - chain_id: transaction.signature.chain_id().map(U64::from), - standard_v: U256::from(transaction.signature.standard_v()), - v: U256::from(transaction.signature.v()), - r: U256::from(transaction.signature.r().as_bytes()), - s: U256::from(transaction.signature.s().as_bytes()), - } + // // Block hash. + // transaction.block_hash = ; + // // Block number. + // transaction.block_number = ; + // // Transaction index. + // transaction.transaction_index = ; + // // From. + // transaction.from; + // // To. + // transaction.to; + // // Creates. + // transaction.creates; + // // Public key. + // transaction.public_key; + + transaction } pub fn public_key(transaction: &EthereumTransaction) -> ruc::Result<[u8; 64]> { + // let mut sig = [0u8; 65]; + // let mut msg = [0u8; 32]; + // sig[0..32].copy_from_slice(&transaction.signature.r()[..]); + // sig[32..64].copy_from_slice(&transaction.signature.s()[..]); + // sig[64] = transaction.signature.standard_v(); + // msg.copy_from_slice( + // &EthereumTransactionMessage::from(transaction.clone()).hash()[..], + // ); + + // fp_types::crypto::secp256k1_ecdsa_recover(&sig, &msg) + let mut sig = [0u8; 65]; let mut msg = [0u8; 32]; - sig[0..32].copy_from_slice(&transaction.signature.r()[..]); - sig[32..64].copy_from_slice(&transaction.signature.s()[..]); - sig[64] = transaction.signature.standard_v(); - msg.copy_from_slice( - &EthereumTransactionMessage::from(transaction.clone()).hash()[..], - ); - + match transaction { + EthereumTransaction::Legacy(t) => { + sig[0..32].copy_from_slice(&t.signature.r()[..]); + sig[32..64].copy_from_slice(&t.signature.s()[..]); + sig[64] = t.signature.standard_v(); + msg.copy_from_slice( + ðereum::LegacyTransactionMessage::from(t.clone()).hash()[..], + ); + } + EthereumTransaction::EIP1559(t) => { + sig[0..32].copy_from_slice(&t.r[..]); + sig[32..64].copy_from_slice(&t.s[..]); + sig[64] = t.odd_y_parity as u8; + msg.copy_from_slice( + ðereum::EIP1559TransactionMessage::from(t.clone()).hash()[..], + ); + } + _ => { + return Err(eg!("Transaction Type Error")); + } + } fp_types::crypto::secp256k1_ecdsa_recover(&sig, &msg) } @@ -1610,6 +2041,45 @@ fn native_block_id(number: Option) -> Option { } } +struct FeeDetails { + gas_price: Option, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, +} + +fn fee_details( + request_gas_price: Option, + request_max_fee: Option, + request_priority: Option, +) -> Result { + match (request_gas_price, request_max_fee, request_priority) { + (gas_price, None, None) => { + // Legacy request, all default to gas price. + Ok(FeeDetails { + gas_price, + max_fee_per_gas: gas_price, + max_priority_fee_per_gas: gas_price, + }) + } + (_, max_fee, max_priority) => { + // eip-1559 + // Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`. + if let Some(max_priority) = max_priority { + let max_fee = max_fee.unwrap_or_default(); + if max_priority > max_fee { + return Err(internal_err( + "Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`" + )); + } + } + Ok(FeeDetails { + gas_price: max_fee, + max_fee_per_gas: max_fee, + max_priority_fee_per_gas: max_priority, + }) + } + } +} fn dummy_block(height: u64, full: bool) -> Rich { let hash = if height == *EVM_FIRST_BLOCK_HEIGHT - 1 { H256([0; 32]) @@ -1626,6 +2096,9 @@ fn dummy_block(height: u64, full: bool) -> Rich { BlockTransactions::Hashes(vec![]) }; + let gas_price = + ::FeeCalculator::min_gas_price(height); + let inner = Block { hash: Some(hash), parent_hash, @@ -1661,6 +2134,7 @@ fn dummy_block(height: u64, full: bool) -> Rich { uncles: vec![], transactions, size: Some(U256::from(0x1_u32)), + base_fee_per_gas: Some(gas_price), }; Rich { diff --git a/src/components/contracts/rpc/src/eth_filter.rs b/src/components/contracts/rpc/src/eth_filter.rs index 18c71814d..7dce5b914 100644 --- a/src/components/contracts/rpc/src/eth_filter.rs +++ b/src/components/contracts/rpc/src/eth_filter.rs @@ -1,6 +1,6 @@ use crate::{filter_block_logs, internal_err}; use baseapp::BaseApp; -use ethereum::BlockV0 as EthereumBlock; +use ethereum::BlockV2 as EthereumBlock; use ethereum_types::{H256, U256}; use fp_evm::{BlockId, TransactionStatus}; use fp_rpc_core::types::{ diff --git a/src/components/contracts/rpc/src/eth_pubsub.rs b/src/components/contracts/rpc/src/eth_pubsub.rs index 21efe3166..ffa007b57 100644 --- a/src/components/contracts/rpc/src/eth_pubsub.rs +++ b/src/components/contracts/rpc/src/eth_pubsub.rs @@ -1,6 +1,6 @@ use baseapp::tm_events::{get_pendingtx, get_sync_status}; use baseapp::BaseApp; -use ethereum::{BlockV0 as EthereumBlock, ReceiptV0 as Receipt}; +use ethereum::{BlockV2 as EthereumBlock, ReceiptV0 as Receipt}; use ethereum_types::{H256, U256}; use fp_evm::BlockId; use fp_rpc_core::types::pubsub::PubSubSyncStatus; diff --git a/src/components/finutils/Cargo.toml b/src/components/finutils/Cargo.toml index 1782f2f98..83bf14733 100644 --- a/src/components/finutils/Cargo.toml +++ b/src/components/finutils/Cargo.toml @@ -22,7 +22,7 @@ serde = { version = "1.0.124", features = ["derive"] } sha2 = "0.10" tokio = "1.10.1" -wasm-bindgen = { version = "=0.2.73", features = ["serde-serialize"] } +wasm-bindgen = { version = "0.2.83", features = ["serde-serialize"] } getrandom = "0.2" noah = { git = "https://github.com/FindoraNetwork/noah", tag = "v0.3.0" } diff --git a/src/components/finutils/src/txn_builder/mod.rs b/src/components/finutils/src/txn_builder/mod.rs index 102cba254..d4afbd1e5 100644 --- a/src/components/finutils/src/txn_builder/mod.rs +++ b/src/components/finutils/src/txn_builder/mod.rs @@ -635,7 +635,6 @@ impl TransactionBuilder { /// * `enc_key` - XPublicKey of OwnerMemo encryption of receiver /// * `is_bar_transparent` - if transparent bar (ar) #[allow(clippy::too_many_arguments)] - pub fn add_operation_bar_to_abar( &mut self, seed: [u8; 32], diff --git a/src/components/wallet_mobile/Cargo.toml b/src/components/wallet_mobile/Cargo.toml index 53086d49a..d84459968 100644 --- a/src/components/wallet_mobile/Cargo.toml +++ b/src/components/wallet_mobile/Cargo.toml @@ -60,7 +60,7 @@ features = [ jni = "0.20" [target.'cfg(target_arch="wasm32")'.dependencies] -wasm-bindgen = { version = "=0.2.73", features = ["serde-serialize"] } +wasm-bindgen = { version = "0.2.83", features = ["serde-serialize"] } [target.'cfg(not(target_arch="wasm32"))'.dependencies] safer-ffi = "0.0.10" diff --git a/src/components/wasm/Cargo.toml b/src/components/wasm/Cargo.toml index fbd9de16d..970cb8c68 100644 --- a/src/components/wasm/Cargo.toml +++ b/src/components/wasm/Cargo.toml @@ -24,7 +24,7 @@ rand_chacha = "0.3" rand_core = { version = "0.6", default-features = false, features = ["alloc"] } serde = { version = "1.0.124", features = ["derive"] } serde_json = "1.0" -wasm-bindgen = { version = "=0.2.73", features = ["serde-serialize"] } +wasm-bindgen = { version = "0.2.83", features = ["serde-serialize"] } bs58 = "0.4" ring = "0.16.19" @@ -71,7 +71,7 @@ features = [ serde = "1.0.124" serde_json = "1.0.41" vergen = "=3.1.0" -wasm-bindgen = { version = "=0.2.73", features = ["serde-serialize"] } +wasm-bindgen = { version = "0.2.83", features = ["serde-serialize"] } [dev-dependencies] # Must enable the "js"-feature, diff --git a/src/libs/credentials/Cargo.toml b/src/libs/credentials/Cargo.toml index 8b1819d3a..021e14dad 100644 --- a/src/libs/credentials/Cargo.toml +++ b/src/libs/credentials/Cargo.toml @@ -11,6 +11,6 @@ rand_core = { version = "0.6", default-features = false, features = ["alloc"] } linear-map = {version = "1.2.0", features = ["serde_impl"] } serde = "1.0.124" serde_derive = "1.0" -wasm-bindgen = { version = "=0.2.73", features = ["serde-serialize"] } +wasm-bindgen = { version = "0.2.83", features = ["serde-serialize"] } noah = { git = "https://github.com/FindoraNetwork/noah", tag = "v0.3.0" }