diff --git a/frame/evm/Cargo.toml b/frame/evm/Cargo.toml new file mode 100644 index 0000000000..a228dfb566 --- /dev/null +++ b/frame/evm/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "pallet-evm" +version = "2.0.0" +authors = ["Parity Technologies "] +edition = "2018" +license = "Apache-2.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME EVM contracts pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +serde = { version = "1.0.101", optional = true, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "1.3.4", default-features = false } +frame-support = { version = "2.0.0", default-features = false, path = "../support" } +frame-system = { version = "2.0.0", default-features = false, path = "../system" } +pallet-timestamp = { version = "2.0.0", default-features = false, path = "../timestamp" } +pallet-balances = { version = "2.0.0", default-features = false, path = "../balances" } +sp-core = { version = "2.0.0", default-features = false, path = "../../primitives/core" } +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" } +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 } +sha3 = { version = "0.8", default-features = false } +impl-trait-for-tuples = "0.1" +ripemd160 = { version = "0.9", default-features = false } + +[features] +default = ["std"] +std = [ + "serde", + "codec/std", + "sp-core/std", + "sp-runtime/std", + "frame-support/std", + "frame-system/std", + "pallet-balances/std", + "sp-io/std", + "sp-std/std", + "sha3/std", + "rlp/std", + "primitive-types/std", + "evm/std", + "pallet-timestamp/std", + "ripemd160/std", +] diff --git a/frame/evm/README.md b/frame/evm/README.md new file mode 100644 index 0000000000..499a0761cf --- /dev/null +++ b/frame/evm/README.md @@ -0,0 +1,29 @@ +# EVM Module + +The EVM module allows unmodified EVM code to be executed in a Substrate-based blockchain. +- [`evm::Trait`](https://docs.rs/pallet-evm/2.0.0/pallet_evm/trait.Trait.html) + +## EVM Engine + +The EVM module uses [`SputnikVM`](https://github.com/rust-blockchain/evm) as the underlying EVM engine. The engine is overhauled so that it's [`modular`](https://github.com/corepaper/evm). + +## Execution Lifecycle + +There are a separate set of accounts managed by the EVM module. Substrate based accounts can call the EVM Module to deposit or withdraw balance from the Substrate base-currency into a different balance managed and used by the EVM module. Once a user has populated their balance, they can create and call smart contracts using this module. + +There's one-to-one mapping from Substrate accounts and EVM external accounts that is defined by a conversion function. + +## EVM Module vs Ethereum Network + +The EVM module should be able to produce nearly identical results compared to the Ethereum mainnet, including gas cost and balance changes. + +Observable differences include: + +- The available length of block hashes may not be 256 depending on the configuration of the System module in the Substrate runtime. +- Difficulty and coinbase, which do not make sense in this module and is currently hard coded to zero. + +We currently do not aim to make unobservable behaviors, such as state root, to be the same. We also don't aim to follow the exact same transaction / receipt format. However, given one Ethereum transaction and one Substrate account's private key, one should be able to convert any Ethereum transaction into a transaction compatible with this module. + +The gas configurations are configurable. Right now, a pre-defined Istanbul hard fork configuration option is provided. + +License: Apache-2.0 \ No newline at end of file diff --git a/frame/evm/src/backend.rs b/frame/evm/src/backend.rs new file mode 100644 index 0000000000..b625c0c548 --- /dev/null +++ b/frame/evm/src/backend.rs @@ -0,0 +1,216 @@ +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 new file mode 100644 index 0000000000..e7812a5548 --- /dev/null +++ b/frame/evm/src/lib.rs @@ -0,0 +1,678 @@ +// 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 Module +//! +//! The EVM module allows unmodified EVM code to be executed in a Substrate-based blockchain. +//! - [`evm::Trait`] +//! +//! ## EVM Engine +//! +//! The EVM module uses [`SputnikVM`](https://github.com/rust-blockchain/evm) as the underlying EVM engine. +//! The engine is overhauled so that it's [`modular`](https://github.com/corepaper/evm). +//! +//! ## Execution Lifecycle +//! +//! There are a separate set of accounts managed by the EVM module. Substrate based accounts can call the EVM Module +//! to deposit or withdraw balance from the Substrate base-currency into a different balance managed and used by +//! the EVM module. Once a user has populated their balance, they can create and call smart contracts using this module. +//! +//! There's one-to-one mapping from Substrate accounts and EVM external accounts that is defined by a conversion function. +//! +//! ## EVM Module vs Ethereum Network +//! +//! The EVM module should be able to produce nearly identical results compared to the Ethereum mainnet, +//! including gas cost and balance changes. +//! +//! Observable differences include: +//! +//! - The available length of block hashes may not be 256 depending on the configuration of the System module +//! in the Substrate runtime. +//! - Difficulty and coinbase, which do not make sense in this module and is currently hard coded to zero. +//! +//! We currently do not aim to make unobservable behaviors, such as state root, to be the same. We also don't aim to follow +//! the exact same transaction / receipt format. However, given one Ethereum transaction and one Substrate account's +//! private key, one should be able to convert any Ethereum transaction into a transaction compatible with this module. +//! +//! The gas configurations are configurable. Right now, a pre-defined Istanbul hard fork configuration option is provided. + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +mod backend; +mod tests; +pub mod precompiles; + +pub use crate::precompiles::{Precompile, Precompiles}; +pub use crate::backend::{Account, Log, Vicinity, Backend}; + +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::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; + +/// Trait that outputs the current transaction gas price. +pub trait FeeCalculator { + /// Return the minimal required gas price. + fn min_gas_price() -> U256; +} + +impl FeeCalculator for () { + fn min_gas_price() -> U256 { U256::zero() } +} + +pub trait EnsureAddressOrigin { + /// Success return type. + type Success; + + /// Perform the origin check. + fn ensure_address_origin( + address: &H160, + origin: OuterOrigin, + ) -> Result { + Self::try_address_origin(address, origin).map_err(|_| BadOrigin) + } + + /// Try with origin. + fn try_address_origin( + address: &H160, + origin: OuterOrigin, + ) -> Result; +} + +/// Ensure that the EVM address is the same as the Substrate address. This only works if the account +/// ID is `H160`. +pub struct EnsureAddressSame; + +impl EnsureAddressOrigin for EnsureAddressSame where + OuterOrigin: Into, OuterOrigin>> + From>, +{ + type Success = H160; + + fn try_address_origin( + address: &H160, + origin: OuterOrigin, + ) -> Result { + origin.into().and_then(|o| match o { + RawOrigin::Signed(who) if &who == address => Ok(who), + r => Err(OuterOrigin::from(r)) + }) + } +} + +/// Ensure that the origin is root. +pub struct EnsureAddressRoot(sp_std::marker::PhantomData); + +impl EnsureAddressOrigin for EnsureAddressRoot where + OuterOrigin: Into, OuterOrigin>> + From>, +{ + type Success = (); + + fn try_address_origin( + _address: &H160, + origin: OuterOrigin, + ) -> Result<(), OuterOrigin> { + origin.into().and_then(|o| match o { + RawOrigin::Root => Ok(()), + r => Err(OuterOrigin::from(r)), + }) + } +} + +/// Ensure that the origin never happens. +pub struct EnsureAddressNever(sp_std::marker::PhantomData); + +impl EnsureAddressOrigin for EnsureAddressNever { + type Success = AccountId; + + fn try_address_origin( + _address: &H160, + origin: OuterOrigin, + ) -> Result { + Err(origin) + } +} + +/// Ensure that the address is truncated hash of the origin. Only works if the account id is +/// `AccountId32`. +pub struct EnsureAddressTruncated; + +impl EnsureAddressOrigin for EnsureAddressTruncated where + OuterOrigin: Into, OuterOrigin>> + From>, +{ + type Success = AccountId32; + + fn try_address_origin( + address: &H160, + origin: OuterOrigin, + ) -> Result { + origin.into().and_then(|o| match o { + RawOrigin::Signed(who) + if AsRef::<[u8; 32]>::as_ref(&who)[0..20] == address[0..20] => Ok(who), + r => Err(OuterOrigin::from(r)) + }) + } +} + +pub trait AddressMapping { + fn into_account_id(address: H160) -> A; +} + +/// Identity address mapping. +pub struct IdentityAddressMapping; + +impl AddressMapping for IdentityAddressMapping { + fn into_account_id(address: H160) -> H160 { address } +} + +/// Hashed address mapping. +pub struct HashedAddressMapping(sp_std::marker::PhantomData); + +impl> AddressMapping for HashedAddressMapping { + fn into_account_id(address: H160) -> AccountId32 { + let mut data = [0u8; 24]; + data[0..4].copy_from_slice(b"evm:"); + data[4..24].copy_from_slice(&address[..]); + let hash = H::hash(&data); + + AccountId32::from(Into::<[u8; 32]>::into(hash)) + } +} + +/// Substrate system chain ID. +pub struct SystemChainId; + +impl Get for SystemChainId { + fn get() -> u64 { + sp_io::misc::chain_id() + } +} + +static ISTANBUL_CONFIG: Config = Config::istanbul(); + +/// EVM module trait +pub trait Trait: frame_system::Trait + pallet_timestamp::Trait { + /// Calculator for current gas price. + type FeeCalculator: FeeCalculator; + + /// Allow the origin to call on behalf of given address. + type CallOrigin: EnsureAddressOrigin; + /// Allow the origin to withdraw on behalf of given address. + type WithdrawOrigin: EnsureAddressOrigin; + + /// Mapping from address to account id. + type AddressMapping: AddressMapping; + /// Currency type for withdraw and balance storage. + type Currency: Currency; + + /// The overarching event type. + type Event: From> + Into<::Event>; + /// Precompiles associated with this EVM engine. + type Precompiles: Precompiles; + /// Chain ID of EVM. + type ChainId: Get; + + /// EVM config used in the module. + fn config() -> &'static Config { + &ISTANBUL_CONFIG + } +} + +#[cfg(feature = "std")] +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, Serialize, Deserialize)] +/// Account definition used for genesis block construction. +pub struct GenesisAccount { + /// Account nonce. + pub nonce: U256, + /// Account balance. + pub balance: U256, + /// Full account storage. + pub storage: std::collections::BTreeMap, + /// Account code. + pub code: Vec, +} + +decl_storage! { + trait Store for Module as EVM { + AccountCodes get(fn account_codes): map hasher(blake2_128_concat) H160 => Vec; + AccountStorages get(fn account_storages): + double_map hasher(blake2_128_concat) H160, hasher(blake2_128_concat) H256 => H256; + } + + add_extra_genesis { + 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, + }); + AccountCodes::insert(address, &account.code); + + for (index, value) in &account.storage { + AccountStorages::insert(address, index, value); + } + } + }); + } +} + +decl_event! { + /// EVM events + pub enum Event where + ::AccountId, + { + /// Ethereum events from contracts. + Log(Log), + /// A contract has been created at given \[address\]. + Created(H160), + /// A \[contract\] was attempted to be created, but the execution failed. + CreatedFailed(H160), + /// A \[contract\] has been executed successfully with states applied. + Executed(H160), + /// A \[contract\] has been executed with errors. States are reverted with only gas fees applied. + ExecutedFailed(H160), + /// A deposit has been made at a given address. \[sender, address, value\] + BalanceDeposit(AccountId, H160, U256), + /// A withdrawal has been made from a given address. \[sender, address, value\] + BalanceWithdraw(AccountId, H160, U256), + } +} + +decl_error! { + pub enum Error for Module { + /// Not enough balance to perform action + BalanceLow, + /// Calculating total fee overflowed + FeeOverflow, + /// Calculating total payment overflowed + PaymentOverflow, + /// Withdraw fee failed + WithdrawFailed, + /// Gas price is too low. + GasPriceTooLow, + /// Nonce is invalid + InvalidNonce, + } +} + +decl_module! { + pub struct Module for enum Call where origin: T::Origin { + type Error = Error; + + fn deposit_event() = default; + + /// Withdraw balance from EVM into currency/balances module. + #[weight = 0] + fn withdraw(origin, address: H160, value: BalanceOf) { + let destination = T::WithdrawOrigin::ensure_address_origin(&address, origin)?; + let address_account_id = T::AddressMapping::into_account_id(address); + + T::Currency::transfer( + &address_account_id, + &destination, + value, + ExistenceRequirement::AllowDeath + )?; + } + + /// Issue an EVM call operation. This is similar to a message call transaction in Ethereum. + #[weight = (*gas_price).saturated_into::().saturating_mul(*gas_limit as Weight)] + fn call( + origin, + source: H160, + target: H160, + input: Vec, + value: U256, + gas_limit: u32, + gas_price: U256, + nonce: Option, + ) -> DispatchResultWithPostInfo { + T::CallOrigin::ensure_address_origin(&source, origin)?; + + match Self::execute_call( + source, + target, + input, + value, + gas_limit, + gas_price, + nonce, + true, + )? { + (ExitReason::Succeed(_), _, _, _) => { + Module::::deposit_event(Event::::Executed(target)); + }, + (_, _, _, _) => { + Module::::deposit_event(Event::::ExecutedFailed(target)); + }, + } + + Ok(Pays::No.into()) + } + + /// Issue an EVM create operation. This is similar to a contract creation transaction in + /// Ethereum. + #[weight = (*gas_price).saturated_into::().saturating_mul(*gas_limit as Weight)] + fn create( + origin, + source: H160, + init: Vec, + value: U256, + gas_limit: u32, + gas_price: U256, + nonce: Option, + ) -> DispatchResultWithPostInfo { + T::CallOrigin::ensure_address_origin(&source, origin)?; + + match Self::execute_create( + source, + init, + value, + gas_limit, + gas_price, + nonce, + true, + )? { + (ExitReason::Succeed(_), create_address, _, _) => { + Module::::deposit_event(Event::::Created(create_address)); + }, + (_, create_address, _, _) => { + Module::::deposit_event(Event::::CreatedFailed(create_address)); + }, + } + + Ok(Pays::No.into()) + } + + /// Issue an EVM create2 operation. + #[weight = (*gas_price).saturated_into::().saturating_mul(*gas_limit as Weight)] + fn create2( + origin, + source: H160, + init: Vec, + salt: H256, + value: U256, + gas_limit: u32, + gas_price: U256, + nonce: Option, + ) -> DispatchResultWithPostInfo { + T::CallOrigin::ensure_address_origin(&source, origin)?; + + match Self::execute_create2( + source, + init, + salt, + value, + gas_limit, + gas_price, + nonce, + true, + )? { + (ExitReason::Succeed(_), create_address, _, _) => { + Module::::deposit_event(Event::::Created(create_address)); + }, + (_, create_address, _, _) => { + Module::::deposit_event(Event::::CreatedFailed(create_address)); + }, + } + + Ok(Pays::No.into()) + } + } +} + +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); + let code_len = AccountCodes::decode_len(address).unwrap_or(0); + + account.nonce == U256::zero() && + account.balance == U256::zero() && + code_len == 0 + } + + /// Remove an account if its empty. + pub fn remove_account_if_empty(address: &H160) { + if Self::is_account_empty(address) { + Self::remove_account(address); + } + } + + /// Get the account basic in EVM format. + pub fn account_basic(address: &H160) -> Account { + let account_id = T::AddressMapping::into_account_id(*address); + + let nonce = frame_system::Module::::account_nonce(&account_id); + let balance = T::Currency::free_balance(&account_id); + + Account { + nonce: U256::from(UniqueSaturatedInto::::unique_saturated_into(nonce)), + 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 new file mode 100644 index 0000000000..440d9bf1c6 --- /dev/null +++ b/frame/evm/src/precompiles.rs @@ -0,0 +1,167 @@ +// 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. + +//! Builtin precompiles. + +use sp_std::{cmp::min, vec::Vec}; +use sp_core::H160; +use evm::{ExitError, ExitSucceed}; +use ripemd160::Digest; +use impl_trait_for_tuples::impl_for_tuples; + +/// Custom precompiles to be used by EVM engine. +pub trait Precompiles { + /// Try to execute the code address as precompile. If the code address is not + /// a precompile or the precompile is not yet available, return `None`. + /// Otherwise, calculate the amount of gas needed with given `input` and + /// `target_gas`. Return `Some(Ok(status, output, gas_used))` if the execution + /// is successful. Otherwise return `Some(Err(_))`. + fn execute( + address: H160, + input: &[u8], + target_gas: Option, + ) -> Option, usize), ExitError>>; +} + +/// One single precompile used by EVM engine. +pub trait Precompile { + /// Try to execute the precompile. Calculate the amount of gas needed with given `input` and + /// `target_gas`. Return `Ok(status, output, gas_used)` if the execution is + /// successful. Otherwise return `Err(_)`. + fn execute( + input: &[u8], + target_gas: Option, + ) -> core::result::Result<(ExitSucceed, Vec, usize), ExitError>; +} + +#[impl_for_tuples(16)] +#[tuple_types_no_default_trait_bound] +impl Precompiles for Tuple { + for_tuples!( where #( Tuple: Precompile )* ); + + fn execute( + address: H160, + input: &[u8], + target_gas: Option, + ) -> Option, usize), ExitError>> { + let mut index = 0; + + for_tuples!( #( + index += 1; + if address == H160::from_low_u64_be(index) { + return Some(Tuple::execute(input, target_gas)) + } + )* ); + + None + } +} + +/// Linear gas cost +fn ensure_linear_cost( + target_gas: Option, + len: usize, + base: usize, + word: usize +) -> Result { + let cost = base.checked_add( + word.checked_mul(len.saturating_add(31) / 32).ok_or(ExitError::OutOfGas)? + ).ok_or(ExitError::OutOfGas)?; + + if let Some(target_gas) = target_gas { + if cost > target_gas { + return Err(ExitError::OutOfGas) + } + } + + Ok(cost) +} + +/// The identity precompile. +pub struct Identity; + +impl Precompile for Identity { + fn execute( + input: &[u8], + target_gas: Option, + ) -> core::result::Result<(ExitSucceed, Vec, usize), ExitError> { + let cost = ensure_linear_cost(target_gas, input.len(), 15, 3)?; + + Ok((ExitSucceed::Returned, input.to_vec(), cost)) + } +} + +/// The ecrecover precompile. +pub struct ECRecover; + +impl Precompile for ECRecover { + fn execute( + i: &[u8], + target_gas: Option, + ) -> core::result::Result<(ExitSucceed, Vec, usize), ExitError> { + let cost = ensure_linear_cost(target_gas, i.len(), 3000, 0)?; + + let mut input = [0u8; 128]; + input[..min(i.len(), 128)].copy_from_slice(&i[..min(i.len(), 128)]); + + let mut msg = [0u8; 32]; + let mut sig = [0u8; 65]; + + msg[0..32].copy_from_slice(&input[0..32]); + sig[0..32].copy_from_slice(&input[64..96]); + sig[32..64].copy_from_slice(&input[96..128]); + sig[64] = input[63]; + + let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg) + .map_err(|_| ExitError::Other("Public key recover failed"))?; + let mut address = sp_io::hashing::keccak_256(&pubkey); + address[0..12].copy_from_slice(&[0u8; 12]); + + Ok((ExitSucceed::Returned, address.to_vec(), cost)) + } +} + +/// The ripemd precompile. +pub struct Ripemd160; + +impl Precompile for Ripemd160 { + fn execute( + input: &[u8], + target_gas: Option, + ) -> core::result::Result<(ExitSucceed, Vec, usize), ExitError> { + let cost = ensure_linear_cost(target_gas, input.len(), 600, 120)?; + + let mut ret = [0u8; 32]; + ret[12..32].copy_from_slice(&ripemd160::Ripemd160::digest(input)); + Ok((ExitSucceed::Returned, ret.to_vec(), cost)) + } +} + +/// The sha256 precompile. +pub struct Sha256; + +impl Precompile for Sha256 { + fn execute( + input: &[u8], + target_gas: Option, + ) -> core::result::Result<(ExitSucceed, Vec, usize), ExitError> { + let cost = ensure_linear_cost(target_gas, input.len(), 60, 12)?; + + let ret = sp_io::hashing::sha2_256(input); + Ok((ExitSucceed::Returned, ret.to_vec(), cost)) + } +} diff --git a/frame/evm/src/tests.rs b/frame/evm/src/tests.rs new file mode 100644 index 0000000000..d05fdca140 --- /dev/null +++ b/frame/evm/src/tests.rs @@ -0,0 +1,189 @@ +#![cfg(test)] + +use super::*; + +use std::{str::FromStr, collections::BTreeMap}; +use frame_support::{ + assert_ok, impl_outer_origin, parameter_types, impl_outer_dispatch, +}; +use sp_core::{Blake2Hasher, H256}; +use sp_runtime::{ + Perbill, + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +impl_outer_origin! { + pub enum Origin for Test where system = frame_system {} +} + +impl_outer_dispatch! { + pub enum OuterCall for Test where origin: Origin { + self::EVM, + } +} + +#[derive(Clone, Eq, PartialEq)] +pub struct Test; +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} +impl frame_system::Trait for Test { + type BaseCallFilter = (); + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Call = OuterCall; + type Hashing = BlakeTwo256; + type AccountId = AccountId32; + type Lookup = IdentityLookup; + type Header = Header; + type Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type DbWeight = (); + type BlockExecutionWeight = (); + type ExtrinsicBaseWeight = (); + type MaximumExtrinsicWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); + type PalletInfo = (); + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); +} + +parameter_types! { + pub const ExistentialDeposit: u64 = 1; +} +impl pallet_balances::Trait for Test { + type MaxLocks = (); + type Balance = u64; + type DustRemoval = (); + type Event = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); +} + +parameter_types! { + pub const MinimumPeriod: u64 = 1000; +} +impl pallet_timestamp::Trait for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +/// Fixed gas price of `0`. +pub struct FixedGasPrice; +impl FeeCalculator for FixedGasPrice { + fn min_gas_price() -> U256 { + // Gas price is always one token per gas. + 0.into() + } +} + +impl Trait for Test { + type FeeCalculator = FixedGasPrice; + + type CallOrigin = EnsureAddressRoot; + type WithdrawOrigin = EnsureAddressNever; + + type AddressMapping = HashedAddressMapping; + type Currency = Balances; + + type Event = Event; + type Precompiles = (); + type ChainId = SystemChainId; +} + +type System = frame_system::Module; +type Balances = pallet_balances::Module; +type EVM = Module; + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let mut accounts = BTreeMap::new(); + accounts.insert( + H160::from_str("1000000000000000000000000000000000000001").unwrap(), + GenesisAccount { + nonce: U256::from(1), + balance: U256::from(1000000), + storage: Default::default(), + code: vec![ + 0x00, // STOP + ], + } + ); + accounts.insert( + H160::from_str("1000000000000000000000000000000000000002").unwrap(), + GenesisAccount { + nonce: U256::from(1), + balance: U256::from(1000000), + storage: Default::default(), + code: vec![ + 0xff, // INVALID + ], + } + ); + + pallet_balances::GenesisConfig::::default().assimilate_storage(&mut t).unwrap(); + GenesisConfig { accounts }.assimilate_storage::(&mut t).unwrap(); + t.into() +} + +#[test] +fn fail_call_return_ok() { + new_test_ext().execute_with(|| { + assert_ok!(EVM::call( + Origin::root(), + H160::default(), + H160::from_str("1000000000000000000000000000000000000001").unwrap(), + Vec::new(), + U256::default(), + 1000000, + U256::default(), + None, + )); + + assert_ok!(EVM::call( + Origin::root(), + H160::default(), + H160::from_str("1000000000000000000000000000000000000002").unwrap(), + Vec::new(), + U256::default(), + 1000000, + U256::default(), + None, + )); + }); +} + +#[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), + }); + }); +}