diff --git a/Cargo.toml b/Cargo.toml index ba2ddadd2d..2fce0b706e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,6 @@ members = [ "template/runtime", ] resolver = "2" + +[patch.crates-io] +evm = { git = "https://github.com/purestake/evm", branch = "jeremy-precompile-handle-for-subcalls" } \ No newline at end of file diff --git a/client/rpc/src/eth.rs b/client/rpc/src/eth.rs index 0c67479d29..7c0937628f 100644 --- a/client/rpc/src/eth.rs +++ b/client/rpc/src/eth.rs @@ -23,12 +23,12 @@ use std::{ time, }; +use crate::cache::LRUCacheByteLimited; use ethereum::{BlockV2 as EthereumBlock, TransactionV2 as EthereumTransaction}; use ethereum_types::{H160, H256, H512, H64, U256, U64}; use evm::{ExitError, ExitReason}; use futures::{future::TryFutureExt, StreamExt}; use jsonrpc_core::{futures::future, BoxFuture, Error, Result}; -use crate::cache::LRUCacheByteLimited; use tokio::sync::{mpsc, oneshot}; use codec::{Decode, Encode}; @@ -42,8 +42,7 @@ use sc_transaction_pool::{ChainApi, Pool}; use sc_transaction_pool_api::{InPoolTransaction, TransactionPool}; use sp_api::{ApiExt, BlockId, Core, HeaderT, ProvideRuntimeApi}; use sp_block_builder::BlockBuilder; -use sp_blockchain::BlockStatus; -use sp_blockchain::HeaderBackend; +use sp_blockchain::{BlockStatus, HeaderBackend}; use sp_core::hashing::keccak_256; use sp_runtime::{ traits::{BlakeTwo256, Block as BlockT, NumberFor, One, Saturating, UniqueSaturatedInto, Zero}, @@ -63,8 +62,8 @@ use fp_rpc::{ConvertTransactionRuntimeApi, EthereumRuntimeRPCApi, TransactionSta use fp_storage::EthereumStorageSchema; use crate::{ - error_on_execution_failure, format::Formatter, frontier_backend_client, internal_err, overrides::OverrideHandle, - public_key, EthSigner, StorageOverride, + error_on_execution_failure, format::Formatter, frontier_backend_client, internal_err, + overrides::OverrideHandle, public_key, EthSigner, StorageOverride, }; /// Default JSONRPC error code return by geth @@ -89,7 +88,9 @@ pub struct EthApi, } -impl EthApi { +impl + EthApi +{ pub fn new( client: Arc, pool: Arc

, @@ -349,18 +350,13 @@ where let address_bloom_filter = FilteredParams::adresses_bloom_filter(&filter.address); let topics_bloom_filter = FilteredParams::topics_bloom_filter(&topics_input); - - while current_number <= to { let id = BlockId::Number(current_number); let substrate_hash = client .expect_block_hash_from_id(&id) .map_err(|_| internal_err(format!("Expect block number from id: {}", id)))?; - let schema = frontier_backend_client::onchain_storage_schema::( - client, - id, - ); + let schema = frontier_backend_client::onchain_storage_schema::(client, id); let block = block_data_cache.current_block(schema, substrate_hash).await; @@ -706,24 +702,29 @@ where // Indexers heavily rely on the parent hash. // Moonbase client-level patch for inconsistent runtime 1200 state. let number = rich_block.inner.header.number.unwrap_or_default(); - if rich_block.inner.header.parent_hash == H256::default() - && number > U256::zero() { - let id = BlockId::Hash(substrate_hash); - if let Ok(Some(header)) = client.header(id) { - let parent_hash = *header.parent_hash(); - - let parent_id = BlockId::Hash(parent_hash); - let schema = - frontier_backend_client::onchain_storage_schema::(client.as_ref(), parent_id); - if let Some(block) = block_data_cache.current_block(schema, parent_hash).await { - rich_block.inner.header.parent_hash = - H256::from_slice(keccak_256(&rlp::encode(&block.header)).as_slice()); - } + if rich_block.inner.header.parent_hash == H256::default() + && number > U256::zero() + { + let id = BlockId::Hash(substrate_hash); + if let Ok(Some(header)) = client.header(id) { + let parent_hash = *header.parent_hash(); + + let parent_id = BlockId::Hash(parent_hash); + let schema = frontier_backend_client::onchain_storage_schema::( + client.as_ref(), + parent_id, + ); + if let Some(block) = + block_data_cache.current_block(schema, parent_hash).await + { + rich_block.inner.header.parent_hash = H256::from_slice( + keccak_256(&rlp::encode(&block.header)).as_slice(), + ); } + } } Ok(Some(rich_block)) - - }, + } _ => Ok(None), } }) @@ -782,19 +783,24 @@ where // Indexers heavily rely on the parent hash. // Moonbase client-level patch for inconsistent runtime 1200 state. let number = rich_block.inner.header.number.unwrap_or_default(); - if rich_block.inner.header.parent_hash == H256::default() - && number > U256::zero() { - + if rich_block.inner.header.parent_hash == H256::default() + && number > U256::zero() + { let id = BlockId::Hash(substrate_hash); if let Ok(Some(header)) = client.header(id) { let parent_hash = *header.parent_hash(); let parent_id = BlockId::Hash(parent_hash); - let schema = - frontier_backend_client::onchain_storage_schema::(client.as_ref(), parent_id); - if let Some(block) = block_data_cache.current_block(schema, parent_hash).await { - rich_block.inner.header.parent_hash = - H256::from_slice(keccak_256(&rlp::encode(&block.header)).as_slice()); + let schema = frontier_backend_client::onchain_storage_schema::( + client.as_ref(), + parent_id, + ); + if let Some(block) = + block_data_cache.current_block(schema, parent_hash).await + { + rich_block.inner.header.parent_hash = H256::from_slice( + keccak_256(&rlp::encode(&block.header)).as_slice(), + ); } } } @@ -2876,7 +2882,8 @@ where // Make sure only block hashes marked as best are referencing cache checkpoints. if notification.block == client.info().best_hash { // Just map the change set to the actual data. - let storage: Vec> = notification.changes + let storage: Vec> = notification + .changes .iter() .filter_map(|(o_sk, _k, v)| { if o_sk.is_none() { diff --git a/frame/ethereum/src/lib.rs b/frame/ethereum/src/lib.rs index f8e55e83da..8ab7349a14 100644 --- a/frame/ethereum/src/lib.rs +++ b/frame/ethereum/src/lib.rs @@ -545,7 +545,7 @@ impl Pallet { (Some(gas_price), None, None) => { priority = gas_price.saturating_sub(base_fee).unique_saturated_into(); gas_price - }, + } // EIP-1559 transaction without tip. (None, Some(max_fee_per_gas), None) => max_fee_per_gas, // EIP-1559 transaction with tip. @@ -555,7 +555,7 @@ impl Pallet { .min(max_priority_fee_per_gas) .unique_saturated_into(); max_fee_per_gas - }, + } _ => return Err(InvalidTransaction::Payment.into()), }; @@ -747,18 +747,16 @@ impl Pallet { match transaction { // max_fee_per_gas and max_priority_fee_per_gas in legacy and 2930 transactions is // the provided gas_price. - Transaction::Legacy(t) => { - ( - t.input.clone(), - t.value, - t.gas_limit, - Some(t.gas_price), - Some(t.gas_price), - Some(t.nonce), - t.action, - Vec::new(), - ) - } + Transaction::Legacy(t) => ( + t.input.clone(), + t.value, + t.gas_limit, + Some(t.gas_price), + Some(t.gas_price), + Some(t.nonce), + t.action, + Vec::new(), + ), Transaction::EIP2930(t) => { let access_list: Vec<(H160, Vec)> = t .access_list diff --git a/frame/evm/precompile/blake2/src/lib.rs b/frame/evm/precompile/blake2/src/lib.rs index 022e6913bc..b27443872a 100644 --- a/frame/evm/precompile/blake2/src/lib.rs +++ b/frame/evm/precompile/blake2/src/lib.rs @@ -23,8 +23,8 @@ mod eip_152; use core::mem::size_of; use fp_evm::{ - Context, ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileOutput, - PrecompileResult, + Context, ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileHandle, + PrecompileOutput, PrecompileResult, }; pub struct Blake2F; @@ -37,6 +37,7 @@ impl Precompile for Blake2F { /// Format of `input`: /// [4 bytes for rounds][64 bytes for h][128 bytes for m][8 bytes for t_0][8 bytes for t_1][1 byte for f] fn execute( + handle: &mut impl PrecompileHandle, input: &[u8], target_gas: Option, _context: &Context, @@ -65,6 +66,8 @@ impl Precompile for Blake2F { } } + handle.record_cost(gas_cost)?; + // we use from_le_bytes below to effectively swap byte order to LE if architecture is BE let mut h_buf: [u8; 64] = [0; 64]; @@ -116,9 +119,7 @@ impl Precompile for Blake2F { Ok(PrecompileOutput { exit_status: ExitSucceed::Returned, - cost: gas_cost, output: output_buf.to_vec(), - logs: Default::default(), }) } } diff --git a/frame/evm/precompile/bn128/src/lib.rs b/frame/evm/precompile/bn128/src/lib.rs index c57ac215db..670b310cbc 100644 --- a/frame/evm/precompile/bn128/src/lib.rs +++ b/frame/evm/precompile/bn128/src/lib.rs @@ -21,8 +21,8 @@ extern crate alloc; use alloc::vec::Vec; use fp_evm::{ - Context, ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileOutput, - PrecompileResult, + Context, ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileHandle, + PrecompileOutput, PrecompileResult, }; use sp_core::U256; @@ -77,6 +77,7 @@ impl Bn128Add { impl Precompile for Bn128Add { fn execute( + handle: &mut impl PrecompileHandle, input: &[u8], _target_gas: Option, _context: &Context, @@ -84,6 +85,8 @@ impl Precompile for Bn128Add { ) -> PrecompileResult { use bn::AffineG1; + handle.record_cost(Bn128Add::GAS_COST)?; + let p1 = read_point(input, 0)?; let p2 = read_point(input, 64)?; @@ -108,9 +111,7 @@ impl Precompile for Bn128Add { Ok(PrecompileOutput { exit_status: ExitSucceed::Returned, - cost: Bn128Add::GAS_COST, output: buf.to_vec(), - logs: Default::default(), }) } } @@ -124,6 +125,7 @@ impl Bn128Mul { impl Precompile for Bn128Mul { fn execute( + handle: &mut impl PrecompileHandle, input: &[u8], _target_gas: Option, _context: &Context, @@ -131,6 +133,8 @@ impl Precompile for Bn128Mul { ) -> PrecompileResult { use bn::AffineG1; + handle.record_cost(Bn128Mul::GAS_COST)?; + let p = read_point(input, 0)?; let fr = read_fr(input, 64)?; @@ -155,9 +159,7 @@ impl Precompile for Bn128Mul { Ok(PrecompileOutput { exit_status: ExitSucceed::Returned, - cost: Bn128Mul::GAS_COST, output: buf.to_vec(), - logs: Default::default(), }) } } @@ -173,6 +175,7 @@ impl Bn128Pairing { impl Precompile for Bn128Pairing { fn execute( + handle: &mut impl PrecompileHandle, input: &[u8], target_gas: Option, _context: &Context, @@ -282,14 +285,14 @@ impl Precompile for Bn128Pairing { } }; + handle.record_cost(gas_cost)?; + let mut buf = [0u8; 32]; ret_val.to_big_endian(&mut buf); Ok(PrecompileOutput { exit_status: ExitSucceed::Returned, - cost: gas_cost, output: buf.to_vec(), - logs: Default::default(), }) } } diff --git a/frame/evm/precompile/dispatch/src/lib.rs b/frame/evm/precompile/dispatch/src/lib.rs index 54a2f91e9f..0d48851d8f 100644 --- a/frame/evm/precompile/dispatch/src/lib.rs +++ b/frame/evm/precompile/dispatch/src/lib.rs @@ -21,8 +21,8 @@ extern crate alloc; use core::marker::PhantomData; use fp_evm::{ - Context, ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileOutput, - PrecompileResult, + Context, ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileHandle, + PrecompileOutput, PrecompileResult, }; use frame_support::{ codec::Decode, @@ -42,6 +42,7 @@ where ::Origin: From>, { fn execute( + handle: &mut impl PrecompileHandle, input: &[u8], target_gas: Option, context: &Context, @@ -75,11 +76,12 @@ where let cost = T::GasWeightMapping::weight_to_gas( post_info.actual_weight.unwrap_or(info.weight), ); + + handle.record_cost(cost)?; + Ok(PrecompileOutput { exit_status: ExitSucceed::Stopped, - cost, output: Default::default(), - logs: Default::default(), }) } Err(_) => Err(PrecompileFailure::Error { diff --git a/frame/evm/precompile/modexp/src/lib.rs b/frame/evm/precompile/modexp/src/lib.rs index 5badd12216..4cd976a83e 100644 --- a/frame/evm/precompile/modexp/src/lib.rs +++ b/frame/evm/precompile/modexp/src/lib.rs @@ -25,8 +25,8 @@ use core::{cmp::max, ops::BitAnd}; use num::{BigUint, FromPrimitive, One, ToPrimitive, Zero}; use fp_evm::{ - Context, ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileOutput, - PrecompileResult, + Context, ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileHandle, + PrecompileOutput, PrecompileResult, }; pub struct Modexp; @@ -111,6 +111,7 @@ fn calculate_gas_cost( impl Precompile for Modexp { fn execute( + handle: &mut impl PrecompileHandle, input: &[u8], target_gas: Option, _context: &Context, @@ -195,6 +196,8 @@ impl Precompile for Modexp { } }; + handle.record_cost(gas_cost)?; + // write output to given memory, left padded and same length as the modulus. let bytes = r.to_bytes_be(); @@ -203,9 +206,7 @@ impl Precompile for Modexp { if bytes.len() == mod_len { Ok(PrecompileOutput { exit_status: ExitSucceed::Returned, - cost: gas_cost, output: bytes.to_vec(), - logs: Default::default(), }) } else if bytes.len() < mod_len { let mut ret = Vec::with_capacity(mod_len); @@ -213,9 +214,7 @@ impl Precompile for Modexp { ret.extend_from_slice(&bytes[..]); Ok(PrecompileOutput { exit_status: ExitSucceed::Returned, - cost: gas_cost, output: ret.to_vec(), - logs: Default::default(), }) } else { Err(PrecompileFailure::Error { diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index a71ea4ad79..e7670f3d49 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -270,7 +270,9 @@ impl RunnerT for Runner { |executor| { let address = executor.create_address(evm::CreateScheme::Legacy { caller: source }); ( - executor.transact_create(source, value, init, gas_limit, access_list).0, + executor + .transact_create(source, value, init, gas_limit, access_list) + .0, address, ) }, @@ -309,7 +311,9 @@ impl RunnerT for Runner { salt, }); ( - executor.transact_create2(source, value, init, salt, gas_limit, access_list).0, + executor + .transact_create2(source, value, init, salt, gas_limit, access_list) + .0, address, ) }, diff --git a/frame/evm/src/tests.rs b/frame/evm/src/tests.rs index 0219097a8f..d836f4c528 100644 --- a/frame/evm/src/tests.rs +++ b/frame/evm/src/tests.rs @@ -325,9 +325,7 @@ fn refunds_and_priority_should_work() { ); let base_fee = ::FeeCalculator::min_gas_price(); let actual_tip = (max_fee_per_gas - base_fee).min(tip) * used_gas; - let total_cost = (used_gas * base_fee) - + U256::from(actual_tip) - + U256::from(1); + let total_cost = (used_gas * base_fee) + U256::from(actual_tip) + U256::from(1); let after_call = EVM::account_basic(&H160::default()).balance; // The tip is deducted but never refunded to the caller. assert_eq!(after_call, before_call - total_cost); diff --git a/frame/evm/test-vector-support/Cargo.toml b/frame/evm/test-vector-support/Cargo.toml index 9de41dbdf9..23a72588f7 100644 --- a/frame/evm/test-vector-support/Cargo.toml +++ b/frame/evm/test-vector-support/Cargo.toml @@ -14,6 +14,7 @@ hex = { version = "0.4.0", optional = true } serde = { version = "1.0.101", features = ["derive"], optional = true } serde_json = { version = "1.0", optional = true } +sp-core = { version = "6.0.0", git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.18", default-features = false } fp-evm = { version = "3.0.0-dev", path = "../../../primitives/evm", default-features = false } [features] @@ -24,5 +25,6 @@ std = [ "serde", "serde_json", + "sp-core/std", "fp-evm/std", ] diff --git a/frame/evm/test-vector-support/src/lib.rs b/frame/evm/test-vector-support/src/lib.rs index 2579f47ce7..60b9e5c671 100644 --- a/frame/evm/test-vector-support/src/lib.rs +++ b/frame/evm/test-vector-support/src/lib.rs @@ -15,8 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use evm::{Context, ExitSucceed}; -use fp_evm::Precompile; +use evm::{Context, ExitError, ExitReason, ExitSucceed, Transfer}; +use fp_evm::{Precompile, PrecompileHandle}; +use sp_core::{H160, H256}; #[cfg(feature = "std")] use serde::Deserialize; @@ -31,6 +32,33 @@ struct EthConsensusTest { Gas: Option, } +struct MockHandle(u64); + +impl PrecompileHandle for MockHandle { + /// Perform subcall in provided context. + /// Precompile specifies in which context the subcall is executed. + fn call( + &mut self, + _: H160, + _: Option, + _: Vec, + _: Option, + _: bool, + _: &Context, + ) -> (ExitReason, Vec) { + unimplemented!() + } + + fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> { + self.0 += cost; + Ok(()) + } + + fn log(&mut self, _: H160, _: Vec, _: Vec) { + unimplemented!() + } +} + /// Tests a precompile against the ethereum consensus tests defined in the given file at filepath. /// The file is expected to be in JSON format and contain an array of test vectors, where each /// vector can be deserialized into an "EthConsensusTest". @@ -41,6 +69,7 @@ pub fn test_precompile_test_vectors(filepath: &str) -> Result<(), let data = fs::read_to_string(&filepath).expect("Failed to read blake2F.json"); let tests: Vec = serde_json::from_str(&data).expect("expected json array"); + let mut handle = MockHandle(0); for test in tests { let input: Vec = hex::decode(test.Input).expect("Could not hex-decode test input data"); @@ -53,7 +82,7 @@ pub fn test_precompile_test_vectors(filepath: &str) -> Result<(), apparent_value: From::from(0), }; - match P::execute(&input, Some(cost), &context, false) { + match P::execute(&mut handle, &input, Some(cost), &context, false) { Ok(result) => { let as_hex: String = hex::encode(result.output); assert_eq!( @@ -70,7 +99,7 @@ pub fn test_precompile_test_vectors(filepath: &str) -> Result<(), ); if let Some(expected_gas) = test.Gas { assert_eq!( - result.cost, expected_gas, + handle.0, expected_gas, "test '{}' failed (different gas cost)", test.Name ); diff --git a/primitives/evm/src/lib.rs b/primitives/evm/src/lib.rs index 283fc1813a..0fdd71491d 100644 --- a/primitives/evm/src/lib.rs +++ b/primitives/evm/src/lib.rs @@ -30,7 +30,8 @@ pub use evm::backend::{Basic as Account, Log}; pub use self::precompile::{ Context, ExitError, ExitRevert, ExitSucceed, LinearCostPrecompile, Precompile, - PrecompileFailure, PrecompileOutput, PrecompileResult, PrecompileSet, + PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult, PrecompileSet, + Transfer, }; #[derive(Clone, Eq, PartialEq, Encode, Decode, Default)] diff --git a/primitives/evm/src/precompile.rs b/primitives/evm/src/precompile.rs index 73b3be8a10..72083923f0 100644 --- a/primitives/evm/src/precompile.rs +++ b/primitives/evm/src/precompile.rs @@ -16,8 +16,8 @@ // limitations under the License. pub use evm::{ - executor::stack::{PrecompileFailure, PrecompileOutput, PrecompileSet}, - Context, ExitError, ExitRevert, ExitSucceed, + executor::stack::{PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileSet}, + Context, ExitError, ExitRevert, ExitSucceed, Transfer, }; use sp_std::vec::Vec; @@ -29,6 +29,7 @@ pub trait Precompile { /// `target_gas`. Return `Ok(status, output, gas_used)` if the execution is /// successful. Otherwise return `Err(_)`. fn execute( + handle: &mut impl PrecompileHandle, input: &[u8], target_gas: Option, context: &Context, @@ -47,15 +48,20 @@ pub trait LinearCostPrecompile { } impl Precompile for T { - fn execute(input: &[u8], target_gas: Option, _: &Context, _: bool) -> PrecompileResult { + fn execute( + handle: &mut impl PrecompileHandle, + input: &[u8], + target_gas: Option, + _: &Context, + _: bool, + ) -> PrecompileResult { let cost = ensure_linear_cost(target_gas, input.len() as u64, T::BASE, T::WORD)?; + handle.record_cost(cost)?; let (exit_status, output) = T::execute(input, cost)?; Ok(PrecompileOutput { exit_status, - cost, output, - logs: Default::default(), }) } }