From 940093d3c53bfa3bbc5497d28a1919cfaa744c1a Mon Sep 17 00:00:00 2001
From: tuxmain <tuxmain@zettascript.org>
Date: Thu, 4 Aug 2022 17:13:39 +0200
Subject: [PATCH] feat(oneshot-account): Pallet oneshot-account

---
 Cargo.lock                                    |  21 +
 Cargo.toml                                    |   1 +
 .../cucumber-features/oneshot_account.feature |  21 +
 end2end-tests/tests/common/mod.rs             |   1 +
 end2end-tests/tests/common/oneshot.rs         | 116 ++++++
 end2end-tests/tests/cucumber_tests.rs         | 105 ++++-
 pallets/oneshot-account/Cargo.toml            |  84 ++++
 pallets/oneshot-account/src/benchmarking.rs   | 138 +++++++
 pallets/oneshot-account/src/check_nonce.rs    |  86 ++++
 pallets/oneshot-account/src/lib.rs            | 377 ++++++++++++++++++
 pallets/oneshot-account/src/mock.rs           | 123 ++++++
 pallets/oneshot-account/src/types.rs          |  24 ++
 pallets/oneshot-account/src/weights.rs        |  52 +++
 resources/metadata.scale                      | Bin 122124 -> 125548 bytes
 runtime/common/Cargo.toml                     |   2 +
 runtime/common/src/pallets_config.rs          |   8 +-
 .../src/weights/pallet_oneshot_account.rs     |  70 ++++
 runtime/g1/Cargo.toml                         |   2 +
 runtime/g1/src/lib.rs                         |   3 +-
 runtime/gdev/Cargo.toml                       |   2 +
 runtime/gdev/src/lib.rs                       |   5 +-
 runtime/gdev/tests/integration_tests.rs       |  78 ++++
 runtime/gtest/Cargo.toml                      |   2 +
 runtime/gtest/src/lib.rs                      |   1 +
 24 files changed, 1317 insertions(+), 5 deletions(-)
 create mode 100644 end2end-tests/cucumber-features/oneshot_account.feature
 create mode 100644 end2end-tests/tests/common/oneshot.rs
 create mode 100644 pallets/oneshot-account/Cargo.toml
 create mode 100644 pallets/oneshot-account/src/benchmarking.rs
 create mode 100644 pallets/oneshot-account/src/check_nonce.rs
 create mode 100644 pallets/oneshot-account/src/lib.rs
 create mode 100644 pallets/oneshot-account/src/mock.rs
 create mode 100644 pallets/oneshot-account/src/types.rs
 create mode 100644 pallets/oneshot-account/src/weights.rs
 create mode 100644 runtime/common/src/weights/pallet_oneshot_account.rs

