pallet-evm: move to Frontier (Part IV) (#7573)

This commit is contained in:
Wei Tang
2020-11-22 10:01:05 +01:00
committed by GitHub
parent dfb922d5d8
commit 5693a1b88a
9 changed files with 2 additions and 1470 deletions
+2 -136
View File
@@ -1439,23 +1439,6 @@ dependencies = [
"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"
@@ -1476,56 +1459,6 @@ version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
[[package]]
name = "evm"
version = "0.17.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16c8deca0ec3efa361b03d9cae6fe94321a1d2d0a523437edd720b3d140e3c08"
dependencies = [
"ethereum",
"evm-core",
"evm-gasometer",
"evm-runtime",
"log",
"parity-scale-codec",
"primitive-types",
"rlp",
"serde",
"sha3 0.8.2",
]
[[package]]
name = "evm-core"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf2d732b3c36df36833761cf67df8f65866be1d368d20508bc3e13e6f256c8c5"
dependencies = [
"log",
"primitive-types",
]
[[package]]
name = "evm-gasometer"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46de1b91ccd744627484183729f1b5af484b3bf15505007fc28cc54264cb9ea1"
dependencies = [
"evm-core",
"evm-runtime",
"primitive-types",
]
[[package]]
name = "evm-runtime"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2c1d1ffe96f833788512c890d702457d790dba4917ac6f64f8f60fbd9bc40b8"
dependencies = [
"evm-core",
"primitive-types",
"sha3 0.8.2",
]
[[package]]
name = "exit-future"
version = "0.2.0"
@@ -3248,7 +3181,7 @@ dependencies = [
"pin-project 0.4.27",
"rand 0.7.3",
"salsa20",
"sha3 0.9.1",
"sha3",
]
[[package]]
@@ -3740,7 +3673,7 @@ dependencies = [
"digest 0.9.0",
"sha-1 0.9.2",
"sha2 0.9.2",
"sha3 0.9.1",
"sha3",
"unsigned-varint 0.5.1",
]
@@ -4686,28 +4619,6 @@ dependencies = [
"substrate-test-utils",
]
[[package]]
name = "pallet-evm"
version = "2.0.0"
dependencies = [
"evm",
"frame-support",
"frame-system",
"impl-trait-for-tuples",
"pallet-balances",
"pallet-timestamp",
"parity-scale-codec",
"primitive-types",
"ripemd160",
"rlp",
"serde",
"sha3 0.8.2",
"sp-core",
"sp-io",
"sp-runtime",
"sp-std",
]
[[package]]
name = "pallet-example"
version = "2.0.0"
@@ -6344,17 +6255,6 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "ripemd160"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251"
dependencies = [
"block-buffer 0.9.0",
"digest 0.9.0",
"opaque-debug 0.3.0",
]
[[package]]
name = "rlp"
version = "0.4.6"
@@ -6364,17 +6264,6 @@ 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"
@@ -7923,19 +7812,6 @@ dependencies = [
"opaque-debug 0.3.0",
]
[[package]]
name = "sha3"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf"
dependencies = [
"block-buffer 0.7.3",
"byte-tools",
"digest 0.8.1",
"keccak",
"opaque-debug 0.2.3",
]
[[package]]
name = "sha3"
version = "0.9.1"
@@ -9843,16 +9719,6 @@ 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"
-1
View File
@@ -75,7 +75,6 @@ members = [
"frame/democracy",
"frame/elections-phragmen",
"frame/elections",
"frame/evm",
"frame/example",
"frame/example-offchain-worker",
"frame/example-parallel",
-3
View File
@@ -45,9 +45,6 @@
# Contracts
/frame/contracts/ @athei
# EVM
/frame/evm/ @sorpaas
# NPoS and election
/frame/staking/ @kianenigma
/frame/elections/ @kianenigma
-51
View File
@@ -1,51 +0,0 @@
[package]
name = "pallet-evm"
version = "2.0.0"
authors = ["Parity Technologies <admin@parity.io>"]
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",
]
-29
View File
@@ -1,29 +0,0 @@
# 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
-216
View File
@@ -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<H256>,
/// Byte array data of the log.
pub data: Vec<u8>,
}
#[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<T>,
}
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::<T>::block_hash(number).as_ref())
}
}
fn block_number(&self) -> U256 {
let number: u128 = frame_system::Module::<T>::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::<T>::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::<T>::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<u8> {
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<A, I, L>(
&mut self,
values: A,
logs: L,
delete_empty: bool,
) where
A: IntoIterator<Item=Apply<I>>,
I: IntoIterator<Item=(H256, H256)>,
L: IntoIterator<Item=evm::backend::Log>,
{
for apply in values {
match apply {
Apply::Modify {
address, basic, code, storage, reset_storage,
} => {
Module::<T>::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::<T>::remove_account_if_empty(&address);
}
},
Apply::Delete { address } => {
debug::debug!(
target: "evm",
"Deleting account at {:?}",
address
);
Module::<T>::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::<T>::deposit_event(Event::<T>::Log(Log {
address: log.address,
topics: log.topics,
data: log.data,
}));
}
}
}
-678
View File
@@ -1,678 +0,0 @@
// 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<T> = <<T as Trait>::Currency as Currency<<T as frame_system::Trait>::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<OuterOrigin> {
/// Success return type.
type Success;
/// Perform the origin check.
fn ensure_address_origin(
address: &H160,
origin: OuterOrigin,
) -> Result<Self::Success, BadOrigin> {
Self::try_address_origin(address, origin).map_err(|_| BadOrigin)
}
/// Try with origin.
fn try_address_origin(
address: &H160,
origin: OuterOrigin,
) -> Result<Self::Success, OuterOrigin>;
}
/// 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<OuterOrigin> EnsureAddressOrigin<OuterOrigin> for EnsureAddressSame where
OuterOrigin: Into<Result<RawOrigin<H160>, OuterOrigin>> + From<RawOrigin<H160>>,
{
type Success = H160;
fn try_address_origin(
address: &H160,
origin: OuterOrigin,
) -> Result<H160, OuterOrigin> {
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<AccountId>(sp_std::marker::PhantomData<AccountId>);
impl<OuterOrigin, AccountId> EnsureAddressOrigin<OuterOrigin> for EnsureAddressRoot<AccountId> where
OuterOrigin: Into<Result<RawOrigin<AccountId>, OuterOrigin>> + From<RawOrigin<AccountId>>,
{
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<AccountId>(sp_std::marker::PhantomData<AccountId>);
impl<OuterOrigin, AccountId> EnsureAddressOrigin<OuterOrigin> for EnsureAddressNever<AccountId> {
type Success = AccountId;
fn try_address_origin(
_address: &H160,
origin: OuterOrigin,
) -> Result<AccountId, OuterOrigin> {
Err(origin)
}
}
/// Ensure that the address is truncated hash of the origin. Only works if the account id is
/// `AccountId32`.
pub struct EnsureAddressTruncated;
impl<OuterOrigin> EnsureAddressOrigin<OuterOrigin> for EnsureAddressTruncated where
OuterOrigin: Into<Result<RawOrigin<AccountId32>, OuterOrigin>> + From<RawOrigin<AccountId32>>,
{
type Success = AccountId32;
fn try_address_origin(
address: &H160,
origin: OuterOrigin,
) -> Result<AccountId32, OuterOrigin> {
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<A> {
fn into_account_id(address: H160) -> A;
}
/// Identity address mapping.
pub struct IdentityAddressMapping;
impl AddressMapping<H160> for IdentityAddressMapping {
fn into_account_id(address: H160) -> H160 { address }
}
/// Hashed address mapping.
pub struct HashedAddressMapping<H>(sp_std::marker::PhantomData<H>);
impl<H: Hasher<Out=H256>> AddressMapping<AccountId32> for HashedAddressMapping<H> {
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<u64> 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<Self::Origin>;
/// Allow the origin to withdraw on behalf of given address.
type WithdrawOrigin: EnsureAddressOrigin<Self::Origin, Success=Self::AccountId>;
/// Mapping from address to account id.
type AddressMapping: AddressMapping<Self::AccountId>;
/// Currency type for withdraw and balance storage.
type Currency: Currency<Self::AccountId>;
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
/// Precompiles associated with this EVM engine.
type Precompiles: Precompiles;
/// Chain ID of EVM.
type ChainId: Get<u64>;
/// 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<H256, H256>,
/// Account code.
pub code: Vec<u8>,
}
decl_storage! {
trait Store for Module<T: Trait> as EVM {
AccountCodes get(fn account_codes): map hasher(blake2_128_concat) H160 => Vec<u8>;
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<H160, GenesisAccount>;
build(|config: &GenesisConfig| {
for (address, account) in &config.accounts {
Module::<T>::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<T> where
<T as frame_system::Trait>::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<T: Trait> {
/// 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<T: Trait> for enum Call where origin: T::Origin {
type Error = Error<T>;
fn deposit_event() = default;
/// Withdraw balance from EVM into currency/balances module.
#[weight = 0]
fn withdraw(origin, address: H160, value: BalanceOf<T>) {
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::<Weight>().saturating_mul(*gas_limit as Weight)]
fn call(
origin,
source: H160,
target: H160,
input: Vec<u8>,
value: U256,
gas_limit: u32,
gas_price: U256,
nonce: Option<U256>,
) -> 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::<T>::deposit_event(Event::<T>::Executed(target));
},
(_, _, _, _) => {
Module::<T>::deposit_event(Event::<T>::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::<Weight>().saturating_mul(*gas_limit as Weight)]
fn create(
origin,
source: H160,
init: Vec<u8>,
value: U256,
gas_limit: u32,
gas_price: U256,
nonce: Option<U256>,
) -> 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::<T>::deposit_event(Event::<T>::Created(create_address));
},
(_, create_address, _, _) => {
Module::<T>::deposit_event(Event::<T>::CreatedFailed(create_address));
},
}
Ok(Pays::No.into())
}
/// Issue an EVM create2 operation.
#[weight = (*gas_price).saturated_into::<Weight>().saturating_mul(*gas_limit as Weight)]
fn create2(
origin,
source: H160,
init: Vec<u8>,
salt: H256,
value: U256,
gas_limit: u32,
gas_price: U256,
nonce: Option<U256>,
) -> 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::<T>::deposit_event(Event::<T>::Created(create_address));
},
(_, create_address, _, _) => {
Module::<T>::deposit_event(Event::<T>::CreatedFailed(create_address));
},
}
Ok(Pays::No.into())
}
}
}
impl<T: Trait> Module<T> {
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::<T>::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::<T>::account_nonce(&account_id);
let balance = T::Currency::free_balance(&account_id);
Account {
nonce: U256::from(UniqueSaturatedInto::<u128>::unique_saturated_into(nonce)),
balance: U256::from(UniqueSaturatedInto::<u128>::unique_saturated_into(balance)),
}
}
/// Execute a create transaction on behalf of given sender.
pub fn execute_create(
source: H160,
init: Vec<u8>,
value: U256,
gas_limit: u32,
gas_price: U256,
nonce: Option<U256>,
apply_state: bool,
) -> Result<(ExitReason, H160, U256, Vec<Log>), Error<T>> {
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<u8>,
salt: H256,
value: U256,
gas_limit: u32,
gas_price: U256,
nonce: Option<U256>,
apply_state: bool,
) -> Result<(ExitReason, H160, U256, Vec<Log>), Error<T>> {
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<u8>,
value: U256,
gas_limit: u32,
gas_price: U256,
nonce: Option<U256>,
apply_state: bool,
) -> Result<(ExitReason, Vec<u8>, U256, Vec<Log>), Error<T>> {
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<F, R>(
source: H160,
value: U256,
gas_limit: u32,
gas_price: U256,
nonce: Option<U256>,
apply_state: bool,
f: F,
) -> Result<(ExitReason, R, U256, Vec<Log>), Error<T>> where
F: FnOnce(&mut StackExecutor<Backend<T>>) -> (ExitReason, R),
{
// Gas price check is skipped when performing a gas estimation.
if apply_state {
ensure!(gas_price >= T::FeeCalculator::min_gas_price(), Error::<T>::GasPriceTooLow);
}
let vicinity = Vicinity {
gas_price,
origin: source,
};
let mut backend = Backend::<T>::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::<T>::FeeOverflow)?;
let total_payment = value.checked_add(total_fee).ok_or(Error::<T>::PaymentOverflow)?;
let source_account = Self::account_basic(&source);
ensure!(source_account.balance >= total_payment, Error::<T>::BalanceLow);
executor.withdraw(source, total_fee).map_err(|_| Error::<T>::WithdrawFailed)?;
if let Some(nonce) = nonce {
ensure!(source_account.nonce == nonce, Error::<T>::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::<Vec<_>>();
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))
}
}
-167
View File
@@ -1,167 +0,0 @@
// 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<usize>,
) -> Option<core::result::Result<(ExitSucceed, Vec<u8>, 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<usize>,
) -> core::result::Result<(ExitSucceed, Vec<u8>, 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<usize>,
) -> Option<core::result::Result<(ExitSucceed, Vec<u8>, 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<usize>,
len: usize,
base: usize,
word: usize
) -> Result<usize, ExitError> {
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<usize>,
) -> core::result::Result<(ExitSucceed, Vec<u8>, 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<usize>,
) -> core::result::Result<(ExitSucceed, Vec<u8>, 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<usize>,
) -> core::result::Result<(ExitSucceed, Vec<u8>, 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<usize>,
) -> core::result::Result<(ExitSucceed, Vec<u8>, 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))
}
}
-189
View File
@@ -1,189 +0,0 @@
#![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<Self::AccountId>;
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<u64>;
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<Self::AccountId>;
type WithdrawOrigin = EnsureAddressNever<Self::AccountId>;
type AddressMapping = HashedAddressMapping<Blake2Hasher>;
type Currency = Balances;
type Event = Event<Test>;
type Precompiles = ();
type ChainId = SystemChainId;
}
type System = frame_system::Module<Test>;
type Balances = pallet_balances::Module<Test>;
type EVM = Module<Test>;
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().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::<Test>::default().assimilate_storage(&mut t).unwrap();
GenesisConfig { accounts }.assimilate_storage::<Test>(&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),
});
});
}