From cf5657484d628cebfa8cdd54878b846fd717e4b5 Mon Sep 17 00:00:00 2001
From: Dmitry Lavrenov <39522748+dmitrylavrenov@users.noreply.github.com>
Date: Tue, 31 Oct 2023 16:53:16 +0300
Subject: [PATCH 1/4] 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
---
 Cargo.lock                    |  17 ++
 Cargo.toml                    |   3 +
 frame/evm-system/Cargo.toml   |  45 ++++++
 frame/evm-system/src/lib.rs   | 236 +++++++++++++++++++++++++++
 frame/evm-system/src/mock.rs  | 142 +++++++++++++++++
 frame/evm-system/src/tests.rs | 291 ++++++++++++++++++++++++++++++++++
 6 files changed, 734 insertions(+)
 create mode 100644 frame/evm-system/Cargo.toml
 create mode 100644 frame/evm-system/src/lib.rs
 create mode 100644 frame/evm-system/src/mock.rs
 create mode 100644 frame/evm-system/src/tests.rs

diff --git a/Cargo.lock b/Cargo.lock
index 4c0e7f4083..6b3c7c068e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5442,6 +5442,23 @@ dependencies = [
  "sp-io",
 ]
 
+[[package]]
+name = "pallet-evm-system"
+version = "1.0.0-dev"
+dependencies = [
+ "fp-evm",
+ "frame-support",
+ "frame-system",
+ "log",
+ "mockall",
+ "parity-scale-codec",
+ "scale-info",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
 [[package]]
 name = "pallet-evm-test-vector-support"
 version = "1.0.0-dev"
diff --git a/Cargo.toml b/Cargo.toml
index 2c8c6ffbe7..77a3844afb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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",
@@ -51,6 +52,7 @@ kvdb-rocksdb = "0.17.0"
 libsecp256k1 = { version = "0.7.1", default-features = false }
 log = { version = "0.4.17", default-features = false }
 parity-db = "0.4.6"
+mockall = "0.11"
 rlp = { version = "0.5", default-features = false }
 scale-codec = { package = "parity-scale-codec", version = "3.2.1", default-features = false, features = ["derive"] }
 scale-info = { version = "2.3.1", default-features = false, features = ["derive"] }
@@ -149,6 +151,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 }
diff --git a/frame/evm-system/Cargo.toml b/frame/evm-system/Cargo.toml
new file mode 100644
index 0000000000..44e7ad46ad
--- /dev/null
+++ b/frame/evm-system/Cargo.toml
@@ -0,0 +1,45 @@
+[package]
+name = "pallet-evm-system"
+version = "1.0.0-dev"
+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",
+]
diff --git a/frame/evm-system/src/lib.rs b/frame/evm-system/src/lib.rs
new file mode 100644
index 0000000000..a5808a7714
--- /dev/null
+++ b/frame/evm-system/src/lib.rs
@@ -0,0 +1,236 @@
+//! # 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::generate_store(pub(super) trait Store)]
+	#[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]
+	#[pallet::getter(fn full_account)]
+	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) {}
+}
diff --git a/frame/evm-system/src/mock.rs b/frame/evm-system/src/mock.rs
new file mode 100644
index 0000000000..0782c3c758
--- /dev/null
+++ b/frame/evm-system/src/mock.rs
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: Apache-2.0
+// This file is part of Frontier.
+//
+// Copyright (c) 2020-2022 Parity Technologies (UK) Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Test mock for unit tests.
+
+use frame_support::{
+	traits::{ConstU32, ConstU64},
+};
+use mockall::mock;
+use sp_core::{H160, H256};
+use sp_runtime::{
+	generic,
+	traits::{BlakeTwo256, IdentityLookup}, BuildStorage,
+};
+use sp_std::{boxed::Box, prelude::*};
+
+use crate::{self as pallet_evm_system, *};
+
+mock! {
+	#[derive(Debug)]
+	pub DummyOnNewAccount {}
+
+	impl OnNewAccount<H160> for DummyOnNewAccount {
+		pub fn on_new_account(who: &H160);
+	}
+}
+
+mock! {
+	#[derive(Debug)]
+	pub DummyOnKilledAccount {}
+
+	impl OnKilledAccount<H160> for DummyOnKilledAccount {
+		pub fn on_killed_account(who: &H160);
+	}
+}
+
+type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
+type Block = frame_system::mocking::MockBlock<Test>;
+
+frame_support::construct_runtime! {
+	pub enum Test where
+		Block = Block,
+		NodeBlock = Block,
+		UncheckedExtrinsic = UncheckedExtrinsic,
+	{
+		System: frame_system,
+		EvmSystem: pallet_evm_system,
+	}
+}
+
+impl frame_system::Config for Test {
+	type BaseCallFilter = frame_support::traits::Everything;
+	type BlockWeights = ();
+	type BlockLength = ();
+	type RuntimeOrigin = RuntimeOrigin;
+	type RuntimeCall = RuntimeCall;
+	type Index = u64;
+	type BlockNumber = u64;
+	type Hash = H256;
+	type Hashing = BlakeTwo256;
+	type AccountId = H160;
+	type Lookup = IdentityLookup<Self::AccountId>;
+	type Header = generic::Header<u64, BlakeTwo256>;
+	type RuntimeEvent = RuntimeEvent;
+	type BlockHashCount = ConstU64<250>;
+	type DbWeight = ();
+	type Version = ();
+	type PalletInfo = PalletInfo;
+	type AccountData = ();
+	type OnNewAccount = ();
+	type OnKilledAccount = ();
+	type SystemWeightInfo = ();
+	type SS58Prefix = ();
+	type OnSetCode = ();
+	type MaxConsumers = ConstU32<16>;
+}
+
+impl pallet_evm_system::Config for Test {
+	type RuntimeEvent = RuntimeEvent;
+	type AccountId = H160;
+	type Index = u64;
+	type AccountData = u64;
+	type OnNewAccount = MockDummyOnNewAccount;
+	type OnKilledAccount = MockDummyOnKilledAccount;
+}
+
+/// Build test externalities from the custom genesis.
+/// Using this call requires manual assertions on the genesis init logic.
+pub fn new_test_ext() -> sp_io::TestExternalities {
+    // Build genesis.
+    let config = GenesisConfig {
+        ..Default::default()
+    };
+    let storage = config.build_storage().unwrap();
+
+    // Make test externalities from the storage.
+    storage.into()
+}
+
+pub fn runtime_lock() -> std::sync::MutexGuard<'static, ()> {
+    static MOCK_RUNTIME_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
+
+    // Ignore the poisoning for the tests that panic.
+    // We only care about concurrency here, not about the poisoning.
+    match MOCK_RUNTIME_MUTEX.lock() {
+        Ok(guard) => guard,
+        Err(poisoned) => poisoned.into_inner(),
+    }
+}
+
+pub trait TestExternalitiesExt {
+    fn execute_with_ext<R, E>(&mut self, execute: E) -> R
+    where
+        E: for<'e> FnOnce(&'e ()) -> R;
+}
+
+impl TestExternalitiesExt for frame_support::sp_io::TestExternalities {
+    fn execute_with_ext<R, E>(&mut self, execute: E) -> R
+    where
+        E: for<'e> FnOnce(&'e ()) -> R,
+    {
+        let guard = runtime_lock();
+        let result = self.execute_with(|| execute(&guard));
+        drop(guard);
+        result
+    }
+}
+
diff --git a/frame/evm-system/src/tests.rs b/frame/evm-system/src/tests.rs
new file mode 100644
index 0000000000..28fc971842
--- /dev/null
+++ b/frame/evm-system/src/tests.rs
@@ -0,0 +1,291 @@
+//! Unit tests.
+
+use sp_std::str::FromStr;
+
+use frame_support::{assert_ok, assert_noop};
+use mockall::predicate;
+use sp_core::H160;
+
+use crate::{mock::*, *};
+
+/// This test verifies that creating account works in the happy path.
+#[test]
+fn create_account_works() {
+    new_test_ext().execute_with_ext(|_| {
+		// Prepare test data.
+		let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap();
+
+		// Check test preconditions.
+		assert!(!EvmSystem::account_exists(&account_id));
+
+		// Set block number to enable events.
+		System::set_block_number(1);
+
+		// Set mock expectations.
+		let on_new_account_ctx = MockDummyOnNewAccount::on_new_account_context();
+		on_new_account_ctx
+			.expect()
+			.once()
+			.with(
+				predicate::eq(account_id),
+			)
+			.return_const(());
+
+		// Invoke the function under test.
+		assert_ok!(EvmSystem::create_account(&account_id));
+
+		// Assert state changes.
+		assert!(EvmSystem::account_exists(&account_id));
+		System::assert_has_event(RuntimeEvent::EvmSystem(Event::NewAccount { account: account_id } ));
+
+		// Assert mock invocations.
+		on_new_account_ctx.checkpoint();
+	});
+}
+
+/// This test verifies that creating account fails when the account already exists.
+#[test]
+fn create_account_fails() {
+    new_test_ext().execute_with(|| {
+		// Prepare test data.
+		let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap();
+		<Account<Test>>::insert(account_id.clone(), AccountInfo::<_, _>::default());
+
+		// Invoke the function under test.
+		assert_noop!(EvmSystem::create_account(&account_id), Error::<Test>::AccountAlreadyExist);
+	});
+}
+
+/// This test verifies that removing account works in the happy path.
+#[test]
+fn remove_account_works() {
+    new_test_ext().execute_with(|| {
+		// Prepare test data.
+		let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap();
+		<Account<Test>>::insert(account_id.clone(), AccountInfo::<_, _>::default());
+
+		// Set block number to enable events.
+		System::set_block_number(1);
+
+		// Set mock expectations.
+		let on_killed_account_ctx = MockDummyOnKilledAccount::on_killed_account_context();
+		on_killed_account_ctx
+			.expect()
+			.once()
+			.with(
+				predicate::eq(account_id),
+			)
+			.return_const(());
+
+		// Invoke the function under test.
+		assert_ok!(EvmSystem::remove_account(&account_id));
+
+		// Assert state changes.
+		assert!(!EvmSystem::account_exists(&account_id));
+		System::assert_has_event(RuntimeEvent::EvmSystem(Event::KilledAccount { account: account_id } ));
+
+		// Assert mock invocations.
+		on_killed_account_ctx.checkpoint();
+	});
+}
+
+/// This test verifies that removing account fails when the account doesn't exist.
+#[test]
+fn remove_account_fails() {
+    new_test_ext().execute_with(|| {
+		// Prepare test data.
+		let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap();
+
+		// Invoke the function under test.
+		assert_noop!(EvmSystem::remove_account(&account_id), Error::<Test>::AccountNotExist);
+	});
+}
+
+/// This test verifies that incrementing account nonce works in the happy path.
+#[test]
+fn inc_account_nonce_works() {
+    new_test_ext().execute_with(|| {
+		// Prepare test data.
+		let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap();
+
+		// Check test preconditions.
+		let nonce_before = EvmSystem::account_nonce(&account_id);
+
+		// Invoke the function under test.
+		EvmSystem::inc_account_nonce(&account_id);
+
+		// Assert state changes.
+		assert_eq!(EvmSystem::account_nonce(&account_id), nonce_before + 1);
+	});
+}
+
+/// This test verifies that try_mutate_exists works as expected in case data wasn't providing
+/// and returned data is `Some`. As a result, a new account has been created.
+#[test]
+fn try_mutate_exists_account_created() {
+	new_test_ext().execute_with(|| {
+		// Prepare test data.
+		let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap();
+
+		// Check test preconditions.
+		assert!(!EvmSystem::account_exists(&account_id));
+
+		// Set mock expectations.
+		let on_new_account_ctx = MockDummyOnNewAccount::on_new_account_context();
+		on_new_account_ctx
+			.expect()
+			.once()
+			.with(predicate::eq(account_id))
+			.return_const(());
+
+		// Set block number to enable events.
+		System::set_block_number(1);
+
+		// Invoke the function under test.
+		EvmSystem::try_mutate_exists(&account_id, |maybe_data| -> Result<(), DispatchError> {
+			*maybe_data = Some(1);
+			Ok(())
+		})
+		.unwrap();
+
+		// Assert state changes.
+		assert!(EvmSystem::account_exists(&account_id));
+		assert_eq!(EvmSystem::get(&account_id), 1);
+		System::assert_has_event(RuntimeEvent::EvmSystem(Event::NewAccount {
+			account: account_id,
+		}));
+
+		// Assert mock invocations.
+		on_new_account_ctx.checkpoint();
+	});
+}
+
+/// This test verifies that try_mutate_exists works as expected in case data was providing
+/// and returned data is `Some`. As a result, the account has been updated.
+#[test]
+fn try_mutate_exists_account_updated() {
+	new_test_ext().execute_with(|| {
+		// Prepare test data.
+		let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap();
+		let nonce = 1;
+		let data = 1;
+		<Account<Test>>::insert(account_id.clone(), AccountInfo { nonce, data });
+
+		// Check test preconditions.
+		assert!(EvmSystem::account_exists(&account_id));
+
+		// Set block number to enable events.
+		System::set_block_number(1);
+
+		// Invoke the function under test.
+		EvmSystem::try_mutate_exists(&account_id, |maybe_data| -> Result<(), DispatchError> {
+			if let Some(ref mut data) = maybe_data {
+				*data += 1;
+			}
+			Ok(())
+		})
+		.unwrap();
+
+		// Assert state changes.
+		assert!(EvmSystem::account_exists(&account_id));
+		assert_eq!(EvmSystem::get(&account_id), data + 1);
+	});
+}
+
+/// This test verifies that try_mutate_exists works as expected in case data was providing
+/// and returned data is `None`. As a result, the account has been removed.
+#[test]
+fn try_mutate_exists_account_removed() {
+	new_test_ext().execute_with(|| {
+		// Prepare test data.
+		let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap();
+		let nonce = 1;
+		let data = 1;
+		<Account<Test>>::insert(account_id.clone(), AccountInfo { nonce, data });
+
+		// Check test preconditions.
+		assert!(EvmSystem::account_exists(&account_id));
+
+		// Set mock expectations.
+		let on_killed_account_ctx = MockDummyOnKilledAccount::on_killed_account_context();
+		on_killed_account_ctx
+			.expect()
+			.once()
+			.with(predicate::eq(account_id))
+			.return_const(());
+
+		// Set block number to enable events.
+		System::set_block_number(1);
+
+		// Invoke the function under test.
+		EvmSystem::try_mutate_exists(&account_id, |maybe_data| -> Result<(), DispatchError> {
+			*maybe_data = None;
+			Ok(())
+		})
+		.unwrap();
+
+		// Assert state changes.
+		assert!(!EvmSystem::account_exists(&account_id));
+		System::assert_has_event(RuntimeEvent::EvmSystem(Event::KilledAccount {
+			account: account_id,
+		}));
+
+		// Assert mock invocations.
+		on_killed_account_ctx.checkpoint();
+	});
+}
+
+/// This test verifies that try_mutate_exists works as expected in case data wasn't providing
+/// and returned data is `None`. As a result, the account hasn't been created.
+#[test]
+fn try_mutate_exists_account_not_created() {
+	new_test_ext().execute_with(|| {
+		// Prepare test data.
+		let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap();
+
+		// Check test preconditions.
+		assert!(!EvmSystem::account_exists(&account_id));
+
+		// Set block number to enable events.
+		System::set_block_number(1);
+
+		// Invoke the function under test.
+		<Account<Test>>::try_mutate_exists(account_id, |maybe_data| -> Result<(), ()> {
+			*maybe_data = None;
+			Ok(())
+		})
+		.unwrap();
+
+		// Assert state changes.
+		assert!(!EvmSystem::account_exists(&account_id));
+	});
+}
+
+/// This test verifies that try_mutate_exists works as expected in case getting error
+/// during data mutation.
+#[test]
+fn try_mutate_exists_fails_without_changes() {
+	new_test_ext().execute_with(|| {
+		// Prepare test data.
+		let account_id = H160::from_str("1000000000000000000000000000000000000001").unwrap();
+		let nonce = 1;
+		let data = 1;
+		<Account<Test>>::insert(account_id.clone(), AccountInfo { nonce, data });
+
+		// Check test preconditions.
+		assert!(EvmSystem::account_exists(&account_id));
+
+		// Invoke the function under test.
+		assert_noop!(
+			<Account<Test>>::try_mutate_exists(account_id, |maybe_data| -> Result<(), ()> {
+				*maybe_data = None;
+				Err(())
+			}),
+			()
+		);
+
+		// Assert state changes.
+		assert!(EvmSystem::account_exists(&account_id));
+		assert_eq!(EvmSystem::get(&account_id), data);
+	});
+}

