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
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
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