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