// 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 execution module for Substrate // 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::{ensure, decl_module, decl_storage, decl_event, decl_error}; use frame_support::weights::Weight; use frame_support::traits::{Currency, ExistenceRequirement, Get}; use frame_system::RawOrigin; use sp_core::{U256, H256, H160, Hasher}; use sp_runtime::{ DispatchResult, 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, ) -> DispatchResult { T::CallOrigin::ensure_address_origin(&source, origin)?; match Self::execute_call( source, target, input, value, gas_limit, Some(gas_price), nonce, true, )? { (ExitReason::Succeed(_), _, _) => { Module::::deposit_event(Event::::Executed(target)); }, (_, _, _) => { Module::::deposit_event(Event::::ExecutedFailed(target)); }, } Ok(()) } /// 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, ) -> DispatchResult { T::CallOrigin::ensure_address_origin(&source, origin)?; match Self::execute_create( source, init, value, gas_limit, Some(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(()) } /// 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, ) -> DispatchResult { T::CallOrigin::ensure_address_origin(&source, origin)?; match Self::execute_create2( source, init, salt, value, gas_limit, Some(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(()) } } } 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: Option, nonce: Option, apply_state: bool, ) -> Result<(ExitReason, H160, U256), 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: Option, nonce: Option, apply_state: bool, ) -> Result<(ExitReason, H160, U256), 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: Option, nonce: Option, apply_state: bool, ) -> Result<(ExitReason, Vec, U256), 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: Option, nonce: Option, apply_state: bool, f: F, ) -> Result<(ExitReason, R, U256), Error> where F: FnOnce(&mut StackExecutor>) -> (ExitReason, R), { let gas_price = match gas_price { Some(gas_price) => { ensure!(gas_price >= T::FeeCalculator::min_gas_price(), Error::::GasPriceTooLow); gas_price }, None => U256::zero(), }; 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); executor.deposit(source, total_fee.saturating_sub(actual_fee)); if apply_state { let (values, logs) = executor.deconstruct(); backend.apply(values, logs, true); } Ok((retv, reason, used_gas)) } }