From 5995741d36a5f63654d2056b9e581a4ee35b3018 Mon Sep 17 00:00:00 2001 From: bear Date: Mon, 7 Jun 2021 17:39:29 +0800 Subject: [PATCH] Upgrade EVM pallet to FRAMEv2 (#648) * darwinia-evm new style * Update license * Change `Module::` to `Pallet` * Add lost comment * update format Co-authored-by: Xavier Lau --- bin/node/runtime/pangolin/src/lib.rs | 4 +- frame/dvm-dynamic-fee/src/lib.rs | 25 +- .../evm/precompile/contracts/kton/src/lib.rs | 6 +- frame/evm/src/lib.rs | 635 +++++++++--------- frame/evm/src/runner/mod.rs | 35 +- frame/evm/src/runner/stack.rs | 84 ++- frame/evm/src/tests.rs | 43 +- 7 files changed, 439 insertions(+), 393 deletions(-) diff --git a/bin/node/runtime/pangolin/src/lib.rs b/bin/node/runtime/pangolin/src/lib.rs index 30f0a2907a..43bd720b38 100644 --- a/bin/node/runtime/pangolin/src/lib.rs +++ b/bin/node/runtime/pangolin/src/lib.rs @@ -656,7 +656,7 @@ impl_runtime_apis! { } fn account_code_at(address: H160) -> Vec { - darwinia_evm::Module::::account_codes(address) + darwinia_evm::Pallet::::account_codes(address) } fn author() -> H160 { @@ -666,7 +666,7 @@ impl_runtime_apis! { fn storage_at(address: H160, index: U256) -> H256 { let mut tmp = [0u8; 32]; index.to_big_endian(&mut tmp); - darwinia_evm::Module::::account_storages(address, H256::from_slice(&tmp[..])) + darwinia_evm::Pallet::::account_storages(address, H256::from_slice(&tmp[..])) } fn call( diff --git a/frame/dvm-dynamic-fee/src/lib.rs b/frame/dvm-dynamic-fee/src/lib.rs index 48a6420850..0101a53240 100644 --- a/frame/dvm-dynamic-fee/src/lib.rs +++ b/frame/dvm-dynamic-fee/src/lib.rs @@ -1,19 +1,20 @@ -// SPDX-License-Identifier: Apache-2.0 -// This file is part of Frontier. +// This file is part of Darwinia. // -// Copyright (c) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2018-2021 Darwinia Network +// SPDX-License-Identifier: GPL-3.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 +// Darwinia is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// http://www.apache.org/licenses/LICENSE-2.0 +// Darwinia is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 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. +// You should have received a copy of the GNU General Public License +// along with Darwinia. If not, see . #![cfg_attr(not(feature = "std"), no_std)] diff --git a/frame/evm/precompile/contracts/kton/src/lib.rs b/frame/evm/precompile/contracts/kton/src/lib.rs index b91e8c3506..4062bb5742 100644 --- a/frame/evm/precompile/contracts/kton/src/lib.rs +++ b/frame/evm/precompile/contracts/kton/src/lib.rs @@ -30,7 +30,7 @@ use sp_core::{H160, U256}; use sp_runtime::{traits::UniqueSaturatedInto, SaturatedConversion}; use sp_std::{borrow::ToOwned, marker::PhantomData, prelude::*, vec::Vec}; -use darwinia_evm::{Account, AccountBasic, Config, Module, Runner}; +use darwinia_evm::{Account, AccountBasic, Config, Pallet, Runner}; use darwinia_support::evm::POW_9; use dp_evm::Precompile; use dvm_ethereum::{ @@ -73,7 +73,7 @@ impl Precompile for Kton { Action::TransferAndCall(call_data) => { // Ensure wkton is a contract ensure!( - !crate::Module::::is_contract_code_empty(&call_data.wkton_address), + !crate::Pallet::::is_contract_code_empty(&call_data.wkton_address), ExitError::Other("Wkton must be a contract!".into()) ); // Ensure context's apparent_value is zero, since the transfer value is encoded in input field @@ -119,7 +119,7 @@ impl Precompile for Kton { Action::Withdraw(wd) => { // Ensure wkton is a contract ensure!( - !crate::Module::::is_contract_code_empty(&context.caller), + !crate::Pallet::::is_contract_code_empty(&context.caller), ExitError::Other("The caller must be wkton contract!".into()) ); // Ensure context's apparent_value is zero diff --git a/frame/evm/src/lib.rs b/frame/evm/src/lib.rs index ee33493d9b..5b373d674c 100644 --- a/frame/evm/src/lib.rs +++ b/frame/evm/src/lib.rs @@ -1,40 +1,48 @@ -// 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 +// This file is part of Darwinia. +// +// Copyright (C) 2018-2021 Darwinia Network +// SPDX-License-Identifier: GPL-3.0 // -// http://www.apache.org/licenses/LICENSE-2.0 +// Darwinia is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 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. +// Darwinia is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Darwinia. If not, see . -//! EVM execution module for Substrate +//! EVM execution pallet for Substrate // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] pub mod runner; + +#[cfg(test)] mod tests; pub use crate::runner::Runner; -// --- darwinia --- pub use dp_evm::{ Account, CallInfo, CreateInfo, ExecutionInfo, LinearCostPrecompile, Log, Precompile, PrecompileSet, Vicinity, }; + +// --- crates.io --- +#[cfg(feature = "std")] +use codec::{Decode, Encode}; +use evm::{Config as EvmConfig, ExitError, ExitReason}; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + // --- substrate --- use frame_support::{ - decl_error, decl_event, decl_module, decl_storage, - dispatch::DispatchResultWithPostInfo, - traits::{Currency, Get}, - weights::{Pays, PostDispatchInfo, Weight}, + traits::Currency, + weights::{PostDispatchInfo, Weight}, }; use frame_system::RawOrigin; use sp_core::{H160, H256, U256}; @@ -42,206 +50,60 @@ use sp_runtime::{ traits::{BadOrigin, UniqueSaturatedInto}, AccountId32, DispatchResult, }; -use sp_std::vec::Vec; -// --- std --- -#[cfg(feature = "std")] -use codec::{Decode, Encode}; -use evm::{Config as EvmConfig, ExitError, ExitReason}; -#[cfg(feature = "std")] -use serde::{Deserialize, Serialize}; - -/// Config 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 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; -} - -pub struct ConcatAddressMapping; - -/// The ConcatAddressMapping used for transfer from evm 20-length to substrate 32-length address -/// The concat rule inclued three parts: -/// 1. AccountId Prefix: concat("dvm", "0x00000000000000"), length: 11 byetes -/// 2. EVM address: the original evm address, length: 20 bytes -/// 3. CheckSum: byte_xor(AccountId Prefix + EVM address), length: 1 bytes -impl AddressMapping for ConcatAddressMapping { - fn into_account_id(address: H160) -> AccountId32 { - let mut data = [0u8; 32]; - data[0..4].copy_from_slice(b"dvm:"); - data[11..31].copy_from_slice(&address[..]); - let checksum: u8 = data[1..31].iter().fold(data[0], |sum, &byte| sum ^ byte); - data[31] = checksum; - AccountId32::from(data) - } -} - -pub trait AccountBasic { - fn account_basic(address: &H160) -> Account; - fn mutate_account_basic(address: &H160, new: Account); - fn transfer(source: &H160, target: &H160, value: U256) -> Result<(), ExitError>; -} - -/// A mapping function that converts Ethereum gas to Substrate weight -pub trait GasWeightMapping { - fn gas_to_weight(gas: u64) -> Weight; - fn weight_to_gas(weight: Weight) -> u64; -} -impl GasWeightMapping for () { - fn gas_to_weight(gas: u64) -> Weight { - gas as Weight - } - fn weight_to_gas(weight: Weight) -> u64 { - weight - } -} - -/// A contract handle for ethereum issuing -pub trait IssuingHandler { - fn handle(address: H160, caller: H160, input: &[u8]) -> DispatchResult; -} -/// A default empty issuingHandler, usually used in the test scenario. -impl IssuingHandler for () { - fn handle(_: H160, _: H160, _: &[u8]) -> DispatchResult { - Ok(()) - } -} +use sp_std::prelude::*; static ISTANBUL_CONFIG: EvmConfig = EvmConfig::istanbul(); -/// EVM module trait -pub trait Config: frame_system::Config + pallet_timestamp::Config { - /// Calculator for current gas price. - type FeeCalculator: FeeCalculator; - /// Maps Ethereum gas to Substrate weight. - type GasWeightMapping: GasWeightMapping; - /// Allow the origin to call on behalf of given address. - type CallOrigin: EnsureAddressOrigin; - - /// Mapping from address to account id. - type AddressMapping: AddressMapping; - /// Ring Currency type - type RingCurrency: Currency; - /// Kton Currency type - type KtonCurrency: Currency; - - /// The overarching event type. - type Event: From> + Into<::Event>; - /// Precompiles associated with this EVM engine. - type Precompiles: PrecompileSet; - /// Chain ID of EVM. - type ChainId: Get; - /// The block gas limit. Can be a simple constant, or an adjustment algorithm in another pallet. - type BlockGasLimit: Get; - /// EVM execution runner. - type Runner: Runner; - /// The account basic mapping way - type RingAccountBasic: AccountBasic; - type KtonAccountBasic: AccountBasic; - /// Issuing contracts handler - type IssuingHandler: IssuingHandler; - - /// EVM config used in the module. - fn config() -> &'static EvmConfig { - &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 { - pub AccountCodes get(fn account_codes): map hasher(blake2_128_concat) H160 => Vec; - pub 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 { - T::RingAccountBasic::mutate_account_basic(&address, Account { - balance: account.balance, - nonce: account.nonce, - }); - T::KtonAccountBasic::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); - } - } - }); +#[frame_support::pallet] +pub mod pallet { + // --- substrate --- + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + // --- darwinia --- + use crate::*; + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_timestamp::Config { + /// Calculator for current gas price. + type FeeCalculator: FeeCalculator; + /// Maps Ethereum gas to Substrate weight. + type GasWeightMapping: GasWeightMapping; + /// Allow the origin to call on behalf of given address. + type CallOrigin: EnsureAddressOrigin; + + /// Mapping from address to account id. + type AddressMapping: AddressMapping; + /// Ring Currency type + type RingCurrency: Currency; + /// Kton Currency type + type KtonCurrency: Currency; + + /// The overarching event type. + type Event: From> + IsType<::Event>; + /// Precompiles associated with this EVM engine. + type Precompiles: PrecompileSet; + /// Chain ID of EVM. + type ChainId: Get; + /// The block gas limit. Can be a simple constant, or an adjustment algorithm in another pallet. + type BlockGasLimit: Get; + /// EVM execution runner. + type Runner: Runner; + /// The account basic mapping way + type RingAccountBasic: AccountBasic; + type KtonAccountBasic: AccountBasic; + /// Issuing contracts handler + type IssuingHandler: IssuingHandler; + + /// EVM config used in the Pallet. + fn config() -> &'static EvmConfig { + &ISTANBUL_CONFIG + } } -} -decl_event! { - /// EVM events - pub enum Event where - ::AccountId, - { + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + #[pallet::metadata(T::AccountId = "AccountId")] + pub enum Event { /// Ethereum events from contracts. Log(Log), /// A contract has been created at given \[address\]. @@ -253,14 +115,13 @@ decl_event! { /// 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), + BalanceDeposit(T::AccountId, H160, U256), /// A withdrawal has been made from a given address. \[sender, address, value\] - BalanceWithdraw(AccountId, H160, U256), + BalanceWithdraw(T::AccountId, H160, U256), } -} -decl_error! { - pub enum Error for Module { + #[pallet::error] + pub enum Error { /// Not enough balance to perform action BalanceLow, /// Calculating total fee overflowed @@ -274,18 +135,68 @@ decl_error! { /// Nonce is invalid InvalidNonce, } -} -decl_module! { - pub struct Module for enum Call where origin: T::Origin { - type Error = Error; + #[pallet::storage] + #[pallet::getter(fn account_codes)] + pub type AccountCodes = StorageMap<_, Blake2_128Concat, H160, Vec, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn account_storages)] + pub type AccountStorages = + StorageDoubleMap<_, Blake2_128Concat, H160, Blake2_128Concat, H256, H256, ValueQuery>; - fn deposit_event() = default; + #[pallet::genesis_config] + pub struct GenesisConfig { + pub accounts: std::collections::BTreeMap, + } + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + Self { + accounts: Default::default(), + } + } + } + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + let extra_genesis_builder: fn(&Self) = |config: &GenesisConfig| { + for (address, account) in &config.accounts { + T::RingAccountBasic::mutate_account_basic( + &address, + Account { + balance: account.balance, + nonce: account.nonce, + }, + ); + T::KtonAccountBasic::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); + } + } + }; + extra_genesis_builder(self); + } + } + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData); + #[pallet::hooks] + impl Hooks> for Pallet {} + #[pallet::call] + impl Pallet { /// Issue an EVM call operation. This is similar to a message call transaction in Ethereum. - #[weight = T::GasWeightMapping::gas_to_weight(*gas_limit)] - fn call( - origin, + #[pallet::weight(T::GasWeightMapping::gas_to_weight(*gas_limit))] + pub(super) fn call( + origin: OriginFor, source: H160, target: H160, input: Vec, @@ -309,24 +220,26 @@ decl_module! { match info.exit_reason { ExitReason::Succeed(_) => { - Module::::deposit_event(Event::::Executed(target)); - }, + Pallet::::deposit_event(Event::::Executed(target)); + } _ => { - Module::::deposit_event(Event::::ExecutedFailed(target)); - }, + Pallet::::deposit_event(Event::::ExecutedFailed(target)); + } }; Ok(PostDispatchInfo { - actual_weight: Some(T::GasWeightMapping::gas_to_weight(info.used_gas.unique_saturated_into())), + actual_weight: Some(T::GasWeightMapping::gas_to_weight( + info.used_gas.unique_saturated_into(), + )), pays_fee: Pays::No, }) } /// Issue an EVM create operation. This is similar to a contract creation transaction in /// Ethereum. - #[weight = T::GasWeightMapping::gas_to_weight(*gas_limit)] + #[pallet::weight(T::GasWeightMapping::gas_to_weight(*gas_limit))] fn create( - origin, + origin: OriginFor, source: H160, init: Vec, value: U256, @@ -351,27 +264,29 @@ decl_module! { value: create_address, .. } => { - Module::::deposit_event(Event::::Created(create_address)); - }, + Pallet::::deposit_event(Event::::Created(create_address)); + } CreateInfo { exit_reason: _, value: create_address, .. } => { - Module::::deposit_event(Event::::CreatedFailed(create_address)); - }, + Pallet::::deposit_event(Event::::CreatedFailed(create_address)); + } } Ok(PostDispatchInfo { - actual_weight: Some(T::GasWeightMapping::gas_to_weight(info.used_gas.unique_saturated_into())), + actual_weight: Some(T::GasWeightMapping::gas_to_weight( + info.used_gas.unique_saturated_into(), + )), pays_fee: Pays::No, }) } /// Issue an EVM create2 operation. - #[weight = T::GasWeightMapping::gas_to_weight(*gas_limit)] + #[pallet::weight(T::GasWeightMapping::gas_to_weight(*gas_limit))] fn create2( - origin, + origin: OriginFor, source: H160, init: Vec, salt: H256, @@ -398,95 +313,213 @@ decl_module! { value: create_address, .. } => { - Module::::deposit_event(Event::::Created(create_address)); - }, + Pallet::::deposit_event(Event::::Created(create_address)); + } CreateInfo { exit_reason: _, value: create_address, .. } => { - Module::::deposit_event(Event::::CreatedFailed(create_address)); - }, + Pallet::::deposit_event(Event::::CreatedFailed(create_address)); + } } Ok(PostDispatchInfo { - actual_weight: Some(T::GasWeightMapping::gas_to_weight(info.used_gas.unique_saturated_into())), + actual_weight: Some(T::GasWeightMapping::gas_to_weight( + info.used_gas.unique_saturated_into(), + )), pays_fee: Pays::No, }) } } -} + impl Pallet { + pub fn remove_account(address: &H160) { + if AccountCodes::::contains_key(address) { + let account_id = T::AddressMapping::into_account_id(*address); + let _ = >::dec_consumers(&account_id); + } -impl Module { - fn remove_account(address: &H160) { - if AccountCodes::contains_key(address) { - let account_id = T::AddressMapping::into_account_id(*address); - let _ = >::dec_consumers(&account_id); + AccountCodes::::remove(address); + AccountStorages::::remove_prefix(address); } - AccountCodes::remove(address); - AccountStorages::remove_prefix(address); - } + /// Create an account. + pub fn create_account(address: H160, code: Vec) { + if code.is_empty() { + return; + } + + if !AccountCodes::::contains_key(&address) { + let account_id = T::AddressMapping::into_account_id(address); + let _ = >::inc_consumers(&account_id); + } + + AccountCodes::::insert(address, code); + } + + /// Check whether an account is empty. + pub fn is_account_empty(address: &H160) -> bool { + let account = T::RingAccountBasic::account_basic(address); + let code_len = AccountCodes::::decode_len(address).unwrap_or(0); + + account.nonce == U256::zero() && account.balance == U256::zero() && code_len == 0 + } + + pub fn is_contract_code_empty(address: &H160) -> bool { + let code_len = AccountCodes::::decode_len(address).unwrap_or(0); + 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); + } + } + + /// Withdraw fee. + pub fn withdraw_fee(address: &H160, value: U256) { + let account = T::RingAccountBasic::account_basic(address); + let new_account_balance = account.balance.saturating_sub(value); - /// Create an account. - pub fn create_account(address: H160, code: Vec) { - if code.is_empty() { - return; + T::RingAccountBasic::mutate_account_basic( + &address, + Account { + nonce: account.nonce, + balance: new_account_balance, + }, + ); } - if !AccountCodes::contains_key(&address) { - let account_id = T::AddressMapping::into_account_id(address); - let _ = >::inc_consumers(&account_id); + /// Deposit fee. + pub fn deposit_fee(address: &H160, value: U256) { + let account = T::RingAccountBasic::account_basic(address); + let new_account_balance = account.balance.saturating_add(value); + + T::RingAccountBasic::mutate_account_basic( + &address, + Account { + nonce: account.nonce, + balance: new_account_balance, + }, + ); } + } +} +pub use pallet::*; - AccountCodes::insert(address, code); +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) } - /// Check whether an account is empty. - pub fn is_account_empty(address: &H160) -> bool { - let account = T::RingAccountBasic::account_basic(address); - let code_len = AccountCodes::decode_len(address).unwrap_or(0); + /// Try with origin. + fn try_address_origin( + address: &H160, + origin: OuterOrigin, + ) -> Result; +} + +pub trait AddressMapping { + fn into_account_id(address: H160) -> A; +} - account.nonce == U256::zero() && account.balance == U256::zero() && code_len == 0 +/// Get account basic info +pub trait AccountBasic { + fn account_basic(address: &H160) -> Account; + fn mutate_account_basic(address: &H160, new: Account); + fn transfer(source: &H160, target: &H160, value: U256) -> Result<(), ExitError>; +} + +/// Config 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 fn is_contract_code_empty(address: &H160) -> bool { - let code_len = AccountCodes::decode_len(address).unwrap_or(0); - code_len == 0 +/// A mapping function that converts Ethereum gas to Substrate weight +pub trait GasWeightMapping { + fn gas_to_weight(gas: u64) -> Weight; + fn weight_to_gas(weight: Weight) -> u64; +} +impl GasWeightMapping for () { + fn gas_to_weight(gas: u64) -> Weight { + gas as Weight } + fn weight_to_gas(weight: Weight) -> u64 { + weight + } +} - /// Remove an account if its empty. - pub fn remove_account_if_empty(address: &H160) { - if Self::is_account_empty(address) { - Self::remove_account(address); - } +/// A contract handle for ethereum issuing +pub trait IssuingHandler { + fn handle(address: H160, caller: H160, input: &[u8]) -> DispatchResult; +} +/// A default empty issuingHandler, usually used in the test scenario. +impl IssuingHandler for () { + fn handle(_: H160, _: H160, _: &[u8]) -> DispatchResult { + Ok(()) } +} + +/// 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; - /// Withdraw fee. - pub fn withdraw_fee(address: &H160, value: U256) { - let account = T::RingAccountBasic::account_basic(address); - let new_account_balance = account.balance.saturating_sub(value); - - T::RingAccountBasic::mutate_account_basic( - &address, - Account { - nonce: account.nonce, - balance: new_account_balance, - }, - ); + 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)), + }) } +} - /// Deposit fee. - pub fn deposit_fee(address: &H160, value: U256) { - let account = T::RingAccountBasic::account_basic(address); - let new_account_balance = account.balance.saturating_add(value); - - T::RingAccountBasic::mutate_account_basic( - &address, - Account { - nonce: account.nonce, - balance: new_account_balance, - }, - ); +pub struct ConcatAddressMapping; +/// The ConcatAddressMapping used for transfer from evm 20-length to substrate 32-length address +/// The concat rule inclued three parts: +/// 1. AccountId Prefix: concat("dvm", "0x00000000000000"), length: 11 byetes +/// 2. EVM address: the original evm address, length: 20 bytes +/// 3. CheckSum: byte_xor(AccountId Prefix + EVM address), length: 1 bytes +impl AddressMapping for ConcatAddressMapping { + fn into_account_id(address: H160) -> AccountId32 { + let mut data = [0u8; 32]; + data[0..4].copy_from_slice(b"dvm:"); + data[11..31].copy_from_slice(&address[..]); + let checksum: u8 = data[1..31].iter().fold(data[0], |sum, &byte| sum ^ byte); + data[31] = checksum; + AccountId32::from(data) } } + +#[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, +} diff --git a/frame/evm/src/runner/mod.rs b/frame/evm/src/runner/mod.rs index b475b66528..3a152cdfa2 100644 --- a/frame/evm/src/runner/mod.rs +++ b/frame/evm/src/runner/mod.rs @@ -1,26 +1,29 @@ -// 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 +// This file is part of Darwinia. +// +// Copyright (C) 2018-2021 Darwinia Network +// SPDX-License-Identifier: GPL-3.0 // -// http://www.apache.org/licenses/LICENSE-2.0 +// Darwinia is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 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. +// Darwinia is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Darwinia. If not, see . pub mod stack; +// --- substrate --- +use sp_core::{H160, H256, U256}; +use sp_std::prelude::*; +// --- darwinia --- use crate::Config; use dp_evm::{CallInfo, CreateInfo}; -use sp_core::{H160, H256, U256}; -use sp_std::vec::Vec; pub trait Runner { type Error: Into; diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index 8029c801a9..ebe4c73d42 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -1,50 +1,46 @@ -// 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 +// This file is part of Darwinia. +// +// Copyright (C) 2018-2021 Darwinia Network +// SPDX-License-Identifier: GPL-3.0 +// +// Darwinia is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// http://www.apache.org/licenses/LICENSE-2.0 +// Darwinia is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 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. +// You should have received a copy of the GNU General Public License +// along with Darwinia. If not, see . //! EVM stack-based runner. -use crate::runner::Runner as RunnerT; -use crate::{ - AccountBasic, AccountCodes, AccountStorages, AddressMapping, Config, Error, Event, - FeeCalculator, Module, PrecompileSet, +// --- crates.io --- +use evm::{ + backend::Backend as BackendT, + executor::{StackExecutor, StackState as StackStateT, StackSubstateMetadata}, + ExitError, ExitReason, Transfer, }; - -// --- darwinia --- -use dp_evm::{CallInfo, CreateInfo, ExecutionInfo, Log, Vicinity}; +use sha3::{Digest, Keccak256}; // --- substrate --- -use frame_support::{ - ensure, - storage::{StorageDoubleMap, StorageMap}, - traits::Get, -}; +use frame_support::{ensure, traits::Get}; use sp_core::{H160, H256, U256}; use sp_runtime::traits::UniqueSaturatedInto; -use sp_std::{boxed::Box, collections::btree_set::BTreeSet, marker::PhantomData, mem, vec::Vec}; -// --- std --- -use evm::backend::Backend as BackendT; -use evm::executor::{StackExecutor, StackState as StackStateT, StackSubstateMetadata}; -use evm::{ExitError, ExitReason, Transfer}; -use sha3::{Digest, Keccak256}; +use sp_std::{collections::btree_set::BTreeSet, marker::PhantomData, mem, prelude::*}; +// --- darwinia --- +use crate::{ + runner::Runner as RunnerT, AccountBasic, AccountCodes, AccountStorages, AddressMapping, Config, + Error, Event, FeeCalculator, Pallet, PrecompileSet, +}; +use dp_evm::{CallInfo, CreateInfo, ExecutionInfo, Log, Vicinity}; #[derive(Default)] pub struct Runner { _marker: PhantomData, } - impl Runner { /// Execute an EVM operation. pub fn execute<'config, F, R>( @@ -99,7 +95,7 @@ impl Runner { ensure!(source_account.nonce == nonce, Error::::InvalidNonce); } - Module::::withdraw_fee(&source, total_fee); + Pallet::::withdraw_fee(&source, total_fee); let (reason, retv) = f(&mut executor); let used_gas = U256::from(executor.used_gas()); @@ -113,7 +109,7 @@ impl Runner { gas_limit, actual_fee ); - Module::::deposit_fee(&source, total_fee.saturating_sub(actual_fee)); + Pallet::::deposit_fee(&source, total_fee.saturating_sub(actual_fee)); let state = executor.into_state(); @@ -123,7 +119,7 @@ impl Runner { "Deleting account at {:?}", address ); - Module::::remove_account(&address) + >::remove_account(&address) } for substrate_log in &state.substate.logs { @@ -136,7 +132,7 @@ impl Runner { substrate_log.data.len(), substrate_log.data ); - Module::::deposit_event(Event::::Log(Log { + Pallet::::deposit_event(Event::::Log(Log { address: substrate_log.address, topics: substrate_log.topics.clone(), data: substrate_log.data.clone(), @@ -400,11 +396,11 @@ impl<'vicinity, 'config, T: Config> BackendT for SubstrateStackState<'vicinity, } fn code(&self, address: H160) -> Vec { - AccountCodes::get(&address) + AccountCodes::::get(&address) } fn storage(&self, address: H160, index: H256) -> H256 { - AccountStorages::get(address, index) + AccountStorages::::get(address, index) } fn original_storage(&self, _address: H160, _index: H256) -> Option { @@ -440,7 +436,7 @@ impl<'vicinity, 'config, T: Config> StackStateT<'config> } fn is_empty(&self, address: H160) -> bool { - Module::::is_account_empty(&address) + Pallet::::is_account_empty(&address) } fn deleted(&self, address: H160) -> bool { @@ -460,7 +456,7 @@ impl<'vicinity, 'config, T: Config> StackStateT<'config> address, index, ); - AccountStorages::remove(address, index); + AccountStorages::::remove(address, index); } else { log::debug!( target: "evm", @@ -469,12 +465,12 @@ impl<'vicinity, 'config, T: Config> StackStateT<'config> index, value, ); - AccountStorages::insert(address, index, value); + AccountStorages::::insert(address, index, value); } } fn reset_storage(&mut self, address: H160) { - AccountStorages::remove_prefix(address); + AccountStorages::::remove_prefix(address); } fn log(&mut self, address: H160, topics: Vec, data: Vec) { @@ -492,7 +488,7 @@ impl<'vicinity, 'config, T: Config> StackStateT<'config> code.len(), address ); - Module::::create_account(address, code); + Pallet::::create_account(address, code); } fn transfer(&mut self, transfer: Transfer) -> Result<(), ExitError> { diff --git a/frame/evm/src/tests.rs b/frame/evm/src/tests.rs index a5f8a7a7e8..2da5463a97 100644 --- a/frame/evm/src/tests.rs +++ b/frame/evm/src/tests.rs @@ -1,6 +1,24 @@ -#![cfg(test)] +// This file is part of Darwinia. +// +// Copyright (C) 2018-2021 Darwinia Network +// SPDX-License-Identifier: GPL-3.0 +// +// Darwinia is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Darwinia is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Darwinia. If not, see . -use crate::{self as darwinia_evm, *}; +// --- std --- +use std::{collections::BTreeMap, str::FromStr}; +// --- substrate --- use frame_support::{assert_ok, traits::GenesisBuild}; use frame_system::mocking::*; use sp_core::H256; @@ -9,7 +27,8 @@ use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, RuntimeDebug, }; -use std::{collections::BTreeMap, str::FromStr}; +// --- darwinia --- +use crate::{self as darwinia_evm, runner::stack::Runner, *}; type Block = MockBlock; type UncheckedExtrinsic = MockUncheckedExtrinsic; @@ -90,7 +109,6 @@ impl FeeCalculator for FixedGasPrice { } pub struct RawAccountBasic(sp_std::marker::PhantomData); - impl AccountBasic for RawAccountBasic { /// Get the account basic in EVM format. fn account_basic(address: &H160) -> Account { @@ -133,7 +151,6 @@ impl AccountBasic for RawAccountBasic { /// Ensure that the origin is root. pub struct EnsureAddressRoot(sp_std::marker::PhantomData); - impl EnsureAddressOrigin for EnsureAddressRoot where OuterOrigin: Into, OuterOrigin>> + From>, @@ -161,7 +178,7 @@ impl Config for Test { type Precompiles = (); type ChainId = (); type BlockGasLimit = (); - type Runner = crate::runner::stack::Runner; + type Runner = Runner; type IssuingHandler = (); type RingAccountBasic = RawAccountBasic; type KtonAccountBasic = RawAccountBasic; @@ -210,15 +227,11 @@ pub fn new_test_ext() -> sp_io::TestExternalities { }, ); - >::default() - .assimilate_storage(&mut t) - .unwrap(); - >::default() - .assimilate_storage(&mut t) - .unwrap(); - darwinia_evm::GenesisConfig { accounts } - .assimilate_storage::(&mut t) - .unwrap(); + >::assimilate_storage( + &darwinia_evm::GenesisConfig { accounts }, + &mut t, + ) + .unwrap(); t.into() }