diff --git a/substrate/frame/evm/src/backend.rs b/substrate/frame/evm/src/backend.rs index 09f31d8aeb..495034b2c1 100644 --- a/substrate/frame/evm/src/backend.rs +++ b/substrate/frame/evm/src/backend.rs @@ -9,7 +9,7 @@ use frame_support::traits::Get; use frame_support::storage::{StorageMap, StorageDoubleMap}; use sha3::{Keccak256, Digest}; use evm::backend::{Backend as BackendT, ApplyBackend, Apply}; -use crate::{Trait, Accounts, AccountStorages, AccountCodes, Module, Event}; +use crate::{Trait, AccountStorages, AccountCodes, Module, Event}; #[derive(Clone, Eq, PartialEq, Encode, Decode, Default)] #[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] @@ -100,7 +100,7 @@ impl<'vicinity, T: Trait> BackendT for Backend<'vicinity, T> { } fn basic(&self, address: H160) -> evm::backend::Basic { - let account = Accounts::get(&address); + let account = Module::::account_basic(&address); evm::backend::Basic { balance: account.balance, @@ -141,9 +141,9 @@ impl<'vicinity, T: Trait> ApplyBackend for Backend<'vicinity, T> { Apply::Modify { address, basic, code, storage, reset_storage, } => { - Accounts::mutate(&address, |account| { - account.balance = basic.balance; - account.nonce = basic.nonce; + Module::::mutate_account_basic(&address, Account { + nonce: basic.nonce, + balance: basic.balance, }); if let Some(code) = code { diff --git a/substrate/frame/evm/src/lib.rs b/substrate/frame/evm/src/lib.rs index d55c21ea2a..68e86a9b1f 100644 --- a/substrate/frame/evm/src/lib.rs +++ b/substrate/frame/evm/src/lib.rs @@ -25,19 +25,18 @@ mod tests; pub use crate::backend::{Account, Log, Vicinity, Backend}; -use sp_std::{vec::Vec, marker::PhantomData}; +use sp_std::vec::Vec; #[cfg(feature = "std")] use codec::{Encode, Decode}; #[cfg(feature = "std")] use serde::{Serialize, Deserialize}; use frame_support::{ensure, decl_module, decl_storage, decl_event, decl_error}; use frame_support::weights::Weight; -use frame_support::traits::{Currency, WithdrawReason, ExistenceRequirement, Get}; -use frame_system::ensure_signed; -use sp_runtime::ModuleId; +use frame_support::traits::{Currency, ExistenceRequirement, Get}; +use frame_system::RawOrigin; use sp_core::{U256, H256, H160, Hasher}; use sp_runtime::{ - DispatchResult, traits::{UniqueSaturatedInto, AccountIdConversion, SaturatedConversion}, + DispatchResult, AccountId32, traits::{UniqueSaturatedInto, SaturatedConversion, BadOrigin}, }; use sha3::{Digest, Keccak256}; pub use evm::{ExitReason, ExitSucceed, ExitError, ExitRevert, ExitFatal}; @@ -58,41 +57,121 @@ impl FeeCalculator for () { fn min_gas_price() -> U256 { U256::zero() } } -/// 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 { - /// Given a Substrate address, return the corresponding Ethereum address. - fn convert_account_id(account_id: &A) -> H160; +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; } -/// Hash and then truncate the account id, taking the last 160-bit as the Ethereum address. -pub struct HashTruncateConvertAccountId(PhantomData); +/// 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 Default for HashTruncateConvertAccountId { - fn default() -> Self { - Self(PhantomData) +impl EnsureAddressOrigin for EnsureAddressSame where + OuterOrigin: Into, OuterOrigin>> + From>, +{ + type Success = H160; + + fn try_address_origin( + address: &H160, + origin: OuterOrigin, + ) -> Result { + origin.into().and_then(|o| match o { + RawOrigin::Signed(who) if &who == address => Ok(who), + r => Err(OuterOrigin::from(r)) + }) } } -impl> ConvertAccountId for HashTruncateConvertAccountId { - fn convert_account_id(account_id: &A) -> H160 { - let account_id = H::hash(account_id.as_ref()); - let account_id_len = account_id.as_ref().len(); - let mut value = [0u8; 20]; - let value_len = value.len(); +/// Ensure that the origin is root. +pub struct EnsureAddressRoot(sp_std::marker::PhantomData); - if value_len > account_id_len { - value[(value_len - account_id_len)..].copy_from_slice(account_id.as_ref()); - } else { - value.copy_from_slice(&account_id.as_ref()[(account_id_len - value_len)..]); - } +impl EnsureAddressOrigin for EnsureAddressRoot where + OuterOrigin: Into, OuterOrigin>> + From>, +{ + type Success = (); - H160::from(value) + fn try_address_origin( + _address: &H160, + origin: OuterOrigin, + ) -> Result<(), OuterOrigin> { + origin.into().and_then(|o| match o { + RawOrigin::Root => Ok(()), + r => Err(OuterOrigin::from(r)), + }) + } +} + +/// Ensure that the origin never happens. +pub struct EnsureAddressNever(sp_std::marker::PhantomData); + +impl EnsureAddressOrigin for EnsureAddressNever { + type Success = AccountId; + + fn try_address_origin( + _address: &H160, + origin: OuterOrigin, + ) -> Result { + Err(origin) + } +} + +/// Ensure that the address is truncated hash of the origin. Only works if the account id is +/// `AccountId32`. +pub struct EnsureAddressTruncated; + +impl EnsureAddressOrigin for EnsureAddressTruncated where + OuterOrigin: Into, OuterOrigin>> + From>, +{ + type Success = AccountId32; + + fn try_address_origin( + address: &H160, + origin: OuterOrigin, + ) -> Result { + origin.into().and_then(|o| match o { + RawOrigin::Signed(who) + if AsRef::<[u8; 32]>::as_ref(&who)[0..20] == address[0..20] => Ok(who), + r => Err(OuterOrigin::from(r)) + }) + } +} + +pub trait AddressMapping { + fn into_account_id(address: H160) -> A; +} + +/// Identity address mapping. +pub struct IdentityAddressMapping; + +impl AddressMapping for IdentityAddressMapping { + fn into_account_id(address: H160) -> H160 { address } +} + +/// Hashed address mapping. +pub struct HashedAddressMapping(sp_std::marker::PhantomData); + +impl> AddressMapping for HashedAddressMapping { + fn into_account_id(address: H160) -> AccountId32 { + let mut data = [0u8; 24]; + data[0..4].copy_from_slice(b"evm:"); + data[4..24].copy_from_slice(&address[..]); + let hash = H::hash(&data); + + AccountId32::from(Into::<[u8; 32]>::into(hash)) } } @@ -133,14 +212,19 @@ static ISTANBUL_CONFIG: Config = Config::istanbul(); /// EVM module trait pub trait Trait: frame_system::Trait + pallet_timestamp::Trait { - /// The EVM's module id - type ModuleId: Get; /// Calculator for current gas price. type FeeCalculator: FeeCalculator; - /// Convert account ID to H160; - type ConvertAccountId: ConvertAccountId; - /// Currency type for deposit and withdraw. + + /// Allow the origin to call on behalf of given address. + type CallOrigin: EnsureAddressOrigin; + /// Allow the origin to withdraw on behalf of given address. + type WithdrawOrigin: EnsureAddressOrigin; + + /// Mapping from address to account id. + type AddressMapping: AddressMapping; + /// Currency type for withdraw and balance storage. type Currency: Currency; + /// The overarching event type. type Event: From> + Into<::Event>; /// Precompiles associated with this EVM engine. @@ -170,7 +254,6 @@ pub struct GenesisAccount { decl_storage! { trait Store for Module as EVM { - Accounts get(fn accounts): map hasher(blake2_128_concat) H160 => Account; AccountCodes get(fn account_codes): map hasher(blake2_128_concat) H160 => Vec; AccountStorages get(fn account_storages): double_map hasher(blake2_128_concat) H160, hasher(blake2_128_concat) H256 => H256; @@ -180,7 +263,7 @@ decl_storage! { config(accounts): std::collections::BTreeMap; build(|config: &GenesisConfig| { for (address, account) in &config.accounts { - Accounts::insert(address, Account { + Module::::mutate_account_basic(&address, Account { balance: account.balance, nonce: account.nonce, }); @@ -239,57 +322,25 @@ decl_module! { fn deposit_event() = default; - const ModuleId: ModuleId = T::ModuleId::get(); - - /// Deposit balance from currency/balances module into EVM. - #[weight = 0] - fn deposit_balance(origin, value: BalanceOf) { - let sender = ensure_signed(origin)?; - - let imbalance = T::Currency::withdraw( - &sender, - value, - WithdrawReason::Reserve.into(), - ExistenceRequirement::AllowDeath, - )?; - T::Currency::resolve_creating(&Self::account_id(), imbalance); - - let bvalue = U256::from(UniqueSaturatedInto::::unique_saturated_into(value)); - let address = T::ConvertAccountId::convert_account_id(&sender); - Accounts::mutate(&address, |account| { - account.balance += bvalue; - }); - Module::::deposit_event(Event::::BalanceDeposit(sender, address, bvalue)); - } - /// Withdraw balance from EVM into currency/balances module. #[weight = 0] - fn withdraw_balance(origin, value: BalanceOf) { - let sender = ensure_signed(origin)?; - let address = T::ConvertAccountId::convert_account_id(&sender); - let bvalue = U256::from(UniqueSaturatedInto::::unique_saturated_into(value)); + fn withdraw(origin, address: H160, value: BalanceOf) { + let destination = T::WithdrawOrigin::ensure_address_origin(&address, origin)?; + let address_account_id = T::AddressMapping::into_account_id(address); - let mut account = Accounts::get(&address); - account.balance = account.balance.checked_sub(bvalue) - .ok_or(Error::::BalanceLow)?; - - let imbalance = T::Currency::withdraw( - &Self::account_id(), + T::Currency::transfer( + &address_account_id, + &destination, value, - WithdrawReason::Reserve.into(), ExistenceRequirement::AllowDeath )?; - - Accounts::insert(&address, account); - - T::Currency::resolve_creating(&sender, imbalance); - Module::::deposit_event(Event::::BalanceWithdraw(sender, address, bvalue)); } /// Issue an EVM call operation. This is similar to a message call transaction in Ethereum. #[weight = (*gas_price).saturated_into::().saturating_mul(*gas_limit as Weight)] fn call( origin, + source: H160, target: H160, input: Vec, value: U256, @@ -298,9 +349,7 @@ decl_module! { nonce: Option, ) -> DispatchResult { ensure!(gas_price >= T::FeeCalculator::min_gas_price(), Error::::GasPriceTooLow); - - let sender = ensure_signed(origin)?; - let source = T::ConvertAccountId::convert_account_id(&sender); + T::CallOrigin::ensure_address_origin(&source, origin)?; match Self::execute_call( source, @@ -328,6 +377,7 @@ decl_module! { #[weight = (*gas_price).saturated_into::().saturating_mul(*gas_limit as Weight)] fn create( origin, + source: H160, init: Vec, value: U256, gas_limit: u32, @@ -335,9 +385,7 @@ decl_module! { nonce: Option, ) -> DispatchResult { ensure!(gas_price >= T::FeeCalculator::min_gas_price(), Error::::GasPriceTooLow); - - let sender = ensure_signed(origin)?; - let source = T::ConvertAccountId::convert_account_id(&sender); + T::CallOrigin::ensure_address_origin(&source, origin)?; match Self::execute_create( source, @@ -363,6 +411,7 @@ decl_module! { #[weight = (*gas_price).saturated_into::().saturating_mul(*gas_limit as Weight)] fn create2( origin, + source: H160, init: Vec, salt: H256, value: U256, @@ -371,9 +420,7 @@ decl_module! { nonce: Option, ) -> DispatchResult { ensure!(gas_price >= T::FeeCalculator::min_gas_price(), Error::::GasPriceTooLow); - - let sender = ensure_signed(origin)?; - let source = T::ConvertAccountId::convert_account_id(&sender); + T::CallOrigin::ensure_address_origin(&source, origin)?; match Self::execute_create2( source, @@ -399,17 +446,35 @@ decl_module! { } impl Module { - /// The account ID of the EVM module. - /// - /// This actually does computation. If you need to keep using it, then make sure you cache the - /// value and only call this once. - pub fn account_id() -> T::AccountId { - T::ModuleId::get().into_account() + fn remove_account(address: &H160) { + AccountCodes::remove(address); + AccountStorages::remove_prefix(address); + } + + fn mutate_account_basic(address: &H160, new: Account) { + let account_id = T::AddressMapping::into_account_id(*address); + let current = Self::account_basic(address); + + if current.nonce < new.nonce { + // ASSUME: in one single EVM transaction, the nonce will not increase more than + // `u128::max_value()`. + for _ in 0..(new.nonce - current.nonce).low_u128() { + frame_system::Module::::inc_account_nonce(&account_id); + } + } + + if current.balance < new.balance { + let diff = new.balance - current.balance; + T::Currency::slash(&account_id, diff.low_u128().unique_saturated_into()); + } else if current.balance > new.balance { + let diff = current.balance - new.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 = Accounts::get(address); + let account = Self::account_basic(address); let code_len = AccountCodes::decode_len(address).unwrap_or(0); account.nonce == U256::zero() && @@ -420,15 +485,21 @@ impl Module { /// Remove an account if its empty. pub fn remove_account_if_empty(address: &H160) { if Self::is_account_empty(address) { - Self::remove_account(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); + /// Get the account basic in EVM format. + pub fn account_basic(address: &H160) -> Account { + let account_id = T::AddressMapping::into_account_id(*address); + + let nonce = frame_system::Module::::account_nonce(&account_id); + let balance = T::Currency::free_balance(&account_id); + + Account { + nonce: U256::from(UniqueSaturatedInto::::unique_saturated_into(nonce)), + balance: U256::from(UniqueSaturatedInto::::unique_saturated_into(balance)), + } } /// Execute a create transaction on behalf of given sender. @@ -552,7 +623,7 @@ impl Module { let total_fee = gas_price.checked_mul(U256::from(gas_limit)) .ok_or(Error::::FeeOverflow)?; let total_payment = value.checked_add(total_fee).ok_or(Error::::PaymentOverflow)?; - let source_account = Accounts::get(&source); + let source_account = Self::account_basic(&source); ensure!(source_account.balance >= total_payment, Error::::BalanceLow); executor.withdraw(source, total_fee).map_err(|_| Error::::WithdrawFailed)?; diff --git a/substrate/frame/evm/src/tests.rs b/substrate/frame/evm/src/tests.rs index f8ff74d477..f818ee630b 100644 --- a/substrate/frame/evm/src/tests.rs +++ b/substrate/frame/evm/src/tests.rs @@ -6,7 +6,7 @@ use std::{str::FromStr, collections::BTreeMap}; use frame_support::{ assert_ok, impl_outer_origin, parameter_types, impl_outer_dispatch, }; -use sp_core::H256; +use sp_core::{Blake2Hasher, H256}; use sp_runtime::{ Perbill, testing::Header, @@ -39,7 +39,7 @@ impl frame_system::Trait for Test { type Hash = H256; type Call = OuterCall; type Hashing = BlakeTwo256; - type AccountId = H256; + type AccountId = AccountId32; type Lookup = IdentityLookup; type Header = Header; type Event = (); @@ -89,17 +89,19 @@ impl FeeCalculator for FixedGasPrice { 0.into() } } -parameter_types! { - pub const EVMModuleId: ModuleId = ModuleId(*b"py/evmpa"); -} + impl Trait for Test { - type ChainId = SystemChainId; - type ModuleId = EVMModuleId; type FeeCalculator = FixedGasPrice; - type ConvertAccountId = HashTruncateConvertAccountId; + + type CallOrigin = EnsureAddressRoot; + type WithdrawOrigin = EnsureAddressNever; + + type AddressMapping = HashedAddressMapping; type Currency = Balances; + type Event = Event; type Precompiles = (); + type ChainId = SystemChainId; } type System = frame_system::Module; @@ -134,7 +136,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { ); pallet_balances::GenesisConfig::::default().assimilate_storage(&mut t).unwrap(); - GenesisConfig { accounts }.assimilate_storage(&mut t).unwrap(); + GenesisConfig { accounts }.assimilate_storage::(&mut t).unwrap(); t.into() } @@ -142,7 +144,8 @@ pub fn new_test_ext() -> sp_io::TestExternalities { fn fail_call_return_ok() { new_test_ext().execute_with(|| { assert_ok!(EVM::call( - Origin::signed(H256::default()), + Origin::root(), + H160::default(), H160::from_str("1000000000000000000000000000000000000001").unwrap(), Vec::new(), U256::default(), @@ -152,7 +155,8 @@ fn fail_call_return_ok() { )); assert_ok!(EVM::call( - Origin::signed(H256::default()), + Origin::root(), + H160::default(), H160::from_str("1000000000000000000000000000000000000002").unwrap(), Vec::new(), U256::default(),