forked from polkadot-evm/frontier
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Sovereign accounts and EVM system layer (#94)
* Sovereign accounts and EVM system layer (#86) * Frame evm system (#62) * Add initial impl of evm-system * Check account existence * Improve creation account logic * Add new line * Add default implementations * Add mock * Use DispatchResult instead of custom enums * Basic create account tests * Add simple tests with remove account and nonce update * Remove default implementations for OnNewAccount and OnKilledAccount * Add mock objects for OnNewAccount and OnKilledAccount * Use mock logic in tests * Some tests improvements * Add docs to tests * Check events in tests * Add default implementation for OnNewAccount and OnKilledAccount for empty tuple (#63) * Implement StoredMap for EvmSystem (#64) * Add try-runtime feature into `pallet-evm-system` (#67) Add try-runtime feature at pallet-evm-system * Use `sp_std` library to add FromStr trait for tests at `pallet-evm-system` (#68) Use sp_std library to add FromStr trait for tests at pallet-evm-system * Rename FullAccount into Account at `pallet-evm-system` (#69) Rename FullAccount into Account at pallet-evm-system * Fix `try_mutate_exists` implementation and add tests to check it (#70) * Fix try_mutate_exists logic * Add tests * Fix AccountData type at mock * Remove redundant mock expectations * Add comments for new tests * More explicitly handle (none,false) case * Rename some_data back to maybe_account_data * Add data changes for try_mutate_exists_fails_without_changes test * Add try_mutate_exists_account_not_created test * Add assert_noop to check state chages * Return success for try_mutate_exists_account_not_created test * Use workspace deps * Remove license * Implement missed AccountProvider for EvmSystem * Remove deprecated trait Store * Add Apache-2.0 license * Remove deprecated storage getter
- Loading branch information
1 parent
1d7c4f1
commit 8a1c198
Showing
6 changed files
with
735 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
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,46 @@ | ||
[package] | ||
name = "pallet-evm-system" | ||
version = "1.0.0-dev" | ||
license = "Apache-2.0" | ||
description = "FRAME EVM SYSTEM pallet." | ||
edition = { workspace = true } | ||
repository = { workspace = true } | ||
|
||
[package.metadata.docs.rs] | ||
targets = ["x86_64-unknown-linux-gnu"] | ||
|
||
[dependencies] | ||
log = { workspace = true, default-features = false } | ||
scale-codec = { package = "parity-scale-codec", workspace = true } | ||
scale-info = { workspace = true } | ||
# Substrate | ||
frame-support = { workspace = true } | ||
frame-system = { workspace = true } | ||
sp-runtime = { workspace = true } | ||
sp-std = { workspace = true } | ||
# Frontier | ||
fp-evm = { workspace = true } | ||
|
||
[dev-dependencies] | ||
mockall = { workspace = true } | ||
sp-core = { workspace = true } | ||
sp-io = { workspace = true } | ||
|
||
[features] | ||
default = ["std"] | ||
std = [ | ||
"log/std", | ||
"scale-codec/std", | ||
"scale-info/std", | ||
# Substrate | ||
"frame-support/std", | ||
"frame-system/std", | ||
"sp-runtime/std", | ||
"sp-std/std", | ||
# Frontier | ||
"fp-evm/std", | ||
] | ||
try-runtime = [ | ||
"frame-support/try-runtime", | ||
"frame-system/try-runtime", | ||
] |
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,236 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
//! # EVM System Pallet. | ||
|
||
// Ensure we're `no_std` when compiling for Wasm. | ||
#![cfg_attr(not(feature = "std"), no_std)] | ||
|
||
use frame_support::traits::StoredMap; | ||
use sp_runtime::{traits::One, RuntimeDebug, DispatchResult, DispatchError}; | ||
use scale_codec::{Encode, Decode, MaxEncodedLen, FullCodec}; | ||
use scale_info::TypeInfo; | ||
|
||
#[cfg(test)] | ||
mod mock; | ||
#[cfg(test)] | ||
mod tests; | ||
|
||
pub use pallet::*; | ||
|
||
/// Account information. | ||
#[derive(Clone, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] | ||
pub struct AccountInfo<Index, AccountData> { | ||
/// The number of transactions this account has sent. | ||
pub nonce: Index, | ||
/// The additional data that belongs to this account. Used to store the balance(s) in a lot of | ||
/// chains. | ||
pub data: AccountData, | ||
} | ||
|
||
#[frame_support::pallet] | ||
pub mod pallet { | ||
use super::*; | ||
use frame_support::pallet_prelude::*; | ||
use sp_runtime::traits::{MaybeDisplay, AtLeast32Bit}; | ||
use sp_std::fmt::Debug; | ||
|
||
#[pallet::pallet] | ||
#[pallet::without_storage_info] | ||
pub struct Pallet<T>(PhantomData<T>); | ||
|
||
#[pallet::config] | ||
pub trait Config: frame_system::Config { | ||
/// The overarching event type. | ||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; | ||
|
||
/// The user account identifier type. | ||
type AccountId: Parameter | ||
+ Member | ||
+ MaybeSerializeDeserialize | ||
+ Debug | ||
+ MaybeDisplay | ||
+ Ord | ||
+ MaxEncodedLen; | ||
|
||
/// Account index (aka nonce) type. This stores the number of previous transactions | ||
/// associated with a sender account. | ||
type Index: Parameter | ||
+ Member | ||
+ MaybeSerializeDeserialize | ||
+ Debug | ||
+ Default | ||
+ MaybeDisplay | ||
+ AtLeast32Bit | ||
+ Copy | ||
+ MaxEncodedLen; | ||
|
||
/// Data to be associated with an account (other than nonce/transaction counter, which this | ||
/// pallet does regardless). | ||
type AccountData: Member + FullCodec + Clone + Default + TypeInfo + MaxEncodedLen; | ||
|
||
/// Handler for when a new account has just been created. | ||
type OnNewAccount: OnNewAccount<<Self as Config>::AccountId>; | ||
|
||
/// A function that is invoked when an account has been determined to be dead. | ||
/// | ||
/// All resources should be cleaned up associated with the given account. | ||
type OnKilledAccount: OnKilledAccount<<Self as Config>::AccountId>; | ||
} | ||
|
||
/// The full account information for a particular account ID. | ||
#[pallet::storage] | ||
pub type Account<T: Config> = StorageMap< | ||
_, | ||
Blake2_128Concat, | ||
<T as Config>::AccountId, | ||
AccountInfo<<T as Config>::Index, <T as Config>::AccountData>, | ||
ValueQuery, | ||
>; | ||
|
||
#[pallet::event] | ||
#[pallet::generate_deposit(pub(super) fn deposit_event)] | ||
pub enum Event<T: Config> { | ||
/// A new account was created. | ||
NewAccount { account: <T as Config>::AccountId }, | ||
/// An account was reaped. | ||
KilledAccount { account: <T as Config>::AccountId }, | ||
} | ||
|
||
#[pallet::error] | ||
pub enum Error<T> { | ||
/// The account already exists in case creating it. | ||
AccountAlreadyExist, | ||
/// The account doesn't exist in case removing it. | ||
AccountNotExist, | ||
} | ||
} | ||
|
||
impl<T: Config> Pallet<T> { | ||
/// Check the account existence. | ||
pub fn account_exists(who: &<T as Config>::AccountId) -> bool { | ||
Account::<T>::contains_key(who) | ||
} | ||
|
||
/// An account is being created. | ||
fn on_created_account(who: <T as Config>::AccountId) { | ||
<T as Config>::OnNewAccount::on_new_account(&who); | ||
Self::deposit_event(Event::NewAccount { account: who }); | ||
} | ||
|
||
/// Do anything that needs to be done after an account has been killed. | ||
fn on_killed_account(who: <T as Config>::AccountId) { | ||
<T as Config>::OnKilledAccount::on_killed_account(&who); | ||
Self::deposit_event(Event::KilledAccount { account: who }); | ||
} | ||
|
||
/// Retrieve the account transaction counter from storage. | ||
pub fn account_nonce(who: &<T as Config>::AccountId) -> <T as Config>::Index { | ||
Account::<T>::get(who).nonce | ||
} | ||
|
||
/// Increment a particular account's nonce by 1. | ||
pub fn inc_account_nonce(who: &<T as Config>::AccountId) { | ||
Account::<T>::mutate(who, |a| a.nonce += <T as pallet::Config>::Index::one()); | ||
} | ||
|
||
/// Create an account. | ||
pub fn create_account(who: &<T as Config>::AccountId) -> DispatchResult { | ||
if Self::account_exists(who) { | ||
return Err(Error::<T>::AccountAlreadyExist.into()); | ||
} | ||
|
||
Account::<T>::insert(who.clone(), AccountInfo::<_, _>::default()); | ||
Self::on_created_account(who.clone()); | ||
Ok(()) | ||
} | ||
|
||
/// Remove an account. | ||
pub fn remove_account(who: &<T as Config>::AccountId) -> DispatchResult { | ||
if !Self::account_exists(who) { | ||
return Err(Error::<T>::AccountNotExist.into()); | ||
} | ||
|
||
Account::<T>::remove(who); | ||
Self::on_killed_account(who.clone()); | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl<T: Config> StoredMap<<T as Config>::AccountId, <T as Config>::AccountData> for Pallet<T> { | ||
fn get(k: &<T as Config>::AccountId) -> <T as Config>::AccountData { | ||
Account::<T>::get(k).data | ||
} | ||
|
||
fn try_mutate_exists<R, E: From<DispatchError>>( | ||
k: &<T as Config>::AccountId, | ||
f: impl FnOnce(&mut Option<<T as Config>::AccountData>) -> Result<R, E>, | ||
) -> Result<R, E> { | ||
let (mut maybe_account_data, was_providing) = if Self::account_exists(k) { | ||
(Some(Account::<T>::get(k).data), true) | ||
} else { | ||
(None, false) | ||
}; | ||
|
||
let result = f(&mut maybe_account_data)?; | ||
|
||
match (maybe_account_data, was_providing) { | ||
(Some(data), false) => { | ||
Account::<T>::mutate(k, |a| a.data = data); | ||
Self::on_created_account(k.clone()); | ||
} | ||
(Some(data), true) => { | ||
Account::<T>::mutate(k, |a| a.data = data); | ||
} | ||
(None, true) => { | ||
Account::<T>::remove(k); | ||
Self::on_killed_account(k.clone()); | ||
} | ||
(None, false) => { | ||
// Do nothing. | ||
} | ||
} | ||
|
||
Ok(result) | ||
} | ||
} | ||
|
||
impl<T: Config> fp_evm::AccountProvider for Pallet<T> { | ||
type AccountId = <T as Config>::AccountId; | ||
type Index = <T as Config>::Index; | ||
|
||
fn create_account(who: &Self::AccountId) { | ||
let _ = Self::create_account(who); | ||
} | ||
|
||
fn remove_account(who: &Self::AccountId) { | ||
let _ = Self::remove_account(who); | ||
} | ||
|
||
fn account_nonce(who: &Self::AccountId) -> Self::Index { | ||
Self::account_nonce(who) | ||
} | ||
|
||
fn inc_account_nonce(who: &Self::AccountId) { | ||
Self::inc_account_nonce(who); | ||
} | ||
} | ||
|
||
/// Interface to handle account creation. | ||
pub trait OnNewAccount<AccountId> { | ||
/// A new account `who` has been registered. | ||
fn on_new_account(who: &AccountId); | ||
} | ||
|
||
impl<AccountId> OnNewAccount<AccountId> for () { | ||
fn on_new_account(_who: &AccountId) {} | ||
} | ||
|
||
/// Interface to handle account killing. | ||
pub trait OnKilledAccount<AccountId> { | ||
/// The account with the given id was reaped. | ||
fn on_killed_account(who: &AccountId); | ||
} | ||
|
||
impl<AccountId> OnKilledAccount<AccountId> for () { | ||
fn on_killed_account(_who: &AccountId) {} | ||
} |
Oops, something went wrong.