diff --git a/Cargo.lock b/Cargo.lock index 62cb03dedafa3..ced68d0cf4082 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1395,11 +1395,29 @@ checksum = "71a6567e6fd35589fea0c63b94b4cf2e55573e413901bdbe60ab15cf0e25e5df" dependencies = [ "crunchy", "fixed-hash", + "impl-codec", "impl-rlp", "impl-serde", "tiny-keccak", ] +[[package]] +name = "ethereum" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df706418ff7d3874b9506424b04ea0bef569a2b39412b43a27ea86e679be108e" +dependencies = [ + "ethereum-types", + "hash-db", + "hash256-std-hasher", + "parity-scale-codec", + "rlp", + "rlp-derive", + "serde", + "sha3 0.9.1", + "triehash", +] + [[package]] name = "ethereum-types" version = "0.9.2" @@ -1408,6 +1426,7 @@ checksum = "473aecff686bd8e7b9db0165cbbb53562376b39bf35b427f0c60446a9e1634b0" dependencies = [ "ethbloom", "fixed-hash", + "impl-codec", "impl-rlp", "impl-serde", "primitive-types", @@ -1422,13 +1441,16 @@ checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" [[package]] name = "evm" -version = "0.17.0" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68224b0aa788720ef0c8a23030a4412a021ed73df069a922bee8f0db9ed617e2" +checksum = "f1fc70736bd5ec89622647ea9346b70567557917596a39538d76e8f2a17ff59e" dependencies = [ + "ethereum", "evm-core", "evm-gasometer", "evm-runtime", + "log", + "parity-scale-codec", "primitive-types", "rlp", "serde", @@ -1437,18 +1459,20 @@ dependencies = [ [[package]] name = "evm-core" -version = "0.17.0" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a040378759577447945c89da1b07d6e33fda32a97a104afe0ec3fa1c382949d" +checksum = "63c6c39300d7779427f461408d867426e202ea72ac7ece2455689ff0e4bddb6f" dependencies = [ + "parity-scale-codec", "primitive-types", + "serde", ] [[package]] name = "evm-gasometer" -version = "0.17.0" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb5bc051afad6bb0735c82b46656bbdfac41917861307a608b1404a546fec42" +checksum = "689c481648c3f45b64b1278077c04284ad535e068c9d6872153c7b74da7ccb03" dependencies = [ "evm-core", "evm-runtime", @@ -1457,9 +1481,9 @@ dependencies = [ [[package]] name = "evm-runtime" -version = "0.17.0" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7410f5677a52203d3fca02b0eb8f96f9799f3a45cff82946a8ed28379e6b1b04" +checksum = "61a148ad1b3e0af31aa03c6c3cc9df3a529e279dad8e29b4ef90dccad32601e4" dependencies = [ "evm-core", "primitive-types", @@ -4602,6 +4626,8 @@ name = "pallet-evm" version = "2.0.0" dependencies = [ "evm", + "evm-gasometer", + "evm-runtime", "frame-support", "frame-system", "impl-trait-for-tuples", @@ -4614,6 +4640,7 @@ dependencies = [ "serde", "sha3 0.8.2", "sp-core", + "sp-evm", "sp-io", "sp-runtime", "sp-std", @@ -6292,6 +6319,17 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "rlp-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "rocksdb" version = "0.15.0" @@ -8330,6 +8368,17 @@ dependencies = [ "syn", ] +[[package]] +name = "sp-evm" +version = "0.8.0" +dependencies = [ + "evm", + "parity-scale-codec", + "serde", + "sp-core", + "sp-std", +] + [[package]] name = "sp-externalities" version = "0.8.0" @@ -9797,6 +9846,16 @@ dependencies = [ "keccak-hasher", ] +[[package]] +name = "triehash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f490aa7aa4e4d07edeba442c007e42e3e7f43aafb5112c5b047fff0b1aa5449c" +dependencies = [ + "hash-db", + "rlp", +] + [[package]] name = "try-lock" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index b78c4da055801..436763172f058 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -135,6 +135,7 @@ members = [ "primitives/debug-derive", "primitives/storage", "primitives/externalities", + "primitives/evm", "primitives/finality-grandpa", "primitives/inherents", "primitives/keyring", diff --git a/frame/evm/Cargo.toml b/frame/evm/Cargo.toml index a228dfb566be2..de5fea9e9011e 100644 --- a/frame/evm/Cargo.toml +++ b/frame/evm/Cargo.toml @@ -23,9 +23,12 @@ sp-core = { version = "2.0.0", default-features = false, path = "../../primitive sp-runtime = { version = "2.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "2.0.0", default-features = false, path = "../../primitives/std" } sp-io = { version = "2.0.0", default-features = false, path = "../../primitives/io" } +sp-evm = { version = "0.8.0", default-features = false, path = "../../primitives/evm" } primitive-types = { version = "0.7.0", default-features = false, features = ["rlp", "byteorder"] } rlp = { version = "0.4", default-features = false } -evm = { version = "0.17", default-features = false } +evm = { version = "0.18", default-features = false, features = ["with-codec"] } +evm-runtime = { version = "0.18", default-features = false } +evm-gasometer = { version = "0.18", default-features = false } sha3 = { version = "0.8", default-features = false } impl-trait-for-tuples = "0.1" ripemd160 = { version = "0.9", default-features = false } @@ -42,10 +45,14 @@ std = [ "pallet-balances/std", "sp-io/std", "sp-std/std", + "sp-evm/std", "sha3/std", "rlp/std", "primitive-types/std", "evm/std", + "evm/with-serde", + "evm-runtime/std", + "evm-gasometer/std", "pallet-timestamp/std", "ripemd160/std", ] diff --git a/frame/evm/src/backend.rs b/frame/evm/src/backend.rs deleted file mode 100644 index b625c0c548026..0000000000000 --- a/frame/evm/src/backend.rs +++ /dev/null @@ -1,216 +0,0 @@ -use sp_std::marker::PhantomData; -use sp_std::vec::Vec; -#[cfg(feature = "std")] -use serde::{Serialize, Deserialize}; -use codec::{Encode, Decode}; -use sp_core::{U256, H256, H160}; -use sp_runtime::traits::UniqueSaturatedInto; -use frame_support::traits::Get; -use frame_support::{debug, storage::{StorageMap, StorageDoubleMap}}; -use sha3::{Keccak256, Digest}; -use evm::backend::{Backend as BackendT, ApplyBackend, Apply}; -use crate::{Trait, AccountStorages, AccountCodes, Module, Event}; - -#[derive(Clone, Eq, PartialEq, Encode, Decode, Default)] -#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] -/// Ethereum account nonce, balance and code. Used by storage. -pub struct Account { - /// Account nonce. - pub nonce: U256, - /// Account balance. - pub balance: U256, -} - -#[derive(Clone, Eq, PartialEq, Encode, Decode)] -#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] -/// Ethereum log. Used for `deposit_event`. -pub struct Log { - /// Source address of the log. - pub address: H160, - /// Topics of the log. - pub topics: Vec, - /// Byte array data of the log. - pub data: Vec, -} - -#[derive(Clone, Eq, PartialEq, Encode, Decode, Default)] -#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] -/// External input from the transaction. -pub struct Vicinity { - /// Current transaction gas price. - pub gas_price: U256, - /// Origin of the transaction. - pub origin: H160, -} - -/// Substrate backend for EVM. -pub struct Backend<'vicinity, T> { - vicinity: &'vicinity Vicinity, - _marker: PhantomData, -} - -impl<'vicinity, T> Backend<'vicinity, T> { - /// Create a new backend with given vicinity. - pub fn new(vicinity: &'vicinity Vicinity) -> Self { - Self { vicinity, _marker: PhantomData } - } -} - -impl<'vicinity, T: Trait> BackendT for Backend<'vicinity, T> { - fn gas_price(&self) -> U256 { self.vicinity.gas_price } - fn origin(&self) -> H160 { self.vicinity.origin } - - fn block_hash(&self, number: U256) -> H256 { - if number > U256::from(u32::max_value()) { - H256::default() - } else { - let number = T::BlockNumber::from(number.as_u32()); - H256::from_slice(frame_system::Module::::block_hash(number).as_ref()) - } - } - - fn block_number(&self) -> U256 { - let number: u128 = frame_system::Module::::block_number().unique_saturated_into(); - U256::from(number) - } - - fn block_coinbase(&self) -> H160 { - H160::default() - } - - fn block_timestamp(&self) -> U256 { - let now: u128 = pallet_timestamp::Module::::get().unique_saturated_into(); - U256::from(now / 1000) - } - - fn block_difficulty(&self) -> U256 { - U256::zero() - } - - fn block_gas_limit(&self) -> U256 { - U256::zero() - } - - fn chain_id(&self) -> U256 { - U256::from(T::ChainId::get()) - } - - fn exists(&self, _address: H160) -> bool { - true - } - - fn basic(&self, address: H160) -> evm::backend::Basic { - let account = Module::::account_basic(&address); - - evm::backend::Basic { - balance: account.balance, - nonce: account.nonce, - } - } - - fn code_size(&self, address: H160) -> usize { - AccountCodes::decode_len(&address).unwrap_or(0) - } - - fn code_hash(&self, address: H160) -> H256 { - H256::from_slice(Keccak256::digest(&AccountCodes::get(&address)).as_slice()) - } - - fn code(&self, address: H160) -> Vec { - AccountCodes::get(&address) - } - - fn storage(&self, address: H160, index: H256) -> H256 { - AccountStorages::get(address, index) - } -} - -impl<'vicinity, T: Trait> ApplyBackend for Backend<'vicinity, T> { - fn apply( - &mut self, - values: A, - logs: L, - delete_empty: bool, - ) where - A: IntoIterator>, - I: IntoIterator, - L: IntoIterator, - { - for apply in values { - match apply { - Apply::Modify { - address, basic, code, storage, reset_storage, - } => { - Module::::mutate_account_basic(&address, Account { - nonce: basic.nonce, - balance: basic.balance, - }); - - if let Some(code) = code { - debug::debug!( - target: "evm", - "Inserting code ({} bytes) at {:?}", - code.len(), - address - ); - AccountCodes::insert(address, code); - } - - if reset_storage { - AccountStorages::remove_prefix(address); - } - - for (index, value) in storage { - if value == H256::default() { - debug::debug!( - target: "evm", - "Removing storage for {:?} [index: {:?}]", - address, - index - ); - AccountStorages::remove(address, index); - } else { - debug::debug!( - target: "evm", - "Updating storage for {:?} [index: {:?}, value: {:?}]", - address, - index, - value - ); - AccountStorages::insert(address, index, value); - } - } - - if delete_empty { - Module::::remove_account_if_empty(&address); - } - }, - Apply::Delete { address } => { - debug::debug!( - target: "evm", - "Deleting account at {:?}", - address - ); - Module::::remove_account(&address) - }, - } - } - - for log in logs { - debug::trace!( - target: "evm", - "Inserting log for {:?}, topics ({}) {:?}, data ({}): {:?}]", - log.address, - log.topics.len(), - log.topics, - log.data.len(), - log.data - ); - Module::::deposit_event(Event::::Log(Log { - address: log.address, - topics: log.topics, - data: log.data, - })); - } - } -} diff --git a/frame/evm/src/lib.rs b/frame/evm/src/lib.rs index e7812a55482fd..bf6a6ff4b8047 100644 --- a/frame/evm/src/lib.rs +++ b/frame/evm/src/lib.rs @@ -53,30 +53,28 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -mod backend; mod tests; +pub mod runner; pub mod precompiles; pub use crate::precompiles::{Precompile, Precompiles}; -pub use crate::backend::{Account, Log, Vicinity, Backend}; +pub use crate::runner::Runner; +pub use sp_evm::{Account, Log, Vicinity, ExecutionInfo, CallInfo, CreateInfo}; +pub use evm::{ExitReason, ExitSucceed, ExitError, ExitRevert, ExitFatal}; use sp_std::vec::Vec; #[cfg(feature = "std")] use codec::{Encode, Decode}; #[cfg(feature = "std")] use serde::{Serialize, Deserialize}; -use frame_support::{debug, ensure, decl_module, decl_storage, decl_event, decl_error}; +use frame_support::{decl_module, decl_storage, decl_event, decl_error}; use frame_support::weights::{Weight, Pays}; use frame_support::traits::{Currency, ExistenceRequirement, Get}; use frame_support::dispatch::DispatchResultWithPostInfo; use frame_system::RawOrigin; use sp_core::{U256, H256, H160, Hasher}; use sp_runtime::{AccountId32, traits::{UniqueSaturatedInto, SaturatedConversion, BadOrigin}}; -use sha3::{Digest, Keccak256}; -pub use evm::{ExitReason, ExitSucceed, ExitError, ExitRevert, ExitFatal}; use evm::Config; -use evm::executor::StackExecutor; -use evm::backend::ApplyBackend; /// Type alias for currency balance. pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -241,6 +239,8 @@ pub trait Trait: frame_system::Trait + pallet_timestamp::Trait { type Precompiles: Precompiles; /// Chain ID of EVM. type ChainId: Get; + /// EVM execution runner. + type Runner: Runner; /// EVM config used in the module. fn config() -> &'static Config { @@ -273,10 +273,19 @@ decl_storage! { config(accounts): std::collections::BTreeMap; build(|config: &GenesisConfig| { for (address, account) in &config.accounts { - Module::::mutate_account_basic(&address, Account { - balance: account.balance, - nonce: account.nonce, - }); + let account_id = T::AddressMapping::into_account_id(*address); + + // ASSUME: in one single EVM transaction, the nonce will not increase more than + // `u128::max_value()`. + for _ in 0..account.nonce.low_u128() { + frame_system::Module::::inc_account_nonce(&account_id); + } + + T::Currency::deposit_creating( + &account_id, + account.balance.low_u128().unique_saturated_into(), + ); + AccountCodes::insert(address, &account.code); for (index, value) in &account.storage { @@ -360,20 +369,22 @@ decl_module! { ) -> DispatchResultWithPostInfo { T::CallOrigin::ensure_address_origin(&source, origin)?; - match Self::execute_call( + match T::Runner::call( source, target, input, value, gas_limit, - gas_price, + Some(gas_price), nonce, - true, )? { - (ExitReason::Succeed(_), _, _, _) => { + CallInfo { + exit_reason: ExitReason::Succeed(_), + .. + } => { Module::::deposit_event(Event::::Executed(target)); }, - (_, _, _, _) => { + _ => { Module::::deposit_event(Event::::ExecutedFailed(target)); }, } @@ -395,19 +406,26 @@ decl_module! { ) -> DispatchResultWithPostInfo { T::CallOrigin::ensure_address_origin(&source, origin)?; - match Self::execute_create( + match T::Runner::create( source, init, value, gas_limit, - gas_price, + Some(gas_price), nonce, - true, )? { - (ExitReason::Succeed(_), create_address, _, _) => { + CreateInfo { + exit_reason: ExitReason::Succeed(_), + value: create_address, + .. + } => { Module::::deposit_event(Event::::Created(create_address)); }, - (_, create_address, _, _) => { + CreateInfo { + exit_reason: _, + value: create_address, + .. + } => { Module::::deposit_event(Event::::CreatedFailed(create_address)); }, } @@ -429,20 +447,27 @@ decl_module! { ) -> DispatchResultWithPostInfo { T::CallOrigin::ensure_address_origin(&source, origin)?; - match Self::execute_create2( + match T::Runner::create2( source, init, salt, value, gas_limit, - gas_price, + Some(gas_price), nonce, - true, )? { - (ExitReason::Succeed(_), create_address, _, _) => { + CreateInfo { + exit_reason: ExitReason::Succeed(_), + value: create_address, + .. + } => { Module::::deposit_event(Event::::Created(create_address)); }, - (_, create_address, _, _) => { + CreateInfo { + exit_reason: _, + value: create_address, + .. + } => { Module::::deposit_event(Event::::CreatedFailed(create_address)); }, } @@ -453,32 +478,6 @@ decl_module! { } impl Module { - fn remove_account(address: &H160) { - AccountCodes::remove(address); - AccountStorages::remove_prefix(address); - } - - fn mutate_account_basic(address: &H160, new: Account) { - let account_id = T::AddressMapping::into_account_id(*address); - let current = Self::account_basic(address); - - if current.nonce < new.nonce { - // ASSUME: in one single EVM transaction, the nonce will not increase more than - // `u128::max_value()`. - for _ in 0..(new.nonce - current.nonce).low_u128() { - frame_system::Module::::inc_account_nonce(&account_id); - } - } - - if current.balance > new.balance { - let diff = current.balance - new.balance; - T::Currency::slash(&account_id, diff.low_u128().unique_saturated_into()); - } else if current.balance < new.balance { - let diff = new.balance - current.balance; - T::Currency::deposit_creating(&account_id, diff.low_u128().unique_saturated_into()); - } - } - /// Check whether an account is empty. pub fn is_account_empty(address: &H160) -> bool { let account = Self::account_basic(address); @@ -496,6 +495,12 @@ impl Module { } } + /// Remove an account. + pub fn remove_account(address: &H160) { + AccountCodes::remove(address); + AccountStorages::remove_prefix(address); + } + /// Get the account basic in EVM format. pub fn account_basic(address: &H160) -> Account { let account_id = T::AddressMapping::into_account_id(*address); @@ -508,171 +513,4 @@ impl Module { balance: U256::from(UniqueSaturatedInto::::unique_saturated_into(balance)), } } - - /// Execute a create transaction on behalf of given sender. - pub fn execute_create( - source: H160, - init: Vec, - value: U256, - gas_limit: u32, - gas_price: U256, - nonce: Option, - apply_state: bool, - ) -> Result<(ExitReason, H160, U256, Vec), Error> { - Self::execute_evm( - source, - value, - gas_limit, - gas_price, - nonce, - apply_state, - |executor| { - let address = executor.create_address( - evm::CreateScheme::Legacy { caller: source }, - ); - (executor.transact_create( - source, - value, - init, - gas_limit as usize, - ), address) - }, - ) - } - - /// Execute a create2 transaction on behalf of a given sender. - pub fn execute_create2( - source: H160, - init: Vec, - salt: H256, - value: U256, - gas_limit: u32, - gas_price: U256, - nonce: Option, - apply_state: bool, - ) -> Result<(ExitReason, H160, U256, Vec), Error> { - let code_hash = H256::from_slice(Keccak256::digest(&init).as_slice()); - Self::execute_evm( - source, - value, - gas_limit, - gas_price, - nonce, - apply_state, - |executor| { - let address = executor.create_address( - evm::CreateScheme::Create2 { caller: source, code_hash, salt }, - ); - (executor.transact_create2( - source, - value, - init, - salt, - gas_limit as usize, - ), address) - }, - ) - } - - /// Execute a call transaction on behalf of a given sender. - pub fn execute_call( - source: H160, - target: H160, - input: Vec, - value: U256, - gas_limit: u32, - gas_price: U256, - nonce: Option, - apply_state: bool, - ) -> Result<(ExitReason, Vec, U256, Vec), Error> { - Self::execute_evm( - source, - value, - gas_limit, - gas_price, - nonce, - apply_state, - |executor| executor.transact_call( - source, - target, - value, - input, - gas_limit as usize, - ), - ) - } - - /// Execute an EVM operation. - fn execute_evm( - source: H160, - value: U256, - gas_limit: u32, - gas_price: U256, - nonce: Option, - apply_state: bool, - f: F, - ) -> Result<(ExitReason, R, U256, Vec), Error> where - F: FnOnce(&mut StackExecutor>) -> (ExitReason, R), - { - - // Gas price check is skipped when performing a gas estimation. - if apply_state { - ensure!(gas_price >= T::FeeCalculator::min_gas_price(), Error::::GasPriceTooLow); - } - - let vicinity = Vicinity { - gas_price, - origin: source, - }; - - let mut backend = Backend::::new(&vicinity); - let mut executor = StackExecutor::new_with_precompile( - &backend, - gas_limit as usize, - T::config(), - T::Precompiles::execute, - ); - - let total_fee = gas_price.checked_mul(U256::from(gas_limit)) - .ok_or(Error::::FeeOverflow)?; - let total_payment = value.checked_add(total_fee).ok_or(Error::::PaymentOverflow)?; - let source_account = Self::account_basic(&source); - ensure!(source_account.balance >= total_payment, Error::::BalanceLow); - executor.withdraw(source, total_fee).map_err(|_| Error::::WithdrawFailed)?; - - if let Some(nonce) = nonce { - ensure!(source_account.nonce == nonce, Error::::InvalidNonce); - } - - let (retv, reason) = f(&mut executor); - - let used_gas = U256::from(executor.used_gas()); - let actual_fee = executor.fee(gas_price); - debug::debug!( - target: "evm", - "Execution {:?} [source: {:?}, value: {}, gas_limit: {}, used_gas: {}, actual_fee: {}]", - retv, - source, - value, - gas_limit, - used_gas, - actual_fee - ); - executor.deposit(source, total_fee.saturating_sub(actual_fee)); - - let (values, logs) = executor.deconstruct(); - let logs_data = logs.into_iter().map(|x| x ).collect::>(); - let logs_result = logs_data.clone().into_iter().map(|it| { - Log { - address: it.address, - topics: it.topics, - data: it.data - } - }).collect(); - if apply_state { - backend.apply(values, logs_data, true); - } - - Ok((retv, reason, used_gas, logs_result)) - } } diff --git a/frame/evm/src/precompiles.rs b/frame/evm/src/precompiles.rs index 440d9bf1c68c2..3473e3595c77a 100644 --- a/frame/evm/src/precompiles.rs +++ b/frame/evm/src/precompiles.rs @@ -127,7 +127,7 @@ impl Precompile for ECRecover { sig[64] = input[63]; let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg) - .map_err(|_| ExitError::Other("Public key recover failed"))?; + .map_err(|_| ExitError::Other("Public key recover failed".into()))?; let mut address = sp_io::hashing::keccak_256(&pubkey); address[0..12].copy_from_slice(&[0u8; 12]); diff --git a/frame/evm/src/runner/mod.rs b/frame/evm/src/runner/mod.rs new file mode 100644 index 0000000000000..5f2390787863f --- /dev/null +++ b/frame/evm/src/runner/mod.rs @@ -0,0 +1,57 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod stack; +pub mod native; + +use sp_std::vec::Vec; +use sp_core::{H160, U256, H256}; +use sp_evm::{CallInfo, CreateInfo}; +use crate::Trait; + +pub trait Runner { + type Error: Into; + + fn call( + source: H160, + target: H160, + input: Vec, + value: U256, + gas_limit: u32, + gas_price: Option, + nonce: Option, + ) -> Result; + + fn create( + source: H160, + init: Vec, + value: U256, + gas_limit: u32, + gas_price: Option, + nonce: Option, + ) -> Result; + + fn create2( + source: H160, + init: Vec, + salt: H256, + value: U256, + gas_limit: u32, + gas_price: Option, + nonce: Option, + ) -> Result; +} diff --git a/frame/evm/src/runner/native.rs b/frame/evm/src/runner/native.rs new file mode 100644 index 0000000000000..58ac631459d64 --- /dev/null +++ b/frame/evm/src/runner/native.rs @@ -0,0 +1,797 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Native EVM runner. + +use sp_std::{ + convert::Infallible, marker::PhantomData, rc::Rc, + collections::btree_set::BTreeSet, vec::Vec, mem, cmp::min, +}; +use sp_core::{H160, U256, H256}; +use sp_runtime::{TransactionOutcome, traits::UniqueSaturatedInto}; +use frame_support::{ + ensure, traits::{Get, Currency, ExistenceRequirement}, + storage::{StorageMap, StorageDoubleMap}, +}; +use sha3::{Keccak256, Digest}; +use evm::{ + ExternalOpcode, Opcode, ExitError, ExitReason, Capture, Context, CreateScheme, Stack, + Transfer, ExitSucceed, Runtime, +}; +use evm_runtime::{Config, Handler as HandlerT}; +use evm_gasometer::{self as gasometer, Gasometer}; +use crate::{ + Trait, Vicinity, Module, Event, Log, AccountCodes, AccountStorages, AddressMapping, + Runner as RunnerT, Error, CallInfo, CreateInfo, FeeCalculator, precompiles::Precompiles, +}; + +#[derive(Default)] +pub struct Runner { + _marker: PhantomData, +} + +impl RunnerT for Runner { + type Error = Error; + + fn call( + source: H160, + target: H160, + input: Vec, + value: U256, + gas_limit: u32, + gas_price: Option, + nonce: Option, + ) -> Result { + let gas_price = match gas_price { + Some(gas_price) => { + ensure!(gas_price >= T::FeeCalculator::min_gas_price(), Error::::GasPriceTooLow); + gas_price + }, + None => Default::default(), + }; + + if let Some(nonce) = nonce { + ensure!(Module::::account_basic(&source).nonce == nonce, Error::::InvalidNonce); + } + + let account_id = T::AddressMapping::into_account_id(source); + frame_system::Module::::inc_account_nonce(&account_id); + + frame_support::storage::with_transaction(|| { + let vicinity = Vicinity { + gas_price, + origin: source, + }; + + let config = T::config(); + + let mut substate = Handler::::new_with_precompile( + &vicinity, + gas_limit as usize, + false, + config, + T::Precompiles::execute, + ); + + let code = substate.code(target); + + let (reason, out) = substate.execute( + source, + target, + value, + code, + input, + ); + + let used_gas = U256::from(substate.used_gas()); + let logs = substate.logs.clone(); + + let call_info = CallInfo { + exit_reason: reason.clone().into(), + value: out, + used_gas, + logs, + }; + + match reason { + ExitReason::Succeed(_) => TransactionOutcome::Commit(Ok(call_info)), + ExitReason::Revert(_) => TransactionOutcome::Rollback(Ok(call_info)), + ExitReason::Error(_) => TransactionOutcome::Rollback(Ok(call_info)), + ExitReason::Fatal(_) => TransactionOutcome::Rollback(Ok(call_info)), + } + }) + } + + fn create( + source: H160, + init: Vec, + value: U256, + gas_limit: u32, + gas_price: Option, + nonce: Option, + ) -> Result { + let gas_price = match gas_price { + Some(gas_price) => { + ensure!(gas_price >= T::FeeCalculator::min_gas_price(), Error::::GasPriceTooLow); + gas_price + }, + None => Default::default(), + }; + + if let Some(nonce) = nonce { + ensure!(Module::::account_basic(&source).nonce == nonce, Error::::InvalidNonce); + } + + let account_id = T::AddressMapping::into_account_id(source); + frame_system::Module::::inc_account_nonce(&account_id); + + frame_support::storage::with_transaction(|| { + let vicinity = Vicinity { + gas_price, + origin: source, + }; + + let config = T::config(); + + let mut substate = Handler::::new_with_precompile( + &vicinity, + gas_limit as usize, + false, + config, + T::Precompiles::execute, + ); + + let address = substate.create_address(CreateScheme::Legacy { caller: source }); + + let (reason, out) = substate.execute( + source, + address, + value, + init, + Vec::new() + ); + + let used_gas = U256::from(substate.used_gas()); + let logs = substate.logs.clone(); + + let mut create_info = CreateInfo { + exit_reason: reason.clone().into(), + value: address, + used_gas, + logs, + }; + + match reason { + ExitReason::Succeed(_) => { + match substate.gasometer.record_deposit(out.len()) { + Ok(()) => { + AccountCodes::insert(address, out); + TransactionOutcome::Commit(Ok(create_info)) + }, + Err(e) => { + create_info.exit_reason = e.into(); + TransactionOutcome::Rollback(Ok(create_info)) + }, + } + }, + ExitReason::Revert(_) => TransactionOutcome::Rollback(Ok(create_info)), + ExitReason::Error(_) => TransactionOutcome::Rollback(Ok(create_info)), + ExitReason::Fatal(_) => TransactionOutcome::Rollback(Ok(create_info)), + } + }) + } + + fn create2( + source: H160, + init: Vec, + salt: H256, + value: U256, + gas_limit: u32, + gas_price: Option, + nonce: Option, + ) -> Result { + let gas_price = match gas_price { + Some(gas_price) => { + ensure!(gas_price >= T::FeeCalculator::min_gas_price(), Error::::GasPriceTooLow); + gas_price + }, + None => Default::default(), + }; + + if let Some(nonce) = nonce { + ensure!(Module::::account_basic(&source).nonce == nonce, Error::::InvalidNonce); + } + + let account_id = T::AddressMapping::into_account_id(source); + frame_system::Module::::inc_account_nonce(&account_id); + + frame_support::storage::with_transaction(|| { + let vicinity = Vicinity { + gas_price, + origin: source, + }; + + let config = T::config(); + + let mut substate = Handler::::new_with_precompile( + &vicinity, + gas_limit as usize, + false, + config, + T::Precompiles::execute, + ); + + let code_hash = H256::from_slice(Keccak256::digest(&init).as_slice()); + let address = substate.create_address( + CreateScheme::Create2 { caller: source, code_hash, salt } + ); + + let (reason, out) = substate.execute( + source, + address, + value, + init, + Vec::new() + ); + + let used_gas = U256::from(substate.used_gas()); + let logs = substate.logs.clone(); + + let mut create_info = CreateInfo { + exit_reason: reason.clone().into(), + value: address, + used_gas, + logs, + }; + + match reason { + ExitReason::Succeed(_) => { + match substate.gasometer.record_deposit(out.len()) { + Ok(()) => { + AccountCodes::insert(address, out); + TransactionOutcome::Commit(Ok(create_info)) + }, + Err(e) => { + create_info.exit_reason = e.into(); + TransactionOutcome::Rollback(Ok(create_info)) + }, + } + }, + ExitReason::Revert(_) => TransactionOutcome::Rollback(Ok(create_info)), + ExitReason::Error(_) => TransactionOutcome::Rollback(Ok(create_info)), + ExitReason::Fatal(_) => TransactionOutcome::Rollback(Ok(create_info)), + } + }) + } +} + +fn l64(gas: usize) -> usize { + gas - gas / 64 +} + +pub struct Handler<'vicinity, 'config, T: Trait> { + vicinity: &'vicinity Vicinity, + config: &'config Config, + gasometer: Gasometer<'config>, + deleted: BTreeSet, + logs: Vec, + precompile: fn(H160, &[u8], Option) -> + Option, usize), ExitError>>, + is_static: bool, + _marker: PhantomData, +} + +impl<'vicinity, 'config, T: Trait> Handler<'vicinity, 'config, T> { + /// Create a new handler with given vicinity. + pub fn new_with_precompile( + vicinity: &'vicinity Vicinity, + gas_limit: usize, + is_static: bool, + config: &'config Config, + precompile: fn(H160, &[u8], Option) -> + Option, usize), ExitError>>, + ) -> Self { + Self { + vicinity, + config, + is_static, + gasometer: Gasometer::new(gas_limit, config), + precompile, + logs: Vec::new(), + deleted: BTreeSet::default(), + _marker: PhantomData, + } + } + + /// Get used gas for the current executor, given the price. + pub fn used_gas( + &self, + ) -> usize { + self.gasometer.total_used_gas() - + min(self.gasometer.total_used_gas() / 2, self.gasometer.refunded_gas() as usize) + } + + pub fn execute( + &mut self, + caller: H160, + address: H160, + value: U256, + code: Vec, + input: Vec, + ) -> (ExitReason, Vec) { + let context = Context { + caller, + address, + apparent_value: value, + }; + + let mut runtime = Runtime::new( + Rc::new(code), + Rc::new(input), + context, + self.config, + ); + + let reason = match runtime.run(self) { + Capture::Exit(s) => s, + Capture::Trap(_) => unreachable!("Trap is Infallible"), + }; + + match reason { + ExitReason::Succeed(s) => { + (s.into(), runtime.machine().return_value()) + }, + ExitReason::Error(e) => { + (e.into(), Vec::new()) + }, + ExitReason::Revert(e) => { + (e.into(), runtime.machine().return_value()) + }, + ExitReason::Fatal(e) => { + self.gasometer.fail(); + (e.into(), Vec::new()) + }, + } + } + + fn transfer(&self, transfer: Transfer) -> Result<(), ExitError> { + let source = T::AddressMapping::into_account_id(transfer.source); + let target = T::AddressMapping::into_account_id(transfer.target); + + T::Currency::transfer( + &source, + &target, + transfer.value.low_u128().unique_saturated_into(), + ExistenceRequirement::AllowDeath, + ).map_err(|_| ExitError::OutOfGas) + } + + fn nonce(&self, address: H160) -> U256 { + let account = Module::::account_basic(&address); + account.nonce + } + + fn inc_nonce(&self, address: H160) { + let account_id = T::AddressMapping::into_account_id(address); + frame_system::Module::::inc_account_nonce(&account_id); + } + + fn create_address(&self, scheme: CreateScheme) -> H160 { + match scheme { + CreateScheme::Create2 { caller, code_hash, salt } => { + let mut hasher = Keccak256::new(); + hasher.input(&[0xff]); + hasher.input(&caller[..]); + hasher.input(&salt[..]); + hasher.input(&code_hash[..]); + H256::from_slice(hasher.result().as_slice()).into() + }, + CreateScheme::Legacy { caller } => { + let nonce = self.nonce(caller); + let mut stream = rlp::RlpStream::new_list(2); + stream.append(&caller); + stream.append(&nonce); + H256::from_slice(Keccak256::digest(&stream.out()).as_slice()).into() + }, + CreateScheme::Fixed(naddress) => { + naddress + }, + } + } +} + +impl<'vicinity, 'config, T: Trait> HandlerT for Handler<'vicinity, 'config, T> { + type CreateInterrupt = Infallible; + type CreateFeedback = Infallible; + type CallInterrupt = Infallible; + type CallFeedback = Infallible; + + fn balance(&self, address: H160) -> U256 { + let account = Module::::account_basic(&address); + account.balance + } + + fn code_size(&self, address: H160) -> U256 { + U256::from(AccountCodes::decode_len(&address).unwrap_or(0)) + } + + fn code_hash(&self, address: H160) -> H256 { + H256::from_slice(Keccak256::digest(&AccountCodes::get(&address)).as_slice()) + } + + fn code(&self, address: H160) -> Vec { + AccountCodes::get(&address) + } + + fn storage(&self, address: H160, index: H256) -> H256 { + AccountStorages::get(address, index) + } + + fn original_storage(&self, _address: H160, _index: H256) -> H256 { + // We do not have the concept of original storage in the native runner, so we always return + // empty value. This only affects gas calculation in the current EVM specification. + H256::default() + } + + fn gas_left(&self) -> U256 { + U256::from(self.gasometer.gas()) + } + + fn gas_price(&self) -> U256 { + self.vicinity.gas_price + } + + fn origin(&self) -> H160 { + self.vicinity.origin + } + + fn block_hash(&self, number: U256) -> H256 { + if number > U256::from(u32::max_value()) { + H256::default() + } else { + let number = T::BlockNumber::from(number.as_u32()); + H256::from_slice(frame_system::Module::::block_hash(number).as_ref()) + } + } + + fn block_number(&self) -> U256 { + let number: u128 = frame_system::Module::::block_number().unique_saturated_into(); + U256::from(number) + } + + fn block_coinbase(&self) -> H160 { + H160::default() + } + + fn block_timestamp(&self) -> U256 { + let now: u128 = pallet_timestamp::Module::::get().unique_saturated_into(); + U256::from(now / 1000) + } + + fn block_difficulty(&self) -> U256 { + U256::zero() + } + + fn block_gas_limit(&self) -> U256 { + U256::zero() + } + + fn chain_id(&self) -> U256 { + U256::from(T::ChainId::get()) + } + + fn exists(&self, _address: H160) -> bool { + true + } + + fn deleted(&self, address: H160) -> bool { + self.deleted.contains(&address) + } + + fn set_storage(&mut self, address: H160, index: H256, value: H256) -> Result<(), ExitError> { + if self.is_static { + return Err(ExitError::OutOfGas) + } + + if value == H256::default() { + AccountStorages::remove(address, index); + } else { + AccountStorages::insert(address, index, value); + } + + Ok(()) + } + + fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError> { + Module::::deposit_event(Event::::Log(Log { + address, + topics: topics.clone(), + data: data.clone(), + })); + + self.logs.push(Log { + address, topics, data + }); + + Ok(()) + } + + fn mark_delete(&mut self, address: H160, target: H160) -> Result<(), ExitError> { + if self.is_static { + return Err(ExitError::OutOfGas) + } + + let balance = self.balance(address); + + self.transfer(Transfer { + source: address, + target: target, + value: balance + })?; + + self.deleted.insert(address); + + Ok(()) + } + + fn create( + &mut self, + caller: H160, + scheme: CreateScheme, + value: U256, + init_code: Vec, + target_gas: Option, + ) -> Capture<(ExitReason, Option, Vec), Self::CreateInterrupt> { + macro_rules! try_or_fail { + ( $e:expr ) => { + match $e { + Ok(v) => v, + Err(e) => return Capture::Exit((e.into(), None, Vec::new())), + } + } + } + + if self.is_static { + return Capture::Exit((ExitError::OutOfGas.into(), None, Vec::new())) + } + + let mut after_gas = self.gasometer.gas(); + if self.config.call_l64_after_gas { + after_gas = l64(after_gas); + } + let target_gas = target_gas.unwrap_or(after_gas); + try_or_fail!(self.gasometer.record_cost(target_gas)); + + let address = self.create_address(scheme); + + frame_support::storage::with_transaction(|| { + macro_rules! try_or_fail { + ( $e:expr ) => { + match $e { + Ok(v) => v, + Err(e) => return TransactionOutcome::Rollback( + Capture::Exit((e.into(), None, Vec::new())) + ), + } + } + } + + let mut substate = Self::new_with_precompile( + self.vicinity, + target_gas, + self.is_static, + self.config, + self.precompile, + ); + + match substate.transfer(Transfer { + source: caller, + target: address, + value, + }) { + Ok(()) => (), + Err(e) => return TransactionOutcome::Rollback( + Capture::Exit((e.into(), None, Vec::new())) + ), + } + + substate.inc_nonce(caller); + + let (reason, out) = substate.execute( + caller, + address, + value, + init_code, + Vec::new(), + ); + + match reason { + ExitReason::Succeed(s) => { + match self.gasometer.record_deposit(out.len()) { + Ok(()) => { + try_or_fail!( + self.gasometer.record_stipend(substate.gasometer.gas()) + ); + try_or_fail!( + self.gasometer.record_refund(substate.gasometer.refunded_gas()) + ); + AccountCodes::insert(address, out); + + self.deleted.append(&mut substate.deleted); + self.logs.append(&mut substate.logs); + + TransactionOutcome::Commit( + Capture::Exit((s.into(), Some(address), Vec::new())) + ) + }, + Err(e) => { + TransactionOutcome::Rollback( + Capture::Exit((e.into(), None, Vec::new())) + ) + }, + } + }, + ExitReason::Revert(r) => { + TransactionOutcome::Rollback( + Capture::Exit((r.into(), None, out)) + ) + }, + ExitReason::Error(e) => { + TransactionOutcome::Rollback( + Capture::Exit((e.into(), None, Vec::new())) + ) + }, + ExitReason::Fatal(e) => { + self.gasometer.fail(); + TransactionOutcome::Rollback( + Capture::Exit((e.into(), None, Vec::new())) + ) + }, + } + }) + } + + fn call( + &mut self, + code_address: H160, + transfer: Option, + input: Vec, + target_gas: Option, + is_static: bool, + context: Context, + ) -> Capture<(ExitReason, Vec), Self::CallInterrupt> { + macro_rules! try_or_fail { + ( $e:expr ) => { + match $e { + Ok(v) => v, + Err(e) => return Capture::Exit((e.into(), Vec::new())), + } + } + } + + if self.is_static && transfer.is_some() { + return Capture::Exit((ExitError::OutOfGas.into(), Vec::new())) + } + + let mut after_gas = self.gasometer.gas(); + if self.config.call_l64_after_gas { + after_gas = l64(after_gas); + } + let target_gas = target_gas.unwrap_or(after_gas); + try_or_fail!(self.gasometer.record_cost(target_gas)); + + let code = self.code(code_address); + + frame_support::storage::with_transaction(|| { + macro_rules! try_or_fail { + ( $e:expr ) => { + match $e { + Ok(v) => v, + Err(e) => return TransactionOutcome::Rollback( + Capture::Exit((e.into(), Vec::new())) + ), + } + } + } + + let mut substate = Self::new_with_precompile( + self.vicinity, + target_gas, + self.is_static || is_static, + self.config, + self.precompile, + ); + + if let Some(transfer) = transfer { + match substate.transfer(transfer) { + Ok(()) => (), + Err(e) => return TransactionOutcome::Rollback( + Capture::Exit((e.into(), Vec::new())) + ), + } + } + + let (reason, out) = substate.execute( + context.caller, + context.address, + context.apparent_value, + code, + input, + ); + + match reason { + ExitReason::Succeed(s) => { + try_or_fail!( + self.gasometer.record_stipend(substate.gasometer.gas()) + ); + try_or_fail!( + self.gasometer.record_refund(substate.gasometer.refunded_gas()) + ); + + self.deleted.append(&mut substate.deleted); + self.logs.append(&mut substate.logs); + + TransactionOutcome::Commit( + Capture::Exit((s.into(), out)) + ) + }, + ExitReason::Revert(r) => { + TransactionOutcome::Rollback( + Capture::Exit((r.into(), out)) + ) + }, + ExitReason::Error(e) => { + TransactionOutcome::Rollback( + Capture::Exit((e.into(), Vec::new())) + ) + }, + ExitReason::Fatal(e) => { + self.gasometer.fail(); + TransactionOutcome::Rollback( + Capture::Exit((e.into(), Vec::new())) + ) + }, + } + }) + } + + fn pre_validate( + &mut self, + context: &Context, + opcode: Result, + stack: &Stack + ) -> Result<(), ExitError> { + let (gas_cost, memory_cost) = gasometer::opcode_cost( + context.address, opcode, stack, self.is_static, &self.config, self + )?; + + self.gasometer.record_opcode(gas_cost, memory_cost)?; + + Ok(()) + } +} + +impl<'vicinity, 'config, T: Trait> Drop for Handler<'vicinity, 'config, T> { + fn drop(&mut self) { + let mut deleted = BTreeSet::new(); + mem::swap(&mut deleted, &mut self.deleted); + + for address in deleted { + Module::::remove_account(&address); + } + } +} diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs new file mode 100644 index 0000000000000..511468815fac3 --- /dev/null +++ b/frame/evm/src/runner/stack.rs @@ -0,0 +1,391 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! EVM stack-based runner. + +use sp_std::marker::PhantomData; +use sp_std::vec::Vec; +use sp_core::{U256, H256, H160}; +use sp_runtime::traits::UniqueSaturatedInto; +use frame_support::{debug, ensure, traits::{Get, Currency}, storage::{StorageMap, StorageDoubleMap}}; +use sha3::{Keccak256, Digest}; +use sp_evm::{ExecutionInfo, CallInfo, CreateInfo, Account, Log, Vicinity}; +use evm::ExitReason; +use evm::backend::{Backend as BackendT, ApplyBackend, Apply}; +use evm::executor::StackExecutor; +use crate::{Trait, AccountStorages, FeeCalculator, AccountCodes, Module, Event, Error, AddressMapping}; +use crate::runner::Runner as RunnerT; +use crate::precompiles::Precompiles; + +#[derive(Default)] +pub struct Runner { + _marker: PhantomData, +} + +impl Runner { + /// Execute an EVM operation. + pub fn execute( + source: H160, + value: U256, + gas_limit: u32, + gas_price: Option, + nonce: Option, + f: F, + ) -> Result, Error> where + F: FnOnce(&mut StackExecutor>) -> (ExitReason, R), + { + // Gas price check is skipped when performing a gas estimation. + let gas_price = match gas_price { + Some(gas_price) => { + ensure!(gas_price >= T::FeeCalculator::min_gas_price(), Error::::GasPriceTooLow); + gas_price + }, + None => Default::default(), + }; + + let vicinity = Vicinity { + gas_price, + origin: source, + }; + + let mut backend = Backend::::new(&vicinity); + let mut executor = StackExecutor::new_with_precompile( + &backend, + gas_limit as usize, + T::config(), + T::Precompiles::execute, + ); + + let total_fee = gas_price.checked_mul(U256::from(gas_limit)) + .ok_or(Error::::FeeOverflow)?; + let total_payment = value.checked_add(total_fee).ok_or(Error::::PaymentOverflow)?; + let source_account = Module::::account_basic(&source); + ensure!(source_account.balance >= total_payment, Error::::BalanceLow); + executor.withdraw(source, total_fee).map_err(|_| Error::::WithdrawFailed)?; + + if let Some(nonce) = nonce { + ensure!(source_account.nonce == nonce, Error::::InvalidNonce); + } + + let (reason, retv) = f(&mut executor); + + let used_gas = U256::from(executor.used_gas()); + let actual_fee = executor.fee(gas_price); + debug::debug!( + target: "evm", + "Execution {:?} [source: {:?}, value: {}, gas_limit: {}, actual_fee: {}]", + reason, + source, + value, + gas_limit, + actual_fee + ); + executor.deposit(source, total_fee.saturating_sub(actual_fee)); + + let (values, logs) = executor.deconstruct(); + let logs_data = logs.into_iter().map(|x| x).collect::>(); + backend.apply(values, logs_data.clone(), true); + + Ok(ExecutionInfo { + value: retv, + exit_reason: reason, + used_gas, + logs: logs_data, + }) + } +} + +impl RunnerT for Runner { + type Error = Error; + + fn call( + source: H160, + target: H160, + input: Vec, + value: U256, + gas_limit: u32, + gas_price: Option, + nonce: Option, + ) -> Result { + Self::execute( + source, + value, + gas_limit, + gas_price, + nonce, + |executor| executor.transact_call( + source, + target, + value, + input, + gas_limit as usize, + ), + ) + } + + fn create( + source: H160, + init: Vec, + value: U256, + gas_limit: u32, + gas_price: Option, + nonce: Option, + ) -> Result { + Self::execute( + source, + value, + gas_limit, + gas_price, + nonce, + |executor| { + let address = executor.create_address( + evm::CreateScheme::Legacy { caller: source }, + ); + (executor.transact_create( + source, + value, + init, + gas_limit as usize, + ), address) + }, + ) + } + + fn create2( + source: H160, + init: Vec, + salt: H256, + value: U256, + gas_limit: u32, + gas_price: Option, + nonce: Option, + ) -> Result { + let code_hash = H256::from_slice(Keccak256::digest(&init).as_slice()); + Self::execute( + source, + value, + gas_limit, + gas_price, + nonce, + |executor| { + let address = executor.create_address( + evm::CreateScheme::Create2 { caller: source, code_hash, salt }, + ); + (executor.transact_create2( + source, + value, + init, + salt, + gas_limit as usize, + ), address) + }, + ) + } +} + +/// Substrate backend for EVM. +pub struct Backend<'vicinity, T> { + vicinity: &'vicinity Vicinity, + _marker: PhantomData, +} + +impl<'vicinity, T: Trait> Backend<'vicinity, T> { + /// Create a new backend with given vicinity. + pub fn new(vicinity: &'vicinity Vicinity) -> Self { + Self { vicinity, _marker: PhantomData } + } + + fn mutate_account_basic(&self, address: &H160, new: Account) { + let account_id = T::AddressMapping::into_account_id(*address); + let current = Module::::account_basic(address); + + if current.nonce < new.nonce { + // ASSUME: in one single EVM transaction, the nonce will not increase more than + // `u128::max_value()`. + for _ in 0..(new.nonce - current.nonce).low_u128() { + frame_system::Module::::inc_account_nonce(&account_id); + } + } + + if current.balance > new.balance { + let diff = current.balance - new.balance; + T::Currency::slash(&account_id, diff.low_u128().unique_saturated_into()); + } else if current.balance < new.balance { + let diff = new.balance - current.balance; + T::Currency::deposit_creating(&account_id, diff.low_u128().unique_saturated_into()); + } + } +} + +impl<'vicinity, T: Trait> BackendT for Backend<'vicinity, T> { + fn gas_price(&self) -> U256 { self.vicinity.gas_price } + fn origin(&self) -> H160 { self.vicinity.origin } + + fn block_hash(&self, number: U256) -> H256 { + if number > U256::from(u32::max_value()) { + H256::default() + } else { + let number = T::BlockNumber::from(number.as_u32()); + H256::from_slice(frame_system::Module::::block_hash(number).as_ref()) + } + } + + fn block_number(&self) -> U256 { + let number: u128 = frame_system::Module::::block_number().unique_saturated_into(); + U256::from(number) + } + + fn block_coinbase(&self) -> H160 { + H160::default() + } + + fn block_timestamp(&self) -> U256 { + let now: u128 = pallet_timestamp::Module::::get().unique_saturated_into(); + U256::from(now / 1000) + } + + fn block_difficulty(&self) -> U256 { + U256::zero() + } + + fn block_gas_limit(&self) -> U256 { + U256::zero() + } + + fn chain_id(&self) -> U256 { + U256::from(T::ChainId::get()) + } + + fn exists(&self, _address: H160) -> bool { + true + } + + fn basic(&self, address: H160) -> evm::backend::Basic { + let account = Module::::account_basic(&address); + + evm::backend::Basic { + balance: account.balance, + nonce: account.nonce, + } + } + + fn code_size(&self, address: H160) -> usize { + AccountCodes::decode_len(&address).unwrap_or(0) + } + + fn code_hash(&self, address: H160) -> H256 { + H256::from_slice(Keccak256::digest(&AccountCodes::get(&address)).as_slice()) + } + + fn code(&self, address: H160) -> Vec { + AccountCodes::get(&address) + } + + fn storage(&self, address: H160, index: H256) -> H256 { + AccountStorages::get(address, index) + } +} + +impl<'vicinity, T: Trait> ApplyBackend for Backend<'vicinity, T> { + fn apply( + &mut self, + values: A, + logs: L, + delete_empty: bool, + ) where + A: IntoIterator>, + I: IntoIterator, + L: IntoIterator, + { + for apply in values { + match apply { + Apply::Modify { + address, basic, code, storage, reset_storage, + } => { + self.mutate_account_basic(&address, Account { + nonce: basic.nonce, + balance: basic.balance, + }); + + if let Some(code) = code { + debug::debug!( + target: "evm", + "Inserting code ({} bytes) at {:?}", + code.len(), + address + ); + AccountCodes::insert(address, code); + } + + if reset_storage { + AccountStorages::remove_prefix(address); + } + + for (index, value) in storage { + if value == H256::default() { + debug::debug!( + target: "evm", + "Removing storage for {:?} [index: {:?}]", + address, + index + ); + AccountStorages::remove(address, index); + } else { + debug::debug!( + target: "evm", + "Updating storage for {:?} [index: {:?}, value: {:?}]", + address, + index, + value + ); + AccountStorages::insert(address, index, value); + } + } + + if delete_empty { + Module::::remove_account_if_empty(&address); + } + }, + Apply::Delete { address } => { + debug::debug!( + target: "evm", + "Deleting account at {:?}", + address + ); + Module::::remove_account(&address) + }, + } + } + + for log in logs { + debug::trace!( + target: "evm", + "Inserting log for {:?}, topics ({}) {:?}, data ({}): {:?}]", + log.address, + log.topics.len(), + log.topics, + log.data.len(), + log.data + ); + Module::::deposit_event(Event::::Log(Log { + address: log.address, + topics: log.topics, + data: log.data, + })); + } + } +} diff --git a/frame/evm/src/tests.rs b/frame/evm/src/tests.rs index d05fdca1407e5..eae78a35f3cbb 100644 --- a/frame/evm/src/tests.rs +++ b/frame/evm/src/tests.rs @@ -99,6 +99,7 @@ impl Trait for Test { type AddressMapping = HashedAddressMapping; type Currency = Balances; + type Runner = crate::runner::stack::Runner; type Event = Event; type Precompiles = (); @@ -167,23 +168,3 @@ fn fail_call_return_ok() { )); }); } - -#[test] -fn mutate_account_works() { - new_test_ext().execute_with(|| { - EVM::mutate_account_basic( - &H160::from_str("1000000000000000000000000000000000000001").unwrap(), - Account { - nonce: U256::from(10), - balance: U256::from(1000), - }, - ); - - assert_eq!(EVM::account_basic( - &H160::from_str("1000000000000000000000000000000000000001").unwrap() - ), Account { - nonce: U256::from(10), - balance: U256::from(1000), - }); - }); -} diff --git a/primitives/evm/Cargo.toml b/primitives/evm/Cargo.toml new file mode 100644 index 0000000000000..dd8b9db099601 --- /dev/null +++ b/primitives/evm/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "sp-evm" +version = "0.8.0" +license = "Apache-2.0" +authors = ["Parity Technologies "] +edition = "2018" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "Primitive EVM abstractions for Substrate." +documentation = "https://docs.rs/sp-evm" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +sp-core = { version = "2.0.0", path = "../core", default-features = false } +sp-std = { version = "2.0.0", path = "../std", default-features = false } +serde = { version = "1.0.101", optional = true, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "1.3.4", default-features = false } +evm = { version = "0.18", default-features = false, features = ["with-codec"] } + +[features] +default = ["std"] +std = [ + "sp-core/std", + "sp-std/std", + "serde", + "codec/std", + "evm/std", + "evm/with-serde", +] diff --git a/primitives/evm/src/lib.rs b/primitives/evm/src/lib.rs new file mode 100644 index 0000000000000..f4effc0990388 --- /dev/null +++ b/primitives/evm/src/lib.rs @@ -0,0 +1,56 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Encode, Decode}; +#[cfg(feature = "std")] +use serde::{Serialize, Deserialize}; +use sp_std::vec::Vec; +use sp_core::{U256, H160}; +use evm::ExitReason; + +pub use evm::backend::{Basic as Account, Log}; + +#[derive(Clone, Eq, PartialEq, Encode, Decode, Default)] +#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] +/// External input from the transaction. +pub struct Vicinity { + /// Current transaction gas price. + pub gas_price: U256, + /// Origin of the transaction. + pub origin: H160, +} + +#[derive(Clone, Eq, PartialEq, Encode, Decode)] +#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] +pub struct ExecutionInfo { + pub exit_reason: ExitReason, + pub value: T, + pub used_gas: U256, + pub logs: Vec, +} + +pub type CallInfo = ExecutionInfo>; +pub type CreateInfo = ExecutionInfo; + +#[derive(Clone, Eq, PartialEq, Encode, Decode)] +#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] +pub enum CallOrCreateInfo { + Call(CallInfo), + Create(CreateInfo), +}