From 0406fec80d6b274ad2935dc2f53a44a90073e5bf Mon Sep 17 00:00:00 2001 From: tuxmain <tuxmain@zettascript.org> Date: Fri, 20 May 2022 12:27:31 +0200 Subject: [PATCH] feat(duniter-account): finalized PoC oneshot account --- .../cucumber-features/oneshot_account.feature | 21 +++ end2end-tests/tests/common/mod.rs | 3 +- end2end-tests/tests/common/oneshot.rs | 102 ++++++++++++ end2end-tests/tests/cucumber_tests.rs | 101 ++++++++++++ pallets/duniter-account/src/lib.rs | 149 +++++++++++++++--- pallets/duniter-account/src/mock.rs | 142 +++++++++++++++++ pallets/duniter-account/src/tests.rs | 15 ++ pallets/duniter-account/src/types.rs | 3 + resources/metadata.scale | Bin 115699 -> 118566 bytes runtime/gdev/src/check_nonce.rs | 83 ++++++++++ runtime/gdev/src/lib.rs | 5 +- runtime/gdev/tests/integration_tests.rs | 73 +++++++++ 12 files changed, 673 insertions(+), 24 deletions(-) create mode 100644 end2end-tests/cucumber-features/oneshot_account.feature create mode 100644 end2end-tests/tests/common/oneshot.rs create mode 100644 pallets/duniter-account/src/mock.rs create mode 100644 pallets/duniter-account/src/tests.rs create mode 100644 runtime/gdev/src/check_nonce.rs diff --git a/end2end-tests/cucumber-features/oneshot_account.feature b/end2end-tests/cucumber-features/oneshot_account.feature new file mode 100644 index 000000000..f3a92de27 --- /dev/null +++ b/end2end-tests/cucumber-features/oneshot_account.feature @@ -0,0 +1,21 @@ +Feature: Oneshot account + + Scenario: Simple oneshot consumption + When alice sends 8 ĞD to oneshot dave + Then alice should have 2 ĞD + Then dave should have oneshot 8 ĞD + When oneshot dave consumes into account bob + Then dave should have oneshot 0 ĞD + Then bob should have 18 ĞD + Then bob should have oneshot 0 ĞD + + Scenario: Double oneshot consumption + When alice sends 8 ĞD to oneshot dave + Then alice should have 2 ĞD + Then dave should have oneshot 8 ĞD + When oneshot dave consumes 6 ĞD into account bob and the rest into oneshot charlie + Then dave should have oneshot 0 ĞD + Then bob should have 16 ĞD + Then bob should have oneshot 0 ĞD + Then charlie should have 10 ĞD + Then charlie should have oneshot 2 ĞD diff --git a/end2end-tests/tests/common/mod.rs b/end2end-tests/tests/common/mod.rs index 078abd747..8ee843104 100644 --- a/end2end-tests/tests/common/mod.rs +++ b/end2end-tests/tests/common/mod.rs @@ -17,6 +17,7 @@ #![allow(clippy::enum_variant_names, dead_code, unused_imports)] pub mod balances; +pub mod oneshot; #[subxt::subxt(runtime_metadata_path = "../resources/metadata.scale")] pub mod node_runtime {} @@ -28,7 +29,7 @@ use std::path::PathBuf; use std::process::Command; use std::str::FromStr; use subxt::rpc::{rpc_params, ClientT, SubscriptionClientT}; -use subxt::{ClientBuilder, DefaultConfig, DefaultExtra}; +use subxt::{ClientBuilder, DefaultConfig, DefaultExtra, PairSigner}; pub type Api = node_runtime::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>; pub type Client = subxt::Client<DefaultConfig>; diff --git a/end2end-tests/tests/common/oneshot.rs b/end2end-tests/tests/common/oneshot.rs new file mode 100644 index 000000000..59a86a939 --- /dev/null +++ b/end2end-tests/tests/common/oneshot.rs @@ -0,0 +1,102 @@ +// Copyright 2021 Axiom-Team +// +// This file is part of Substrate-Libre-Currency. +// +// Substrate-Libre-Currency is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Substrate-Libre-Currency 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. + +use super::node_runtime::runtime_types::gdev_runtime; +use super::node_runtime::runtime_types::pallet_balances; +use super::*; +use sp_keyring::AccountKeyring; +use subxt::{sp_runtime::MultiAddress, PairSigner}; + +pub async fn create_oneshot_account( + api: &Api, + client: &Client, + from: AccountKeyring, + amount: u64, + to: AccountKeyring, +) -> Result<()> { + let from = PairSigner::new(from.pair()); + let to = to.to_account_id(); + + let _events = create_block_with_extrinsic( + client, + api.tx() + .account() + .create_oneshot_account(to.clone().into(), amount) + .create_signed(&from, ()) + .await?, + ) + .await?; + + Ok(()) +} + +pub async fn consume_oneshot_account( + api: &Api, + client: &Client, + from: AccountKeyring, + to: AccountKeyring, + is_dest_oneshot: bool, +) -> Result<()> { + let from = PairSigner::new(from.pair()); + let to = to.to_account_id(); + + let _events = create_block_with_extrinsic( + client, + api.tx() + .account() + .consume_oneshot_account(0, to.clone().into(), is_dest_oneshot) + .create_signed(&from, ()) + .await?, + ) + .await?; + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +pub async fn consume_oneshot_account_two_dests( + api: &Api, + client: &Client, + from: AccountKeyring, + amount: u64, + to1: AccountKeyring, + is_dest1_oneshot: bool, + to2: AccountKeyring, + is_dest2_oneshot: bool, +) -> Result<()> { + let from = PairSigner::new(from.pair()); + let to1 = to1.to_account_id(); + let to2 = to2.to_account_id(); + + let _events = create_block_with_extrinsic( + client, + api.tx() + .account() + .consume_oneshot_account_two_dests( + 0, + to1.into(), + is_dest1_oneshot, + to2.into(), + is_dest2_oneshot, + amount, + ) + .create_signed(&from, ()) + .await?, + ) + .await?; + + Ok(()) +} diff --git a/end2end-tests/tests/cucumber_tests.rs b/end2end-tests/tests/cucumber_tests.rs index ffe54030a..2004e8a04 100644 --- a/end2end-tests/tests/cucumber_tests.rs +++ b/end2end-tests/tests/cucumber_tests.rs @@ -155,6 +155,89 @@ async fn transfer( } } +#[when(regex = r"([a-zA-Z]+) sends (\d+) (ĞD|cĞD) to oneshot ([a-zA-Z]+)")] +async fn create_oneshot_account( + world: &mut DuniterWorld, + from: String, + amount: u64, + unit: String, + to: String, +) -> Result<()> { + // Parse inputs + let from = AccountKeyring::from_str(&from).expect("unknown from"); + let to = AccountKeyring::from_str(&to).expect("unknown to"); + let (amount, is_ud) = parse_amount(amount, &unit); + + assert!(!is_ud); + + common::oneshot::create_oneshot_account(world.api(), world.client(), from, amount, to).await +} + +#[when(regex = r"oneshot ([a-zA-Z]+) consumes into (oneshot|account) ([a-zA-Z]+)")] +async fn consume_oneshot_account( + world: &mut DuniterWorld, + from: String, + is_dest_oneshot: String, + to: String, +) -> Result<()> { + // Parse inputs + let from = AccountKeyring::from_str(&from).expect("unknown from"); + let to = AccountKeyring::from_str(&to).expect("unknown to"); + let is_dest_oneshot = match is_dest_oneshot.as_str() { + "oneshot" => true, + "account" => false, + _ => unreachable!(), + }; + + common::oneshot::consume_oneshot_account(world.api(), world.client(), from, to, is_dest_oneshot) + .await +} + +#[when( + regex = r"oneshot ([a-zA-Z]+) consumes (\d+) (ĞD|cĞD) into (oneshot|account) ([a-zA-Z]+) and the rest into (oneshot|account) ([a-zA-Z]+)" +)] +#[allow(clippy::too_many_arguments)] +async fn consume_oneshot_account_two_dests( + world: &mut DuniterWorld, + from: String, + amount: u64, + unit: String, + is_dest1_oneshot: String, + to1: String, + is_dest2_oneshot: String, + to2: String, +) -> Result<()> { + // Parse inputs + let from = AccountKeyring::from_str(&from).expect("unknown from"); + let to1 = AccountKeyring::from_str(&to1).expect("unknown to1"); + let to2 = AccountKeyring::from_str(&to2).expect("unknown to2"); + let is_dest1_oneshot = match is_dest1_oneshot.as_str() { + "oneshot" => true, + "account" => false, + _ => unreachable!(), + }; + let is_dest2_oneshot = match is_dest2_oneshot.as_str() { + "oneshot" => true, + "account" => false, + _ => unreachable!(), + }; + let (amount, is_ud) = parse_amount(amount, &unit); + + assert!(!is_ud); + + common::oneshot::consume_oneshot_account_two_dests( + world.api(), + world.client(), + from, + amount, + to1, + is_dest1_oneshot, + to2, + is_dest2_oneshot, + ) + .await +} + #[when(regex = r"([a-zA-Z]+) sends all (?:his|her) (?:ĞDs?|DUs?) to ([a-zA-Z]+)")] async fn send_all_to(world: &mut DuniterWorld, from: String, to: String) -> Result<()> { // Parse inputs @@ -177,6 +260,24 @@ async fn should_have(world: &mut DuniterWorld, who: String, amount: u64) -> Resu Ok(()) } +#[then(regex = r"([a-zA-Z]+) should have oneshot (\d+) ĞD")] +async fn should_have_oneshot(world: &mut DuniterWorld, who: String, amount: u64) -> Result<()> { + // Parse inputs + let who = AccountKeyring::from_str(&who) + .expect("unknown to") + .to_account_id(); + let amount = amount * 100; + + let oneshot_amount = world + .api() + .storage() + .account() + .oneshot_accounts(who, None) + .await?; + assert_eq!(oneshot_amount.unwrap_or(0), amount); + Ok(()) +} + #[then(regex = r"Current UD amount should be (\d+).(\d+)")] async fn current_ud_amount_should_be( world: &mut DuniterWorld, diff --git a/pallets/duniter-account/src/lib.rs b/pallets/duniter-account/src/lib.rs index 1b7f59f8a..b848a4b51 100644 --- a/pallets/duniter-account/src/lib.rs +++ b/pallets/duniter-account/src/lib.rs @@ -148,8 +148,8 @@ pub mod pallet { }, OneshotAccountConsumed { account: T::AccountId, - balance: T::Balance, - dest: T::AccountId, + dest1: (T::AccountId, T::Balance), + dest2: Option<(T::AccountId, T::Balance)>, }, /// Random id assigned /// [account_id, random_id] @@ -160,15 +160,19 @@ pub mod pallet { #[pallet::error] pub enum Error<T> { - /// DestAccountNotExist + /// Block height is in the future + BlockHeightInFuture, + /// Block height is too old + BlockHeightTooOld, + /// Destination account does not exist DestAccountNotExist, - /// ExistentialDeposit + /// Destination account has balance less than existential deposit ExistentialDeposit, - /// InsufficientBalance + /// Source account has insufficient balance InsufficientBalance, - /// OneshotAccouncAlreadyCreated + /// Destination oneshot account already exists OneshotAccountAlreadyCreated, - /// OneshotAccountNotExist + /// Source oneshot account does not exist OneshotAccountNotExist, } @@ -254,6 +258,10 @@ pub mod pallet { // CALLS // #[pallet::call] impl<T: Config> Pallet<T> { + /// Create an account that can only be consumed once + /// + /// - `dest`: The oneshot account to be created. + /// - `balance`: The balance to be transfered to this oneshot account. #[pallet::weight(500_000_000)] pub fn create_oneshot_account( origin: OriginFor<T>, @@ -292,43 +300,142 @@ pub mod pallet { Ok(()) } + /// Consume a oneshot account and transfer its balance to an account + /// + /// - `block_height`: Must be a recent block number. The limit is `BlockHashCount` in the past. (this is to prevent replay attacks) + /// - `dest`: The destination account. + /// - `dest_is_oneshot`: If set to `true`, then a oneshot account is created at `dest`. Else, `dest` has to be an existing account. #[pallet::weight(500_000_000)] pub fn consume_oneshot_account( origin: OriginFor<T>, - dest: (bool, <T::Lookup as StaticLookup>::Source), + block_height: T::BlockNumber, + dest: <T::Lookup as StaticLookup>::Source, + dest_is_oneshot: bool, ) -> DispatchResult { let transactor = ensure_signed(origin)?; - let dest_is_oneshot = dest.0; - let dest = T::Lookup::lookup(dest.1)?; + let dest = T::Lookup::lookup(dest)?; let value = OneshotAccounts::<T>::take(&transactor) .ok_or(Error::<T>::OneshotAccountNotExist)?; + ensure!( + block_height <= frame_system::Pallet::<T>::block_number(), + Error::<T>::BlockHeightInFuture + ); + ensure!( + frame_system::pallet::BlockHash::<T>::contains_key(block_height), + Error::<T>::BlockHeightTooOld + ); if dest_is_oneshot { ensure!( OneshotAccounts::<T>::get(&dest).is_none(), Error::<T>::OneshotAccountAlreadyCreated ); OneshotAccounts::<T>::insert(&dest, value); - Self::deposit_event(Event::OneshotAccountConsumed { - account: transactor.clone(), - balance: value, - dest: dest.clone(), - }); Self::deposit_event(Event::OneshotAccountCreated { - account: dest, + account: dest.clone(), balance: value, - creator: transactor, + creator: transactor.clone(), }); } else { let dest_data = frame_system::Account::<T>::get(&dest).data; ensure!(dest_data.was_providing(), Error::<T>::DestAccountNotExist); - Self::deposit_event(Event::OneshotAccountConsumed { - account: transactor, - balance: value, - dest, + frame_system::Account::<T>::mutate(&dest, |a| a.data.add_free(value)); + } + OneshotAccounts::<T>::remove(&transactor); + Self::deposit_event(Event::OneshotAccountConsumed { + account: transactor, + dest1: (dest, value), + dest2: None, + }); + + Ok(()) + } + /// Consume a oneshot account and transfer its balance to two accounts + /// + /// - `block_height`: Must be a recent block number. The limit is `BlockHashCount` in the past. (this is to prevent replay attacks) + /// - `dest`: The first destination account. + /// - `dest_is_oneshot`: If set to `true`, then a oneshot account is created at `dest`. Else, `dest` has to be an existing account. + /// - `dest2`: The second destination account. + /// - `dest2_is_oneshot`: If set to `true`, then a oneshot account is created at `dest2`. Else, `dest2` has to be an existing account. + /// - `balance1`: The amount transfered to `dest`, the leftover being transfered to `dest2`. + #[pallet::weight(500_000_000)] + pub fn consume_oneshot_account_two_dests( + origin: OriginFor<T>, + block_height: T::BlockNumber, + dest: <T::Lookup as StaticLookup>::Source, + dest_is_oneshot: bool, + dest2: <T::Lookup as StaticLookup>::Source, + dest2_is_oneshot: bool, + #[pallet::compact] balance: T::Balance, + ) -> DispatchResult { + let transactor = ensure_signed(origin)?; + let dest1 = T::Lookup::lookup(dest)?; + let dest2 = T::Lookup::lookup(dest2)?; + + let value = OneshotAccounts::<T>::take(&transactor) + .ok_or(Error::<T>::OneshotAccountNotExist)?; + + let balance1 = balance; + ensure!(value > balance1, Error::<T>::InsufficientBalance); + let balance2 = value.saturating_sub(balance1); + ensure!( + block_height <= frame_system::Pallet::<T>::block_number(), + Error::<T>::BlockHeightInFuture + ); + ensure!( + frame_system::pallet::BlockHash::<T>::contains_key(block_height), + Error::<T>::BlockHeightTooOld + ); + if dest_is_oneshot { + ensure!( + OneshotAccounts::<T>::get(&dest1).is_none(), + Error::<T>::OneshotAccountAlreadyCreated + ); + ensure!( + balance1 >= T::ExistentialDeposit::get(), + Error::<T>::ExistentialDeposit + ); + } else { + let dest1_data = frame_system::Account::<T>::get(&dest1).data; + ensure!(dest1_data.was_providing(), Error::<T>::DestAccountNotExist); + } + if dest2_is_oneshot { + ensure!( + OneshotAccounts::<T>::get(&dest2).is_none(), + Error::<T>::OneshotAccountAlreadyCreated + ); + ensure!( + balance2 >= T::ExistentialDeposit::get(), + Error::<T>::ExistentialDeposit + ); + OneshotAccounts::<T>::insert(&dest2, balance2); + Self::deposit_event(Event::OneshotAccountCreated { + account: dest2.clone(), + balance: balance2, + creator: transactor.clone(), + }); + } else { + let dest2_data = frame_system::Account::<T>::get(&dest2).data; + ensure!(dest2_data.was_providing(), Error::<T>::DestAccountNotExist); + frame_system::Account::<T>::mutate(&dest2, |a| a.data.add_free(balance2)); + } + if dest_is_oneshot { + OneshotAccounts::<T>::insert(&dest1, balance1); + Self::deposit_event(Event::OneshotAccountCreated { + account: dest1.clone(), + balance: balance1, + creator: transactor.clone(), }); + } else { + frame_system::Account::<T>::mutate(&dest1, |a| a.data.add_free(balance1)); } + OneshotAccounts::<T>::remove(&transactor); + Self::deposit_event(Event::OneshotAccountConsumed { + account: transactor, + dest1: (dest1, balance1), + dest2: Some((dest2, balance2)), + }); Ok(()) } diff --git a/pallets/duniter-account/src/mock.rs b/pallets/duniter-account/src/mock.rs new file mode 100644 index 000000000..aaffbb4cb --- /dev/null +++ b/pallets/duniter-account/src/mock.rs @@ -0,0 +1,142 @@ +// Copyright 2021 Axiom-Team +// +// This file is part of Substrate-Libre-Currency. +// +// Substrate-Libre-Currency is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Substrate-Libre-Currency 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. + +use super::*; +use crate::{self as pallet_duniter_account}; +use frame_support::{parameter_types, traits::Everything, PalletId}; +use frame_system as system; +use sp_core::H256; +use sp_runtime::{ + testing::{Header, TestSignature, UintAuthorityId}, + traits::{BlakeTwo256, IdentityLookup}, + {Perbill, Permill}, +}; +use std::collections::BTreeMap; + +type AccountId = u64; +type Balance = u64; +type Block = frame_system::mocking::MockBlock<Test>; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event<T>}, + Account: pallet_duniter_account::{Pallet, Storage, Config<T>, Event<T>}, + Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>}, + ProvideRandomness: pallet_provide_randomness::{Pallet, Call, Storage, Event}, + } +); + +// System +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; +} + +impl system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup<Self::AccountId>; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = types::AccountData<Balance>; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; +} + +impl pallet_balances::Config for Test { + type Balance = Balance; + type DustRemoval = (); + type Event = Event; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = pallet_balances::weights::SubstrateWeight<Test>; + type MaxLocks = MaxLocks; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; +} + +parameter_types! { + pub const Burn: Permill = Permill::zero(); + pub const ProposalBond: Permill = Permill::from_percent(1); + pub const ProposalBondMaximum: Option<Balance> = None; + pub const SpendPeriod: <Test as system::Config>::BlockNumber = 10; + // Treasury account address: + // gdev/gtest: 5EYCAe5ijiYfyeZ2JJCGq56LmPyNRAKzpG4QkoQkkQNB5e6Z + pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); +} +impl pallet_treasury::Config for Test { + type ApproveOrigin = (); + type Burn = Burn; + type BurnDestination = (); + type Currency = Balances; + type Event = Event; + type OnSlash = pallet_treasury::Treasury; + type ProposalBond = ProposalBond; + type ProposalBondMinimum = frame_support::traits::ConstU64<10_000>; + type ProposalBondMaximum = ProposalBondMaximum; + type MaxApprovals = frame_support::traits::ConstU32<100>; + type PalletId = TreasuryPalletId; + type RejectOrigin = (); + type SpendFunds = (); + type SpendPeriod = SpendPeriod; + type WeightInfo = pallet_treasury::weights::SubstrateWeight<Self>; +} + +impl pallet_provide_randomness::Config for Runtime { + type Currency = Balances; + type Event = Event; + type GetCurrentEpochIndex = GetCurrentEpochIndex<Self>; + type MaxRequests = frame_support::traits::ConstU32<100>; + type RequestPrice = frame_support::traits::ConstU64<2_000>; + type OnFilledRandomness = Account; + type OnUnbalanced = Treasury; + type CurrentBlockRandomness = pallet_babe::CurrentBlockRandomness<Self>; + type RandomnessFromOneEpochAgo = pallet_babe::RandomnessFromOneEpochAgo<Self>; +} + +impl pallet_duniter_account::Config for Test { + type AccountIdToSalt = sp_runtime::traits::ConvertInto; + type Event = Event; + type MaxNewAccountsPerBlock = frame_support::pallet_prelude::ConstU32<1>; + type NewAccountPrice = frame_support::traits::ConstU64<300>; +} diff --git a/pallets/duniter-account/src/tests.rs b/pallets/duniter-account/src/tests.rs new file mode 100644 index 000000000..86934326a --- /dev/null +++ b/pallets/duniter-account/src/tests.rs @@ -0,0 +1,15 @@ +// Copyright 2021 Axiom-Team +// +// This file is part of Substrate-Libre-Currency. +// +// Substrate-Libre-Currency is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Substrate-Libre-Currency 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. diff --git a/pallets/duniter-account/src/types.rs b/pallets/duniter-account/src/types.rs index 8f1a6b16b..486be537b 100644 --- a/pallets/duniter-account/src/types.rs +++ b/pallets/duniter-account/src/types.rs @@ -29,6 +29,9 @@ pub struct AccountData<Balance> { } impl<Balance: Copy + Saturating + Zero> AccountData<Balance> { + pub fn add_free(&mut self, amount: Balance) { + self.free = self.free.saturating_add(amount); + } pub fn free_and_reserved(&self) -> Balance { self.free.saturating_add(self.reserved) } diff --git a/resources/metadata.scale b/resources/metadata.scale index 1f01cc4a16551841453d80d1d36da2e23037db3f..098f7c83f194cb5a3a6da8537a6ded2a4ff2e3c1 100644 GIT binary patch delta 9516 zcmey|&c19OJ6mpQNg`kAMz)#EjE0jJFv~KUPTt2nfw5q+KZ^ll$K(bUbH)jicd%G6 z&Y1j>MU`>EWN}s%#wC+&SXCK!Oip6eVZ1Q8pVffz#^eL6ri^zce`B>|d@$L7&5-#8 z<Dbcf?81}F*~}Q<Opa%FoBV}Mg82(vRBN&iyAb1#$?@!QjDIHYV-I9vWSlI(VZ_Kg z*_p$VMU;`TVe-Pql9Ok02r<e|Uc;fos5$v2hZ>{qCe8~if&vC^`9;a8E~&*OMfsH+ zj53oQg+wRc=M|mI&DSIz;-8mVoRMGRn4Fwnnpfgnl$uzQn!+<#QAld?S6(qzMw#TI z)X5JVMMW7R(3Ixq6_-w~wU7@=Ni8lh%wWvW2(hw)n&X+GqYz?c<&>C{n3tTY$-pQA zQe>3F*x_GLl9``pgIlc~0~5pKUOp>E=E*1crs*;;uy8O)FyydEKy<ROgfM0>atQe4 z=cO_*FbD+a=cckSWH2x=GB6ZOj^jVdSTb2fKuM;8(FUTyCM-4CrqsgDj)764D7CmW zr=(_bl7J&bnKU+KBB@11`9&?09|~k~mVoRR5MZd793rS<RKp?>1U7<$C4{ko#g#FF zk%PlOn}wl)fq{X6$F-=4g&_j$lm-R{76yhEfyo=hMJMkUG-T}9{82ERnXzZHr)Vk@ zW6$LE;vS3>CjSt3W}GtFMxujp%I2dI;Y{pP7#NrY7-mdXmNsCVGdV>14fh-d24(>U z1_lm>1=DR+86`KT$cQpBE}2{<YtOi1^DbFtM*bBH3`}bn)_|P4L4sk!<TG+Aj9Vsu zloMv$GMQCglX1sneR(&=J(J7iXE7d`EU0jmanIyiv(zT@Dsqb+V6-VNGBPzaw6wAc zC{4=AOlDv>Fj-eolm7?<1JenH6ChI=&M=&roT_NacwzE<MH9xF$yXHp7_UsWTQ4!$ zS}BXMX7Vzn`HWX4yD6(M-k6-N?8sO%d9Csm#ygW;7Ab9RQMt>=_+YxB0i)F98nr8o zPc{dt=QFZDVPIfoVt6sV%al=OvZX7}=Jy(gOpI?fD{40|GJe>6#L$6}{R0C7D+9w9 zG&Mhv)iC~<d~co9W_e>hkS;%y*^G=elV6$kGuCXbGAm*PWt9y_q$hJ&crmduP7bz6 zWBf3Am&IXbRz{A=8;=N2o@8mn$T|6_r9Lw!RD{ban2~pKoYfsh!O7OvyTPPC2R!Zg z*_48X{3Z+8PGJ<ByxcaJNtAK&{Kra@<?I$PN>1KyH;G9SBp^LG&%T&ZcJd>8OU9bX zG7b|N6(_HAP-9fxeAU5?kx_B7jMH*P#mPsUoH<k(85meu7&JHYJ6C}`(B(3jQFpVP zt2L;siSh{LH)UjCU{YYP1W_dnwv+dHm@_&~{^p^?_++xAr!0pfBLf4Q0E6pfJ5Liv z&&|c28Z3;yljrz4F@|ow@B4s}(R1<ve|>RZkp4hMhCoJ8v}$NDgn|S#Cch7mm@F6I z#uz&}H9(CimT~e$MjeYpMg|^6iM;%x+{BzjMn;wp21cFyg4Ci!P!5I*spKZ+r6iW* z7ga*kFeFa?7!VYY%E)K|&4A&lnduoNb_|Rvxrr6=CHW<ZIoM<sic*VHi^@_{ut+kb zPM#d7%9uI%V}RJ?^MN9=xr~e<0jWhUnZ*T(CCM4iIf=!^Hl@Z!b_@);VAoI92{L9Z zoSYEk%UC*jU62;zlgT%Oq#2(~{u&g;SUEW$Si`uIk%h-OzZ_Je)G{)#Fw`<KFn|fB zMn;B4a0s<B3NW-XGB7Z6fOBByWIIo>%?E?`FfsKqPJAmdc^@ykmVk3&P7Vtwba*0? z!R2RsKBQ2MPlS}J9E<{>;&0;Qz*4>Xxr~gU!Vg+BGB5}nfE2n4iFpc8^$H~!i6sij ziFpe7c{!B|NvR6Spu#pcHANvmFFBQAgRVjX$iM_Eg^-L?1*mNZ-T7cGV5g+$aWd*E zBqTu!;sh&&dXNq%SD^%~vZN?6uQ)BWC^ZEnP?C{ZjBcb}4AcSWKI36DNXp4i&W_Ij z$FTsT1*p^qiTIV~CZ!fJKz+{0*kco7W#yBfKRLQouHFXZ`uNOZm~kH%1(Ndfa~K#! z7#W=*UR6j$cZ5P>UWx+DtqPeX#R^E?Km;DcU$BQkZh*Qf!Ailmw73KmD2WP1smZDJ zc_j*9XDj4^U8x5S#+=OD%o2snVub`JkY<m>;tXd{otU7InWs>ak*ZLTSX`o~paBk` z%wkYj6%?hGrRJ3=6r~pABvvXUmXstWXBTUBpvDi#vzd9|Tmy-t`n(c7uq#0_NWqX` zrQn&SP@Gx<GCQH9s5CV}2V`>|mcYm?R)8e76oteRXmZd~aLp-B)q(O9G7`a#OiBeM zt<;Ll;*!j~bdU@DLEh49!4aqNCFS|?AeR+O5E;9Z14}g&aK|agEF)0j*Qj>@vyr?E zk9iqrWTIscCIwD1Q$$I5KFF^iuNK47L_H#@Vk8P0r-=oiG*O>gTmsFsX_-aEB?_S6 zKuH?3O&RqBlLjcb^j3h}np#|91a)h1YI1&F3RcH*GQrd$MJmp8Tu)p=HcCKA$wmpd zlQP)7iQpU$DdG&FZcfYvNx{ntNZDoxDK^2GN+BmTtt7uJwMZc;6_hVvB@09)SfNpZ z-ekf5K8zD52R1t?G%$*Ac&4y0FfgjXTe%Dj3?iO+DXA4K3{xj>ZuZum$;hY>U20*i zU}O*rY5`6K)yoYGGeLET2)GWJ3lfoFSO~2@IO{=;O>p%g;{z#pz(uKNo?B^2X;CTz z14{{5SOHSRf(j^Td6WiL-~yKQ0BZ`#&-c$sVPIs50Sh8&Dap@Q$j?df0d)hQuJ_9? zajnQKRxe>-V(9>hqa{Rzl>F3Ug}nR{1#sChy-|iyOzR6ag`k24ntc^=Qj3d0^;h0x zgEEnNAJ4qv(zLY9<jmB(5?GbT!ZIf~zqBYh6_!82(VK~+5>~PopmsSNbBa<EQz{|# zWeNi;%L-)IpjU&5IYo{j<q+o=qjg`PzAs^5W0?YV4Y~m!|4(0T!dPLr6x2#u$;hyh z5xE<(mJy>LvX&9t`d>SFU6`8HMn)ZQG(f6CMgfz6#GIVel4Ad&%=FAW#*0jdbi~NO zAi}7Sn^>8Y8lRa`F_|yiLTV!;Be-e7z-R%HC`c{J%umTo-a9!V+)(Ty6C<jD3=A6? zVf_uR$;ZO^gSIjXfNC*D1x5~$u*96wR2GJ<;C37X16YEEK>?xy)Op&<$YGG4l3EsD zRGL?knVSl=J3}J4vbZEQmxaNVk#X&0#i*Fc%@M+rd04m@dna#<j)(a{M<EEJ&yInS zK_xgjBQ>QoC$)%$VJ9QwPOvtnNLj{%lNBOsSYE<}{zNJ=y=9zSm=ZU+HcEr#BO~Kg zu+si$w#nSlDvUoT8$>rSGEV-O96wngMvjpe%<7AgXLOugkP<(cH&%uzk!iAGWZdM= zSVfjpCdQLs{akT+jG2?)C&f>m5vR$R3s$Qbuf){LG}$mZZt~)IIi{HqZHfshEOVKl z*6Js+O<tX#!nkzug@jI)l?Y+>$p;dZnbv}I$4_=iQe@l+Hu7kaCga}8?~__t4l*&W z1lxQiS%&fC<d4a9jCUs&q?9u4WSktwFF7gImT~W7yVM}Yjgu#)UO{3@Oum>l0fm1y zeI*M2QN~{Ny(sCf1S8p%fK%PU$;UE18IMjD$TDNRIe9_0$m9*Z+?&&~m>6M#n-^ri zU}V0_$T&H_P;B$U+-#7{#sbmFO8L@CCmA_BQy4EYG6*m#<fo;j=B1<-U1S6aGB64h z=j4}MogAMprhbx<QNklNF$L7PGlA3$@flz~k_^Mi$#e3J8P86>kgvh$IQeIO6yw>+ zAq6Uo7bjO1C}Zt(UY)$LK#lR{<QoO9jG>d|3QZXAP7W@#W_&ogzfe~0DI=qSV@^(f zd1^{<PJT%-D8)VmH_;^|7@kf(RanaSa<W5_KI7cY1w{*)K&k4Zpv2@K>-Z)+lnQ`K z@KQa-xtkkGJ6OOR4K9Ve)bjYWqSVy6ldY>{80T(|sG7wHkz+$N*BR$-4yX}_i0d)V zojkqvHAKzi)pgvAA2x5VJIo9fSlqOh@#AF0mITJHlbc%17^h6$+0w)F6;$$rh6gwp zeohW-wPXA{c|vO?*I!VHa*~mOkqOkP7oV)vCe6gkwAsDQg_VhwX>xD3F5`yDySr@} zFHZj1?ZCKavQ>{S6DQN;&K_qFb*@KU3|r5gmuWJ0uQ!Vz6XTxAIoVQ^ulA`k*)mOD zm@T|{LoYic773BfLj9^NjDnLbCI`ujGBJ9%7M5m~<tKxit2Q1w3VwDB45CaNKE(y` zliy60oqTMvD2pT$qvd4BDF&1Ir|2;nPPUs8!6-TT<y2L!4J=`dvP>+&jEa*brYY%e zfTSJ~aAQZ8iBUo$Oh+MDlYvnJl5miO7-T1lPW5F{WSV?NPI7YXR6a>nCPt6c6zI@I zaAta5BBVZ4oxEhKf~qDHw4yaLiDh8WWMW`ob^zC}x=f(1tt^uw6N4e#O{O^9WQpk} zBq0XV$?4Pl6)nLgnu1KUW#W)2E{IRgFG>{%%g;;!B|}H1$@=n=lRr#TWOSV@G+l!+ za<TPvCPvrEHZ!9bJtt3}na$`qnR%8Gqwi$1St^WylcQ%zGWt#~ou$qh$i%?F6w1U9 zI(g|VW5&qIH)okL#!lv+EzTG_S$(zwKdL7aCx^|}Vca_T+#i|E^Jl9uLYa@|lt9v_ z`sBKKZy|}1O&~cjCkLW*+x#3xrc|cM*~)U0ofaxXbWLtrcz`i;vhyNGum(`l-m<6| z&JJI^kSUi5G-e|)S!hWZ<CDpmOClHxCm&j3$yhr1&yo<v#>s(8tr%M;_bjz%?3}E) zUUc%qrGAWslMR;TGnP(XuuPLtb@HiYN?`AZaWgWAxF)9*C$cb9PUc%K&R98FZMmF( zEj$iQV;LA~L2>8^j>ASK<a+iAMtS!HT;8>ULhlJfCx~KT=w$*86G=^8yWC@P<?3X{ zsgncNh)&L1p$uhduTcTB(k2_N^kSSkxpt*0xDwEoo!qfDj`71}g;igf<{}JXTsXOE zwLas*$*Wcyfi+??ESYiP<SlDF7{5;byG9c{6eFX-Xi%J5T%4Jo7oVM4S^Sle(IXhb z_fD-W4lhb9C`c`0U|7mDSrOT4#+8%ru5$(lr3~Yh&35bkSQ)oY?%iU@=(u_R79U1N z$H_d~B;i64+d^3w9Vf5bCB^t*^T}PpOpK0`@9xo%n#;&20U2*$U^K`{%}X!Ih)>Ed z%}a6IEWfv(opJ5t^+yUBIX5dFm1Sn++-!ehF%#29rpfVE5|jU&GGXkTY<OB4l0r6T zp4MYxRGrLsPYUcRvB`Yb1sJzZmcOnx=^U&4P9{bZ|ANHA(p1p!hfRnb1EWT9Nq$jk zJSgRT*sOAH2M4C^&GOgZv7rdt-{S%gr^#*3xOb9ea_oOTSwYNpl^~>DwUY_bz$ttz zEwq)9fr9}w>;-Gn?43OCu?M5%<mZo@84pg*eiFcVbn^N?a!}uVf1=L>9&*#%9Ppe4 z#;kk!1InDd;0-&Y>gE-1*jN}9C$Ik?E~v`L$Wv-)#=s~6X?Z(NzWza$(Q)(F4?DSG zMsGg%r=LmdC`xiw#B{zQ#Q7(gCJQ)=PoDQ*lKm_bV?tPJ^5ln4#5Z64@5js!w+)eV zxBD?O<}fjy-M*WZF_($);&e?8MtO*Lwx@D1`Z02z1ttBhj0_j2@8M+BV7xm0DJP>D z<IU+ZT#SA!woHtIlNF;Jr?2N?<Y2ryeFqm~Amh#HT-=NeOm~?k%ezWU?pVt^eIqxc zIOE*uC%G9Fz}a7YyC4r^5+kGI_7Yx3LlC`;kFkuA@!@t^0me2)rl(BP*GVyoPv0ZR zsG{+bi6s~`(*2f+Q35p19%9G9@Df~HUSyPDcnfmgOD2YoU@b<{ED*(CkraOgDPdvw zIXzT}QI+xU^lBkSLr9e`f>Gr&FfwnyC&YM|k%^Ug`(hEsUKS=!=IKU~j1AM*NHI!+ zUBoHK46#IV`b#NB`RU?Pj53U^(@mrp_4q`Y8D)Y}6N~fniWwM0rx!>uns7=oGcYg- zGBe0dUn|9EEUF0At;&q>kK**NQjA8zt&B3z4h*9N0|z5VW=iXHD``eePF0W*ip&g} z({rR5jTv>fFOX)Oz{F@c-B6BEOURU&QJ~b&$bx~vl$n8nm7T$IdafL!Etf4wmm@QS zBlF~ojN;Qz%Q1TCc``D}1f&)vW#;5CFe>DwrYDwUmZidnNF-8H(~DA5IT$%gEf^SF zr#s6tE@1Lxp8P{xc{_&!<6A~fUyz}J%nX6k|0yz7h+TmX!8(?fWaJltn&u3l+k2E4 z-!L*pPOns9wBkF#z=3G)otS=5g;C71lZnv;6q`Y*1^GoKsYP}S46)3hT6K;FLn6qE zRAz=$umETXKNHN#W!7NGoi3otsLfco-A0u$nUS${`T{k^X^fTA&D9xG7;C4`QD;;a zn99Vc0v#6ywJd5u;VuD=I^i{tm|KAe`o`@_8jPOIjI9V&vU?z^I+0?i6Xd2n486?Y z(qZ~7ZANjSiOjI#VInhV=$c{TbY>mKGRCRXt8^Ic8D~!4rNbD=I2B>=bPrudA;y^q z0rjcS#4s1hs<|MmrZO`u1bbS5fnh20WPN$*={I#5&oHfIo;+VsZ2De3#t^2p%##h5 ziBH$kXY3c+$jqqWnNm_2T#{H)TFk((5hC|UpV3-)E6Bi|%nUmr25w|#*t^}pfbl&G z<H6|)ri>ZeXV@@mGf&@a!C1s}mU*(Gw%BxAOU4+ciy*fA_A~a3_m~*3ZtpZ@6k=w& zi7a>*<Q3!T_na8nc}*D+f%<UzCnv^ysZJ(F3s59OM*}=l>=+mY@)9eHJEvDWGpaK^ zWd=>GY~Sb1xQ&VXCD?TgEDUd%CvP|+y?vG&<6}m~kJIZs7(EyTryuuV3{=EoAj4OX zKAxRS3=#}KnJ33vNo^1IWb9|*`U~<kBMSo~%jEfwLGvVR(|P?EjhR?Me5L6revE6U z|Mh1~nqKVBsKKZ>eU3k)38Uili~fu%)6WGka&duDqb!RAgDeZEG%^xZgqAI;EC`P( zP7erRtm9N=VPIfVWMR<Uem#J3Dif0~%VYs($?4t(jGWUa1T#)&G@Pyx!q~}V3NcDV z0FuC_APll(nS9V+Zn}6VqX47rbd69(S^ic=Mj7xpk3}p4gComic~`0Fd7+Hg7+t5k zhB3O?PK5N`8MiX3z?-g&rA#8O6(vQ9jGinEEDRb93=Ezu3=AwXzAS+(p)8Rsi7c5c zxhxF6(*wd8rKUd$W7K90gs?Tj8Pyp>L2MhwNDw78eR(*e8dod}BadrQA_GJ0_Uqw{ z8wH{gS$I%-@jA&Fsma;#d7ya%7njn!%#ze1XONH|NCZ@Lr?N1%q!lIRrp6b8JNi~u z2zeWLpPnHVV(a1zMmc^*Mn=%=E_mRJfgyAHg$zb3rd*ckd2)<0(={>~6@=$9G8#Zj zOaC-b<A9-XdvqqF6APo|_IbIC(M*iC(|_eN7BhaBo>u^-9u+V)Fd9xzE@adYc4cJr zkT3v`oj}?$#gWtJ6*4Yk%$)98#JGX6aQe3*#(2ik>0!l;7Z_EitCujo)$3$rkb$OU zMi_&~26X_bn1PXni9v#efnmC4DWeT<186j_myt1rfpH<@^t@6=WyYn`CzUd$GL}sL zQOfAXvXhaqV7pTpqbVcf!|4^}j9!dSrynk7)MLCl{Zlz(E#uYg1r>~rOpJG@Z>nP4 z!}xT2el_E4P{!b{WsGF}IX$(OaVg`^>D+aU-x*7`GuJcLGco?1-rUGo#rSvnzedJh z#)I2?ni#8?m>8L-bF?x>GJcqz)XHeY_;UNyR>mq&O*dVzo$)#|Cv(Ge#db!8?c5!V zM$AmS%+u|=8M_%}r=RX-v}e?u&fUZ4&-#{;v4aORPA9y*sE1L3kx_U0<X%QQM#Jfs zdKpz1Ew_K~WmIKhw4E+HiSbC>6cz>s7yrDR%)C@c^^jSTnOe-q0224{FD*&W&&*55 zkdg6C1x@ULLXuU$m6?Ts!3HYgT2YW$l<J?y$jHJfFlD;^WJXyokXei$85t+=Oh41Y zXgPiUWJWzk&*|qTGb%IsPX9ES(TVZHbkiw}CX7F)XH8+;z!*ARdMcwbW8`%Esf;De zvCO8^w@qbCXPmQLZW^N=Bjb$ezS9|v89S#pPG`(y{4o9XbjB{m#OY--7#A~6nJzVx z@jVkO({%1xj24WU(_LmUdZ{onF)m<WEM(@BaW6{DODRZXgfS#UK;>~NBSY!*O|uv+ z87rs1nZ?-1Sh78DHe)*@3ojGnlIcI^Fd8s6Zr7U2SjNQII(^}M#z3Z$-sxZFGfrb% zIK6uTV>62*6XTBQLJJu$GA^C|WFcdSY$6lm1qQ~I%pxYC1?feJDXEb0J_g2ACdM1n zLl-exGB#}QU&I*8$dbv#cxU>D#f;*Np4&N=FkWF~Ts!^lQpRegCk@l1mNA+#Zk;}1 z8KV+QE)(N}>D!huhA{4(&b^#bi}B#}m^F;)j8mr1Th1so{oWczRmLgP?=5GPVr-Z$ zzm`#%6CCipOpGtKd#`1j#K@r~;G9}i!YDBP<2pt;#*@<p)-!rBm7JX(vz{@8amw_K z>ltkrEvLU*&QlZo-obde2=nbW;jGah5SI9+HBqbI73j0>mVU&E+^&Ca<@j9)-@ zPGDrbI{nT%Mq~VzEoEZ-v0Y>Xqdp_!&FL;18I2h4PA}icXvw&B`o@inE{qSSf7{5I z#@IPMb`#?s_LWSGe;62_PFL8>sKfh`nUxWgf7dcGGBR!t-^^&v#Q1i4|5ip-=9kPL zr*GfNXv*~!lt39bGBNUQ|F)G;fQem|k%NnY<;`^dy^P}1Rdz6{GX9+IwS%!-;V(#K zKu~I7X=-svaS&+W*s;X5AU`<+l%6I2PJg(AQIxS^`u81-{w#Z$7)7Uh?PM%uVr7}W zcPFC^6DP~`+dCO|GfGaMzKhWkG#)6s{mL%J78XHG7JHe{lFXdUl1c`~lT3`7(=+!o zdNS%x-@KnOjnQzr)B#2VM$_ru2N+#M?lLjzGRhbP6y+D>7boU8<>#eXZeM(WQJax5 zb^7^(jFTBHrzam`3}dvNzUvU<`g%teM^_dW83%BQ9bAx_9F|%H>UsLI2v~r{LMosl zfh+<BU=jD!ywu{%;!qX^hzj5QqLM_A<04rUFe`{emIfWLHb^;~$<mPEoRL_Ro*Dw0 YGZsor1}y~$NUY3F%`3@eX((g?08i}DHvj+t delta 7009 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 diff --git a/runtime/gdev/src/check_nonce.rs b/runtime/gdev/src/check_nonce.rs new file mode 100644 index 000000000..52d24c7ba --- /dev/null +++ b/runtime/gdev/src/check_nonce.rs @@ -0,0 +1,83 @@ +// Copyright 2021 Axiom-Team +// +// This file is part of Substrate-Libre-Currency. +// +// Substrate-Libre-Currency is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Substrate-Libre-Currency 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. + +use crate::Runtime; + +use codec::{Decode, Encode}; +use frame_system::Config; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{DispatchInfoOf, SignedExtension}, + transaction_validity::{TransactionValidity, TransactionValidityError}, +}; + +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] +#[scale_info(skip_type_params(Runtime))] +pub struct DuniterCheckNonce(pub frame_system::CheckNonce<Runtime>); + +impl sp_std::fmt::Debug for DuniterCheckNonce { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "DuniterCheckNonce({})", self.0 .0) + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } +} + +impl SignedExtension for DuniterCheckNonce { + type AccountId = <Runtime as Config>::AccountId; + type Call = <Runtime as Config>::Call; + type AdditionalSigned = (); + type Pre = (); + const IDENTIFIER: &'static str = "DuniterCheckNonce"; + + fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { + self.0.additional_signed() + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf<Self::Call>, + len: usize, + ) -> Result<(), TransactionValidityError> { + if matches!( + call, + Self::Call::Account( + pallet_duniter_account::Call::consume_oneshot_account { .. } + | pallet_duniter_account::Call::consume_oneshot_account_two_dests { .. } + ) + ) { + Ok(()) + } else { + self.0.pre_dispatch(who, call, info, len) + } + } + + fn validate( + &self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf<Self::Call>, + len: usize, + ) -> TransactionValidity { + self.0.validate(who, call, info, len) + } +} diff --git a/runtime/gdev/src/lib.rs b/runtime/gdev/src/lib.rs index 3b57eda4a..fe26de099 100644 --- a/runtime/gdev/src/lib.rs +++ b/runtime/gdev/src/lib.rs @@ -22,6 +22,7 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +mod check_nonce; pub mod parameters; pub use self::parameters::*; @@ -110,7 +111,7 @@ pub type SignedExtra = ( frame_system::CheckTxVersion<Runtime>, frame_system::CheckGenesis<Runtime>, frame_system::CheckEra<Runtime>, - frame_system::CheckNonce<Runtime>, + check_nonce::DuniterCheckNonce, frame_system::CheckWeight<Runtime>, pallet_transaction_payment::ChargeTransactionPayment<Runtime>, ); @@ -250,7 +251,7 @@ construct_runtime!( { // Basic stuff System: frame_system::{Pallet, Call, Config, Storage, Event<T>} = 0, - Account: pallet_duniter_account::{Pallet, Storage, Config<T>, Event<T>} = 1, + Account: pallet_duniter_account::{Pallet, Call, Storage, Config<T>, Event<T>} = 1, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event<T>} = 2, // Block creation diff --git a/runtime/gdev/tests/integration_tests.rs b/runtime/gdev/tests/integration_tests.rs index 6ffc2f586..e28b616b3 100644 --- a/runtime/gdev/tests/integration_tests.rs +++ b/runtime/gdev/tests/integration_tests.rs @@ -344,3 +344,76 @@ fn test_create_new_idty() { ); }); } + +#[test] +fn test_oneshot_accounts() { + ExtBuilder::new(1, 3, 4) + .with_initial_balances(vec![ + (AccountKeyring::Alice.to_account_id(), 1_000), + (AccountKeyring::Eve.to_account_id(), 1_000), + ]) + .build() + .execute_with(|| { + run_to_block(6); + + assert_ok!(Account::create_oneshot_account( + frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(), + MultiAddress::Id(AccountKeyring::Eve.to_account_id()), + 400 + )); + assert_eq!( + Balances::free_balance(AccountKeyring::Alice.to_account_id()), + 600 + ); + run_to_block(7); + + assert_ok!(Account::consume_oneshot_account_two_dests( + frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(), + 0, + MultiAddress::Id(AccountKeyring::Ferdie.to_account_id()), + true, + MultiAddress::Id(AccountKeyring::Alice.to_account_id()), + false, + 300 + )); + assert_eq!( + Balances::free_balance(AccountKeyring::Alice.to_account_id()), + 700 + ); + assert_err!( + Account::consume_oneshot_account( + frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(), + 0, + MultiAddress::Id(AccountKeyring::Ferdie.to_account_id()), + true + ), + pallet_duniter_account::Error::<Runtime>::OneshotAccountNotExist + ); + run_to_block(8); + // Oneshot account consumption should not increment the nonce + assert_eq!( + System::account(AccountKeyring::Eve.to_account_id()).nonce, + 0 + ); + + assert_ok!(Account::consume_oneshot_account( + frame_system::RawOrigin::Signed(AccountKeyring::Ferdie.to_account_id()).into(), + 0, + MultiAddress::Id(AccountKeyring::Alice.to_account_id()), + false, + )); + assert_eq!( + Balances::free_balance(AccountKeyring::Alice.to_account_id()), + 1000 + ); + assert_err!( + Account::consume_oneshot_account( + frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(), + 0, + MultiAddress::Id(AccountKeyring::Alice.to_account_id()), + false + ), + pallet_duniter_account::Error::<Runtime>::OneshotAccountNotExist + ); + }); +} -- GitLab