Skip to content

Commit

Permalink
Sovereign accounts and EVM system layer (#94)
Browse files Browse the repository at this point in the history
* 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
dmitrylavrenov committed Dec 20, 2023
1 parent 1d7c4f1 commit 8a1c198
Show file tree
Hide file tree
Showing 6 changed files with 735 additions and 0 deletions.
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"frame/ethereum",
"frame/evm",
"frame/evm-chain-id",
"frame/evm-system",
"frame/hotfix-sufficients",
"frame/evm/precompile/sha3fips",
"frame/evm/precompile/simple",
Expand Down Expand Up @@ -54,6 +55,7 @@ jsonrpsee = "0.16.2"
kvdb-rocksdb = "0.18.0"
libsecp256k1 = { version = "0.7.1", default-features = false }
log = { version = "0.4.17", default-features = false }
mockall = "0.11"
parity-db = "0.4.8"
parking_lot = "0.12.1"
rlp = { version = "0.5", default-features = false }
Expand Down Expand Up @@ -156,6 +158,7 @@ pallet-dynamic-fee = { version = "4.0.0-dev", path = "frame/dynamic-fee", defaul
pallet-ethereum = { version = "4.0.0-dev", path = "frame/ethereum", default-features = false }
pallet-evm = { version = "6.0.0-dev", path = "frame/evm", default-features = false }
pallet-evm-chain-id = { version = "1.0.0-dev", path = "frame/evm-chain-id", default-features = false }
pallet-evm-system = { version = "1.0.0-dev", path = "frame/evm-system", default-features = false }
pallet-evm-precompile-modexp = { version = "2.0.0-dev", path = "frame/evm/precompile/modexp", default-features = false }
pallet-evm-precompile-sha3fips = { version = "2.0.0-dev", path = "frame/evm/precompile/sha3fips", default-features = false }
pallet-evm-precompile-simple = { version = "2.0.0-dev", path = "frame/evm/precompile/simple", default-features = false }
Expand Down
46 changes: 46 additions & 0 deletions frame/evm-system/Cargo.toml
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",
]
236 changes: 236 additions & 0 deletions frame/evm-system/src/lib.rs
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) {}
}
Loading

0 comments on commit 8a1c198

Please sign in to comment.