-
Notifications
You must be signed in to change notification settings - Fork 498
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #206 from paritytech/sp-evm-move
pallet-evm: move to Frontier (Part I)
- Loading branch information
Showing
6 changed files
with
1,330 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
[package] | ||
name = "pallet-evm" | ||
version = "2.0.0" | ||
authors = ["Parity Technologies <[email protected]>"] | ||
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", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<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, | ||
})); | ||
} | ||
} | ||
} |
Oops, something went wrong.