diff --git a/Cargo.lock b/Cargo.lock
index 0029d6e7f..427dd1171 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -887,6 +887,7 @@ dependencies = [
  "pallet-identity",
  "pallet-membership",
  "pallet-multisig",
+ "pallet-oneshot-account",
  "pallet-provide-randomness",
  "pallet-proxy",
  "pallet-scheduler",
@@ -2426,6 +2427,7 @@ dependencies = [
  "pallet-membership",
  "pallet-multisig",
  "pallet-offences",
+ "pallet-oneshot-account",
  "pallet-preimage",
  "pallet-provide-randomness",
  "pallet-proxy",
@@ -2494,6 +2496,7 @@ dependencies = [
  "pallet-membership",
  "pallet-multisig",
  "pallet-offences",
+ "pallet-oneshot-account",
  "pallet-preimage",
  "pallet-provide-randomness",
  "pallet-proxy",
@@ -2776,6 +2779,7 @@ dependencies = [
  "pallet-membership",
  "pallet-multisig",
  "pallet-offences",
+ "pallet-oneshot-account",
  "pallet-preimage",
  "pallet-provide-randomness",
  "pallet-proxy",
@@ -5309,6 +5313,23 @@ dependencies = [
  "sp-std",
 ]
 
+[[package]]
+name = "pallet-oneshot-account"
+version = "3.0.0"
+dependencies = [
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "log",
+ "pallet-transaction-payment",
+ "parity-scale-codec",
+ "scale-info",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
 [[package]]
 name = "pallet-preimage"
 version = "4.0.0-dev"
diff --git a/Cargo.toml b/Cargo.toml
index d6ddf72f3..63920b577 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -133,6 +133,7 @@ members = [
     'pallets/duniter-wot',
     'pallets/identity',
     'pallets/membership',
+    'pallets/oneshot-account',
     'pallets/authority-members',
     'pallets/universal-dividend',
     'pallets/upgrade-origin',
diff --git a/end2end-tests/cucumber-features/oneshot_account.feature b/end2end-tests/cucumber-features/oneshot_account.feature
new file mode 100644
index 000000000..f3d74b790
--- /dev/null
+++ b/end2end-tests/cucumber-features/oneshot_account.feature
@@ -0,0 +1,21 @@
+Feature: Oneshot account
+
+  Scenario: Simple oneshot consumption
+    When alice sends 7 ĞD to oneshot dave
+    Then alice should have 3 ĞD
+    Then dave should have oneshot 7 ĞD
+    When oneshot dave consumes into account bob
+    Then dave should have oneshot 0 ĞD
+    Then bob should have 1699 cĞD
+    Then bob should have oneshot 0 ĞD
+
+  Scenario: Double oneshot consumption
+    When alice sends 7 ĞD to oneshot dave
+    Then alice should have 3 ĞD
+    Then dave should have oneshot 7 ĞD
+    When oneshot dave consumes 4 ĞD into account bob and the rest into oneshot charlie
+    Then dave should have oneshot 0 ĞD
+    Then bob should have 14 ĞD
+    Then bob should have oneshot 0 ĞD
+    Then charlie should have 10 ĞD
+    Then charlie should have oneshot 299 cĞD
diff --git a/end2end-tests/tests/common/mod.rs b/end2end-tests/tests/common/mod.rs
index 56317abf6..fb464d5a7 100644
--- a/end2end-tests/tests/common/mod.rs
+++ b/end2end-tests/tests/common/mod.rs
@@ -18,6 +18,7 @@
 
 pub mod balances;
 pub mod cert;
+pub mod oneshot;
 
 #[subxt::subxt(runtime_metadata_path = "../resources/metadata.scale")]
 pub mod node_runtime {}
diff --git a/end2end-tests/tests/common/oneshot.rs b/end2end-tests/tests/common/oneshot.rs
new file mode 100644
index 000000000..b8827fe88
--- /dev/null
+++ b/end2end-tests/tests/common/oneshot.rs
@@ -0,0 +1,116 @@
+// 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::node_runtime::runtime_types::pallet_oneshot_account;
+use super::*;
+use sp_keyring::AccountKeyring;
+use subxt::{
+    sp_runtime::{AccountId32, MultiAddress},
+    PairSigner,
+};
+
+pub enum Account {
+    Normal(AccountKeyring),
+    Oneshot(AccountKeyring),
+}
+
+impl Account {
+    fn to_account_id(
+        &self,
+    ) -> pallet_oneshot_account::types::Account<MultiAddress<AccountId32, ()>> {
+        match self {
+            Account::Normal(account) => {
+                pallet_oneshot_account::types::Account::Normal(account.to_account_id().into())
+            }
+            Account::Oneshot(account) => {
+                pallet_oneshot_account::types::Account::Oneshot(account.to_account_id().into())
+            }
+        }
+    }
+}
+
+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()
+            .oneshot_account()
+            .create_oneshot_account(to.into(), amount)?
+            .create_signed(&from, BaseExtrinsicParamsBuilder::new())
+            .await?,
+    )
+    .await?;
+
+    Ok(())
+}
+
+pub async fn consume_oneshot_account(
+    api: &Api,
+    client: &Client,
+    from: AccountKeyring,
+    to: Account,
+) -> Result<()> {
+    let from = PairSigner::new(from.pair());
+    let to = to.to_account_id();
+
+    let _events = create_block_with_extrinsic(
+        client,
+        api.tx()
+            .oneshot_account()
+            .consume_oneshot_account(0, to)?
+            .create_signed(&from, BaseExtrinsicParamsBuilder::new())
+            .await?,
+    )
+    .await?;
+
+    Ok(())
+}
+
+#[allow(clippy::too_many_arguments)]
+pub async fn consume_oneshot_account_with_remaining(
+    api: &Api,
+    client: &Client,
+    from: AccountKeyring,
+    amount: u64,
+    to: Account,
+    remaining_to: Account,
+) -> Result<()> {
+    let from = PairSigner::new(from.pair());
+    let to = to.to_account_id();
+    let remaining_to = remaining_to.to_account_id();
+
+    let _events = create_block_with_extrinsic(
+        client,
+        api.tx()
+            .oneshot_account()
+            .consume_oneshot_account_with_remaining(0, to, remaining_to, amount)?
+            .create_signed(&from, BaseExtrinsicParamsBuilder::new())
+            .await?,
+    )
+    .await?;
+
+    Ok(())
+}
diff --git a/end2end-tests/tests/cucumber_tests.rs b/end2end-tests/tests/cucumber_tests.rs
index 265b78295..303787361 100644
--- a/end2end-tests/tests/cucumber_tests.rs
+++ b/end2end-tests/tests/cucumber_tests.rs
@@ -155,7 +155,7 @@ async fn n_blocks_later(world: &mut DuniterWorld, n: usize) -> Result<()> {
     Ok(())
 }
 
-#[when(regex = r"([a-zA-Z]+) sends? (\d+) (ĞD|cĞD|UD|mUD) to ([a-zA-Z]+)")]
+#[when(regex = r"([a-zA-Z]+) sends? (\d+) (ĞD|cĞD|UD|mUD) to ([a-zA-Z]+)$")]
 async fn transfer(
     world: &mut DuniterWorld,
     from: String,
@@ -181,6 +181,86 @@ 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 to = match is_dest_oneshot.as_str() {
+        "oneshot" => common::oneshot::Account::Oneshot(to),
+        "account" => common::oneshot::Account::Normal(to),
+        _ => unreachable!(),
+    };
+
+    common::oneshot::consume_oneshot_account(world.api(), world.client(), from, to).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_with_remaining(
+    world: &mut DuniterWorld,
+    from: String,
+    amount: u64,
+    unit: String,
+    is_dest_oneshot: String,
+    to: String,
+    is_remaining_to_oneshot: String,
+    remaining_to: String,
+) -> Result<()> {
+    // Parse inputs
+    let from = AccountKeyring::from_str(&from).expect("unknown from");
+    let to = AccountKeyring::from_str(&to).expect("unknown to");
+    let remaining_to = AccountKeyring::from_str(&remaining_to).expect("unknown remaining_to");
+    let to = match is_dest_oneshot.as_str() {
+        "oneshot" => common::oneshot::Account::Oneshot(to),
+        "account" => common::oneshot::Account::Normal(to),
+        _ => unreachable!(),
+    };
+    let remaining_to = match is_remaining_to_oneshot.as_str() {
+        "oneshot" => common::oneshot::Account::Oneshot(remaining_to),
+        "account" => common::oneshot::Account::Normal(remaining_to),
+        _ => unreachable!(),
+    };
+    let (amount, is_ud) = parse_amount(amount, &unit);
+
+    assert!(!is_ud);
+
+    common::oneshot::consume_oneshot_account_with_remaining(
+        world.api(),
+        world.client(),
+        from,
+        amount,
+        to,
+        remaining_to,
+    )
+    .await
+}
+
 #[when(regex = r"([a-zA-Z]+) sends? all (?:his|her) (?:ĞDs?|DUs?|UDs?) to ([a-zA-Z]+)")]
 async fn send_all_to(world: &mut DuniterWorld, from: String, to: String) -> Result<()> {
     // Parse inputs
@@ -219,6 +299,29 @@ async fn should_have(
     Ok(())
 }
 
+#[then(regex = r"([a-zA-Z]+) should have oneshot (\d+) (ĞD|cĞD)")]
+async fn should_have_oneshot(
+    world: &mut DuniterWorld,
+    who: String,
+    amount: u64,
+    unit: String,
+) -> Result<()> {
+    // Parse inputs
+    let who = AccountKeyring::from_str(&who)
+        .expect("unknown to")
+        .to_account_id();
+    let (amount, _is_ud) = parse_amount(amount, &unit);
+
+    let oneshot_amount = world
+        .api()
+        .storage()
+        .oneshot_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/oneshot-account/Cargo.toml b/pallets/oneshot-account/Cargo.toml
new file mode 100644
index 000000000..bba1f932d
--- /dev/null
+++ b/pallets/oneshot-account/Cargo.toml
@@ -0,0 +1,84 @@
+[package]
+authors = ['librelois <c@elo.tf>']
+description = 'FRAME pallet oneshot account.'
+edition = '2018'
+homepage = 'https://substrate.dev'
+license = 'AGPL-3.0'
+name = 'pallet-oneshot-account'
+readme = 'README.md'
+repository = 'https://git.duniter.org/nodes/rust/duniter-v2s'
+version = '3.0.0'
+
+[features]
+default = ['std']
+runtime-benchmarks = ['frame-benchmarking']
+std = [
+    'codec/std',
+    'frame-support/std',
+    'frame-system/std',
+    'frame-benchmarking/std',
+    'sp-core/std',
+    'sp-io/std',
+    'sp-runtime/std',
+    'sp-std/std',
+]
+try-runtime = ['frame-support/try-runtime']
+
+[dependencies]
+# crates.io
+codec = { package = 'parity-scale-codec', version = "3.1.5", default-features = false, features = ["derive"] }
+log = { version = "0.4.14", default-features = false }
+scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
+
+# substrate
+[dependencies.frame-benchmarking]
+default-features = false
+git = 'https://github.com/duniter/substrate'
+optional = true
+branch = 'duniter-substrate-v0.9.23'
+
+[dependencies.frame-support]
+default-features = false
+git = 'https://github.com/duniter/substrate'
+branch = 'duniter-substrate-v0.9.23'
+
+[dependencies.frame-system]
+default-features = false
+git = 'https://github.com/duniter/substrate'
+branch = 'duniter-substrate-v0.9.23'
+
+[dependencies.pallet-transaction-payment]
+default-features = false
+git = 'https://github.com/duniter/substrate'
+branch = 'duniter-substrate-v0.9.23'
+
+[dependencies.sp-core]
+default-features = false
+git = 'https://github.com/duniter/substrate'
+branch = 'duniter-substrate-v0.9.23'
+
+[dependencies.sp-io]
+default-features = false
+git = 'https://github.com/duniter/substrate'
+branch = 'duniter-substrate-v0.9.23'
+
+[dependencies.sp-runtime]
+default-features = false
+git = 'https://github.com/duniter/substrate'
+branch = 'duniter-substrate-v0.9.23'
+
+[dependencies.sp-std]
+default-features = false
+git = 'https://github.com/duniter/substrate'
+branch = 'duniter-substrate-v0.9.23'
+
+### DOC ###
+
+[package.metadata.docs.rs]
+targets = ['x86_64-unknown-linux-gnu']
+
+### DEV ###
+
+[dev-dependencies.sp-io]
+git = 'https://github.com/duniter/substrate'
+branch = 'duniter-substrate-v0.9.23'
diff --git a/pallets/oneshot-account/src/benchmarking.rs b/pallets/oneshot-account/src/benchmarking.rs
new file mode 100644
index 000000000..fab093943
--- /dev/null
+++ b/pallets/oneshot-account/src/benchmarking.rs
@@ -0,0 +1,138 @@
+// Copyright 2021-2022 Axiom-Team
+//
+// This file is part of Duniter-v2S.
+//
+// Duniter-v2S 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.
+//
+// Duniter-v2S 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 Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
+
+#![cfg(feature = "runtime-benchmarks")]
+
+use super::*;
+
+use frame_benchmarking::{account, benchmarks, whitelisted_caller};
+use frame_support::pallet_prelude::IsType;
+use frame_support::traits::Get;
+use frame_system::RawOrigin;
+use pallet_balances::Pallet as Balances;
+
+use crate::Pallet;
+
+const SEED: u32 = 0;
+
+benchmarks! {
+    where_clause { where
+        T: pallet_balances::Config,
+        T::Balance: From<u64>,
+        <T::Currency as Currency<T::AccountId>>::Balance: IsType<T::Balance>
+    }
+    create_oneshot_account {
+        let existential_deposit = T::ExistentialDeposit::get();
+        let caller = whitelisted_caller();
+
+        // Give some multiple of the existential deposit
+        let balance = existential_deposit.saturating_mul((2).into());
+        let _ = T::Currency::make_free_balance_be(&caller, balance.into());
+
+        let recipient: T::AccountId = account("recipient", 0, SEED);
+        let recipient_lookup: <T::Lookup as StaticLookup>::Source =
+            T::Lookup::unlookup(recipient.clone());
+        let transfer_amount = existential_deposit;
+    }: _(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount.into())
+    verify {
+        assert_eq!(Balances::<T>::free_balance(&caller), transfer_amount);
+        assert_eq!(OneshotAccounts::<T>::get(&recipient), Some(transfer_amount.into()));
+    }
+    where_clause { where
+        T: pallet_balances::Config,
+        T::Balance: From<u64>,
+        <T::Currency as Currency<T::AccountId>>::Balance: IsType<T::Balance>+From<T::Balance>
+    }
+    consume_oneshot_account {
+        let existential_deposit = T::ExistentialDeposit::get();
+        let caller: T::AccountId = whitelisted_caller();
+
+        // Give some multiple of the existential deposit
+        let balance = existential_deposit.saturating_mul((2).into());
+        OneshotAccounts::<T>::insert(
+            caller.clone(),
+            Into::<<T::Currency as Currency<T::AccountId>>::Balance>::into(balance)
+        );
+
+        // Deposit into a normal account is more expensive than into a oneshot account
+        // so we create the recipient account with an existential deposit.
+        let recipient: T::AccountId = account("recipient", 0, SEED);
+        let recipient_lookup: <T::Lookup as StaticLookup>::Source =
+            T::Lookup::unlookup(recipient.clone());
+        let _ = T::Currency::make_free_balance_be(&recipient, existential_deposit.into());
+    }: _(
+        RawOrigin::Signed(caller.clone()),
+        T::BlockNumber::zero(),
+        Account::<T>::Normal(recipient_lookup)
+    )
+    verify {
+        assert_eq!(OneshotAccounts::<T>::get(&caller), None);
+        assert_eq!(
+            Balances::<T>::free_balance(&recipient),
+            existential_deposit.saturating_mul((3).into())
+        );
+    }
+    where_clause { where
+        T: pallet_balances::Config,
+        T::Balance: From<u64>,
+        <T::Currency as Currency<T::AccountId>>::Balance: IsType<T::Balance>+From<T::Balance>
+    }
+    consume_oneshot_account_with_remaining {
+        let existential_deposit = T::ExistentialDeposit::get();
+        let caller: T::AccountId = whitelisted_caller();
+
+        // Give some multiple of the existential deposit
+        let balance = existential_deposit.saturating_mul((2).into());
+        OneshotAccounts::<T>::insert(
+            caller.clone(),
+            Into::<<T::Currency as Currency<T::AccountId>>::Balance>::into(balance)
+        );
+
+        // Deposit into a normal account is more expensive than into a oneshot account
+        // so we create the recipient accounts with an existential deposits.
+        let recipient1: T::AccountId = account("recipient1", 0, SEED);
+        let recipient1_lookup: <T::Lookup as StaticLookup>::Source =
+            T::Lookup::unlookup(recipient1.clone());
+        let _ = T::Currency::make_free_balance_be(&recipient1, existential_deposit.into());
+        let recipient2: T::AccountId = account("recipient2", 1, SEED);
+        let recipient2_lookup: <T::Lookup as StaticLookup>::Source =
+            T::Lookup::unlookup(recipient2.clone());
+        let _ = T::Currency::make_free_balance_be(&recipient2, existential_deposit.into());
+    }: _(
+        RawOrigin::Signed(caller.clone()),
+        T::BlockNumber::zero(),
+        Account::<T>::Normal(recipient1_lookup),
+        Account::<T>::Normal(recipient2_lookup),
+        existential_deposit.into()
+    )
+    verify {
+        assert_eq!(OneshotAccounts::<T>::get(&caller), None);
+        assert_eq!(
+            Balances::<T>::free_balance(&recipient1),
+            existential_deposit.saturating_mul((2).into())
+        );
+        assert_eq!(
+            Balances::<T>::free_balance(&recipient2),
+            existential_deposit.saturating_mul((2).into())
+        );
+    }
+
+    impl_benchmark_test_suite!(
+        Pallet,
+        crate::mock::new_test_ext(),
+        crate::mock::Test
+    );
+}
diff --git a/pallets/oneshot-account/src/check_nonce.rs b/pallets/oneshot-account/src/check_nonce.rs
new file mode 100644
index 000000000..056f616f2
--- /dev/null
+++ b/pallets/oneshot-account/src/check_nonce.rs
@@ -0,0 +1,86 @@
+// 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::Config;
+
+use codec::{Decode, Encode};
+use frame_support::traits::IsSubType;
+use frame_support::weights::DispatchInfo;
+//use frame_system::Config;
+use scale_info::TypeInfo;
+use sp_runtime::{
+    traits::{DispatchInfoOf, Dispatchable, SignedExtension},
+    transaction_validity::{TransactionValidity, TransactionValidityError},
+};
+
+#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
+#[scale_info(skip_type_params(Runtime))]
+pub struct CheckNonce<T: Config>(pub frame_system::CheckNonce<T>);
+
+impl<T: Config> sp_std::fmt::Debug for CheckNonce<T> {
+    #[cfg(feature = "std")]
+    fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
+        write!(f, "CheckNonce({})", self.0 .0)
+    }
+
+    #[cfg(not(feature = "std"))]
+    fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
+        Ok(())
+    }
+}
+
+impl<T: Config + TypeInfo> SignedExtension for CheckNonce<T>
+where
+    T::Call: Dispatchable<Info = DispatchInfo> + IsSubType<crate::Call<T>>,
+{
+    type AccountId = <T as frame_system::Config>::AccountId;
+    type Call = <T as frame_system::Config>::Call;
+    type AdditionalSigned = ();
+    type Pre = ();
+    const IDENTIFIER: &'static str = "OneshotAccountCheckNonce";
+
+    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 let Some(
+            crate::Call::consume_oneshot_account { .. }
+            | crate::Call::consume_oneshot_account_with_remaining { .. },
+        ) = call.is_sub_type()
+        {
+            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/pallets/oneshot-account/src/lib.rs b/pallets/oneshot-account/src/lib.rs
new file mode 100644
index 000000000..8622faef3
--- /dev/null
+++ b/pallets/oneshot-account/src/lib.rs
@@ -0,0 +1,377 @@
+// 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/>.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+mod check_nonce;
+mod types;
+
+pub use check_nonce::CheckNonce;
+pub use pallet::*;
+pub use types::*;
+
+use frame_support::pallet_prelude::*;
+use frame_support::traits::{
+    Currency, ExistenceRequirement, Imbalance, IsSubType, WithdrawReasons,
+};
+use frame_system::pallet_prelude::*;
+use pallet_transaction_payment::OnChargeTransaction;
+use sp_runtime::traits::{DispatchInfoOf, PostDispatchInfoOf, Saturating, StaticLookup, Zero};
+use sp_std::convert::TryInto;
+
+#[frame_support::pallet]
+pub mod pallet {
+    use super::*;
+    use frame_support::traits::StorageVersion;
+
+    /// The current storage version.
+    const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
+
+    #[pallet::pallet]
+    #[pallet::generate_store(pub(super) trait Store)]
+    #[pallet::storage_version(STORAGE_VERSION)]
+    #[pallet::without_storage_info]
+    pub struct Pallet<T>(_);
+
+    // CONFIG //
+
+    #[pallet::config]
+    pub trait Config: frame_system::Config + pallet_transaction_payment::Config {
+        type Currency: Currency<Self::AccountId>;
+        type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
+        type InnerOnChargeTransaction: OnChargeTransaction<Self>;
+    }
+
+    // STORAGE //
+
+    #[pallet::storage]
+    #[pallet::getter(fn oneshot_account)]
+    pub type OneshotAccounts<T: Config> = StorageMap<
+        _,
+        Blake2_128Concat,
+        T::AccountId,
+        <T::Currency as Currency<T::AccountId>>::Balance,
+        OptionQuery,
+    >;
+
+    // EVENTS //
+
+    #[allow(clippy::type_complexity)]
+    #[pallet::event]
+    #[pallet::generate_deposit(pub(super) fn deposit_event)]
+    pub enum Event<T: Config> {
+        OneshotAccountCreated {
+            account: T::AccountId,
+            balance: <T::Currency as Currency<T::AccountId>>::Balance,
+            creator: T::AccountId,
+        },
+        OneshotAccountConsumed {
+            account: T::AccountId,
+            dest1: (
+                T::AccountId,
+                <T::Currency as Currency<T::AccountId>>::Balance,
+            ),
+            dest2: Option<(
+                T::AccountId,
+                <T::Currency as Currency<T::AccountId>>::Balance,
+            )>,
+        },
+        Withdraw {
+            account: T::AccountId,
+            balance: <T::Currency as Currency<T::AccountId>>::Balance,
+        },
+    }
+
+    // ERRORS //
+
+    #[pallet::error]
+    pub enum Error<T> {
+        /// Block height is in the future
+        BlockHeightInFuture,
+        /// Block height is too old
+        BlockHeightTooOld,
+        /// Destination account does not exist
+        DestAccountNotExist,
+        /// Destination account has balance less than existential deposit
+        ExistentialDeposit,
+        /// Source account has insufficient balance
+        InsufficientBalance,
+        /// Destination oneshot account already exists
+        OneshotAccountAlreadyCreated,
+        /// Source oneshot account does not exist
+        OneshotAccountNotExist,
+    }
+
+    // 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.
+        ///
+        /// Origin account is kept alive.
+        #[pallet::weight(500_000_000)]
+        pub fn create_oneshot_account(
+            origin: OriginFor<T>,
+            dest: <T::Lookup as StaticLookup>::Source,
+            #[pallet::compact] value: <T::Currency as Currency<T::AccountId>>::Balance,
+        ) -> DispatchResult {
+            let transactor = ensure_signed(origin)?;
+            let dest = T::Lookup::lookup(dest)?;
+
+            ensure!(
+                value >= <T::Currency as Currency<T::AccountId>>::minimum_balance(),
+                Error::<T>::ExistentialDeposit
+            );
+            ensure!(
+                OneshotAccounts::<T>::get(&dest).is_none(),
+                Error::<T>::OneshotAccountAlreadyCreated
+            );
+
+            <T::Currency as Currency<T::AccountId>>::withdraw(
+                &transactor,
+                value,
+                WithdrawReasons::TRANSFER,
+                ExistenceRequirement::KeepAlive,
+            )?;
+            OneshotAccounts::<T>::insert(&dest, value);
+            Self::deposit_event(Event::OneshotAccountCreated {
+                account: dest,
+                balance: value,
+                creator: transactor,
+            });
+
+            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>,
+            block_height: T::BlockNumber,
+            dest: Account<<T::Lookup as StaticLookup>::Source>,
+        ) -> DispatchResult {
+            let transactor = ensure_signed(origin)?;
+
+            let (dest, dest_is_oneshot) = match dest {
+                Account::Normal(account) => (account, false),
+                Account::Oneshot(account) => (account, true),
+            };
+            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::OneshotAccountCreated {
+                    account: dest.clone(),
+                    balance: value,
+                    creator: transactor.clone(),
+                });
+            } else {
+                <T::Currency as Currency<T::AccountId>>::deposit_into_existing(&dest, value)?;
+            }
+            OneshotAccounts::<T>::remove(&transactor);
+            Self::deposit_event(Event::OneshotAccountConsumed {
+                account: transactor,
+                dest1: (dest, value),
+                dest2: None,
+            });
+
+            Ok(())
+        }
+        /// Consume a oneshot account then transfer some amount to an account,
+        /// and the remaining amount to another 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.
+        /// - `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_with_remaining(
+            origin: OriginFor<T>,
+            block_height: T::BlockNumber,
+            dest: Account<<T::Lookup as StaticLookup>::Source>,
+            remaining_to: Account<<T::Lookup as StaticLookup>::Source>,
+            #[pallet::compact] balance: <T::Currency as Currency<T::AccountId>>::Balance,
+        ) -> DispatchResult {
+            let transactor = ensure_signed(origin)?;
+
+            let (dest1, dest1_is_oneshot) = match dest {
+                Account::Normal(account) => (account, false),
+                Account::Oneshot(account) => (account, true),
+            };
+            let dest1 = T::Lookup::lookup(dest1)?;
+            let (dest2, dest2_is_oneshot) = match remaining_to {
+                Account::Normal(account) => (account, false),
+                Account::Oneshot(account) => (account, true),
+            };
+            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 dest1_is_oneshot {
+                ensure!(
+                    OneshotAccounts::<T>::get(&dest1).is_none(),
+                    Error::<T>::OneshotAccountAlreadyCreated
+                );
+                ensure!(
+                    balance1 >= <T::Currency as Currency<T::AccountId>>::minimum_balance(),
+                    Error::<T>::ExistentialDeposit
+                );
+            } else {
+                ensure!(
+                    !<T::Currency as Currency<T::AccountId>>::free_balance(&dest1).is_zero(),
+                    Error::<T>::DestAccountNotExist
+                );
+            }
+            if dest2_is_oneshot {
+                ensure!(
+                    OneshotAccounts::<T>::get(&dest2).is_none(),
+                    Error::<T>::OneshotAccountAlreadyCreated
+                );
+                ensure!(
+                    balance2 >= <T::Currency as Currency<T::AccountId>>::minimum_balance(),
+                    Error::<T>::ExistentialDeposit
+                );
+                OneshotAccounts::<T>::insert(&dest2, balance2);
+                Self::deposit_event(Event::OneshotAccountCreated {
+                    account: dest2.clone(),
+                    balance: balance2,
+                    creator: transactor.clone(),
+                });
+            } else {
+                <T::Currency as Currency<T::AccountId>>::deposit_into_existing(&dest2, balance2)?;
+            }
+            if dest1_is_oneshot {
+                OneshotAccounts::<T>::insert(&dest1, balance1);
+                Self::deposit_event(Event::OneshotAccountCreated {
+                    account: dest1.clone(),
+                    balance: balance1,
+                    creator: transactor.clone(),
+                });
+            } else {
+                <T::Currency as Currency<T::AccountId>>::deposit_into_existing(&dest1, balance1)?;
+            }
+            OneshotAccounts::<T>::remove(&transactor);
+            Self::deposit_event(Event::OneshotAccountConsumed {
+                account: transactor,
+                dest1: (dest1, balance1),
+                dest2: Some((dest2, balance2)),
+            });
+
+            Ok(())
+        }
+    }
+}
+
+impl<T: Config> OnChargeTransaction<T> for Pallet<T>
+where
+    T::Call: IsSubType<Call<T>>,
+    T::InnerOnChargeTransaction: OnChargeTransaction<
+        T,
+        Balance = <T::Currency as Currency<T::AccountId>>::Balance,
+        LiquidityInfo = Option<<T::Currency as Currency<T::AccountId>>::NegativeImbalance>,
+    >,
+{
+    type Balance = <T::Currency as Currency<T::AccountId>>::Balance;
+    type LiquidityInfo = Option<<T::Currency as Currency<T::AccountId>>::NegativeImbalance>;
+    fn withdraw_fee(
+        who: &T::AccountId,
+        call: &T::Call,
+        dispatch_info: &DispatchInfoOf<T::Call>,
+        fee: Self::Balance,
+        tip: Self::Balance,
+    ) -> Result<Self::LiquidityInfo, TransactionValidityError> {
+        if let Some(
+            Call::consume_oneshot_account { .. }
+            | Call::consume_oneshot_account_with_remaining { .. },
+        ) = call.is_sub_type()
+        {
+            if fee.is_zero() {
+                return Ok(None);
+            }
+
+            if let Some(balance) = OneshotAccounts::<T>::get(&who) {
+                if balance >= fee {
+                    OneshotAccounts::<T>::insert(who, balance.saturating_sub(fee));
+                    Self::deposit_event(Event::Withdraw {
+                        account: who.clone(),
+                        balance: fee,
+                    });
+                    // TODO
+                    return Ok(Some(
+                        <T::Currency as Currency<T::AccountId>>::NegativeImbalance::zero(),
+                    ));
+                }
+            }
+            Err(TransactionValidityError::Invalid(
+                InvalidTransaction::Payment,
+            ))
+        } else {
+            T::InnerOnChargeTransaction::withdraw_fee(who, call, dispatch_info, fee, tip)
+        }
+    }
+    fn correct_and_deposit_fee(
+        who: &T::AccountId,
+        dispatch_info: &DispatchInfoOf<T::Call>,
+        post_info: &PostDispatchInfoOf<T::Call>,
+        corrected_fee: Self::Balance,
+        tip: Self::Balance,
+        already_withdrawn: Self::LiquidityInfo,
+    ) -> Result<(), TransactionValidityError> {
+        T::InnerOnChargeTransaction::correct_and_deposit_fee(
+            who,
+            dispatch_info,
+            post_info,
+            corrected_fee,
+            tip,
+            already_withdrawn,
+        )
+    }
+}
diff --git a/pallets/oneshot-account/src/mock.rs b/pallets/oneshot-account/src/mock.rs
new file mode 100644
index 000000000..07ea86c70
--- /dev/null
+++ b/pallets/oneshot-account/src/mock.rs
@@ -0,0 +1,123 @@
+// 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::{self as pallet_oneshot_account};
+use frame_support::{parameter_types, traits::Everything, weights::IdentityFee};
+use frame_system as system;
+use pallet_transaction_payment::CurrencyAdapter;
+use sp_core::H256;
+use sp_runtime::{
+    testing::Header,
+    traits::{BlakeTwo256, IdentityLookup},
+    BuildStorage,
+};
+
+type Balance = u64;
+type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
+type Block = frame_system::mocking::MockBlock<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>},
+        Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
+        TransactionPayment: pallet_transaction_payment::{Pallet, Storage},
+        OneshotAccount: pallet_oneshot_account::{Pallet, Call, Storage, Event<T>},
+    }
+);
+
+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 = u64;
+    type Lookup = IdentityLookup<Self::AccountId>;
+    type Header = Header;
+    type Event = Event;
+    type BlockHashCount = BlockHashCount;
+    type Version = ();
+    type PalletInfo = PalletInfo;
+    type AccountData = pallet_balances::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 = 10;
+    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];
+}
+impl pallet_transaction_payment::Config for Test {
+    type OnChargeTransaction = OneshotAccount;
+    type TransactionByteFee = ();
+    type OperationalFeeMultiplier = frame_support::traits::ConstU8<5>;
+    type WeightToFee = IdentityFee<u64>;
+    type FeeMultiplierUpdate = ();
+}
+impl pallet_oneshot_account::Config for Test {
+    type Currency = Balances;
+    type Event = Event;
+    type InnerOnChargeTransaction = CurrencyAdapter<Balances, HandleFees>;
+}
+
+pub struct HandleFees;
+type NegativeImbalance = <Balances as frame_support::traits::Currency<u64>>::NegativeImbalance;
+impl frame_support::traits::OnUnbalanced<NegativeImbalance> for HandleFees {
+    fn on_nonzero_unbalanced(_amount: NegativeImbalance) {}
+}
+
+// Build genesis storage according to the mock runtime.
+#[allow(dead_code)]
+pub fn new_test_ext() -> sp_io::TestExternalities {
+    GenesisConfig {
+        system: SystemConfig::default(),
+        balances: BalancesConfig::default(),
+    }
+    .build_storage()
+    .unwrap()
+    .into()
+}
diff --git a/pallets/oneshot-account/src/types.rs b/pallets/oneshot-account/src/types.rs
new file mode 100644
index 000000000..753448465
--- /dev/null
+++ b/pallets/oneshot-account/src/types.rs
@@ -0,0 +1,24 @@
+// 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 codec::{Decode, Encode};
+use frame_support::pallet_prelude::*;
+
+#[derive(Clone, Decode, Encode, PartialEq, RuntimeDebug, TypeInfo)]
+pub enum Account<AccountId> {
+    Normal(AccountId),
+    Oneshot(AccountId),
+}
diff --git a/pallets/oneshot-account/src/weights.rs b/pallets/oneshot-account/src/weights.rs
new file mode 100644
index 000000000..ff5e2930e
--- /dev/null
+++ b/pallets/oneshot-account/src/weights.rs
@@ -0,0 +1,52 @@
+// Copyright 2021-2022 Axiom-Team
+//
+// This file is part of Duniter-v2S.
+//
+// Duniter-v2S 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.
+//
+// Duniter-v2S 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 Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
+
+#![allow(clippy::unnecessary_cast)]
+
+use frame_support::weights::{constants::RocksDbWeight, Weight};
+
+/// Weight functions needed for pallet_universal_dividend.
+pub trait WeightInfo {
+    fn create_oneshot_account() -> Weight;
+    fn consume_oneshot_account() -> Weight;
+    fn consume_oneshot_account_two_dests() -> Weight;
+}
+
+// Insecure weights implementation, use it for tests only!
+impl WeightInfo for () {
+    // Storage: OneshotAccount OneshotAccounts (r:1 w:1)
+    fn create_oneshot_account() -> Weight {
+        (45_690_000 as Weight)
+            .saturating_add(RocksDbWeight::get().reads(1 as Weight))
+            .saturating_add(RocksDbWeight::get().writes(1 as Weight))
+    }
+    // Storage: OneshotAccount OneshotAccounts (r:1 w:1)
+    // Storage: System BlockHash (r:1 w:0)
+    // Storage: System Account (r:1 w:1)
+    fn consume_oneshot_account() -> Weight {
+        (50_060_000 as Weight)
+            .saturating_add(RocksDbWeight::get().reads(3 as Weight))
+            .saturating_add(RocksDbWeight::get().writes(2 as Weight))
+    }
+    // Storage: OneshotAccount OneshotAccounts (r:1 w:1)
+    // Storage: System BlockHash (r:1 w:0)
+    // Storage: System Account (r:2 w:2)
+    fn consume_oneshot_account_with_remaining() -> Weight {
+        (69_346_000 as Weight)
+            .saturating_add(RocksDbWeight::get().reads(4 as Weight))
+            .saturating_add(RocksDbWeight::get().writes(3 as Weight))
+    }
+}
diff --git a/resources/metadata.scale b/resources/metadata.scale
index 53eb7112a2ef328bbe23045d60c5ef402470ae39..6550da100926ebbbe9df51ce1996f7a69c682a6b 100644
GIT binary patch
delta 10278
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

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

diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml
index 523f68404..660fa3223 100644
--- a/runtime/common/Cargo.toml
+++ b/runtime/common/Cargo.toml
@@ -39,6 +39,7 @@ std = [
     'pallet-identity/std',
     'pallet-membership/std',
     'pallet-multisig/std',
+    'pallet-oneshot-account/std',
     'pallet-provide-randomness/std',
     'pallet-proxy/std',
     'pallet-scheduler/std',
@@ -68,6 +69,7 @@ pallet-duniter-account = { path = '../../pallets/duniter-account', default-featu
 pallet-duniter-wot = { path = '../../pallets/duniter-wot', default-features = false }
 pallet-identity = { path = '../../pallets/identity', default-features = false }
 pallet-membership = { path = '../../pallets/membership', default-features = false }
+pallet-oneshot-account = { path = '../../pallets/oneshot-account', default-features = false }
 pallet-provide-randomness = { path = '../../pallets/provide-randomness', default-features = false }
 pallet-upgrade-origin = { path = '../../pallets/upgrade-origin', default-features = false }
 pallet-universal-dividend = { path = '../../pallets/universal-dividend', default-features = false }
diff --git a/runtime/common/src/pallets_config.rs b/runtime/common/src/pallets_config.rs
index f9e7531c7..10f5feb5c 100644
--- a/runtime/common/src/pallets_config.rs
+++ b/runtime/common/src/pallets_config.rs
@@ -173,13 +173,19 @@ macro_rules! pallets_config {
                 }
             }
         }
