mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 19:51:05 +00:00
Substrate EVM (#3927)
* srml-evm: init the basic structures * srml-evm: finish executor implementation * srml-evm: implement balance deposit and withdraw * srml-evm: implement the actuall call/create * srml-evm: use crates.io version of evm * srml-evm: fix no-std compile * Remove dependency patch * Update to evm 0.14 * Use double map for account storage * Add precompiles support * Add some basic docs * Use runtime_io::chain_id() * Update srml/evm/src/lib.rs Co-Authored-By: Xiliang Chen <xlchen1291@gmail.com> * Update srml/evm/src/lib.rs Co-Authored-By: Xiliang Chen <xlchen1291@gmail.com> * Fix WithdrawReason * Unique saturate balance to u128 * Unique saturate withdraw to u128 * Remove extern crate alloc * Move account code to a separate storage and use ref for convert_account_id * More match cause for error message * Fix potential interger overflow * Use decode_len for fetching code length
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
[package]
|
||||
name = "srml-evm"
|
||||
version = "2.0.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.101", optional = true, features = ["derive"] }
|
||||
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false }
|
||||
support = { package = "srml-support", path = "../support", default-features = false }
|
||||
system = { package = "srml-system", path = "../system", default-features = false }
|
||||
timestamp = { package = "srml-timestamp", path = "../timestamp", default-features = false }
|
||||
balances = { package = "srml-balances", path = "../balances", default-features = false }
|
||||
primitives = { package = "substrate-primitives", path = "../../core/primitives", default-features = false }
|
||||
sr-primitives = { path = "../../core/sr-primitives", default-features = false }
|
||||
rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false }
|
||||
runtime-io = { package = "sr-io", path = "../../core/sr-io", default-features = false }
|
||||
primitive-types = { version = "0.6", default-features = false, features = ["rlp"] }
|
||||
rlp = { version = "0.4", default-features = false }
|
||||
evm = { version = "0.14", default-features = false }
|
||||
sha3 = { version = "0.8", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"serde",
|
||||
"codec/std",
|
||||
"primitives/std",
|
||||
"sr-primitives/std",
|
||||
"support/std",
|
||||
"system/std",
|
||||
"balances/std",
|
||||
"runtime-io/std",
|
||||
"rstd/std",
|
||||
"sha3/std",
|
||||
"rlp/std",
|
||||
"primitive-types/std",
|
||||
"evm/std",
|
||||
"timestamp/std",
|
||||
]
|
||||
@@ -0,0 +1,187 @@
|
||||
use rstd::marker::PhantomData;
|
||||
use rstd::vec::Vec;
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
use codec::{Encode, Decode};
|
||||
use primitives::{U256, H256, H160};
|
||||
use sr_primitives::traits::UniqueSaturatedInto;
|
||||
use support::storage::{StorageMap, StorageDoubleMap};
|
||||
use sha3::{Keccak256, Digest};
|
||||
use evm::Config;
|
||||
use evm::backend::{Backend as BackendT, ApplyBackend, Apply};
|
||||
use crate::{Trait, Accounts, 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>,
|
||||
/// Bytearray 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,
|
||||
}
|
||||
|
||||
/// Gasometer config used for executor. Currently this is hard-coded to
|
||||
/// Istanbul hard fork.
|
||||
pub static GASOMETER_CONFIG: Config = Config::istanbul();
|
||||
|
||||
/// 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(system::Module::<T>::block_hash(number).as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
fn block_number(&self) -> U256 {
|
||||
let number: u128 = 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 = timestamp::Module::<T>::get().unique_saturated_into();
|
||||
U256::from(now)
|
||||
}
|
||||
|
||||
fn block_difficulty(&self) -> U256 {
|
||||
U256::zero()
|
||||
}
|
||||
|
||||
fn block_gas_limit(&self) -> U256 {
|
||||
U256::zero()
|
||||
}
|
||||
|
||||
fn chain_id(&self) -> U256 {
|
||||
U256::from(runtime_io::chain_id())
|
||||
}
|
||||
|
||||
fn exists(&self, _address: H160) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn basic(&self, address: H160) -> evm::backend::Basic {
|
||||
let account = Accounts::get(&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,
|
||||
} => {
|
||||
Accounts::mutate(&address, |account| {
|
||||
account.balance = basic.balance;
|
||||
account.nonce = basic.nonce;
|
||||
});
|
||||
|
||||
if let Some(code) = code {
|
||||
AccountCodes::insert(address, code);
|
||||
}
|
||||
|
||||
if reset_storage {
|
||||
AccountStorages::remove_prefix(address);
|
||||
}
|
||||
|
||||
for (index, value) in storage {
|
||||
if value == H256::default() {
|
||||
AccountStorages::remove(address, index);
|
||||
} else {
|
||||
AccountStorages::insert(address, index, value);
|
||||
}
|
||||
}
|
||||
|
||||
if delete_empty {
|
||||
Module::<T>::remove_account_if_empty(&address);
|
||||
}
|
||||
},
|
||||
Apply::Delete { address } => {
|
||||
Module::<T>::remove_account(&address)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for log in logs {
|
||||
Module::<T>::deposit_event(Event::Log(Log {
|
||||
address: log.address,
|
||||
topics: log.topics,
|
||||
data: log.data,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
// Copyright 2017-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! EVM execution module for Substrate
|
||||
|
||||
// Ensure we're `no_std` when compiling for Wasm.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
mod backend;
|
||||
|
||||
pub use crate::backend::{Account, Log, Vicinity, Backend};
|
||||
|
||||
use rstd::vec::Vec;
|
||||
use support::{dispatch::Result, decl_module, decl_storage, decl_event};
|
||||
use support::traits::{Currency, WithdrawReason, ExistenceRequirement};
|
||||
use system::ensure_signed;
|
||||
use sr_primitives::weights::SimpleDispatchInfo;
|
||||
use sr_primitives::traits::UniqueSaturatedInto;
|
||||
use primitives::{U256, H256, H160};
|
||||
use evm::{ExitReason, ExitSucceed, ExitError};
|
||||
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 system::Trait>::AccountId>>::Balance;
|
||||
|
||||
/// Trait that outputs the current transaction gas price.
|
||||
pub trait FeeCalculator {
|
||||
/// Return the current gas price.
|
||||
fn gas_price() -> U256;
|
||||
}
|
||||
|
||||
/// Trait for converting account ids of `balances` module into
|
||||
/// `H160` for EVM module.
|
||||
///
|
||||
/// Accounts and contracts of this module are stored in its own
|
||||
/// storage, in an Ethereum-compatible format. In order to communicate
|
||||
/// with the rest of Substrate module, we require an one-to-one
|
||||
/// mapping of Substrate account to Ethereum address.
|
||||
pub trait ConvertAccountId<A> {
|
||||
/// Given a Substrate address, return the corresponding Ethereum address.
|
||||
fn convert_account_id(account_id: &A) -> H160;
|
||||
}
|
||||
|
||||
/// 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>>;
|
||||
}
|
||||
|
||||
impl Precompiles for () {
|
||||
fn execute(
|
||||
_address: H160,
|
||||
_input: &[u8],
|
||||
_target_gas: Option<usize>
|
||||
) -> Option<core::result::Result<(ExitSucceed, Vec<u8>, usize), ExitError>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// EVM module trait
|
||||
pub trait Trait: system::Trait + timestamp::Trait {
|
||||
/// Calculator for current gas price.
|
||||
type FeeCalculator: FeeCalculator;
|
||||
/// Convert account ID to H160;
|
||||
type ConvertAccountId: ConvertAccountId<Self::AccountId>;
|
||||
/// Currency type for deposit and withdraw.
|
||||
type Currency: Currency<Self::AccountId>;
|
||||
/// The overarching event type.
|
||||
type Event: From<Event> + Into<<Self as system::Trait>::Event>;
|
||||
/// Precompiles associated with this EVM engine.
|
||||
type Precompiles: Precompiles;
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as Example {
|
||||
Accounts get(fn accounts) config(): map H160 => Account;
|
||||
AccountCodes: map H160 => Vec<u8>;
|
||||
AccountStorages: double_map H160, blake2_256(H256) => H256;
|
||||
}
|
||||
}
|
||||
|
||||
decl_event!(
|
||||
/// EVM events
|
||||
pub enum Event {
|
||||
/// Ethereum events from contracts.
|
||||
Log(Log),
|
||||
}
|
||||
);
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
fn deposit_event() = default;
|
||||
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(10_000)]
|
||||
fn deposit_balance(origin, value: BalanceOf<T>) -> Result {
|
||||
let sender = ensure_signed(origin)?;
|
||||
|
||||
T::Currency::withdraw(
|
||||
&sender,
|
||||
value,
|
||||
WithdrawReason::Reserve.into(),
|
||||
ExistenceRequirement::KeepAlive,
|
||||
)?;
|
||||
|
||||
let bvalue = U256::from(UniqueSaturatedInto::<u128>::unique_saturated_into(value));
|
||||
let address = T::ConvertAccountId::convert_account_id(&sender);
|
||||
Accounts::mutate(&address, |account| {
|
||||
account.balance += bvalue;
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(10_000)]
|
||||
fn withdraw_balance(origin, value: BalanceOf<T>) -> Result {
|
||||
let sender = ensure_signed(origin)?;
|
||||
let address = T::ConvertAccountId::convert_account_id(&sender);
|
||||
let bvalue = U256::from(UniqueSaturatedInto::<u128>::unique_saturated_into(value));
|
||||
|
||||
if Accounts::get(&address).balance < bvalue {
|
||||
return Err("Not enough balance to withdraw")
|
||||
}
|
||||
|
||||
Accounts::mutate(&address, |account| {
|
||||
account.balance -= bvalue;
|
||||
});
|
||||
|
||||
T::Currency::deposit_creating(&sender, value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(10_000)]
|
||||
fn call(origin, target: H160, input: Vec<u8>, value: U256, gas_limit: u32) -> Result {
|
||||
let sender = ensure_signed(origin)?;
|
||||
let source = T::ConvertAccountId::convert_account_id(&sender);
|
||||
let gas_price = T::FeeCalculator::gas_price();
|
||||
|
||||
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,
|
||||
&backend::GASOMETER_CONFIG,
|
||||
T::Precompiles::execute,
|
||||
);
|
||||
|
||||
let total_fee = gas_price.checked_mul(U256::from(gas_limit))
|
||||
.ok_or("Calculating total fee overflowed")?;
|
||||
if Accounts::get(&source).balance <
|
||||
value.checked_add(total_fee).ok_or("Calculating total payment overflowed")?
|
||||
{
|
||||
return Err("Not enough balance to pay transaction fee")
|
||||
}
|
||||
executor.withdraw(source, total_fee).map_err(|_| "Withdraw fee failed")?;
|
||||
|
||||
let reason = executor.transact_call(
|
||||
source,
|
||||
target,
|
||||
value,
|
||||
input,
|
||||
gas_limit as usize,
|
||||
);
|
||||
|
||||
let ret = match reason {
|
||||
ExitReason::Succeed(_) => Ok(()),
|
||||
ExitReason::Error(_) => Err("Execute message call failed"),
|
||||
ExitReason::Revert(_) => Err("Execute message call reverted"),
|
||||
ExitReason::Fatal(_) => Err("Execute message call returned VM fatal error"),
|
||||
};
|
||||
let actual_fee = executor.fee(gas_price);
|
||||
executor.deposit(source, total_fee.saturating_sub(actual_fee));
|
||||
|
||||
let (values, logs) = executor.deconstruct();
|
||||
backend.apply(values, logs, true);
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(10_000)]
|
||||
fn create(origin, init: Vec<u8>, value: U256, gas_limit: u32) -> Result {
|
||||
let sender = ensure_signed(origin)?;
|
||||
let source = T::ConvertAccountId::convert_account_id(&sender);
|
||||
let gas_price = T::FeeCalculator::gas_price();
|
||||
|
||||
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,
|
||||
&backend::GASOMETER_CONFIG,
|
||||
T::Precompiles::execute,
|
||||
);
|
||||
|
||||
let total_fee = gas_price.checked_mul(U256::from(gas_limit))
|
||||
.ok_or("Calculating total fee overflowed")?;
|
||||
if Accounts::get(&source).balance <
|
||||
value.checked_add(total_fee).ok_or("Calculating total payment overflowed")?
|
||||
{
|
||||
return Err("Not enough balance to pay transaction fee")
|
||||
}
|
||||
executor.withdraw(source, total_fee).map_err(|_| "Withdraw fee failed")?;
|
||||
|
||||
let reason = executor.transact_create(
|
||||
source,
|
||||
value,
|
||||
init,
|
||||
gas_limit as usize,
|
||||
);
|
||||
|
||||
let ret = match reason {
|
||||
ExitReason::Succeed(_) => Ok(()),
|
||||
ExitReason::Error(_) => Err("Execute contract creation failed"),
|
||||
ExitReason::Revert(_) => Err("Execute contract creation reverted"),
|
||||
ExitReason::Fatal(_) => Err("Execute contract creation returned VM fatal error"),
|
||||
};
|
||||
let actual_fee = executor.fee(gas_price);
|
||||
executor.deposit(source, total_fee.saturating_sub(actual_fee));
|
||||
|
||||
let (values, logs) = executor.deconstruct();
|
||||
backend.apply(values, logs, true);
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
/// Check whether an account is empty.
|
||||
pub fn is_account_empty(address: &H160) -> bool {
|
||||
let account = Accounts::get(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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove an account from state.
|
||||
fn remove_account(address: &H160) {
|
||||
Accounts::remove(address);
|
||||
AccountCodes::remove(address);
|
||||
AccountStorages::remove_prefix(address);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user