From 9856d9e60aa95b20f90849d823302121d1314f46 Mon Sep 17 00:00:00 2001 From: Alexander Krupenkin Date: Tue, 7 Feb 2023 08:58:11 +0300 Subject: [PATCH 01/23] Init pallet-account --- Cargo.toml | 1 + frame/pallet-account/Cargo.toml | 41 +++++++++++++++ frame/pallet-account/src/lib.rs | 90 +++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 frame/pallet-account/Cargo.toml create mode 100644 frame/pallet-account/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 0ef30383..e18e116e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "frame/collator-selection", "frame/custom-signatures", "frame/dapps-staking", + "frame/pallet-account", "frame/pallet-xcm", "frame/pallet-xvm", "frame/xc-asset-config", diff --git a/frame/pallet-account/Cargo.toml b/frame/pallet-account/Cargo.toml new file mode 100644 index 00000000..53d8877d --- /dev/null +++ b/frame/pallet-account/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "pallet-account" +authors = ["Stake Technologies"] +edition = "2021" +version = "0.1.0" + +[dependencies] +log = { version = "0.4", default-features = false } +serde = { version = "1.0.106", optional = true } + +# Substrate +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.33", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.33", default-features = false } +scale-info = { version = "2.0", default-features = false, features = ["derive"] } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.33", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.33", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.33", default-features = false } + +# Benchmarks +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.33", optional = true, default-features = false } + +[dev-dependencies] + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "serde", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] + +runtime-benchmarks = [ + "frame-benchmarking", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/pallet-account/src/lib.rs b/frame/pallet-account/src/lib.rs new file mode 100644 index 00000000..4ea0fe66 --- /dev/null +++ b/frame/pallet-account/src/lib.rs @@ -0,0 +1,90 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +//! # Account abstraction pallet +//! +//! ## Overview +//! +//! ## Interface +//! +//! ### Dispatchable Function +//! +//! +//! ### Other +//! +//! + +#![cfg_attr(not(feature = "std"), no_std)] + +#[frame_support::pallet] +#[allow(clippy::module_inception)] +pub mod pallet { + use crate::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// General event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::error] + pub enum Error {} + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + } + + #[pallet::call] + impl Pallet { + /// Dispatch the given `call` from an account that the sender is authorised. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// Parameters: + /// - `derived`: The account will make a call on behalf of. + /// - `call`: The call to be made by the `derived` account. + #[pallet::weight({ + let di = call.get_dispatch_info(); + (T::WeightInfo::call_as(T::MaxAccounts::get()) + // AccountData for inner call origin accountdata. + .saturating_add(T::DbWeight::get().reads_writes(1, 1)) + .saturating_add(di.weight), + di.class) + })] + pub fn call_as( + origin: OriginFor, + derived: AccountIdLookupOf, + call: Box<::RuntimeCall>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let derived = T::Lookup::lookup(real)?; + + Self::do_call_as(derived, *call); + + Ok(()) + } + + } +} From a909f8eaf1fdd56bfd3b41658d75cc0753166768 Mon Sep 17 00:00:00 2001 From: Alexander Krupenkin Date: Mon, 20 Feb 2023 14:57:13 +0300 Subject: [PATCH 02/23] Added simple salt implementation & mocks --- frame/pallet-account/Cargo.toml | 13 +-- frame/pallet-account/src/lib.rs | 109 +++++++++++++++++++----- frame/pallet-account/src/mock.rs | 134 ++++++++++++++++++++++++++++++ frame/pallet-account/src/tests.rs | 38 +++++++++ 4 files changed, 269 insertions(+), 25 deletions(-) create mode 100644 frame/pallet-account/src/mock.rs create mode 100644 frame/pallet-account/src/tests.rs diff --git a/frame/pallet-account/Cargo.toml b/frame/pallet-account/Cargo.toml index 53d8877d..b3415a1a 100644 --- a/frame/pallet-account/Cargo.toml +++ b/frame/pallet-account/Cargo.toml @@ -10,15 +10,16 @@ serde = { version = "1.0.106", optional = true } # Substrate codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.33", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.33", default-features = false } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37", default-features = false } scale-info = { version = "2.0", default-features = false, features = ["derive"] } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.33", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.33", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.33", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37", default-features = false } # Benchmarks -frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.33", optional = true, default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37", optional = true, default-features = false } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" } [dev-dependencies] diff --git a/frame/pallet-account/src/lib.rs b/frame/pallet-account/src/lib.rs index 4ea0fe66..064fe067 100644 --- a/frame/pallet-account/src/lib.rs +++ b/frame/pallet-account/src/lib.rs @@ -20,10 +20,18 @@ //! //! ## Overview //! +//! An accout abstraction pallet make possible to derive new blockchain based +//! account for your existed external owned account (seed phrase based). The onchain +//! account could be drived to multiple address spaces: H160 and SS58. For example, +//! it makes possible predictable interaction between substrate native account and +//! EVM smart contracts. +//! //! ## Interface //! //! ### Dispatchable Function //! +//! * proxy() - make proxy call with derived account as origin +//! //! //! ### Other //! @@ -31,11 +39,53 @@ #![cfg_attr(not(feature = "std"), no_std)] +use codec::{Decode, Encode}; +use frame_support::weights::Weight; +use sp_core::RuntimeDebug; + +pub use pallet::*; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +/// A method to derive new account from existed one +pub trait AccountDeriving { + /// Derive new account from existed one + fn derive(&self, source: &AccountId) -> AccountId; +} + +/// Use simple salt and Blake2 hash for account deriving. +#[derive(Clone, Copy, Eq, PartialEq, Encode, Decode, scale_info::TypeInfo, RuntimeDebug)] +pub struct SimpleSalt(pub u32); + +impl + From<[u8; 32]>> AccountDeriving for SimpleSalt { + fn derive(&self, source: &AccountId) -> AccountId { + let salted_source = [source.as_ref(), &self.encode()[..]].concat(); + sp_core::blake2_256(&salted_source).into() + } +} + +pub trait WeightInfo { + fn call_as() -> Weight; +} + +impl WeightInfo for () { + fn call_as() -> Weight { + Default::default() + } +} + #[frame_support::pallet] #[allow(clippy::module_inception)] pub mod pallet { use crate::*; use frame_support::pallet_prelude::*; + use frame_support::{ + dispatch::{Dispatchable, GetDispatchInfo}, + traits::IsSubType, + }; use frame_system::pallet_prelude::*; #[pallet::pallet] @@ -44,8 +94,19 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { + /// Supported account deriving types. + type DerivingType: Parameter + AccountDeriving; + /// The overarching call type. + type RuntimeCall: Parameter + + Dispatchable + + GetDispatchInfo + + From> + + IsSubType> + + IsType<::RuntimeCall>; /// General event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; } #[pallet::error] @@ -54,37 +115,47 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { + CallExecuted { + account: T::AccountId, + result: DispatchResult, + }, } #[pallet::call] impl Pallet { - /// Dispatch the given `call` from an account that the sender is authorised. - /// - /// The dispatch origin for this call must be _Signed_. - /// - /// Parameters: - /// - `derived`: The account will make a call on behalf of. - /// - `call`: The call to be made by the `derived` account. - #[pallet::weight({ + /// Dispatch the given `call` from an account that the sender is authorised. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// Parameters: + /// - `deriving`: Account deriving method to be used for the call. + /// - `call`: The call to be made by the `derived` account. + #[pallet::weight({ let di = call.get_dispatch_info(); - (T::WeightInfo::call_as(T::MaxAccounts::get()) + (T::WeightInfo::call_as() // AccountData for inner call origin accountdata. .saturating_add(T::DbWeight::get().reads_writes(1, 1)) .saturating_add(di.weight), di.class) })] - pub fn call_as( - origin: OriginFor, - derived: AccountIdLookupOf, - call: Box<::RuntimeCall>, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - let derived = T::Lookup::lookup(real)?; + #[pallet::call_index(0)] + pub fn call_as( + origin: OriginFor, + deriving: T::DerivingType, + call: Box<::RuntimeCall>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; - Self::do_call_as(derived, *call); + let account = deriving.derive(&who); + let origin: T::RuntimeOrigin = frame_system::RawOrigin::Signed(account.clone()).into(); + let e = call.dispatch(origin); - Ok(()) - } + Self::deposit_event(Event::CallExecuted { + account, + result: e.map(|_| ()).map_err(|e| e.error), + }); + Ok(()) + } } } diff --git a/frame/pallet-account/src/mock.rs b/frame/pallet-account/src/mock.rs new file mode 100644 index 00000000..6e677833 --- /dev/null +++ b/frame/pallet-account/src/mock.rs @@ -0,0 +1,134 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +use crate as pallet_account; + +use frame_support::{ + construct_runtime, parameter_types, sp_io::TestExternalities, weights::Weight, +}; + +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + AccountId32, +}; + +pub(crate) type AccountId = AccountId32; +pub(crate) type BlockNumber = u64; +pub(crate) type Balance = u128; + +pub(crate) const ALICE: AccountId = AccountId::new([0u8; 32]); +pub(crate) const BOB: AccountId = AccountId::new([1u8; 32]); + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +/// Value shouldn't be less than 2 for testing purposes, otherwise we cannot test certain corner cases. +pub(crate) const EXISTENTIAL_DEPOSIT: Balance = 2; + +construct_runtime!( + pub struct TestRuntime + where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + Account: pallet_account, + } +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(Weight::from_ref_time(1024)); +} + +impl frame_system::Config for TestRuntime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type RuntimeCall = RuntimeCall; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const MaxLocks: u32 = 4; + pub const ExistentialDeposit: Balance = EXISTENTIAL_DEPOSIT; +} + +impl pallet_balances::Config for TestRuntime { + type MaxLocks = MaxLocks; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); +} + +impl pallet_account::Config for TestRuntime { + type DerivingType = crate::SimpleSalt; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + +pub struct ExternalityBuilder; + +impl ExternalityBuilder { + pub fn build() -> TestExternalities { + let mut storage = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + // This will cause some initial issuance + pallet_balances::GenesisConfig:: { + balances: vec![(ALICE, 9000), (BOB, 800)], + } + .assimilate_storage(&mut storage) + .ok(); + + let mut ext = TestExternalities::from(storage); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} diff --git a/frame/pallet-account/src/tests.rs b/frame/pallet-account/src/tests.rs new file mode 100644 index 00000000..5b37347a --- /dev/null +++ b/frame/pallet-account/src/tests.rs @@ -0,0 +1,38 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +use super::{pallet::Error, Event, *}; +use frame_support::{assert_noop, assert_ok}; +use mock::*; + +#[test] +pub fn is_ok() { + ExternalityBuilder::build().execute_with(|| { + let call: RuntimeCall = pallet_balances::Call::transfer { + dest: BOB, + value: 10, + } + .into(); + + assert_ok!(Account::call_as( + RuntimeOrigin::signed(ALICE).into(), + SimpleSalt(1), + Box::new(call), + )); + }) +} From 3619ac4f8c0aa0cbf8cdd09c105838d0b84d33b2 Mon Sep 17 00:00:00 2001 From: Alexander Krupenkin Date: Tue, 7 Mar 2023 07:44:50 +0300 Subject: [PATCH 03/23] Custom origin driven pallet-account --- frame/pallet-account/src/lib.rs | 103 ++---------------- frame/pallet-account/src/mock.rs | 27 ++++- frame/pallet-account/src/pallet/mod.rs | 139 +++++++++++++++++++++++++ frame/pallet-account/src/tests.rs | 6 +- frame/pallet-account/src/weights.rs | 33 ++++++ 5 files changed, 206 insertions(+), 102 deletions(-) create mode 100644 frame/pallet-account/src/pallet/mod.rs create mode 100644 frame/pallet-account/src/weights.rs diff --git a/frame/pallet-account/src/lib.rs b/frame/pallet-account/src/lib.rs index 064fe067..7d4e92d8 100644 --- a/frame/pallet-account/src/lib.rs +++ b/frame/pallet-account/src/lib.rs @@ -39,17 +39,18 @@ #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Decode, Encode}; -use frame_support::weights::Weight; -use sp_core::RuntimeDebug; +pub mod pallet; +pub use pallet::pallet::*; -pub use pallet::*; +pub mod weights; +pub use weights::*; #[cfg(test)] mod mock; #[cfg(test)] mod tests; +/* /// A method to derive new account from existed one pub trait AccountDeriving { /// Derive new account from existed one @@ -66,96 +67,4 @@ impl + From<[u8; 32]>> AccountDeriving for Sim sp_core::blake2_256(&salted_source).into() } } - -pub trait WeightInfo { - fn call_as() -> Weight; -} - -impl WeightInfo for () { - fn call_as() -> Weight { - Default::default() - } -} - -#[frame_support::pallet] -#[allow(clippy::module_inception)] -pub mod pallet { - use crate::*; - use frame_support::pallet_prelude::*; - use frame_support::{ - dispatch::{Dispatchable, GetDispatchInfo}, - traits::IsSubType, - }; - use frame_system::pallet_prelude::*; - - #[pallet::pallet] - #[pallet::without_storage_info] - pub struct Pallet(PhantomData); - - #[pallet::config] - pub trait Config: frame_system::Config { - /// Supported account deriving types. - type DerivingType: Parameter + AccountDeriving; - /// The overarching call type. - type RuntimeCall: Parameter - + Dispatchable - + GetDispatchInfo - + From> - + IsSubType> - + IsType<::RuntimeCall>; - /// General event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// Weight information for extrinsics in this pallet. - type WeightInfo: WeightInfo; - } - - #[pallet::error] - pub enum Error {} - - #[pallet::event] - #[pallet::generate_deposit(pub(crate) fn deposit_event)] - pub enum Event { - CallExecuted { - account: T::AccountId, - result: DispatchResult, - }, - } - - #[pallet::call] - impl Pallet { - /// Dispatch the given `call` from an account that the sender is authorised. - /// - /// The dispatch origin for this call must be _Signed_. - /// - /// Parameters: - /// - `deriving`: Account deriving method to be used for the call. - /// - `call`: The call to be made by the `derived` account. - #[pallet::weight({ - let di = call.get_dispatch_info(); - (T::WeightInfo::call_as() - // AccountData for inner call origin accountdata. - .saturating_add(T::DbWeight::get().reads_writes(1, 1)) - .saturating_add(di.weight), - di.class) - })] - #[pallet::call_index(0)] - pub fn call_as( - origin: OriginFor, - deriving: T::DerivingType, - call: Box<::RuntimeCall>, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - let account = deriving.derive(&who); - let origin: T::RuntimeOrigin = frame_system::RawOrigin::Signed(account.clone()).into(); - let e = call.dispatch(origin); - - Self::deposit_event(Event::CallExecuted { - account, - result: e.map(|_| ()).map_err(|e| e.error), - }); - - Ok(()) - } - } -} +*/ diff --git a/frame/pallet-account/src/mock.rs b/frame/pallet-account/src/mock.rs index 6e677833..dd3b9e10 100644 --- a/frame/pallet-account/src/mock.rs +++ b/frame/pallet-account/src/mock.rs @@ -19,7 +19,7 @@ use crate as pallet_account; use frame_support::{ - construct_runtime, parameter_types, sp_io::TestExternalities, weights::Weight, + construct_runtime, parameter_types, sp_io::TestExternalities, weights::Weight, RuntimeDebug, }; use sp_core::H256; @@ -29,6 +29,9 @@ use sp_runtime::{ AccountId32, }; +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + pub(crate) type AccountId = AccountId32; pub(crate) type BlockNumber = u64; pub(crate) type Balance = u128; @@ -39,6 +42,25 @@ pub(crate) const BOB: AccountId = AccountId::new([1u8; 32]); type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; +/// Origin for the account module. +#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub enum MyOrigin { + /// Substrate native origin. + Native(AccountId32), + /// The 20-byte length Ethereum like origin. + H160(sp_core::H160), +} + +impl TryInto for MyOrigin { + type Error = (); + fn try_into(self) -> Result { + match self { + MyOrigin::Native(a) => Ok(a), + _ => Err(()), + } + } +} + /// Value shouldn't be less than 2 for testing purposes, otherwise we cannot test certain corner cases. pub(crate) const EXISTENTIAL_DEPOSIT: Balance = 2; @@ -106,7 +128,8 @@ impl pallet_balances::Config for TestRuntime { } impl pallet_account::Config for TestRuntime { - type DerivingType = crate::SimpleSalt; + type CustomOrigin = MyOrigin; + type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); diff --git a/frame/pallet-account/src/pallet/mod.rs b/frame/pallet-account/src/pallet/mod.rs new file mode 100644 index 00000000..f51b7913 --- /dev/null +++ b/frame/pallet-account/src/pallet/mod.rs @@ -0,0 +1,139 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +#[frame_support::pallet] +#[allow(clippy::module_inception)] +pub mod pallet { + use crate::*; + + use frame_support::pallet_prelude::*; + use frame_support::{ + dispatch::{Dispatchable, GetDispatchInfo}, + traits::IsSubType, + }; + use frame_system::pallet_prelude::*; + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Custom origin type that used for derived accounts. + type CustomOrigin: Parameter + TryInto; + /// The runtime origin type. + type RuntimeOrigin: From + + From>; + /// The overarching call type. + type RuntimeCall: Parameter + + Dispatchable::RuntimeOrigin> + + GetDispatchInfo + + From> + + IsSubType> + + IsType<::RuntimeCall>; + /// General event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + /// Origin with given index not registered. + UnregisteredOrigin, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + CallExecuted { + origin: T::CustomOrigin, + result: DispatchResult, + }, + } + + #[pallet::origin] + pub type Origin = ::CustomOrigin; + + /// Account origins + #[pallet::storage] + pub type AccountOrigin = + StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; + + #[pallet::call] + impl Pallet { + /// Dispatch the given `call` from an account that the sender is authorised. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// Parameters: + /// - `origin_index`: Account origin index for using. + /// - `call`: The call to be made by the `derived` account. + #[pallet::weight({ + let di = call.get_dispatch_info(); + (T::WeightInfo::proxy_call() + // AccountData for inner call origin accountdata. + .saturating_add(T::DbWeight::get().reads_writes(1, 1)) + .saturating_add(di.weight), + di.class) + })] + #[pallet::call_index(0)] + pub fn proxy_call( + origin: OriginFor, + #[pallet::compact] origin_index: u32, + call: Box<::RuntimeCall>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + let custom_origin = AccountOrigin::::get(who) + .get(origin_index as usize) + .ok_or(Error::::UnregisteredOrigin)? + .clone(); + + let e = if let Ok(id) = custom_origin.clone().try_into() { + // in case of native dispatch with system signed origin + call.dispatch(frame_system::RawOrigin::Signed(id).into()) + } else { + // in other case dispatch with custom origin + call.dispatch(custom_origin.clone().into()) + }; + + Self::deposit_event(Event::CallExecuted { + origin: custom_origin, + result: e.map(|_| ()).map_err(|e| e.error), + }); + + Ok(()) + } + + /// Derive new origin for account. + /// + /// The dispatch origin for this call must be _Signed_. + #[pallet::weight(T::WeightInfo::new_origin())] + #[pallet::call_index(1)] + pub fn new_origin(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + Ok(()) + } + } +} diff --git a/frame/pallet-account/src/tests.rs b/frame/pallet-account/src/tests.rs index 5b37347a..cb2a4a43 100644 --- a/frame/pallet-account/src/tests.rs +++ b/frame/pallet-account/src/tests.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . -use super::{pallet::Error, Event, *}; +use super::{Error, Event, *}; use frame_support::{assert_noop, assert_ok}; use mock::*; @@ -29,9 +29,9 @@ pub fn is_ok() { } .into(); - assert_ok!(Account::call_as( + assert_ok!(Account::proxy_call( RuntimeOrigin::signed(ALICE).into(), - SimpleSalt(1), + 0, Box::new(call), )); }) diff --git a/frame/pallet-account/src/weights.rs b/frame/pallet-account/src/weights.rs new file mode 100644 index 00000000..70fceb06 --- /dev/null +++ b/frame/pallet-account/src/weights.rs @@ -0,0 +1,33 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +use frame_support::weights::Weight; + +pub trait WeightInfo { + fn proxy_call() -> Weight; + fn new_origin() -> Weight; +} + +impl WeightInfo for () { + fn proxy_call() -> Weight { + Default::default() + } + fn new_origin() -> Weight { + Default::default() + } +} From d81094750ba511038d83a1e087d86391b286dc2c Mon Sep 17 00:00:00 2001 From: Alexander Krupenkin Date: Tue, 28 Mar 2023 08:04:06 +0300 Subject: [PATCH 04/23] proxy call implementation * added unit tests * added meta transactions --- frame/pallet-account/Cargo.toml | 26 +++--- frame/pallet-account/src/lib.rs | 30 ++----- frame/pallet-account/src/mock.rs | 50 ++++++------ frame/pallet-account/src/origins.rs | 64 +++++++++++++++ frame/pallet-account/src/pallet/mod.rs | 109 ++++++++++++++++++++++--- frame/pallet-account/src/tests.rs | 108 +++++++++++++++++++++++- frame/pallet-account/src/weights.rs | 8 +- 7 files changed, 318 insertions(+), 77 deletions(-) create mode 100644 frame/pallet-account/src/origins.rs diff --git a/frame/pallet-account/Cargo.toml b/frame/pallet-account/Cargo.toml index b3415a1a..8d63d309 100644 --- a/frame/pallet-account/Cargo.toml +++ b/frame/pallet-account/Cargo.toml @@ -5,28 +5,30 @@ edition = "2021" version = "0.1.0" [dependencies] -log = { version = "0.4", default-features = false } -serde = { version = "1.0.106", optional = true } +log = { workspace = true } +serde = { workspace = true, optional = true } # Substrate -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37", default-features = false } -scale-info = { version = "2.0", default-features = false, features = ["derive"] } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37", default-features = false } +parity-scale-codec = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +scale-info = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } # Benchmarks -frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37", optional = true, default-features = false } -pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" } +frame-benchmarking = { workspace = true, optional = true } [dev-dependencies] +pallet-balances = { workspace = true, features = ["std"] } +assert_matches = { workspace = true } +hex-literal = { workspace = true } [features] default = ["std"] std = [ - "codec/std", + "parity-scale-codec/std", "frame-support/std", "frame-system/std", "scale-info/std", diff --git a/frame/pallet-account/src/lib.rs b/frame/pallet-account/src/lib.rs index 7d4e92d8..be5a150c 100644 --- a/frame/pallet-account/src/lib.rs +++ b/frame/pallet-account/src/lib.rs @@ -30,15 +30,16 @@ //! //! ### Dispatchable Function //! -//! * proxy() - make proxy call with derived account as origin -//! -//! -//! ### Other -//! +//! * new_origin() - create new origin for account +//! * proxy_call() - make proxy call with derived account as origin +//! * meta_call() - make meta call with dedicated payer account //! #![cfg_attr(not(feature = "std"), no_std)] +pub mod origins; +pub use origins::*; + pub mod pallet; pub use pallet::pallet::*; @@ -49,22 +50,3 @@ pub use weights::*; mod mock; #[cfg(test)] mod tests; - -/* -/// A method to derive new account from existed one -pub trait AccountDeriving { - /// Derive new account from existed one - fn derive(&self, source: &AccountId) -> AccountId; -} - -/// Use simple salt and Blake2 hash for account deriving. -#[derive(Clone, Copy, Eq, PartialEq, Encode, Decode, scale_info::TypeInfo, RuntimeDebug)] -pub struct SimpleSalt(pub u32); - -impl + From<[u8; 32]>> AccountDeriving for SimpleSalt { - fn derive(&self, source: &AccountId) -> AccountId { - let salted_source = [source.as_ref(), &self.encode()[..]].concat(); - sp_core::blake2_256(&salted_source).into() - } -} -*/ diff --git a/frame/pallet-account/src/mock.rs b/frame/pallet-account/src/mock.rs index dd3b9e10..be9d1c50 100644 --- a/frame/pallet-account/src/mock.rs +++ b/frame/pallet-account/src/mock.rs @@ -19,9 +19,9 @@ use crate as pallet_account; use frame_support::{ - construct_runtime, parameter_types, sp_io::TestExternalities, weights::Weight, RuntimeDebug, + construct_runtime, parameter_types, sp_io::TestExternalities, weights::Weight, }; - +use hex_literal::hex; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -29,9 +29,6 @@ use sp_runtime::{ AccountId32, }; -use codec::{Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; - pub(crate) type AccountId = AccountId32; pub(crate) type BlockNumber = u64; pub(crate) type Balance = u128; @@ -39,27 +36,15 @@ pub(crate) type Balance = u128; pub(crate) const ALICE: AccountId = AccountId::new([0u8; 32]); pub(crate) const BOB: AccountId = AccountId::new([1u8; 32]); -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; +pub(crate) const ALICE_ED25519: [u8; 32] = + hex!["88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee"]; -/// Origin for the account module. -#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] -pub enum MyOrigin { - /// Substrate native origin. - Native(AccountId32), - /// The 20-byte length Ethereum like origin. - H160(sp_core::H160), -} +pub(crate) const ALICE_D1_NATIVE: [u8; 32] = + hex!["9f0e444c69f77a49bd0be89db92c38fe713e0963165cca12faf5712d7657120f"]; +pub(crate) const ALICE_D2_H160: [u8; 20] = hex!["5d2532e641a22a8f5e0a42652fe82dc231fd27f8"]; -impl TryInto for MyOrigin { - type Error = (); - fn try_into(self) -> Result { - match self { - MyOrigin::Native(a) => Ok(a), - _ => Err(()), - } - } -} +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; /// Value shouldn't be less than 2 for testing purposes, otherwise we cannot test certain corner cases. pub(crate) const EXISTENTIAL_DEPOSIT: Balance = 2; @@ -127,10 +112,18 @@ impl pallet_balances::Config for TestRuntime { type WeightInfo = (); } +parameter_types! { + pub const ChainMagic: u16 = 0x4200; +} + impl pallet_account::Config for TestRuntime { - type CustomOrigin = MyOrigin; + type CustomOrigin = super::NativeAndEVM; + type CustomOriginKind = super::NativeAndEVMKind; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; + type ChainMagic = ChainMagic; + type Signer = sp_runtime::MultiSigner; + type Signature = sp_runtime::MultiSignature; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); } @@ -145,7 +138,12 @@ impl ExternalityBuilder { // This will cause some initial issuance pallet_balances::GenesisConfig:: { - balances: vec![(ALICE, 9000), (BOB, 800)], + balances: vec![ + (ALICE, 9000), + (ALICE_ED25519.into(), 1000), + (ALICE_D1_NATIVE.into(), 1000), + (BOB, 800), + ], } .assimilate_storage(&mut storage) .ok(); diff --git a/frame/pallet-account/src/origins.rs b/frame/pallet-account/src/origins.rs new file mode 100644 index 00000000..e8b73669 --- /dev/null +++ b/frame/pallet-account/src/origins.rs @@ -0,0 +1,64 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{AccountId32, RuntimeDebug}; + +/// Derive new origin. +pub trait OriginDeriving { + /// Derive new origin depend of account and index + fn derive(&self, source: &AccountId, index: u32) -> Origin; +} + +/// Origin that support native and EVM compatible options. +#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub enum NativeAndEVM { + /// Substrate native origin. + Native(AccountId32), + /// The 20-byte length Ethereum like origin. + H160(sp_core::H160), +} + +impl TryInto for NativeAndEVM { + type Error = (); + fn try_into(self) -> Result { + match self { + NativeAndEVM::Native(a) => Ok(a), + _ => Err(()), + } + } +} + +/// Kind for NativeAndEVM origin. +#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub enum NativeAndEVMKind { + Native, + H160, +} + +impl OriginDeriving for NativeAndEVMKind { + fn derive(&self, source: &AccountId32, index: u32) -> NativeAndEVM { + let salted_source = [source.as_ref(), &index.encode()[..]].concat(); + let derived = sp_core::blake2_256(&salted_source); + match self { + NativeAndEVMKind::Native => NativeAndEVM::Native(derived.into()), + NativeAndEVMKind::H160 => NativeAndEVM::H160(sp_core::H160::from_slice(&derived[..20])), + } + } +} diff --git a/frame/pallet-account/src/pallet/mod.rs b/frame/pallet-account/src/pallet/mod.rs index f51b7913..62b90d49 100644 --- a/frame/pallet-account/src/pallet/mod.rs +++ b/frame/pallet-account/src/pallet/mod.rs @@ -27,6 +27,7 @@ pub mod pallet { traits::IsSubType, }; use frame_system::pallet_prelude::*; + use sp_runtime::traits::{IdentifyAccount, Verify}; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); @@ -39,8 +40,10 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { - /// Custom origin type that used for derived accounts. + /// Custom origin type. type CustomOrigin: Parameter + TryInto; + /// Parameter that defin different origin options and how to create it. + type CustomOriginKind: Parameter + OriginDeriving; /// The runtime origin type. type RuntimeOrigin: From + From>; @@ -51,6 +54,12 @@ pub mod pallet { + From> + IsSubType> + IsType<::RuntimeCall>; + /// Meta transaction chain magic prefix. Required to prevent tx replay attack. + type ChainMagic: Get; + /// Meta transaction signature type. + type Signature: Parameter + Verify; + /// Meta transaction signer type. + type Signer: IdentifyAccount; /// General event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Weight information for extrinsics in this pallet. @@ -61,13 +70,25 @@ pub mod pallet { pub enum Error { /// Origin with given index not registered. UnregisteredOrigin, + /// Signer not match signature, check nonce, magic and try again. + BadSignature, } #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - CallExecuted { + NewOrigin { + account: T::AccountId, origin: T::CustomOrigin, + }, + ProxyCall { + payer: T::AccountId, + origin: T::CustomOrigin, + result: DispatchResult, + }, + MetaCall { + payer: T::AccountId, + origin: T::AccountId, result: DispatchResult, }, } @@ -82,6 +103,32 @@ pub mod pallet { #[pallet::call] impl Pallet { + /// Derive new origin for account. + /// + /// The dispatch origin for this call must be _Signed_. + #[pallet::weight(T::WeightInfo::new_origin())] + #[pallet::call_index(0)] + pub fn new_origin( + origin: OriginFor, + origin_kind: T::CustomOriginKind, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + let mut origins = AccountOrigin::::get(who.clone()); + let next_index = origins.len(); + let new_origin = origin_kind.derive(&who, next_index as u32); + + origins.push(new_origin.clone()); + AccountOrigin::::insert(who.clone(), origins); + + Self::deposit_event(Event::NewOrigin { + account: who, + origin: new_origin, + }); + + Ok(()) + } + /// Dispatch the given `call` from an account that the sender is authorised. /// /// The dispatch origin for this call must be _Signed_. @@ -92,12 +139,11 @@ pub mod pallet { #[pallet::weight({ let di = call.get_dispatch_info(); (T::WeightInfo::proxy_call() - // AccountData for inner call origin accountdata. .saturating_add(T::DbWeight::get().reads_writes(1, 1)) .saturating_add(di.weight), di.class) })] - #[pallet::call_index(0)] + #[pallet::call_index(1)] pub fn proxy_call( origin: OriginFor, #[pallet::compact] origin_index: u32, @@ -105,7 +151,7 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; - let custom_origin = AccountOrigin::::get(who) + let custom_origin = AccountOrigin::::get(who.clone()) .get(origin_index as usize) .ok_or(Error::::UnregisteredOrigin)? .clone(); @@ -118,7 +164,8 @@ pub mod pallet { call.dispatch(custom_origin.clone().into()) }; - Self::deposit_event(Event::CallExecuted { + Self::deposit_event(Event::ProxyCall { + payer: who, origin: custom_origin, result: e.map(|_| ()).map_err(|e| e.error), }); @@ -126,13 +173,55 @@ pub mod pallet { Ok(()) } - /// Derive new origin for account. + /// Send meta transaction: verify call signature and execute transaction. + /// + /// Signature should be made from following payload: + /// * ChainMagic (set in pallet config) + /// * Account Nonce (should be get form chain before call) + /// * Runtime Call (well formatted and encoded runtime call) + /// + /// Fields above should be SCALE encoded and packed together. Signed and then + /// properly encoded into `MultiSignature` depend of selected signer key pair. /// /// The dispatch origin for this call must be _Signed_. - #[pallet::weight(T::WeightInfo::new_origin())] - #[pallet::call_index(1)] - pub fn new_origin(origin: OriginFor) -> DispatchResult { + #[pallet::weight({ + let di = call.get_dispatch_info(); + (T::WeightInfo::meta_call() + .saturating_add(T::DbWeight::get().reads_writes(1, 1)) + .saturating_add(di.weight), + di.class) + })] + #[pallet::call_index(2)] + pub fn meta_call( + origin: OriginFor, + call: Box<::RuntimeCall>, + signer: T::AccountId, + signature: T::Signature, + ) -> DispatchResult { let who = ensure_signed(origin)?; + + let magic = T::ChainMagic::get(); + let nonce = frame_system::Pallet::::account_nonce(signer.clone()); + let payload = (magic, nonce, call.clone()); + + // Verify signature for given call + if !signature.verify(&payload.encode()[..], &signer) { + Err(Error::::BadSignature)? + } + + // Dispatch call using signer as origin + let origin = frame_system::RawOrigin::Signed(signer.clone()).into(); + let e = call.dispatch(origin); + + // Increment signer account nonce + frame_system::Pallet::::inc_account_nonce(signer.clone()); + + Self::deposit_event(Event::MetaCall { + payer: who, + origin: signer, + result: e.map(|_| ()).map_err(|e| e.error), + }); + Ok(()) } } diff --git a/frame/pallet-account/src/tests.rs b/frame/pallet-account/src/tests.rs index cb2a4a43..a5dc3df3 100644 --- a/frame/pallet-account/src/tests.rs +++ b/frame/pallet-account/src/tests.rs @@ -16,12 +16,40 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . -use super::{Error, Event, *}; -use frame_support::{assert_noop, assert_ok}; +use super::*; +use assert_matches::assert_matches; +use frame_support::{assert_err, assert_ok}; use mock::*; #[test] -pub fn is_ok() { +pub fn new_origin_works() { + ExternalityBuilder::build().execute_with(|| { + // Create native origin + assert_ok!(Account::new_origin( + RuntimeOrigin::signed(ALICE).into(), + NativeAndEVMKind::Native, + )); + assert_eq!( + AccountOrigin::::get(ALICE), + vec![NativeAndEVM::Native(ALICE_D1_NATIVE.into())], + ); + // Create EVM origin + assert_ok!(Account::new_origin( + RuntimeOrigin::signed(ALICE).into(), + NativeAndEVMKind::H160, + )); + assert_eq!( + AccountOrigin::::get(ALICE), + vec![ + NativeAndEVM::Native(ALICE_D1_NATIVE.into()), + NativeAndEVM::H160(ALICE_D2_H160.into()) + ], + ); + }) +} + +#[test] +pub fn proxy_call_works() { ExternalityBuilder::build().execute_with(|| { let call: RuntimeCall = pallet_balances::Call::transfer { dest: BOB, @@ -29,10 +57,84 @@ pub fn is_ok() { } .into(); + // Create native origin + assert_ok!(Account::new_origin( + RuntimeOrigin::signed(ALICE).into(), + NativeAndEVMKind::Native, + )); + + // Make call with native origin assert_ok!(Account::proxy_call( RuntimeOrigin::signed(ALICE).into(), 0, Box::new(call), )); + assert_eq!(System::account(BOB).data.free, 810); + assert_matches!( + System::events() + .last() + .expect("events expected") + .event + .clone(), + RuntimeEvent::Account(Event::ProxyCall{origin, ..}) + if origin == NativeAndEVM::Native(ALICE_D1_NATIVE.into()) + ); + }) +} + +#[test] +pub fn meta_call_works() { + use parity_scale_codec::Encode; + use sp_core::Pair; + use sp_runtime::traits::IdentifyAccount; + + ExternalityBuilder::build().execute_with(|| { + let call: RuntimeCall = pallet_balances::Call::transfer { + dest: BOB, + value: 10, + } + .into(); + + let pair = sp_core::ed25519::Pair::from_string("//Alice", None).unwrap(); + let payload = (ChainMagic::get(), 0u64, call.clone()); + let signer: sp_runtime::MultiSigner = pair.public().into(); + let account_id = signer.into_account(); + let signature = pair.sign(&payload.encode()[..]); + + // Make call with signer as origin + assert_eq!(System::account(&account_id).nonce, 0); + + assert_ok!(Account::meta_call( + RuntimeOrigin::signed(ALICE).into(), + Box::new(call), + account_id.clone(), + signature.into(), + )); + + assert_eq!(System::account(&account_id).nonce, 1); + assert_eq!(System::account(BOB).data.free, 810); + }) +} + +#[test] +pub fn meta_call_bad_signature() { + ExternalityBuilder::build().execute_with(|| { + let call: RuntimeCall = pallet_balances::Call::transfer { + dest: BOB, + value: 10, + } + .into(); + + let bad_signature = sp_runtime::MultiSignature::Ecdsa(Default::default()); + + assert_err!( + Account::meta_call( + RuntimeOrigin::signed(ALICE).into(), + Box::new(call), + ALICE, + bad_signature, + ), + Error::::BadSignature, + ); }) } diff --git a/frame/pallet-account/src/weights.rs b/frame/pallet-account/src/weights.rs index 70fceb06..ac663068 100644 --- a/frame/pallet-account/src/weights.rs +++ b/frame/pallet-account/src/weights.rs @@ -19,15 +19,19 @@ use frame_support::weights::Weight; pub trait WeightInfo { - fn proxy_call() -> Weight; fn new_origin() -> Weight; + fn proxy_call() -> Weight; + fn meta_call() -> Weight; } impl WeightInfo for () { + fn new_origin() -> Weight { + Default::default() + } fn proxy_call() -> Weight { Default::default() } - fn new_origin() -> Weight { + fn meta_call() -> Weight { Default::default() } } From 6e71c60db8fddd5e57e8d04fc350b5c4bd685248 Mon Sep 17 00:00:00 2001 From: Aleksandr Krupenkin Date: Thu, 6 Apr 2023 17:10:46 +0300 Subject: [PATCH 05/23] Update frame/pallet-account/src/lib.rs Co-authored-by: Dmitry --- frame/pallet-account/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/pallet-account/src/lib.rs b/frame/pallet-account/src/lib.rs index be5a150c..063feddc 100644 --- a/frame/pallet-account/src/lib.rs +++ b/frame/pallet-account/src/lib.rs @@ -20,7 +20,7 @@ //! //! ## Overview //! -//! An accout abstraction pallet make possible to derive new blockchain based +//! An accout abstraction pallet makes it possible to derive new blockchain based //! account for your existed external owned account (seed phrase based). The onchain //! account could be drived to multiple address spaces: H160 and SS58. For example, //! it makes possible predictable interaction between substrate native account and From c1a740e5ca828a48454f522d4759725e22767828 Mon Sep 17 00:00:00 2001 From: Aleksandr Krupenkin Date: Thu, 6 Apr 2023 17:11:03 +0300 Subject: [PATCH 06/23] Update frame/pallet-account/src/pallet/mod.rs Co-authored-by: PierreOssun <35110271+PierreOssun@users.noreply.github.com> --- frame/pallet-account/src/pallet/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/pallet-account/src/pallet/mod.rs b/frame/pallet-account/src/pallet/mod.rs index 62b90d49..255b1aa3 100644 --- a/frame/pallet-account/src/pallet/mod.rs +++ b/frame/pallet-account/src/pallet/mod.rs @@ -70,7 +70,7 @@ pub mod pallet { pub enum Error { /// Origin with given index not registered. UnregisteredOrigin, - /// Signer not match signature, check nonce, magic and try again. + /// Signature does not match Signer, check nonce, magic and try again. BadSignature, } From 6f5aa270552717c21e11f012a3a2242319d8ccf8 Mon Sep 17 00:00:00 2001 From: Aleksandr Krupenkin Date: Thu, 6 Apr 2023 17:11:32 +0300 Subject: [PATCH 07/23] Update frame/pallet-account/src/lib.rs Co-authored-by: Dmitry --- frame/pallet-account/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/pallet-account/src/lib.rs b/frame/pallet-account/src/lib.rs index 063feddc..08606f99 100644 --- a/frame/pallet-account/src/lib.rs +++ b/frame/pallet-account/src/lib.rs @@ -22,7 +22,7 @@ //! //! An accout abstraction pallet makes it possible to derive new blockchain based //! account for your existed external owned account (seed phrase based). The onchain -//! account could be drived to multiple address spaces: H160 and SS58. For example, +//! account could be derived to multiple address spaces: H160 and SS58. For example, //! it makes possible predictable interaction between substrate native account and //! EVM smart contracts. //! From ee7e727e55154eae6548e251a72c96f1d5d92ec0 Mon Sep 17 00:00:00 2001 From: Aleksandr Krupenkin Date: Thu, 6 Apr 2023 17:11:57 +0300 Subject: [PATCH 08/23] Update frame/pallet-account/src/lib.rs Co-authored-by: Dmitry --- frame/pallet-account/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/pallet-account/src/lib.rs b/frame/pallet-account/src/lib.rs index 08606f99..32f71d52 100644 --- a/frame/pallet-account/src/lib.rs +++ b/frame/pallet-account/src/lib.rs @@ -23,7 +23,7 @@ //! An accout abstraction pallet makes it possible to derive new blockchain based //! account for your existed external owned account (seed phrase based). The onchain //! account could be derived to multiple address spaces: H160 and SS58. For example, -//! it makes possible predictable interaction between substrate native account and +//! it makes possible to predictably interact between substrate native account and //! EVM smart contracts. //! //! ## Interface From 0973c5af4ecc2cf83ec1bfa194c6d56bcd75c2fc Mon Sep 17 00:00:00 2001 From: Aleksandr Krupenkin Date: Thu, 6 Apr 2023 17:12:19 +0300 Subject: [PATCH 09/23] Update frame/pallet-account/src/lib.rs Co-authored-by: Dmitry --- frame/pallet-account/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/pallet-account/src/lib.rs b/frame/pallet-account/src/lib.rs index 32f71d52..dce4a53b 100644 --- a/frame/pallet-account/src/lib.rs +++ b/frame/pallet-account/src/lib.rs @@ -21,7 +21,7 @@ //! ## Overview //! //! An accout abstraction pallet makes it possible to derive new blockchain based -//! account for your existed external owned account (seed phrase based). The onchain +//! account for an existing external owned account (seed phrase based). The onchain //! account could be derived to multiple address spaces: H160 and SS58. For example, //! it makes possible to predictably interact between substrate native account and //! EVM smart contracts. From 98700fa6fe98b8506a04955a56966f4abe390ef2 Mon Sep 17 00:00:00 2001 From: Alexander Krupenkin Date: Thu, 6 Apr 2023 17:34:02 +0300 Subject: [PATCH 10/23] Temporary remove meta_call --- frame/pallet-account/src/lib.rs | 158 ++++++++++++++++- frame/pallet-account/src/pallet/mod.rs | 228 ------------------------- frame/pallet-account/src/tests.rs | 59 +------ 3 files changed, 155 insertions(+), 290 deletions(-) delete mode 100644 frame/pallet-account/src/pallet/mod.rs diff --git a/frame/pallet-account/src/lib.rs b/frame/pallet-account/src/lib.rs index dce4a53b..ef55f86d 100644 --- a/frame/pallet-account/src/lib.rs +++ b/frame/pallet-account/src/lib.rs @@ -32,7 +32,6 @@ //! //! * new_origin() - create new origin for account //! * proxy_call() - make proxy call with derived account as origin -//! * meta_call() - make meta call with dedicated payer account //! #![cfg_attr(not(feature = "std"), no_std)] @@ -40,13 +39,164 @@ pub mod origins; pub use origins::*; -pub mod pallet; -pub use pallet::pallet::*; - pub mod weights; pub use weights::*; +pub use pallet::*; + #[cfg(test)] mod mock; #[cfg(test)] mod tests; + +#[frame_support::pallet] +#[allow(clippy::module_inception)] +pub mod pallet { + use crate::*; + + use frame_support::pallet_prelude::*; + use frame_support::{ + dispatch::{Dispatchable, GetDispatchInfo}, + traits::IsSubType, + }; + use frame_system::pallet_prelude::*; + use sp_runtime::traits::{IdentifyAccount, Verify}; + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Custom origin type. + type CustomOrigin: Parameter + TryInto; + /// Parameter that defin different origin options and how to create it. + type CustomOriginKind: Parameter + OriginDeriving; + /// The runtime origin type. + type RuntimeOrigin: From + + From>; + /// The overarching call type. + type RuntimeCall: Parameter + + Dispatchable::RuntimeOrigin> + + GetDispatchInfo + + From> + + IsSubType> + + IsType<::RuntimeCall>; + /// Meta transaction chain magic prefix. Required to prevent tx replay attack. + type ChainMagic: Get; + /// Meta transaction signature type. + type Signature: Parameter + Verify; + /// Meta transaction signer type. + type Signer: IdentifyAccount; + /// General event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + /// Origin with given index not registered. + UnregisteredOrigin, + /// Signature does not match Signer, check nonce, magic and try again. + BadSignature, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + NewOrigin { + account: T::AccountId, + origin: T::CustomOrigin, + }, + ProxyCall { + origin: T::CustomOrigin, + result: DispatchResult, + }, + } + + #[pallet::origin] + pub type Origin = ::CustomOrigin; + + /// Account origins + #[pallet::storage] + pub type AccountOrigin = + StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; + + #[pallet::call] + impl Pallet { + /// Derive new origin for account. + /// + /// The dispatch origin for this call must be _Signed_. + #[pallet::weight(T::WeightInfo::new_origin())] + #[pallet::call_index(0)] + pub fn new_origin( + origin: OriginFor, + origin_kind: T::CustomOriginKind, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + let mut origins = AccountOrigin::::get(who.clone()); + let next_index = origins.len(); + let new_origin = origin_kind.derive(&who, next_index as u32); + + origins.push(new_origin.clone()); + AccountOrigin::::insert(who.clone(), origins); + + Self::deposit_event(Event::NewOrigin { + account: who, + origin: new_origin, + }); + + Ok(()) + } + + /// Dispatch the given `call` from an account that the sender is authorised. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// Parameters: + /// - `origin_index`: Account origin index for using. + /// - `call`: The call to be made by the `derived` account. + #[pallet::weight({ + let di = call.get_dispatch_info(); + (T::WeightInfo::proxy_call() + .saturating_add(T::DbWeight::get().reads_writes(1, 1)) + .saturating_add(di.weight), + di.class) + })] + #[pallet::call_index(1)] + pub fn proxy_call( + origin: OriginFor, + #[pallet::compact] origin_index: u32, + call: Box<::RuntimeCall>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + let custom_origin = AccountOrigin::::get(who) + .get(origin_index as usize) + .ok_or(Error::::UnregisteredOrigin)? + .clone(); + + let e = if let Ok(id) = custom_origin.clone().try_into() { + // in case of native dispatch with system signed origin + call.dispatch(frame_system::RawOrigin::Signed(id).into()) + } else { + // in other case dispatch with custom origin + call.dispatch(custom_origin.clone().into()) + }; + + Self::deposit_event(Event::ProxyCall { + origin: custom_origin, + result: e.map(|_| ()).map_err(|e| e.error), + }); + + Ok(()) + } + } +} diff --git a/frame/pallet-account/src/pallet/mod.rs b/frame/pallet-account/src/pallet/mod.rs deleted file mode 100644 index 255b1aa3..00000000 --- a/frame/pallet-account/src/pallet/mod.rs +++ /dev/null @@ -1,228 +0,0 @@ -// This file is part of Astar. - -// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later - -// Astar 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. - -// Astar 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 Astar. If not, see . - -#[frame_support::pallet] -#[allow(clippy::module_inception)] -pub mod pallet { - use crate::*; - - use frame_support::pallet_prelude::*; - use frame_support::{ - dispatch::{Dispatchable, GetDispatchInfo}, - traits::IsSubType, - }; - use frame_system::pallet_prelude::*; - use sp_runtime::traits::{IdentifyAccount, Verify}; - - /// The current storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); - - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - #[pallet::storage_version(STORAGE_VERSION)] - #[pallet::without_storage_info] - pub struct Pallet(PhantomData); - - #[pallet::config] - pub trait Config: frame_system::Config { - /// Custom origin type. - type CustomOrigin: Parameter + TryInto; - /// Parameter that defin different origin options and how to create it. - type CustomOriginKind: Parameter + OriginDeriving; - /// The runtime origin type. - type RuntimeOrigin: From - + From>; - /// The overarching call type. - type RuntimeCall: Parameter - + Dispatchable::RuntimeOrigin> - + GetDispatchInfo - + From> - + IsSubType> - + IsType<::RuntimeCall>; - /// Meta transaction chain magic prefix. Required to prevent tx replay attack. - type ChainMagic: Get; - /// Meta transaction signature type. - type Signature: Parameter + Verify; - /// Meta transaction signer type. - type Signer: IdentifyAccount; - /// General event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// Weight information for extrinsics in this pallet. - type WeightInfo: WeightInfo; - } - - #[pallet::error] - pub enum Error { - /// Origin with given index not registered. - UnregisteredOrigin, - /// Signature does not match Signer, check nonce, magic and try again. - BadSignature, - } - - #[pallet::event] - #[pallet::generate_deposit(pub(crate) fn deposit_event)] - pub enum Event { - NewOrigin { - account: T::AccountId, - origin: T::CustomOrigin, - }, - ProxyCall { - payer: T::AccountId, - origin: T::CustomOrigin, - result: DispatchResult, - }, - MetaCall { - payer: T::AccountId, - origin: T::AccountId, - result: DispatchResult, - }, - } - - #[pallet::origin] - pub type Origin = ::CustomOrigin; - - /// Account origins - #[pallet::storage] - pub type AccountOrigin = - StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; - - #[pallet::call] - impl Pallet { - /// Derive new origin for account. - /// - /// The dispatch origin for this call must be _Signed_. - #[pallet::weight(T::WeightInfo::new_origin())] - #[pallet::call_index(0)] - pub fn new_origin( - origin: OriginFor, - origin_kind: T::CustomOriginKind, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - let mut origins = AccountOrigin::::get(who.clone()); - let next_index = origins.len(); - let new_origin = origin_kind.derive(&who, next_index as u32); - - origins.push(new_origin.clone()); - AccountOrigin::::insert(who.clone(), origins); - - Self::deposit_event(Event::NewOrigin { - account: who, - origin: new_origin, - }); - - Ok(()) - } - - /// Dispatch the given `call` from an account that the sender is authorised. - /// - /// The dispatch origin for this call must be _Signed_. - /// - /// Parameters: - /// - `origin_index`: Account origin index for using. - /// - `call`: The call to be made by the `derived` account. - #[pallet::weight({ - let di = call.get_dispatch_info(); - (T::WeightInfo::proxy_call() - .saturating_add(T::DbWeight::get().reads_writes(1, 1)) - .saturating_add(di.weight), - di.class) - })] - #[pallet::call_index(1)] - pub fn proxy_call( - origin: OriginFor, - #[pallet::compact] origin_index: u32, - call: Box<::RuntimeCall>, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - let custom_origin = AccountOrigin::::get(who.clone()) - .get(origin_index as usize) - .ok_or(Error::::UnregisteredOrigin)? - .clone(); - - let e = if let Ok(id) = custom_origin.clone().try_into() { - // in case of native dispatch with system signed origin - call.dispatch(frame_system::RawOrigin::Signed(id).into()) - } else { - // in other case dispatch with custom origin - call.dispatch(custom_origin.clone().into()) - }; - - Self::deposit_event(Event::ProxyCall { - payer: who, - origin: custom_origin, - result: e.map(|_| ()).map_err(|e| e.error), - }); - - Ok(()) - } - - /// Send meta transaction: verify call signature and execute transaction. - /// - /// Signature should be made from following payload: - /// * ChainMagic (set in pallet config) - /// * Account Nonce (should be get form chain before call) - /// * Runtime Call (well formatted and encoded runtime call) - /// - /// Fields above should be SCALE encoded and packed together. Signed and then - /// properly encoded into `MultiSignature` depend of selected signer key pair. - /// - /// The dispatch origin for this call must be _Signed_. - #[pallet::weight({ - let di = call.get_dispatch_info(); - (T::WeightInfo::meta_call() - .saturating_add(T::DbWeight::get().reads_writes(1, 1)) - .saturating_add(di.weight), - di.class) - })] - #[pallet::call_index(2)] - pub fn meta_call( - origin: OriginFor, - call: Box<::RuntimeCall>, - signer: T::AccountId, - signature: T::Signature, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - let magic = T::ChainMagic::get(); - let nonce = frame_system::Pallet::::account_nonce(signer.clone()); - let payload = (magic, nonce, call.clone()); - - // Verify signature for given call - if !signature.verify(&payload.encode()[..], &signer) { - Err(Error::::BadSignature)? - } - - // Dispatch call using signer as origin - let origin = frame_system::RawOrigin::Signed(signer.clone()).into(); - let e = call.dispatch(origin); - - // Increment signer account nonce - frame_system::Pallet::::inc_account_nonce(signer.clone()); - - Self::deposit_event(Event::MetaCall { - payer: who, - origin: signer, - result: e.map(|_| ()).map_err(|e| e.error), - }); - - Ok(()) - } - } -} diff --git a/frame/pallet-account/src/tests.rs b/frame/pallet-account/src/tests.rs index a5dc3df3..feae5f27 100644 --- a/frame/pallet-account/src/tests.rs +++ b/frame/pallet-account/src/tests.rs @@ -18,7 +18,7 @@ use super::*; use assert_matches::assert_matches; -use frame_support::{assert_err, assert_ok}; +use frame_support::assert_ok; use mock::*; #[test] @@ -81,60 +81,3 @@ pub fn proxy_call_works() { ); }) } - -#[test] -pub fn meta_call_works() { - use parity_scale_codec::Encode; - use sp_core::Pair; - use sp_runtime::traits::IdentifyAccount; - - ExternalityBuilder::build().execute_with(|| { - let call: RuntimeCall = pallet_balances::Call::transfer { - dest: BOB, - value: 10, - } - .into(); - - let pair = sp_core::ed25519::Pair::from_string("//Alice", None).unwrap(); - let payload = (ChainMagic::get(), 0u64, call.clone()); - let signer: sp_runtime::MultiSigner = pair.public().into(); - let account_id = signer.into_account(); - let signature = pair.sign(&payload.encode()[..]); - - // Make call with signer as origin - assert_eq!(System::account(&account_id).nonce, 0); - - assert_ok!(Account::meta_call( - RuntimeOrigin::signed(ALICE).into(), - Box::new(call), - account_id.clone(), - signature.into(), - )); - - assert_eq!(System::account(&account_id).nonce, 1); - assert_eq!(System::account(BOB).data.free, 810); - }) -} - -#[test] -pub fn meta_call_bad_signature() { - ExternalityBuilder::build().execute_with(|| { - let call: RuntimeCall = pallet_balances::Call::transfer { - dest: BOB, - value: 10, - } - .into(); - - let bad_signature = sp_runtime::MultiSignature::Ecdsa(Default::default()); - - assert_err!( - Account::meta_call( - RuntimeOrigin::signed(ALICE).into(), - Box::new(call), - ALICE, - bad_signature, - ), - Error::::BadSignature, - ); - }) -} From f8149afdd6dbda03653b0342a6b5d1db4dd3bab0 Mon Sep 17 00:00:00 2001 From: Alexander Krupenkin Date: Thu, 6 Apr 2023 17:53:16 +0300 Subject: [PATCH 11/23] Rewrite origin storage to make it storage info compatible --- frame/pallet-account/src/lib.rs | 37 ++++++++++++++----------------- frame/pallet-account/src/mock.rs | 7 ------ frame/pallet-account/src/tests.rs | 12 +++++----- 3 files changed, 22 insertions(+), 34 deletions(-) diff --git a/frame/pallet-account/src/lib.rs b/frame/pallet-account/src/lib.rs index ef55f86d..471244df 100644 --- a/frame/pallet-account/src/lib.rs +++ b/frame/pallet-account/src/lib.rs @@ -60,7 +60,6 @@ pub mod pallet { traits::IsSubType, }; use frame_system::pallet_prelude::*; - use sp_runtime::traits::{IdentifyAccount, Verify}; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); @@ -68,13 +67,12 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] - #[pallet::without_storage_info] pub struct Pallet(PhantomData); #[pallet::config] pub trait Config: frame_system::Config { /// Custom origin type. - type CustomOrigin: Parameter + TryInto; + type CustomOrigin: Parameter + TryInto + MaxEncodedLen; /// Parameter that defin different origin options and how to create it. type CustomOriginKind: Parameter + OriginDeriving; /// The runtime origin type. @@ -87,12 +85,6 @@ pub mod pallet { + From> + IsSubType> + IsType<::RuntimeCall>; - /// Meta transaction chain magic prefix. Required to prevent tx replay attack. - type ChainMagic: Get; - /// Meta transaction signature type. - type Signature: Parameter + Verify; - /// Meta transaction signer type. - type Signer: IdentifyAccount; /// General event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Weight information for extrinsics in this pallet. @@ -126,7 +118,12 @@ pub mod pallet { /// Account origins #[pallet::storage] pub type AccountOrigin = - StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; + StorageDoubleMap<_, Blake2_128Concat, T::AccountId, Twox64Concat, u32, T::CustomOrigin>; + + /// Account last origin index + #[pallet::storage] + pub type AccountLastOrigin = + StorageMap<_, Twox64Concat, T::AccountId, u32, ValueQuery>; #[pallet::call] impl Pallet { @@ -141,12 +138,10 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; - let mut origins = AccountOrigin::::get(who.clone()); - let next_index = origins.len(); - let new_origin = origin_kind.derive(&who, next_index as u32); - - origins.push(new_origin.clone()); - AccountOrigin::::insert(who.clone(), origins); + let next_index = AccountLastOrigin::::get(&who); + let new_origin = origin_kind.derive(&who, next_index); + AccountOrigin::::insert(&who, next_index, new_origin.clone()); + AccountLastOrigin::::insert(&who, next_index + 1); Self::deposit_event(Event::NewOrigin { account: who, @@ -177,11 +172,13 @@ pub mod pallet { call: Box<::RuntimeCall>, ) -> DispatchResult { let who = ensure_signed(origin)?; + ensure!( + origin_index < AccountLastOrigin::::get(&who), + Error::::UnregisteredOrigin + ); - let custom_origin = AccountOrigin::::get(who) - .get(origin_index as usize) - .ok_or(Error::::UnregisteredOrigin)? - .clone(); + let custom_origin = AccountOrigin::::get(&who, origin_index) + .ok_or(Error::::UnregisteredOrigin)?; let e = if let Ok(id) = custom_origin.clone().try_into() { // in case of native dispatch with system signed origin diff --git a/frame/pallet-account/src/mock.rs b/frame/pallet-account/src/mock.rs index be9d1c50..a6fa5bef 100644 --- a/frame/pallet-account/src/mock.rs +++ b/frame/pallet-account/src/mock.rs @@ -112,18 +112,11 @@ impl pallet_balances::Config for TestRuntime { type WeightInfo = (); } -parameter_types! { - pub const ChainMagic: u16 = 0x4200; -} - impl pallet_account::Config for TestRuntime { type CustomOrigin = super::NativeAndEVM; type CustomOriginKind = super::NativeAndEVMKind; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type ChainMagic = ChainMagic; - type Signer = sp_runtime::MultiSigner; - type Signature = sp_runtime::MultiSignature; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); } diff --git a/frame/pallet-account/src/tests.rs b/frame/pallet-account/src/tests.rs index feae5f27..ca2a16c3 100644 --- a/frame/pallet-account/src/tests.rs +++ b/frame/pallet-account/src/tests.rs @@ -30,20 +30,18 @@ pub fn new_origin_works() { NativeAndEVMKind::Native, )); assert_eq!( - AccountOrigin::::get(ALICE), - vec![NativeAndEVM::Native(ALICE_D1_NATIVE.into())], + AccountOrigin::::get(ALICE, 0), + Some(NativeAndEVM::Native(ALICE_D1_NATIVE.into())), ); + assert_eq!(AccountOrigin::::get(ALICE, 1), None,); // Create EVM origin assert_ok!(Account::new_origin( RuntimeOrigin::signed(ALICE).into(), NativeAndEVMKind::H160, )); assert_eq!( - AccountOrigin::::get(ALICE), - vec![ - NativeAndEVM::Native(ALICE_D1_NATIVE.into()), - NativeAndEVM::H160(ALICE_D2_H160.into()) - ], + AccountOrigin::::get(ALICE, 1), + Some(NativeAndEVM::H160(ALICE_D2_H160.into())), ); }) } From b0c4bb013e813e3ab229cd902fda0e9d6532ac21 Mon Sep 17 00:00:00 2001 From: Alexander Krupenkin Date: Fri, 7 Apr 2023 15:39:39 +0300 Subject: [PATCH 12/23] Use workspace authors --- frame/pallet-account/Cargo.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frame/pallet-account/Cargo.toml b/frame/pallet-account/Cargo.toml index 8d63d309..af86cc0f 100644 --- a/frame/pallet-account/Cargo.toml +++ b/frame/pallet-account/Cargo.toml @@ -1,8 +1,10 @@ [package] name = "pallet-account" -authors = ["Stake Technologies"] -edition = "2021" version = "0.1.0" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true [dependencies] log = { workspace = true } From 11ec601f66f07359358263d1944df5c4e3623d19 Mon Sep 17 00:00:00 2001 From: Alexander Krupenkin Date: Thu, 20 Apr 2023 08:22:41 +0300 Subject: [PATCH 13/23] Added TODO for weights --- frame/pallet-account/src/origins.rs | 10 ++++++++++ frame/pallet-account/src/weights.rs | 6 ++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/frame/pallet-account/src/origins.rs b/frame/pallet-account/src/origins.rs index e8b73669..dbc65905 100644 --- a/frame/pallet-account/src/origins.rs +++ b/frame/pallet-account/src/origins.rs @@ -45,6 +45,16 @@ impl TryInto for NativeAndEVM { } } +impl TryInto for NativeAndEVM { + type Error = (); + fn try_into(self) -> Result { + match self { + NativeAndEVM::H160(a) => Ok(a), + _ => Err(()), + } + } +} + /// Kind for NativeAndEVM origin. #[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] pub enum NativeAndEVMKind { diff --git a/frame/pallet-account/src/weights.rs b/frame/pallet-account/src/weights.rs index ac663068..707ba7a7 100644 --- a/frame/pallet-account/src/weights.rs +++ b/frame/pallet-account/src/weights.rs @@ -18,10 +18,11 @@ use frame_support::weights::Weight; +// TODO: generate weights when pallet comes to testnet + pub trait WeightInfo { fn new_origin() -> Weight; fn proxy_call() -> Weight; - fn meta_call() -> Weight; } impl WeightInfo for () { @@ -31,7 +32,4 @@ impl WeightInfo for () { fn proxy_call() -> Weight { Default::default() } - fn meta_call() -> Weight { - Default::default() - } } From eb56aa28cc0a420b23ea3f97b219476276de97fb Mon Sep 17 00:00:00 2001 From: Alexander Krupenkin Date: Thu, 20 Apr 2023 08:45:28 +0300 Subject: [PATCH 14/23] Fix weights setting --- frame/pallet-account/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frame/pallet-account/src/lib.rs b/frame/pallet-account/src/lib.rs index 471244df..ceeb4cc8 100644 --- a/frame/pallet-account/src/lib.rs +++ b/frame/pallet-account/src/lib.rs @@ -130,7 +130,10 @@ pub mod pallet { /// Derive new origin for account. /// /// The dispatch origin for this call must be _Signed_. - #[pallet::weight(T::WeightInfo::new_origin())] + #[pallet::weight( + T::WeightInfo::new_origin() + .saturating_add(T::DbWeight::get().reads_writes(1, 2)) + )] #[pallet::call_index(0)] pub fn new_origin( origin: OriginFor, @@ -161,7 +164,7 @@ pub mod pallet { #[pallet::weight({ let di = call.get_dispatch_info(); (T::WeightInfo::proxy_call() - .saturating_add(T::DbWeight::get().reads_writes(1, 1)) + .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(di.weight), di.class) })] From b494247685388b836c60703413d738919865fb51 Mon Sep 17 00:00:00 2001 From: Alexander Krupenkin Date: Thu, 20 Apr 2023 08:50:56 +0300 Subject: [PATCH 15/23] Fix tests --- frame/pallet-account/src/origins.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/pallet-account/src/origins.rs b/frame/pallet-account/src/origins.rs index dbc65905..69d56748 100644 --- a/frame/pallet-account/src/origins.rs +++ b/frame/pallet-account/src/origins.rs @@ -45,9 +45,9 @@ impl TryInto for NativeAndEVM { } } -impl TryInto for NativeAndEVM { +impl TryInto for NativeAndEVM { type Error = (); - fn try_into(self) -> Result { + fn try_into(self) -> Result { match self { NativeAndEVM::H160(a) => Ok(a), _ => Err(()), From 0307dcda13dc4f5b71a44e467801eeaf1838c5c5 Mon Sep 17 00:00:00 2001 From: Alexander Krupenkin Date: Thu, 20 Apr 2023 15:09:26 +0300 Subject: [PATCH 16/23] Added negative tests --- frame/pallet-account/src/lib.rs | 2 -- frame/pallet-account/src/tests.rs | 33 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/frame/pallet-account/src/lib.rs b/frame/pallet-account/src/lib.rs index ceeb4cc8..77200d16 100644 --- a/frame/pallet-account/src/lib.rs +++ b/frame/pallet-account/src/lib.rs @@ -95,8 +95,6 @@ pub mod pallet { pub enum Error { /// Origin with given index not registered. UnregisteredOrigin, - /// Signature does not match Signer, check nonce, magic and try again. - BadSignature, } #[pallet::event] diff --git a/frame/pallet-account/src/tests.rs b/frame/pallet-account/src/tests.rs index ca2a16c3..62705ba0 100644 --- a/frame/pallet-account/src/tests.rs +++ b/frame/pallet-account/src/tests.rs @@ -79,3 +79,36 @@ pub fn proxy_call_works() { ); }) } + +#[test] +pub fn proxy_call_fails() { + ExternalityBuilder::build().execute_with(|| { + let call: RuntimeCall = pallet_balances::Call::transfer { + dest: BOB, + value: 10, + } + .into(); + + // Make call with unknown origin + assert_eq!( + Account::proxy_call( + RuntimeOrigin::signed(ALICE).into(), + 0, + Box::new(call.clone()), + ), + Err(Error::::UnregisteredOrigin.into()) + ); + + // Create native origin + assert_ok!(Account::new_origin( + RuntimeOrigin::signed(ALICE).into(), + NativeAndEVMKind::Native, + )); + + // Make call with native origin + assert_eq!( + Account::proxy_call(RuntimeOrigin::signed(ALICE).into(), 1, Box::new(call),), + Err(Error::::UnregisteredOrigin.into()) + ); + }) +} From 34b2d24294bffee2f6bc4ab5e1f69ab95ed92ec1 Mon Sep 17 00:00:00 2001 From: Alexander Krupenkin Date: Thu, 20 Apr 2023 15:45:42 +0300 Subject: [PATCH 17/23] Fix cargo fmt --- frame/pallet-account/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/pallet-account/Cargo.toml b/frame/pallet-account/Cargo.toml index af86cc0f..dd209c70 100644 --- a/frame/pallet-account/Cargo.toml +++ b/frame/pallet-account/Cargo.toml @@ -11,9 +11,9 @@ log = { workspace = true } serde = { workspace = true, optional = true } # Substrate -parity-scale-codec = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +parity-scale-codec = { workspace = true } scale-info = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } @@ -23,9 +23,9 @@ sp-std = { workspace = true } frame-benchmarking = { workspace = true, optional = true } [dev-dependencies] -pallet-balances = { workspace = true, features = ["std"] } assert_matches = { workspace = true } hex-literal = { workspace = true } +pallet-balances = { workspace = true, features = ["std"] } [features] default = ["std"] From c8297868ad10a18539706758be8e054aeec62243 Mon Sep 17 00:00:00 2001 From: Alexander Krupenkin Date: Mon, 24 Apr 2023 07:57:39 +0300 Subject: [PATCH 18/23] Added benchmarking --- frame/pallet-account/Cargo.toml | 6 +- frame/pallet-account/src/benchmarking.rs | 76 ++++++++++++++++++++++++ frame/pallet-account/src/lib.rs | 2 + 3 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 frame/pallet-account/src/benchmarking.rs diff --git a/frame/pallet-account/Cargo.toml b/frame/pallet-account/Cargo.toml index dd209c70..d818561d 100644 --- a/frame/pallet-account/Cargo.toml +++ b/frame/pallet-account/Cargo.toml @@ -21,6 +21,7 @@ sp-std = { workspace = true } # Benchmarks frame-benchmarking = { workspace = true, optional = true } +hex-literal = { workspace = true, optional = true } [dev-dependencies] assert_matches = { workspace = true } @@ -31,6 +32,7 @@ pallet-balances = { workspace = true, features = ["std"] } default = ["std"] std = [ "parity-scale-codec/std", + "frame-benchmarking/std", "frame-support/std", "frame-system/std", "scale-info/std", @@ -39,8 +41,10 @@ std = [ "sp-runtime/std", "sp-std/std", ] - runtime-benchmarks = [ + "hex-literal", "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/pallet-account/src/benchmarking.rs b/frame/pallet-account/src/benchmarking.rs new file mode 100644 index 00000000..a9dad2ef --- /dev/null +++ b/frame/pallet-account/src/benchmarking.rs @@ -0,0 +1,76 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +use super::*; + +use hex_literal::hex; +use parity_scale_codec::{Decode, Encode}; + +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_support::assert_ok; +use frame_system::{Pallet as System, RawOrigin}; + +const WHITELISTED_D1_NATIVE: [u8; 32] = + hex!["53ebafb5200b910e56654c867e08747f7dbd3695d6c133af24084b23c3253a67"]; + +/// Assert that the last event equals the provided one. +fn assert_last_event(generic_event: ::RuntimeEvent) { + System::::assert_last_event(generic_event.into()); +} + +benchmarks! { + new_origin { + let caller = whitelisted_caller::(); + let origin_kind = NativeAndEVMKind::Native.encode(); + let origin = NativeAndEVM::Native(WHITELISTED_D1_NATIVE.into()).encode(); + }: _(RawOrigin::Signed(caller.clone()), Decode::decode(&mut &origin_kind[..]).unwrap()) + verify { + assert_last_event::( + Event::::NewOrigin { + account: caller, + origin: Decode::decode(&mut &origin[..]).unwrap(), + }.into() + ); + } + + proxy_call { + let caller = whitelisted_caller::(); + let origin_kind = NativeAndEVMKind::Native.encode(); + assert_ok!(Pallet::::new_origin( + RawOrigin::Signed(caller.clone()).into(), + Decode::decode(&mut &origin_kind[..]).unwrap() + )); + + let call = Box::new(frame_system::Call::remark { remark: vec![42u8] }.into()); + let origin = NativeAndEVM::Native(WHITELISTED_D1_NATIVE.into()).encode(); + }: _(RawOrigin::Signed(caller.clone()), 0, call) + verify { + assert_last_event::( + Event::::ProxyCall { + origin: Decode::decode(&mut &origin[..]).unwrap(), + result: Ok(()), + }.into() + ); + } + + impl_benchmark_test_suite!( + Pallet, + crate::mock::ExternalityBuilder::build(), + crate::mock::TestRuntime, + ); +} diff --git a/frame/pallet-account/src/lib.rs b/frame/pallet-account/src/lib.rs index 77200d16..00814d3e 100644 --- a/frame/pallet-account/src/lib.rs +++ b/frame/pallet-account/src/lib.rs @@ -44,6 +44,8 @@ pub use weights::*; pub use pallet::*; +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; #[cfg(test)] mod mock; #[cfg(test)] From 564a2e9a2e90ca4a589c5ec7dea7de96de5c64ed Mon Sep 17 00:00:00 2001 From: Alexander Krupenkin Date: Mon, 24 Apr 2023 09:49:04 +0300 Subject: [PATCH 19/23] Fix cargo fmt --- frame/pallet-account/Cargo.toml | 4 ++-- frame/pallet-account/src/lib.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frame/pallet-account/Cargo.toml b/frame/pallet-account/Cargo.toml index d818561d..8dcdbf4f 100644 --- a/frame/pallet-account/Cargo.toml +++ b/frame/pallet-account/Cargo.toml @@ -32,7 +32,7 @@ pallet-balances = { workspace = true, features = ["std"] } default = ["std"] std = [ "parity-scale-codec/std", - "frame-benchmarking/std", + "frame-benchmarking/std", "frame-support/std", "frame-system/std", "scale-info/std", @@ -42,7 +42,7 @@ std = [ "sp-std/std", ] runtime-benchmarks = [ - "hex-literal", + "hex-literal", "frame-benchmarking", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", diff --git a/frame/pallet-account/src/lib.rs b/frame/pallet-account/src/lib.rs index 00814d3e..c7aa9f4e 100644 --- a/frame/pallet-account/src/lib.rs +++ b/frame/pallet-account/src/lib.rs @@ -62,6 +62,7 @@ pub mod pallet { traits::IsSubType, }; use frame_system::pallet_prelude::*; + use sp_std::prelude::*; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); From 83fa23b7865d3738b57f8614d6dfeae9da5a1c4b Mon Sep 17 00:00:00 2001 From: Alexander Krupenkin Date: Mon, 24 Apr 2023 09:52:07 +0300 Subject: [PATCH 20/23] Use runtime level compatible hashing --- frame/pallet-account/Cargo.toml | 2 ++ frame/pallet-account/src/origins.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frame/pallet-account/Cargo.toml b/frame/pallet-account/Cargo.toml index 8dcdbf4f..0834e1e9 100644 --- a/frame/pallet-account/Cargo.toml +++ b/frame/pallet-account/Cargo.toml @@ -16,6 +16,7 @@ frame-system = { workspace = true } parity-scale-codec = { workspace = true } scale-info = { workspace = true } sp-core = { workspace = true } +sp-io = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } @@ -40,6 +41,7 @@ std = [ "sp-core/std", "sp-runtime/std", "sp-std/std", + "sp-io/std", ] runtime-benchmarks = [ "hex-literal", diff --git a/frame/pallet-account/src/origins.rs b/frame/pallet-account/src/origins.rs index 69d56748..63bce39d 100644 --- a/frame/pallet-account/src/origins.rs +++ b/frame/pallet-account/src/origins.rs @@ -65,7 +65,7 @@ pub enum NativeAndEVMKind { impl OriginDeriving for NativeAndEVMKind { fn derive(&self, source: &AccountId32, index: u32) -> NativeAndEVM { let salted_source = [source.as_ref(), &index.encode()[..]].concat(); - let derived = sp_core::blake2_256(&salted_source); + let derived = sp_io::hashing::blake2_256(&salted_source); match self { NativeAndEVMKind::Native => NativeAndEVM::Native(derived.into()), NativeAndEVMKind::H160 => NativeAndEVM::H160(sp_core::H160::from_slice(&derived[..20])), From 3a1ae8b3ecfcbf9623b477ff059b8f28326d68e2 Mon Sep 17 00:00:00 2001 From: Aleksandr Krupenkin Date: Tue, 30 May 2023 17:42:50 +0900 Subject: [PATCH 21/23] Update frame/pallet-account/src/lib.rs Co-authored-by: Shaun Wang --- frame/pallet-account/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/pallet-account/src/lib.rs b/frame/pallet-account/src/lib.rs index c7aa9f4e..599a82b4 100644 --- a/frame/pallet-account/src/lib.rs +++ b/frame/pallet-account/src/lib.rs @@ -76,7 +76,7 @@ pub mod pallet { pub trait Config: frame_system::Config { /// Custom origin type. type CustomOrigin: Parameter + TryInto + MaxEncodedLen; - /// Parameter that defin different origin options and how to create it. + /// Parameter that defines different origin options and how to create it. type CustomOriginKind: Parameter + OriginDeriving; /// The runtime origin type. type RuntimeOrigin: From From 7aca6a1bdcd4feea2a48729ab67419eb15e491cd Mon Sep 17 00:00:00 2001 From: Alexander Krupenkin Date: Tue, 30 May 2023 17:44:50 +0300 Subject: [PATCH 22/23] Added event checks into tests --- frame/pallet-account/src/tests.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/frame/pallet-account/src/tests.rs b/frame/pallet-account/src/tests.rs index 62705ba0..84bf4ccd 100644 --- a/frame/pallet-account/src/tests.rs +++ b/frame/pallet-account/src/tests.rs @@ -34,6 +34,15 @@ pub fn new_origin_works() { Some(NativeAndEVM::Native(ALICE_D1_NATIVE.into())), ); assert_eq!(AccountOrigin::::get(ALICE, 1), None,); + assert_matches!( + System::events() + .last() + .expect("events expected") + .event + .clone(), + RuntimeEvent::Account(Event::NewOrigin{origin, ..}) + if origin == NativeAndEVM::Native(ALICE_D1_NATIVE.into()) + ); // Create EVM origin assert_ok!(Account::new_origin( RuntimeOrigin::signed(ALICE).into(), @@ -43,6 +52,15 @@ pub fn new_origin_works() { AccountOrigin::::get(ALICE, 1), Some(NativeAndEVM::H160(ALICE_D2_H160.into())), ); + assert_matches!( + System::events() + .last() + .expect("events expected") + .event + .clone(), + RuntimeEvent::Account(Event::NewOrigin{origin, ..}) + if origin == NativeAndEVM::H160(ALICE_D2_H160.into()) + ); }) } @@ -77,6 +95,15 @@ pub fn proxy_call_works() { RuntimeEvent::Account(Event::ProxyCall{origin, ..}) if origin == NativeAndEVM::Native(ALICE_D1_NATIVE.into()) ); + assert_matches!( + System::events() + .get(System::events().len() - 2) + .expect("events expected") + .event + .clone(), + RuntimeEvent::Balances(pallet_balances::Event::Transfer{from, ..}) + if from == ALICE_D1_NATIVE.into() + ); }) } From 81f533d5e4f8c46c2ba81f02bf675036e49b6963 Mon Sep 17 00:00:00 2001 From: Alexander Krupenkin Date: Wed, 31 May 2023 16:51:46 +0300 Subject: [PATCH 23/23] Added OnKillAccount handler & creation deposit functionality --- frame/pallet-account/src/lib.rs | 25 ++++++++++++++++++++++++- frame/pallet-account/src/mock.rs | 6 ++++++ frame/pallet-account/src/tests.rs | 4 ++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/frame/pallet-account/src/lib.rs b/frame/pallet-account/src/lib.rs index 599a82b4..90c8e1d2 100644 --- a/frame/pallet-account/src/lib.rs +++ b/frame/pallet-account/src/lib.rs @@ -59,7 +59,7 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_support::{ dispatch::{Dispatchable, GetDispatchInfo}, - traits::IsSubType, + traits::{Currency, IsSubType, OnKilledAccount, ReservableCurrency}, }; use frame_system::pallet_prelude::*; use sp_std::prelude::*; @@ -67,6 +67,9 @@ pub mod pallet { /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + /// Maximum origins that account can hold. + const MAX_ORIGINS: u32 = 50; + #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] @@ -74,6 +77,11 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { + /// Reservable currency to take origin creation deposit. + type Currency: ReservableCurrency; + /// Origin creation security deposit. + #[pallet::constant] + type SecurityDeposit: Get<>::Balance>; /// Custom origin type. type CustomOrigin: Parameter + TryInto + MaxEncodedLen; /// Parameter that defines different origin options and how to create it. @@ -126,6 +134,13 @@ pub mod pallet { pub type AccountLastOrigin = StorageMap<_, Twox64Concat, T::AccountId, u32, ValueQuery>; + impl OnKilledAccount for Pallet { + fn on_killed_account(who: &T::AccountId) { + let _ = >::clear_prefix(who, MAX_ORIGINS, None); + >::remove(who); + } + } + #[pallet::call] impl Pallet { /// Derive new origin for account. @@ -142,7 +157,15 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; + // reserve security deposit + T::Currency::reserve(&who, T::SecurityDeposit::get())?; + let next_index = AccountLastOrigin::::get(&who); + ensure!( + next_index < MAX_ORIGINS, + "account has maximum origins count" + ); + let new_origin = origin_kind.derive(&who, next_index); AccountOrigin::::insert(&who, next_index, new_origin.clone()); AccountLastOrigin::::insert(&who, next_index + 1); diff --git a/frame/pallet-account/src/mock.rs b/frame/pallet-account/src/mock.rs index a6fa5bef..6ac0997d 100644 --- a/frame/pallet-account/src/mock.rs +++ b/frame/pallet-account/src/mock.rs @@ -112,7 +112,13 @@ impl pallet_balances::Config for TestRuntime { type WeightInfo = (); } +parameter_types! { + pub const SecurityDeposit: Balance = 100; +} + impl pallet_account::Config for TestRuntime { + type Currency = Balances; + type SecurityDeposit = SecurityDeposit; type CustomOrigin = super::NativeAndEVM; type CustomOriginKind = super::NativeAndEVMKind; type RuntimeOrigin = RuntimeOrigin; diff --git a/frame/pallet-account/src/tests.rs b/frame/pallet-account/src/tests.rs index 84bf4ccd..89b5ade0 100644 --- a/frame/pallet-account/src/tests.rs +++ b/frame/pallet-account/src/tests.rs @@ -25,10 +25,14 @@ use mock::*; pub fn new_origin_works() { ExternalityBuilder::build().execute_with(|| { // Create native origin + assert_eq!(Balances::free_balance(&ALICE), 9000); assert_ok!(Account::new_origin( RuntimeOrigin::signed(ALICE).into(), NativeAndEVMKind::Native, )); + // check that security deposit consumed + assert_eq!(Balances::free_balance(&ALICE), 8900); + // check that origin created assert_eq!( AccountOrigin::::get(ALICE, 0), Some(NativeAndEVM::Native(ALICE_D1_NATIVE.into())),