From 5b6686a1e7e71645b2aa62d7b84d8b0a3c3a53bf 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 | 101 ++++++++++++ end2end-tests/tests/cucumber_tests.rs | 106 +++++++++++++ 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, 677 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..acb8a99f3 --- /dev/null +++ b/end2end-tests/tests/common/oneshot.rs @@ -0,0 +1,101 @@ +// 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(()) +} + +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..5ea462fff 100644 --- a/end2end-tests/tests/cucumber_tests.rs +++ b/end2end-tests/tests/cucumber_tests.rs @@ -155,6 +155,94 @@ 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]+)" +)] +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 +265,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 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 B&^G`8 delta 7009 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 I>q_MR1CMol761SM 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