From f6fd17a8a29aaa3a87dffac5ad7754e27dae94e2 Mon Sep 17 00:00:00 2001
From: Dmitry Lavrenov <lawrenowdima@gmail.com>
Date: Wed, 22 Nov 2023 12:21:28 +0300
Subject: [PATCH 2/4] Remove deprecated trait Store

---
 frame/evm-system/src/lib.rs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/frame/evm-system/src/lib.rs b/frame/evm-system/src/lib.rs
index a5808a7714..74d34fa96c 100644
--- a/frame/evm-system/src/lib.rs
+++ b/frame/evm-system/src/lib.rs
@@ -33,7 +33,6 @@ pub mod pallet {
 	use sp_std::fmt::Debug;
 
 	#[pallet::pallet]
-	#[pallet::generate_store(pub(super) trait Store)]
 	#[pallet::without_storage_info]
 	pub struct Pallet<T>(PhantomData<T>);
 

From 5923aaac489f8bae8c54de88ac07d9cb7ae55637 Mon Sep 17 00:00:00 2001
From: Dmitry Lavrenov <lawrenowdima@gmail.com>
Date: Wed, 22 Nov 2023 12:22:43 +0300
Subject: [PATCH 3/4] Add Apache-2.0 license

---
 frame/evm-system/Cargo.toml | 1 +
 frame/evm-system/src/lib.rs | 2 ++
 2 files changed, 3 insertions(+)

diff --git a/frame/evm-system/Cargo.toml b/frame/evm-system/Cargo.toml
index 44e7ad46ad..1a114304fe 100644
--- a/frame/evm-system/Cargo.toml
+++ b/frame/evm-system/Cargo.toml
@@ -1,6 +1,7 @@
 [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 }
diff --git a/frame/evm-system/src/lib.rs b/frame/evm-system/src/lib.rs
index 74d34fa96c..9448d8c084 100644
--- a/frame/evm-system/src/lib.rs
+++ b/frame/evm-system/src/lib.rs
@@ -1,3 +1,5 @@
+// SPDX-License-Identifier: Apache-2.0
+
 //! # EVM System Pallet.
 
 // Ensure we're `no_std` when compiling for Wasm.

From a55c0530835d1d63f176e97938ab0f1eea453f5c Mon Sep 17 00:00:00 2001
From: Dmitry Lavrenov <lawrenowdima@gmail.com>
Date: Wed, 22 Nov 2023 12:23:46 +0300
Subject: [PATCH 4/4] Remove deprecated storage getter

---
 frame/evm-system/src/lib.rs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/frame/evm-system/src/lib.rs b/frame/evm-system/src/lib.rs
index 9448d8c084..66ff84fc79 100644
--- a/frame/evm-system/src/lib.rs
+++ b/frame/evm-system/src/lib.rs
@@ -79,7 +79,6 @@ pub mod pallet {
 
 	/// The full account information for a particular account ID.
 	#[pallet::storage]
-	#[pallet::getter(fn full_account)]
 	pub type Account<T: Config> = StorageMap<
 		_,
 		Blake2_128Concat,