+        pub struct OnChargeTransaction;
         impl pallet_transaction_payment::Config for Runtime {
-            type OnChargeTransaction = CurrencyAdapter<Balances, HandleFees>;
+            type OnChargeTransaction = OneshotAccount;
             type OperationalFeeMultiplier = frame_support::traits::ConstU8<5>;
             type WeightToFee = common_runtime::fees::WeightToFeeImpl<Balance>;
             type LengthToFee = common_runtime::fees::LengthToFeeImpl<Balance>;
             type FeeMultiplierUpdate = ();
         }
+        impl pallet_oneshot_account::Config for Runtime {
+            type Currency = Balances;
+            type Event = Event;
+            type InnerOnChargeTransaction = CurrencyAdapter<Balances, HandleFees>;
+        }
 
         // CONSENSUS  //
 
diff --git a/runtime/common/src/weights/pallet_oneshot_account.rs b/runtime/common/src/weights/pallet_oneshot_account.rs
new file mode 100644
index 000000000..c10f189ad
--- /dev/null
+++ b/runtime/common/src/weights/pallet_oneshot_account.rs
@@ -0,0 +1,70 @@
+// Copyright 2021-2022 Axiom-Team
+//
+// This file is part of Duniter-v2S.
+//
+// Duniter-v2S 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.
+//
+// Duniter-v2S 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 Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
+
+//! Autogenerated weights for `pallet_oneshot_account`
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
+//! DATE: 2022-06-28, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
+
+// Executed Command:
+// ./target/release/duniter
+// benchmark
+// --chain=dev
+// --steps=50
+// --repeat=20
+// --pallet=pallet_oneshot-account
+// --extrinsic=*
+// --execution=wasm
+// --wasm-execution=compiled
+// --heap-pages=4096
+// --header=./file_header.txt
+// --output=.
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(clippy::unnecessary_cast)]
+
+use frame_support::{traits::Get, weights::Weight};
+use sp_std::marker::PhantomData;
+
+/// Weight functions for `pallet_oneshot_account`.
+pub struct WeightInfo<T>(PhantomData<T>);
+impl<T: frame_system::Config> pallet_oneshot_account::WeightInfo for WeightInfo<T> {
+	// Storage: OneshotAccount OneshotAccounts (r:1 w:1)
+	fn create_oneshot_account() -> Weight {
+		(45_690_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+	}
+	// Storage: OneshotAccount OneshotAccounts (r:1 w:1)
+	// Storage: System BlockHash (r:1 w:0)
+	// Storage: System Account (r:1 w:1)
+	fn consume_oneshot_account() -> Weight {
+		(50_060_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(3 as Weight))
+			.saturating_add(T::DbWeight::get().writes(2 as Weight))
+	}
+	// Storage: OneshotAccount OneshotAccounts (r:1 w:1)
+	// Storage: System BlockHash (r:1 w:0)
+	// Storage: System Account (r:2 w:2)
+	fn consume_oneshot_account_with_remaining() -> Weight {
+		(69_346_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(4 as Weight))
+			.saturating_add(T::DbWeight::get().writes(3 as Weight))
+	}
+}
diff --git a/runtime/g1/Cargo.toml b/runtime/g1/Cargo.toml
index ee9d7049b..2ce176950 100644
--- a/runtime/g1/Cargo.toml
+++ b/runtime/g1/Cargo.toml
@@ -52,6 +52,7 @@ std = [
     'pallet-provide-randomness/std',
     'pallet-im-online/std',
     'pallet-multisig/std',
+    'pallet-oneshot-account/std',
     'pallet-preimage/std',
     'pallet-proxy/std',
     'pallet-session/std',
@@ -117,6 +118,7 @@ pallet-duniter-account = { path = '../../pallets/duniter-account', default-featu
 pallet-duniter-wot = { path = '../../pallets/duniter-wot', default-features = false }
 pallet-identity = { path = '../../pallets/identity', default-features = false }
 pallet-membership = { path = '../../pallets/membership', default-features = false }
+pallet-oneshot-account = { path = '../../pallets/oneshot-account', default-features = false }
 pallet-provide-randomness = { path = '../../pallets/provide-randomness', default-features = false }
 pallet-universal-dividend = { path = '../../pallets/universal-dividend', default-features = false }
 pallet-upgrade-origin = { path = '../../pallets/upgrade-origin', default-features = false }
diff --git a/runtime/g1/src/lib.rs b/runtime/g1/src/lib.rs
index ec455d7fa..a944041c7 100644
--- a/runtime/g1/src/lib.rs
+++ b/runtime/g1/src/lib.rs
@@ -111,7 +111,7 @@ pub type SignedExtra = (
     frame_system::CheckTxVersion<Runtime>,
     frame_system::CheckGenesis<Runtime>,
     frame_system::CheckEra<Runtime>,
-    frame_system::CheckNonce<Runtime>,
+    pallet_oneshot_account::CheckNonce<Runtime>,
     frame_system::CheckWeight<Runtime>,
     pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
 );
@@ -215,6 +215,7 @@ construct_runtime!(
         // Money management
         Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>} = 6,
         TransactionPayment: pallet_transaction_payment::{Pallet, Storage} = 32,
+        OneshotAccount: pallet_oneshot_account::{Pallet, Call, Storage, Event<T>} = 7,
 
         // Consensus support.
         AuthorityMembers: pallet_authority_members::{Pallet, Call, Storage, Config<T>, Event<T>} = 10,
diff --git a/runtime/gdev/Cargo.toml b/runtime/gdev/Cargo.toml
index bfdc5a59a..c28e43875 100644
--- a/runtime/gdev/Cargo.toml
+++ b/runtime/gdev/Cargo.toml
@@ -68,6 +68,7 @@ std = [
     'pallet-grandpa/std',
     'pallet-identity/std',
     'pallet-membership/std',
+    'pallet-oneshot-account/std',
     'pallet-provide-randomness/std',
     'pallet-im-online/std',
     'pallet-multisig/std',
@@ -139,6 +140,7 @@ pallet-duniter-account = { path = '../../pallets/duniter-account', default-featu
 pallet-duniter-wot = { path = '../../pallets/duniter-wot', default-features = false }
 pallet-identity = { path = '../../pallets/identity', default-features = false }
 pallet-membership = { path = '../../pallets/membership', default-features = false }
+pallet-oneshot-account = { path = '../../pallets/oneshot-account', default-features = false }
 pallet-provide-randomness = { path = '../../pallets/provide-randomness', default-features = false }
 pallet-universal-dividend = { path = '../../pallets/universal-dividend', default-features = false }
 pallet-upgrade-origin = { path = '../../pallets/upgrade-origin', default-features = false }
diff --git a/runtime/gdev/src/lib.rs b/runtime/gdev/src/lib.rs
index 666469fa6..9f6f5ddcd 100644
--- a/runtime/gdev/src/lib.rs
+++ b/runtime/gdev/src/lib.rs
@@ -115,9 +115,9 @@ pub type SignedExtra = (
     frame_system::CheckTxVersion<Runtime>,
     frame_system::CheckGenesis<Runtime>,
     frame_system::CheckEra<Runtime>,
-    frame_system::CheckNonce<Runtime>,
+    pallet_oneshot_account::CheckNonce<Runtime>,
     frame_system::CheckWeight<Runtime>,
-    pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
+    //pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
 );
 
 /// Executive: handles dispatch to the various modules.
@@ -284,6 +284,7 @@ construct_runtime!(
         // Money management
         Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>} = 6,
         TransactionPayment: pallet_transaction_payment::{Pallet, Storage} = 32,
+        OneshotAccount: pallet_oneshot_account::{Pallet, Call, Storage, Event<T>} = 7,
 
         // Consensus support
         AuthorityMembers: pallet_authority_members::{Pallet, Call, Storage, Config<T>, Event<T>} = 10,
diff --git a/runtime/gdev/tests/integration_tests.rs b/runtime/gdev/tests/integration_tests.rs
index 9a1ca4833..c3c43dc8b 100644
--- a/runtime/gdev/tests/integration_tests.rs
+++ b/runtime/gdev/tests/integration_tests.rs
@@ -430,3 +430,81 @@ fn test_create_new_idty_without_founds() {
             );
         });
 }
+
+#[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!(OneshotAccount::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!(OneshotAccount::consume_oneshot_account_with_remaining(
+                frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(),
+                0,
+                pallet_oneshot_account::Account::Oneshot(MultiAddress::Id(
+                    AccountKeyring::Ferdie.to_account_id()
+                )),
+                pallet_oneshot_account::Account::Normal(MultiAddress::Id(
+                    AccountKeyring::Alice.to_account_id()
+                )),
+                300
+            ));
+            assert_eq!(
+                Balances::free_balance(AccountKeyring::Alice.to_account_id()),
+                700
+            );
+            assert_noop!(
+                OneshotAccount::consume_oneshot_account(
+                    frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(),
+                    0,
+                    pallet_oneshot_account::Account::Oneshot(MultiAddress::Id(
+                        AccountKeyring::Ferdie.to_account_id()
+                    )),
+                ),
+                pallet_oneshot_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!(OneshotAccount::consume_oneshot_account(
+                frame_system::RawOrigin::Signed(AccountKeyring::Ferdie.to_account_id()).into(),
+                0,
+                pallet_oneshot_account::Account::Normal(MultiAddress::Id(
+                    AccountKeyring::Alice.to_account_id()
+                )),
+            ));
+            assert_eq!(
+                Balances::free_balance(AccountKeyring::Alice.to_account_id()),
+                1000
+            );
+            assert_noop!(
+                OneshotAccount::consume_oneshot_account(
+                    frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(),
+                    0,
+                    pallet_oneshot_account::Account::Normal(MultiAddress::Id(
+                        AccountKeyring::Alice.to_account_id()
+                    )),
+                ),
+                pallet_oneshot_account::Error::<Runtime>::OneshotAccountNotExist
+            );
+        });
+}
diff --git a/runtime/gtest/Cargo.toml b/runtime/gtest/Cargo.toml
index 30be9e5eb..4d40f3b25 100644
--- a/runtime/gtest/Cargo.toml
+++ b/runtime/gtest/Cargo.toml
@@ -49,6 +49,7 @@ std = [
     'pallet-grandpa/std',
     'pallet-identity/std',
     'pallet-membership/std',
+    'pallet-oneshot-account/std',
     'pallet-provide-randomness/std',
     'pallet-im-online/std',
     'pallet-multisig/std',
@@ -117,6 +118,7 @@ pallet-duniter-account = { path = '../../pallets/duniter-account', default-featu
 pallet-duniter-wot = { path = '../../pallets/duniter-wot', default-features = false }
 pallet-identity = { path = '../../pallets/identity', default-features = false }
 pallet-membership = { path = '../../pallets/membership', default-features = false }
+pallet-oneshot-account = { path = '../../pallets/oneshot-account', default-features = false }
 pallet-provide-randomness = { path = '../../pallets/provide-randomness', default-features = false }
 pallet-universal-dividend = { path = '../../pallets/universal-dividend', default-features = false }
 pallet-upgrade-origin = { path = '../../pallets/upgrade-origin', default-features = false }
diff --git a/runtime/gtest/src/lib.rs b/runtime/gtest/src/lib.rs
index fd21f986d..efb4fb2f4 100644
--- a/runtime/gtest/src/lib.rs
+++ b/runtime/gtest/src/lib.rs
@@ -216,6 +216,7 @@ construct_runtime!(
         // Money management
         Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>} = 6,
         TransactionPayment: pallet_transaction_payment::{Pallet, Storage} = 32,
+        OneshotAccount: pallet_oneshot_account::{Pallet, Call, Storage, Event<T>} = 7,
 
         // Consensus support.
         AuthorityMembers: pallet_authority_members::{Pallet, Call, Storage, Config<T>, Event<T>} = 10,
-- 
GitLab