Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 1000i100-test
  • 105_gitlab_container_registry
  • cgeek/issue-297-cpu
  • ci_cache
  • debug/podman
  • elois-compose-metrics
  • elois-duniter-storage
  • elois-smoldot
  • feature/dc-dump
  • feature/distance-rule
  • feature/show_milestone
  • fix-252
  • fix_picked_up_file_in_runtime_release
  • gdev-800-tests
  • hugo-release/runtime-701
  • hugo-tmp-dockerfile-cache
  • hugo/195-doc
  • hugo/195-graphql-schema
  • hugo/distance-precompute
  • hugo/endpoint-gossip
  • hugo/tmp-0.9.1
  • master
  • network/gdev-800
  • network/gdev-802
  • network/gdev-803
  • network/gdev-900
  • network/gtest-1000
  • pini-check-password
  • release/client-800.2
  • release/hugo-chainspec-gdev5
  • release/poka-chainspec-gdev5
  • release/poka-chainspec-gdev5-pini-docker
  • release/runtime-100
  • release/runtime-200
  • release/runtime-300
  • release/runtime-400
  • release/runtime-401
  • release/runtime-500
  • release/runtime-600
  • release/runtime-700
  • release/runtime-701
  • release/runtime-800
  • runtime/gtest-1000
  • tests/distance-with-oracle
  • tuxmain/anonymous-tx
  • tuxmain/benchmark-distance
  • tuxmain/fix-change-owner-key
  • update-docker-compose-rpc-squid-names
  • upgradable-multisig
  • gdev-800
  • gdev-800-0.8.0
  • gdev-802
  • gdev-803
  • gdev-900-0.10.0
  • gdev-900-0.10.1
  • gdev-900-0.9.0
  • gdev-900-0.9.1
  • gdev-900-0.9.2
  • gtest-1000
  • gtest-1000-0.11.0
  • gtest-1000-0.11.1
  • runtime-100
  • runtime-101
  • runtime-102
  • runtime-103
  • runtime-104
  • runtime-105
  • runtime-200
  • runtime-201
  • runtime-300
  • runtime-301
  • runtime-302
  • runtime-303
  • runtime-400
  • runtime-401
  • runtime-500
  • runtime-600
  • runtime-700
  • runtime-701
  • runtime-800
  • runtime-800-backup
  • runtime-800-bis
  • runtime-801
  • v0.1.0
  • v0.2.0
  • v0.3.0
  • v0.4.0
  • v0.4.1
88 results

Target

Select target project
  • nodes/rust/duniter-v2s
  • llaq/lc-core-substrate
  • pini-gh/duniter-v2s
  • vincentux/duniter-v2s
  • mildred/duniter-v2s
  • d0p1/duniter-v2s
  • bgallois/duniter-v2s
  • Nicolas80/duniter-v2s
8 results
Select Git revision
  • archive_upgrade_polkadot_v0.9.42
  • david-wot-scenarios-cucumber
  • distance
  • elois-ci-binary-release
  • elois-compose-metrics
  • elois-duniter-storage
  • elois-fix-85
  • elois-fix-idty-post-genesis
  • elois-fix-sufficients-change-owner-key
  • elois-opti-cert
  • elois-remove-renewable-period
  • elois-revoc-with-old-key
  • elois-rework-certs
  • elois-smish-members-cant-change-or-rem-idty
  • elois-smoldot
  • elois-substrate-v0.9.23
  • elois-technical-commitee
  • hugo-gtest
  • hugo-remove-duniter-account
  • hugo-rework-genesis
  • hugo-tmp
  • jrx/workspace_tomls
  • master
  • no-bootnodes
  • pallet-benchmark
  • release/poka-chainspec-gdev5
  • release/poka-chainspec-gdev5-pini-docker
  • release/runtime-100
  • release/runtime-200
  • release/runtime-300
  • release/runtime-400
  • test-gen-new-owner-key-msg
  • ts-types
  • ud-time-64
  • upgrade_polkadot_v0.9.42
  • runtime-100
  • runtime-101
  • runtime-102
  • runtime-103
  • runtime-104
  • runtime-105
  • runtime-200
  • runtime-201
  • runtime-300
  • runtime-301
  • runtime-302
  • runtime-303
  • runtime-400
  • v0.1.0
  • v0.2.0
  • v0.3.0
  • v0.4.0
52 results
Show changes
Showing
with 1982 additions and 164 deletions
......@@ -15,32 +15,26 @@
// along with Duniter-v2S. 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_support::{derive_impl, parameter_types, traits::Everything, weights::IdentityFee};
use frame_system as system;
use pallet_transaction_payment::CurrencyAdapter;
use sp_core::H256;
use pallet_transaction_payment::FungibleAdapter;
use sp_core::{ConstU32, H256};
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup},
BuildStorage,
};
use sp_std::convert::{TryFrom, TryInto};
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,
pub enum Test
{
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, Event<T>},
OneshotAccount: pallet_oneshot_account::{Pallet, Call, Storage, Event<T>},
System: frame_system,
Balances: pallet_balances,
TransactionPayment: pallet_transaction_payment,
OneshotAccount: pallet_oneshot_account,
}
);
......@@ -49,31 +43,23 @@ parameter_types! {
pub const SS58Prefix: u8 = 42;
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl system::Config for Test {
type AccountData = pallet_balances::AccountData<Balance>;
type AccountId = u64;
type BaseCallFilter = Everything;
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type Index = u64;
type BlockNumber = u64;
type Block = Block;
type BlockHashCount = BlockHashCount;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = BlockHashCount;
type Version = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
type Nonce = u64;
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<Balance>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type RuntimeCall = RuntimeCall;
type RuntimeEvent = RuntimeEvent;
type RuntimeOrigin = RuntimeOrigin;
type SS58Prefix = SS58Prefix;
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
parameter_types! {
......@@ -82,42 +68,44 @@ parameter_types! {
}
impl pallet_balances::Config for Test {
type AccountStore = System;
type Balance = Balance;
type DoneSlashHandler = ();
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = pallet_balances::weights::SubstrateWeight<Test>;
type FreezeIdentifier = ();
type MaxFreezes = ConstU32<0>;
type MaxLocks = MaxLocks;
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type RuntimeEvent = RuntimeEvent;
type RuntimeFreezeReason = ();
type RuntimeHoldReason = ();
type WeightInfo = pallet_balances::weights::SubstrateWeight<Test>;
}
impl pallet_transaction_payment::Config for Test {
type RuntimeEvent = RuntimeEvent;
type FeeMultiplierUpdate = ();
type LengthToFee = IdentityFee<u64>;
type OnChargeTransaction = OneshotAccount;
type OperationalFeeMultiplier = frame_support::traits::ConstU8<5>;
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
type WeightToFee = IdentityFee<u64>;
type LengthToFee = IdentityFee<u64>;
type FeeMultiplierUpdate = ();
}
impl pallet_oneshot_account::Config for Test {
type Currency = Balances;
type InnerOnChargeTransaction = CurrencyAdapter<Balances, HandleFees>;
type InnerOnChargeTransaction = FungibleAdapter<Balances, ()>;
type RuntimeEvent = RuntimeEvent;
}
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) {}
type WeightInfo = ();
}
// Build genesis storage according to the mock runtime.
#[allow(dead_code)]
pub fn new_test_ext() -> sp_io::TestExternalities {
GenesisConfig {
RuntimeGenesisConfig {
system: SystemConfig::default(),
balances: BalancesConfig::default(),
balances: BalancesConfig::default(), // FIXME (explicit absence of oneshot account in genesis)
transaction_payment: TransactionPaymentConfig::default(),
}
.build_storage()
.unwrap()
......
......@@ -14,11 +14,14 @@
// 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/>.
use codec::{Decode, Encode};
use codec::{Decode, DecodeWithMemTracking, Encode};
use frame_support::pallet_prelude::*;
#[derive(Clone, Decode, Encode, PartialEq, RuntimeDebug, TypeInfo)]
/// The type of account.
#[derive(Clone, Decode, Encode, DecodeWithMemTracking, PartialEq, RuntimeDebug, TypeInfo)]
pub enum Account<AccountId> {
/// Normal account type.
Normal(AccountId),
/// Oneshot account type.
Oneshot(AccountId),
}
......@@ -29,23 +29,25 @@ pub trait WeightInfo {
impl WeightInfo for () {
// Storage: OneshotAccount OneshotAccounts (r:1 w:1)
fn create_oneshot_account() -> Weight {
(Weight::from_ref_time(45_690_000))
(Weight::from_parts(45_690_000, 0))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
// 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 {
(Weight::from_ref_time(50_060_000))
(Weight::from_parts(50_060_000, 0))
.saturating_add(RocksDbWeight::get().reads(3))
.saturating_add(RocksDbWeight::get().writes(2))
}
// 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 {
(Weight::from_ref_time(69_346_000))
(Weight::from_parts(69_346_000, 0))
.saturating_add(RocksDbWeight::get().reads(4))
.saturating_add(RocksDbWeight::get().writes(3))
}
......
[package]
authors = ['librelois <c@elo.tf>']
description = 'FRAME pallet to provide randomness to users.'
edition = "2021"
homepage = 'https://duniter.org'
license = 'AGPL-3.0'
name = 'pallet-provide-randomness'
repository = 'https://git.duniter.org/nodes/rust/duniter-v2s'
version = '3.0.0'
authors.workspace = true
description = "duniter pallet to provide randomness to users"
edition.workspace = true
homepage.workspace = true
license.workspace = true
name = "pallet-provide-randomness"
repository.workspace = true
version.workspace = true
[features]
default = ['std']
runtime-benchmarks = ['frame-benchmarking']
default = ["std"]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-balances/try-runtime",
"sp-runtime/try-runtime",
]
std = [
'codec/std',
'frame-support/std',
'frame-system/std',
'frame-benchmarking/std',
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"pallet-balances/std",
"scale-info/std",
"sp-core/std",
"sp-io/std",
"sp-std/std",
"sp-runtime/std",
]
try-runtime = ['frame-support/try-runtime']
[dependencies]
# substrate
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
[dependencies.codec]
default-features = false
features = ['derive']
package = 'parity-scale-codec'
version = "3.1.5"
[dependencies.frame-benchmarking]
default-features = false
git = 'https://github.com/duniter/substrate'
optional = true
branch = 'duniter-substrate-v0.9.32'
[dependencies.frame-support]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.frame-system]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.sp-core]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.sp-io]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.sp-std]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.sp-runtime]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
### DOC ###
scale-info = { workspace = true, features = ["derive"] }
codec = { workspace = true, features = ["derive"] }
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
sp-runtime = { workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
pallet-balances = { workspace = true }
[package.metadata.docs.rs]
targets = ['x86_64-unknown-linux-gnu']
default-features = false
targets = ["x86_64-unknown-linux-gnu"]
# Duniter provide randomness pallet
TODO
\ No newline at end of file
// Copyright 2021-2023 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")]
#![allow(clippy::multiple_bound_locations)]
use super::*;
use frame_benchmarking::{v2::*, whitelisted_caller};
use frame_support::{
ensure,
pallet_prelude::IsType,
sp_runtime::{traits::One, Saturating},
traits::{fungible::Mutate, Get, OnInitialize},
};
use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin};
use sp_core::H256;
use crate::Pallet;
#[benchmarks(
where
T: pallet_balances::Config,
T::Balance: From<u64>,
BalanceOf<T>: IsType<T::Balance>,
BlockNumberFor<T>: From<u32>,
)]
mod benchmarks {
use super::*;
fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
frame_system::Pallet::<T>::assert_has_event(generic_event.into());
}
fn add_requests_next_block<T: Config>(i: u32) -> Result<(), &'static str> {
for _ in 0..i {
let salt: H256 = H256([0; 32]);
let request_id = RequestIdProvider::<T>::mutate(|next_request_id| {
core::mem::replace(next_request_id, next_request_id.saturating_add(1))
});
RequestsIds::<T>::insert(request_id, ());
RequestsReadyAtNextBlock::<T>::append(Request { request_id, salt });
}
Ok(())
}
fn add_requests_next_epoch<T: Config>(i: u32) -> Result<(), &'static str> {
for _ in 0..i {
let salt: H256 = H256([0; 32]);
let request_id = RequestIdProvider::<T>::mutate(|next_request_id| {
core::mem::replace(next_request_id, next_request_id.saturating_add(1))
});
RequestsIds::<T>::insert(request_id, ());
RequestsReadyAtEpoch::<T>::append(
T::GetCurrentEpochIndex::get(),
Request { request_id, salt },
);
}
Ok(())
}
#[benchmark]
fn request() {
// Get account
let caller: T::AccountId = whitelisted_caller();
// Provide deposit
let existential_deposit = T::ExistentialDeposit::get();
let balance = existential_deposit.saturating_mul((200).into());
let _ = T::Currency::set_balance(&caller, balance.into());
// Set randomness parameters
let random = RandomnessType::RandomnessFromOneEpochAgo;
let salt: H256 = H256([1; 32]);
#[extrinsic_call]
_(RawOrigin::Signed(caller), random, salt);
let request_id = RequestIdProvider::<T>::get() - 1;
assert_has_event::<T>(
Event::RequestedRandomness {
request_id,
salt,
r#type: random,
}
.into(),
);
}
#[benchmark]
fn on_initialize(i: Linear<1, { T::MaxRequests::get() }>) -> Result<(), BenchmarkError> {
add_requests_next_block::<T>(i)?;
ensure!(RequestsIds::<T>::count() == i, "List not filled properly.");
ensure!(
RequestsReadyAtNextBlock::<T>::get().len() == i as usize,
"List not filled properly."
);
let next_epoch_hook_in = NexEpochHookIn::<T>::mutate(|next_in| {
core::mem::replace(next_in, next_in.saturating_sub(1))
});
ensure!(next_epoch_hook_in != 1, "Will be next epoch.");
#[block]
{
Pallet::<T>::on_initialize(BlockNumberFor::<T>::one());
}
ensure!(RequestsIds::<T>::count() == 0, "List not processed.");
ensure!(
RequestsReadyAtNextBlock::<T>::get().is_empty(),
"List not processed."
);
Ok(())
}
#[benchmark]
fn on_initialize_epoch(i: Linear<1, { T::MaxRequests::get() }>) -> Result<(), BenchmarkError> {
add_requests_next_epoch::<T>(i)?;
ensure!(
RequestsReadyAtNextBlock::<T>::get().is_empty(),
"List not filled properly."
);
ensure!(RequestsIds::<T>::count() == i, "List not filled properly.");
ensure!(
RequestsReadyAtEpoch::<T>::get(T::GetCurrentEpochIndex::get()).len() == i as usize,
"List not filled properly."
);
NexEpochHookIn::<T>::mutate(|next_in| core::mem::replace(next_in, 1));
#[block]
{
Pallet::<T>::on_initialize(1.into());
}
ensure!(RequestsIds::<T>::count() == 0, "List not processed.");
ensure!(
RequestsReadyAtEpoch::<T>::get(T::GetCurrentEpochIndex::get()).is_empty(),
"List not processed properly."
);
Ok(())
}
}
// Copyright 2021 Axiom-Team
// Copyright 2021-2023 Axiom-Team
//
// This file is part of Duniter-v2S.
//
......@@ -14,17 +14,33 @@
// 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/>.
//! # Provides Randomness Pallet
//!
//! The Provides Randomness Pallet facilitates the generation of randomness within the Duniter blockchain.
//!
//! This pallet manages randomness requests and emits events upon requesting and fulfilling randomness.
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::boxed_local)]
mod benchmarking;
mod types;
pub mod weights;
use frame_support::pallet_prelude::Weight;
use frame_support::{
pallet_prelude::Weight,
traits::{
fungible::{self, Balanced, Credit},
tokens::{Fortitude, Precision, Preservation},
},
};
use scale_info::prelude::vec::Vec;
use sp_core::H256;
use sp_std::prelude::*;
pub use pallet::*;
pub use types::*;
pub use weights::WeightInfo;
pub type RequestId = u64;
......@@ -37,27 +53,24 @@ impl OnFilledRandomness for () {
}
}
#[allow(unreachable_patterns)]
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_support::traits::{
Currency, ExistenceRequirement, OnUnbalanced, Randomness, StorageVersion, WithdrawReasons,
use frame_support::{
pallet_prelude::*,
traits::{OnUnbalanced, Randomness, StorageVersion},
};
use frame_system::pallet_prelude::*;
use sp_core::H256;
pub type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
pub type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
<T as frame_system::Config>::AccountId,
>>::NegativeImbalance;
type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
pub type BalanceOf<T> = <<T as Config>::Currency as fungible::Inspect<AccountIdOf<T>>>::Balance;
/// 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>(_);
......@@ -65,45 +78,61 @@ pub mod pallet {
/// Configuration trait.
#[pallet::config]
pub trait Config: frame_system::Config<Hash = H256> {
// The currency
type Currency: Currency<Self::AccountId>;
/// Get the current epoch index
// The currency type.
type Currency: fungible::Balanced<Self::AccountId> + fungible::Mutate<Self::AccountId>;
/// Type providing the current epoch index.
type GetCurrentEpochIndex: Get<u64>;
/// Maximum number of not yet filled requests
/// Maximum number of not yet filled requests.
#[pallet::constant]
type MaxRequests: Get<u32>;
/// The price of a request
/// The price of a request.
#[pallet::constant]
type RequestPrice: Get<BalanceOf<Self>>;
/// On filled randomness
/// Handler called when randomness is filled.
type OnFilledRandomness: OnFilledRandomness;
/// Handler for the unbalanced reduction when the requestor pays fees.
type OnUnbalanced: OnUnbalanced<NegativeImbalanceOf<Self>>;
/// A safe source of randomness from the parent block
type ParentBlockRandomness: Randomness<Option<H256>, Self::BlockNumber>;
/// A safe source of randomness from one epoch ago
type RandomnessFromOneEpochAgo: Randomness<H256, Self::BlockNumber>;
/// Handler for unbalanced reduction when the requestor pays fees.
type OnUnbalanced: OnUnbalanced<Credit<Self::AccountId, Self::Currency>>;
/// A safe source of randomness from the parent block.
type ParentBlockRandomness: Randomness<Option<H256>, BlockNumberFor<Self>>;
/// A safe source of randomness from one epoch ago.
type RandomnessFromOneEpochAgo: Randomness<H256, BlockNumberFor<Self>>;
/// The overarching event type.
type RuntimeEvent: From<Event> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Type representing the weight of this pallet.
type WeightInfo: WeightInfo;
}
// STORAGE //
/// The number of blocks before the next epoch.
#[pallet::storage]
pub(super) type NexEpochHookIn<T: Config> = StorageValue<_, u8, ValueQuery>;
/// The request ID.
#[pallet::storage]
pub(super) type RequestIdProvider<T: Config> = StorageValue<_, RequestId, ValueQuery>;
/// The requests that will be fulfilled at the next block.
#[pallet::storage]
#[pallet::getter(fn requests_ready_at_next_block)]
pub type RequestsReadyAtNextBlock<T: Config> = StorageValue<_, Vec<Request>, ValueQuery>;
/// The requests that will be fulfilled at the next epoch.
#[pallet::storage]
#[pallet::getter(fn requests_ready_at_epoch)]
pub type RequestsReadyAtEpoch<T: Config> =
StorageMap<_, Twox64Concat, u64, Vec<Request>, ValueQuery>;
/// The requests being processed.
#[pallet::storage]
#[pallet::getter(fn requests_ids)]
pub type RequestsIds<T: Config> =
......@@ -114,12 +143,12 @@ pub mod pallet {
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event {
/// Filled randomness
/// A request for randomness was fulfilled.
FilledRandomness {
request_id: RequestId,
randomness: H256,
},
/// Requested randomness
/// A request for randomness was made.
RequestedRandomness {
request_id: RequestId,
salt: H256,
......@@ -131,16 +160,17 @@ pub mod pallet {
#[pallet::error]
pub enum Error<T> {
/// The queue is full, pleasy retry later
FullQueue,
/// Request randomness queue is full.
QueueFull,
}
// CALLS //
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Request a randomness
#[pallet::weight(500_000_000)]
/// Request randomness.
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::request())]
pub fn request(
origin: OriginFor<T>,
randomness_type: RandomnessType,
......@@ -164,12 +194,10 @@ pub mod pallet {
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_: T::BlockNumber) -> Weight {
let request_weight = Weight::from_ref_time(100_000);
let mut total_weight = Weight::zero();
fn on_initialize(_: BlockNumberFor<T>) -> Weight {
// Overhead to process an empty request
let mut total_weight = T::WeightInfo::on_initialize(0);
total_weight += request_weight;
for Request { request_id, salt } in RequestsReadyAtNextBlock::<T>::take() {
let randomness = T::ParentBlockRandomness::random(salt.as_ref())
.0
......@@ -180,14 +208,15 @@ pub mod pallet {
request_id,
randomness,
});
total_weight += request_weight;
// Weight to process on request
total_weight +=
T::WeightInfo::on_initialize(2).saturating_sub(T::WeightInfo::on_initialize(1));
}
let next_epoch_hook_in = NexEpochHookIn::<T>::mutate(|next_in| {
core::mem::replace(next_in, next_in.saturating_sub(1))
});
if next_epoch_hook_in == 1 {
total_weight += request_weight;
for Request { request_id, salt } in
RequestsReadyAtEpoch::<T>::take(T::GetCurrentEpochIndex::get())
{
......@@ -199,7 +228,9 @@ pub mod pallet {
request_id,
randomness,
});
total_weight += request_weight;
// Weight to process on request
total_weight += T::WeightInfo::on_initialize_epoch(2)
.saturating_sub(T::WeightInfo::on_initialize_epoch(1));
}
}
......@@ -210,6 +241,7 @@ pub mod pallet {
// PUBLIC FUNCTIONS //
impl<T: Config> Pallet<T> {
/// Initiates a randomness request with specified parameters.
pub fn do_request(
requestor: &T::AccountId,
randomness_type: RandomnessType,
......@@ -218,7 +250,7 @@ pub mod pallet {
// Verify phase
ensure!(
RequestsIds::<T>::count() < T::MaxRequests::get(),
Error::<T>::FullQueue
Error::<T>::QueueFull
);
Self::pay_request(requestor)?;
......@@ -226,9 +258,13 @@ pub mod pallet {
// Apply phase
Ok(Self::apply_request(randomness_type, salt))
}
/// Forcefully initiates a randomness request using the specified parameters.
pub fn force_request(randomness_type: RandomnessType, salt: H256) -> RequestId {
Self::apply_request(randomness_type, salt)
}
/// Set the next epoch hook value to 5.
pub fn on_new_epoch() {
NexEpochHookIn::<T>::put(5)
}
......@@ -237,16 +273,20 @@ pub mod pallet {
// INTERNAL FUNCTIONS //
impl<T: Config> Pallet<T> {
/// Withdraw funds from the requestor's account to pay for a request.
fn pay_request(requestor: &T::AccountId) -> DispatchResult {
let imbalance = T::Currency::withdraw(
requestor,
T::RequestPrice::get(),
WithdrawReasons::FEE,
ExistenceRequirement::KeepAlive,
Precision::Exact,
Preservation::Preserve,
Fortitude::Polite,
)?;
T::OnUnbalanced::on_unbalanced(imbalance);
Ok(())
}
/// Apply a randomness request with the specified type and salt.
fn apply_request(randomness_type: RandomnessType, salt: H256) -> RequestId {
let request_id = RequestIdProvider::<T>::mutate(|next_request_id| {
core::mem::replace(next_request_id, next_request_id.saturating_add(1))
......
......@@ -17,20 +17,29 @@
//! Various basic types for use in pallet provide randomness
use super::RequestId;
use codec::{Decode, Encode};
use codec::{Decode, DecodeWithMemTracking, Encode};
use frame_support::pallet_prelude::*;
use scale_info::TypeInfo;
use sp_core::H256;
#[derive(Clone, Copy, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
/// The type of randomness source.
#[derive(
Clone, DecodeWithMemTracking, Copy, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo,
)]
pub enum RandomnessType {
/// Randomness derived from the previous block.
RandomnessFromPreviousBlock,
/// Randomness derived from one epoch ago.
RandomnessFromOneEpochAgo,
/// Randomness derived from two epochs ago.
RandomnessFromTwoEpochsAgo,
}
/// Represents a randomness request.
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct Request {
/// Request ID.
pub request_id: RequestId,
/// Salt used for the request.
pub salt: H256,
}
// Copyright 2021-2023 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 on_initialize(i: u32) -> Weight;
fn on_initialize_epoch(i: u32) -> Weight;
fn request() -> Weight;
}
// Insecure weights implementation, use it for tests only!
impl WeightInfo for () {
// Storage: ProvideRandomness CounterForRequestsIds (r:1 w:1)
// Storage: ProvideRandomness RequestIdProvider (r:1 w:1)
// Storage: ProvideRandomness RequestsIds (r:1 w:1)
// Storage: Babe EpochIndex (r:1 w:0)
// Storage: ProvideRandomness NexEpochHookIn (r:1 w:0)
// Storage: ProvideRandomness RequestsReadyAtEpoch (r:1 w:1)
fn request() -> Weight {
// Minimum execution time: 321_822 nanoseconds.
Weight::from_parts(338_919_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(6 as u64))
.saturating_add(RocksDbWeight::get().writes(4 as u64))
}
// Storage: ProvideRandomness RequestsReadyAtNextBlock (r:1 w:1)
// Storage: Babe AuthorVrfRandomness (r:1 w:0)
// Storage: ProvideRandomness RequestsIds (r:1 w:1)
// Storage: ProvideRandomness CounterForRequestsIds (r:1 w:1)
// Storage: Account PendingRandomIdAssignments (r:1 w:0)
// Storage: ProvideRandomness NexEpochHookIn (r:1 w:1)
/// The range of component `i` is `[1, 100]`.
fn on_initialize(i: u32) -> Weight {
// Minimum execution time: 175_645 nanoseconds.
Weight::from_parts(461_442_906 as u64, 0)
// Standard Error: 1_523_561
.saturating_add(Weight::from_parts(43_315_015 as u64, 0).saturating_mul(i as u64))
.saturating_add(RocksDbWeight::get().reads(4 as u64))
.saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(i as u64)))
.saturating_add(RocksDbWeight::get().writes(3 as u64))
.saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64)))
}
fn on_initialize_epoch(i: u32) -> Weight {
// Minimum execution time: 175_645 nanoseconds.
Weight::from_parts(461_442_906 as u64, 0)
// Standard Error: 1_523_561
.saturating_add(Weight::from_parts(43_315_015 as u64, 0).saturating_mul(i as u64))
.saturating_add(RocksDbWeight::get().reads(4 as u64))
.saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(i as u64)))
.saturating_add(RocksDbWeight::get().writes(3 as u64))
.saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64)))
}
}
[package]
authors.workspace = true
description = "duniter pallet quota"
edition.workspace = true
homepage.workspace = true
license.workspace = true
name = "pallet-quota"
repository.workspace = true
version.workspace = true
[features]
default = ["std"]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-identity/runtime-benchmarks",
"sp-membership/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-balances/runtime-benchmarks",
"pallet-balances/try-runtime",
"pallet-identity/try-runtime",
"sp-membership/try-runtime",
"sp-runtime/try-runtime",
]
std = [
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"pallet-balances/std",
"pallet-identity/std",
"sp-membership/std",
"scale-info/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true, features = ["derive"] }
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
pallet-balances = { workspace = true }
pallet-identity = { workspace = true }
sp-membership = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
sp-core = { workspace = true }
sp-runtime = { workspace = true }
[dev-dependencies]
sp-io = { workspace = true, default-features = true }
// Copyright 2021-2023 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, v2::*};
use frame_support::traits::fungible::Mutate;
use sp_runtime::traits::One;
fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
frame_system::Pallet::<T>::assert_has_event(generic_event.into());
}
#[benchmarks(
where
IdtyId<T>: From<u32>,
BalanceOf<T>: From<u64>,
T::AccountId: From<[u8; 32]>,
)]
mod benchmarks {
use super::*;
#[benchmark]
fn queue_refund() {
let account: T::AccountId = account("Alice", 1, 1);
let dummy_refund = Refund {
account: account.clone(),
identity: 0u32.into(),
amount: 20u64.into(),
};
let refund = Refund {
account,
identity: 1u32.into(),
amount: 10u64.into(),
};
// Complexity is bound to MAX_QUEUD_REFUNDS where an insertion is O(n-1)
for _ in 0..MAX_QUEUED_REFUNDS - 1 {
Pallet::<T>::queue_refund(dummy_refund.clone())
}
#[block]
{
Pallet::<T>::queue_refund(refund.clone());
}
assert_eq!(RefundQueue::<T>::get().last(), Some(refund).as_ref());
assert_eq!(RefundQueue::<T>::get().len() as u32, MAX_QUEUED_REFUNDS);
}
#[benchmark]
fn spend_quota() {
let idty_id: IdtyId<T> = 1u32.into();
let amount = 2u64;
let quota_amount = 10u64;
IdtyQuota::<T>::insert(
idty_id,
Quota {
last_use: BlockNumberFor::<T>::zero(),
amount: quota_amount.into(),
},
);
#[block]
{
Pallet::<T>::spend_quota(idty_id, amount.into());
}
let quota_growth =
sp_runtime::Perbill::from_rational(BlockNumberFor::<T>::one(), T::ReloadRate::get())
.mul_floor(T::MaxQuota::get());
assert_eq!(
IdtyQuota::<T>::get(idty_id).unwrap().amount,
quota_growth + quota_amount.into() - amount.into()
);
}
#[benchmark]
fn try_refund() {
let account: T::AccountId = account("Alice", 1, 1);
let idty_id: IdtyId<T> = 1u32.into();
IdtyQuota::<T>::insert(
idty_id,
Quota {
last_use: BlockNumberFor::<T>::zero(),
amount: 10u64.into(),
},
);
let _ = CurrencyOf::<T>::set_balance(&T::RefundAccount::get(), u32::MAX.into());
// The worst-case scenario is when the refund fails
// and can only be triggered if the account is dead,
// in this case by having no balance in the account.
let refund = Refund {
account: account.clone(),
identity: 1u32.into(),
amount: 10u64.into(),
};
#[block]
{
Pallet::<T>::try_refund(refund);
}
assert_has_event::<T>(Event::<T>::RefundFailed(account).into());
}
#[benchmark]
fn do_refund() {
let account: T::AccountId = account("Alice", 1, 1);
let _ = CurrencyOf::<T>::set_balance(&T::RefundAccount::get(), u32::MAX.into());
// The worst-case scenario is when the refund fails
// and can only be triggered if the account is dead,
// in this case by having no balance in the account.
let refund = Refund {
account: account.clone(),
identity: 1u32.into(),
amount: 10u64.into(),
};
#[block]
{
Pallet::<T>::try_refund(refund);
}
assert_has_event::<T>(Event::<T>::RefundFailed(account).into());
}
#[benchmark]
fn on_process_refund_queue() {
// The base weight consumed on processing refund queue when empty.
assert_eq!(RefundQueue::<T>::get().len() as u32, 0);
#[block]
{
Pallet::<T>::process_refund_queue(Weight::MAX);
}
}
#[benchmark]
fn on_process_refund_queue_elements(i: Linear<1, MAX_QUEUED_REFUNDS>) {
// The weight consumed on processing refund queue with one element.
// Can deduce the process_refund_queue overhead by subtracting try_refund weight.
let account: T::AccountId = account("Alice", 1, 1);
let idty_id: IdtyId<T> = 1u32.into();
IdtyQuota::<T>::insert(
idty_id,
Quota {
last_use: BlockNumberFor::<T>::zero(),
amount: 10u64.into(),
},
);
let _ = CurrencyOf::<T>::set_balance(&T::RefundAccount::get(), u32::MAX.into());
// The worst-case scenario is when the refund fails
// and can only be triggered if the account is dead,
// in this case by having no balance in the account.
let refund = Refund {
account: account.clone(),
identity: 1u32.into(),
amount: 10u64.into(),
};
for _ in 0..i {
Pallet::<T>::queue_refund(refund.clone());
}
assert_eq!(RefundQueue::<T>::get().len() as u32, i);
#[block]
{
Pallet::<T>::process_refund_queue(Weight::MAX);
}
assert_eq!(RefundQueue::<T>::get().len() as u32, 0);
assert_has_event::<T>(Event::<T>::RefundFailed(account).into());
}
impl_benchmark_test_suite!(
Pallet,
crate::mock::new_test_ext(crate::mock::QuotaConfig {
identities: vec![1, 2]
}),
crate::mock::Test
);
}
// Copyright 2021-2023 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/>.
//! # Duniter Quota Pallet
//!
//! ## Overview
//!
//! This pallet is designed to manage transaction fee refunds based on quotas allocated to identities within the Duniter identity system. Quotas are linked to transaction fees, ensuring efficient handling of fee refunds when transactions occur.
//!
//! ## Refund Mechanism
//!
//! When a transaction is processed:
//! - The `OnChargeTransaction` implementation in the `frame-executive` pallet is called.
//! - The `OnChargeTransaction` implementation in the `duniter-account` pallet checks if the paying account is linked to an identity.
//! - If linked, the `request_refund` function in the `quota` pallet evaluates the eligibility for fee refund based on the identity's quota.
//! - Eligible refunds are added to the `RefundQueue`, managed by `process_refund_queue` during the `on_idle` phase.
//! - Refunds are processed with `try_refund`, using quotas to refund fees via `spend_quota`, and then executing the refund through `do_refund` by transferring currency from the `RefundAccount` back to the paying account.
//!
//! ## Conditions for Refund
//!
//! Refunds are executed under the following conditions:
//! 1. The paying account is linked to an identity.
//! 2. Quotas are allocated to the identity and have a non-zero value after updates.
#![cfg_attr(not(feature = "std"), no_std)]
mod benchmarking;
mod traits;
mod weights;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
use frame_support::{
pallet_prelude::*,
traits::{Currency, ExistenceRequirement},
};
use frame_system::pallet_prelude::*;
use scale_info::prelude::vec::Vec;
use sp_runtime::traits::Zero;
pub use pallet::*;
pub use traits::*;
pub use weights::WeightInfo;
#[allow(unreachable_patterns)]
#[frame_support::pallet]
pub mod pallet {
use super::*;
pub const MAX_QUEUED_REFUNDS: u32 = 256;
// Currency used for quota is the one of pallet balances
pub type CurrencyOf<T> = pallet_balances::Pallet<T>;
// Balance used for quota is the one associated to balance currency
pub type BalanceOf<T> =
<CurrencyOf<T> as Currency<<T as frame_system::Config>::AccountId>>::Balance;
// identity id is pallet identity idty_index
pub type IdtyId<T> = <T as pallet_identity::Config>::IdtyIndex;
#[pallet::pallet]
pub struct Pallet<T>(_);
// CONFIG //
#[pallet::config]
pub trait Config:
frame_system::Config + pallet_balances::Config + pallet_identity::Config
{
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Number of blocks after which the maximum quota is replenished.
type ReloadRate: Get<BlockNumberFor<Self>>;
/// Maximum amount of quota an identity can receive.
type MaxQuota: Get<BalanceOf<Self>>;
/// Account used to refund fees.
#[pallet::constant]
type RefundAccount: Get<Self::AccountId>;
/// Type representing the weight of this pallet.
type WeightInfo: WeightInfo;
}
// TYPES //
/// Represents a refund.
#[derive(Encode, Decode, Clone, TypeInfo, Debug, PartialEq, MaxEncodedLen)]
pub struct Refund<AccountId, IdtyId, Balance> {
/// Account to refund.
pub account: AccountId,
/// Identity to use quota.
pub identity: IdtyId,
/// Amount of refund.
pub amount: Balance,
}
/// Represents a quota.
#[derive(Encode, Decode, Clone, TypeInfo, Debug, PartialEq, MaxEncodedLen)]
pub struct Quota<BlockNumber, Balance> {
/// Block number of the last quota used.
pub last_use: BlockNumber,
/// Amount of remaining quota.
pub amount: Balance,
}
// STORAGE //
/// The quota for each identity.
#[pallet::storage]
#[pallet::getter(fn quota)]
pub type IdtyQuota<T: Config> =
StorageMap<_, Twox64Concat, IdtyId<T>, Quota<BlockNumberFor<T>, BalanceOf<T>>, OptionQuery>;
/// The fees waiting to be refunded.
#[pallet::storage]
pub type RefundQueue<T: Config> = StorageValue<
_,
BoundedVec<Refund<T::AccountId, IdtyId<T>, BalanceOf<T>>, ConstU32<MAX_QUEUED_REFUNDS>>,
ValueQuery,
>;
// EVENTS //
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Transaction fees were refunded.
Refunded {
who: T::AccountId,
identity: IdtyId<T>,
amount: BalanceOf<T>,
},
/// No more quota available for refund.
NoQuotaForIdty(IdtyId<T>),
/// No more currency available for refund.
/// This scenario should never occur if the fees are intended for the refund account.
NoMoreCurrencyForRefund,
/// The refund has failed.
/// This scenario should rarely occur, except when the account was destroyed in the interim between the request and the refund.
RefundFailed(T::AccountId),
/// Refund queue was full.
RefundQueueFull,
}
// This pallet only contains the `on_idle` hook and no call.
// Hooks are infallible by definition, so there are no error. To monitor no-ops
// from inside the quota pallet, we use events as mentioned in
// https://substrate.stackexchange.com/questions/9854/emitting-errors-from-hooks-like-on-initialize
// PUBLIC FUNCTIONS //
impl<T: Config> Pallet<T> {
/// Estimates the quota refund amount for an identity
/// The estimation simulate a refund request at the current block
pub fn estimate_quota_refund(idty_index: IdtyId<T>) -> BalanceOf<T> {
if is_eligible_for_refund::<T>(idty_index) {
if let Some(ref mut quota) = IdtyQuota::<T>::get(idty_index) {
Self::update_quota(quota);
quota.amount
} else {
Zero::zero()
}
} else {
Zero::zero()
}
}
}
// INTERNAL FUNCTIONS //
impl<T: Config> Pallet<T> {
/// Adds a new refund request to the refund queue.
pub fn queue_refund(refund: Refund<T::AccountId, IdtyId<T>, BalanceOf<T>>) {
if RefundQueue::<T>::mutate(|v| v.try_push(refund)).is_err() {
Self::deposit_event(Event::RefundQueueFull);
}
}
/// Attempts to process a refund using available quota.
pub fn try_refund(queued_refund: Refund<T::AccountId, IdtyId<T>, BalanceOf<T>>) -> Weight {
// get the amount of quota that identity is able to spend
let amount = Self::spend_quota(queued_refund.identity, queued_refund.amount);
if amount.is_zero() {
// partial weight
return <T as pallet::Config>::WeightInfo::spend_quota();
}
// only perform refund if amount is not null
Self::do_refund(queued_refund, amount);
// total weight
<T as pallet::Config>::WeightInfo::spend_quota()
.saturating_add(<T as pallet::Config>::WeightInfo::do_refund())
}
/// Performs a refund operation for a specified non-null amount from the refund account to the requester's account.
// opti: more accurate estimation of consumed weight
pub fn do_refund(
queued_refund: Refund<T::AccountId, IdtyId<T>, BalanceOf<T>>,
amount: BalanceOf<T>,
) {
// take money from refund account
let res = CurrencyOf::<T>::withdraw(
&T::RefundAccount::get(),
amount,
frame_support::traits::WithdrawReasons::FEE, // a fee but in reverse
ExistenceRequirement::KeepAlive,
);
// if successful
if let Ok(imbalance) = res {
// perform refund
let res = CurrencyOf::<T>::resolve_into_existing(&queued_refund.account, imbalance);
match res {
// take money from refund account OK + refund account OK → event
Ok(_) => {
Self::deposit_event(Event::Refunded {
who: queued_refund.account,
identity: queued_refund.identity,
amount,
});
}
Err(imbalance) => {
// refund failed (for example account stopped existing) → handle dust
// give back to refund account (should not happen)
CurrencyOf::<T>::resolve_creating(&T::RefundAccount::get(), imbalance);
// if this event is observed, block should be examined carefully
Self::deposit_event(Event::RefundFailed(queued_refund.account));
}
}
} else {
// could not withdraw refund account
Self::deposit_event(Event::NoMoreCurrencyForRefund);
}
}
/// Processes as many refunds as possible from the refund queue within the supplied weight limit.
pub fn process_refund_queue(weight_limit: Weight) -> Weight {
RefundQueue::<T>::mutate(|queue| {
// The weight to process an empty queue
let mut total_weight = <T as pallet::Config>::WeightInfo::on_process_refund_queue();
// The weight to process one element without the actual try_refund weight
let overhead =
<T as pallet::Config>::WeightInfo::on_process_refund_queue_elements(2)
.saturating_sub(
<T as pallet::Config>::WeightInfo::on_process_refund_queue_elements(1),
)
.saturating_sub(<T as pallet::Config>::WeightInfo::try_refund());
// make sure that we have at least the time to handle one try_refund call
if queue.is_empty() {
return total_weight;
}
while total_weight.any_lt(weight_limit.saturating_sub(
<T as pallet::Config>::WeightInfo::try_refund().saturating_add(overhead),
)) {
let Some(queued_refund) = queue.pop() else {
break;
};
let consumed_weight = Self::try_refund(queued_refund);
total_weight = total_weight
.saturating_add(consumed_weight)
.saturating_add(overhead);
}
total_weight
})
}
/// Spends the quota of an identity by deducting the specified `amount` from its quota balance.
pub fn spend_quota(idty_id: IdtyId<T>, amount: BalanceOf<T>) -> BalanceOf<T> {
IdtyQuota::<T>::mutate_exists(idty_id, |quota| {
if let Some(ref mut quota) = quota {
Self::update_quota(quota);
Self::do_spend_quota(quota, amount)
} else {
// error event if identity has no quota
Self::deposit_event(Event::NoQuotaForIdty(idty_id));
BalanceOf::<T>::zero()
}
})
}
/// Update the quota according to the growth rate, maximum value, and last use.
fn update_quota(quota: &mut Quota<BlockNumberFor<T>, BalanceOf<T>>) {
let current_block = frame_system::pallet::Pallet::<T>::block_number();
let quota_growth = sp_runtime::Perbill::from_rational(
current_block - quota.last_use,
T::ReloadRate::get(),
)
.mul_floor(T::MaxQuota::get());
// mutate quota
quota.last_use = current_block;
quota.amount = core::cmp::min(quota.amount + quota_growth, T::MaxQuota::get());
}
/// Spend a certain amount of quota and return the amount that was spent.
fn do_spend_quota(
quota: &mut Quota<BlockNumberFor<T>, BalanceOf<T>>,
amount: BalanceOf<T>,
) -> BalanceOf<T> {
let old_amount = quota.amount;
// entire amount fit in remaining quota
if amount <= old_amount {
quota.amount -= amount;
amount
}
// all quota are spent and only partial refund is possible
else {
quota.amount = BalanceOf::<T>::zero();
old_amount
}
}
}
// GENESIS STUFF //
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub identities: Vec<IdtyId<T>>,
}
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
Self {
identities: Default::default(),
}
}
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
for idty in self.identities.iter() {
IdtyQuota::<T>::insert(
idty,
Quota {
last_use: BlockNumberFor::<T>::zero(),
amount: BalanceOf::<T>::zero(),
},
);
}
}
}
// HOOKS //
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
// process refund queue if space left on block
fn on_idle(_block: BlockNumberFor<T>, remaining_weight: Weight) -> Weight {
Self::process_refund_queue(remaining_weight)
}
}
}
/// Implementing the refund fee trait for the pallet.
impl<T: Config> RefundFee<T> for Pallet<T> {
/// This implementation checks if the identity is eligible for a refund and queues the refund if so.
fn request_refund(account: T::AccountId, identity: IdtyId<T>, amount: BalanceOf<T>) {
if is_eligible_for_refund::<T>(identity) {
Self::queue_refund(Refund {
account,
identity,
amount,
})
}
}
}
/// Checks if an identity is eligible for a refund.
///
/// This function returns `true` only if the identity exists and has a status of `Member`.
/// If the identity does not exist or has a different status, it returns `false`, and the refund request will not be processed.
///
fn is_eligible_for_refund<T: pallet_identity::Config>(idty_index: IdtyId<T>) -> bool {
pallet_identity::Identities::<T>::get(idty_index).map_or_else(
|| false,
|id| id.status == pallet_identity::IdtyStatus::Member,
)
}
/// Implementing the on new membership event handler for the pallet.
impl<T: Config> sp_membership::traits::OnNewMembership<IdtyId<T>> for Pallet<T> {
/// This implementation initializes the identity quota for the newly created identity.
fn on_created(idty_index: &IdtyId<T>) {
IdtyQuota::<T>::insert(
idty_index,
Quota {
last_use: frame_system::pallet::Pallet::<T>::block_number(),
amount: BalanceOf::<T>::zero(),
},
);
}
fn on_renewed(_idty_index: &IdtyId<T>) {}
}
/// Implementing the on remove identity event handler for the pallet.
impl<T: Config> sp_membership::traits::OnRemoveMembership<IdtyId<T>> for Pallet<T> {
/// This implementation removes the identity quota associated with the removed identity.
fn on_removed(idty_id: &IdtyId<T>) -> Weight {
let mut weight = Weight::zero();
let mut add_db_reads_writes = |reads, writes| {
weight = weight.saturating_add(T::DbWeight::get().reads_writes(reads, writes));
};
IdtyQuota::<T>::remove(idty_id);
add_db_reads_writes(1, 1);
weight
}
}
// Copyright 2021 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/>.
// Note: most of this file is copy pasted from common pallet_config and other mocks
use super::*;
pub use crate::pallet as pallet_quota;
use frame_support::{
derive_impl, parameter_types,
traits::{Everything, OnFinalize, OnInitialize},
};
use frame_system as system;
use sp_core::{Pair, H256};
use sp_runtime::{
traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify},
BuildStorage, MultiSignature, MultiSigner,
};
type BlockNumber = u64;
type Balance = u64;
type Block = frame_system::mocking::MockBlock<Test>;
pub type Signature = MultiSignature;
pub type AccountPublic = <Signature as Verify>::Signer;
pub type AccountId = <AccountPublic as IdentifyAccount>::AccountId;
pub fn account(id: u8) -> AccountId {
let pair = sp_core::sr25519::Pair::from_seed(&[id; 32]);
MultiSigner::Sr25519(pair.public()).into_account()
}
// Configure a mock runtime to test the pallet.
frame_support::construct_runtime!(
pub enum Test{
System: frame_system,
Quota: pallet_quota,
Balances: pallet_balances,
Identity: pallet_identity,
}
);
// QUOTA //
pub struct TreasuryAccountId;
impl frame_support::pallet_prelude::Get<AccountId> for TreasuryAccountId {
fn get() -> AccountId {
account(99)
}
}
parameter_types! {
pub const ReloadRate: u64 = 10;
pub const MaxQuota: u64 = 1000;
}
impl Config for Test {
type MaxQuota = MaxQuota;
type RefundAccount = TreasuryAccountId;
type ReloadRate = ReloadRate;
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
}
// SYSTEM //
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const SS58Prefix: u8 = 42;
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl system::Config for Test {
type AccountData = pallet_balances::AccountData<Balance>;
type AccountId = AccountId;
type BaseCallFilter = Everything;
type Block = Block;
type BlockHashCount = BlockHashCount;
type Hash = H256;
type Hashing = BlakeTwo256;
type Lookup = IdentityLookup<Self::AccountId>;
type MaxConsumers = frame_support::traits::ConstU32<16>;
type Nonce = u64;
type PalletInfo = PalletInfo;
type RuntimeCall = RuntimeCall;
type RuntimeEvent = RuntimeEvent;
type RuntimeOrigin = RuntimeOrigin;
type SS58Prefix = SS58Prefix;
}
// BALANCES //
parameter_types! {
pub const ExistentialDeposit: Balance = 1000;
pub const MaxLocks: u32 = 50;
}
impl pallet_balances::Config for Test {
type AccountStore = System;
type Balance = Balance;
type DoneSlashHandler = ();
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type FreezeIdentifier = ();
type MaxFreezes = ConstU32<0>;
type MaxLocks = MaxLocks;
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type RuntimeEvent = RuntimeEvent;
type RuntimeFreezeReason = ();
type RuntimeHoldReason = ();
type WeightInfo = pallet_balances::weights::SubstrateWeight<Test>;
}
// IDENTITY //
parameter_types! {
pub const ChangeOwnerKeyPeriod: u64 = 10;
pub const ConfirmPeriod: u64 = 2;
pub const ValidationPeriod: u64 = 3;
pub const AutorevocationPeriod: u64 = 5;
pub const DeletionPeriod: u64 = 7;
pub const IdtyCreationPeriod: u64 = 3;
}
pub struct IdtyNameValidatorTestImpl;
impl pallet_identity::traits::IdtyNameValidator for IdtyNameValidatorTestImpl {
fn validate(idty_name: &pallet_identity::IdtyName) -> bool {
idty_name.0.len() < 16
}
}
impl pallet_identity::Config for Test {
type AccountId32 = AccountId;
type AccountLinker = ();
type AutorevocationPeriod = AutorevocationPeriod;
type ChangeOwnerKeyPeriod = ChangeOwnerKeyPeriod;
type CheckAccountWorthiness = ();
type CheckIdtyCallAllowed = ();
type ConfirmPeriod = ConfirmPeriod;
type DeletionPeriod = DeletionPeriod;
type IdtyCreationPeriod = IdtyCreationPeriod;
type IdtyData = ();
type IdtyIndex = u64;
type IdtyNameValidator = IdtyNameValidatorTestImpl;
type OnKeyChange = ();
type OnNewIdty = ();
type OnRemoveIdty = ();
type RuntimeEvent = RuntimeEvent;
type Signature = Signature;
type Signer = AccountPublic;
type ValidationPeriod = ValidationPeriod;
type WeightInfo = ();
}
// Build genesis storage according to the mock runtime.
pub fn new_test_ext(gen_conf: pallet_quota::GenesisConfig<Test>) -> sp_io::TestExternalities {
RuntimeGenesisConfig {
system: SystemConfig::default(),
balances: BalancesConfig::default(),
quota: gen_conf,
identity: IdentityConfig::default(),
}
.build_storage()
.unwrap()
.into()
}
pub fn run_to_block(n: BlockNumber) {
while System::block_number() < n {
<frame_system::Pallet<Test> as OnFinalize<BlockNumber>>::on_finalize(System::block_number());
System::reset_events();
System::set_block_number(System::block_number() + 1);
<frame_system::Pallet<Test> as OnInitialize<BlockNumber>>::on_initialize(
System::block_number(),
);
}
}
// Copyright 2021 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/>.
use crate::{mock::*, Weight};
use frame_support::traits::fungible::Mutate;
use sp_core::Get;
// Note: values for reload rate and max quota defined in mock file
// parameter_types! {
// pub const ReloadRate: u64 = 10;
// pub const MaxQuota: u64 = 1000;
// }
// pub const ExistentialDeposit: Balance = 1000;
/// test that quota are well initialized for genesis identities
#[test]
fn test_initial_quota() {
new_test_ext(QuotaConfig {
identities: vec![1, 2, 3],
})
.execute_with(|| {
run_to_block(1);
// quota initialized to 0,0 for a given identity
assert_eq!(
Quota::quota(1),
Some(pallet_quota::Quota {
last_use: 0,
amount: 0
})
);
// no initialized quota for standard account
assert_eq!(Quota::quota(4), None);
})
}
/// test that quota are updated according to the reload rate and max quota values
#[test]
fn test_update_quota() {
new_test_ext(QuotaConfig {
identities: vec![1, 2, 3],
})
.execute_with(|| {
// Block 1
run_to_block(1);
assert_eq!(
Quota::quota(1),
Some(pallet_quota::Quota {
last_use: 0,
amount: 0
})
);
// (spending 0 quota will lead to only update)
// assert zero quota spent
assert_eq!(Quota::spend_quota(1, 0), 0);
assert_eq!(
Quota::quota(1),
Some(pallet_quota::Quota {
last_use: 1, // used at block 1
// max quota × (current block - last use) / reload rate
amount: 100 // 1000 × 1 / 10 = 100
})
);
// Block 2
run_to_block(2);
assert_eq!(Quota::spend_quota(2, 0), 0);
assert_eq!(
Quota::quota(2),
Some(pallet_quota::Quota {
last_use: 2, // used at block 2
// max quota × (current block - last use) / reload rate
amount: 200 // 1000 × 2 / 10 = 200
})
);
// Block 20
run_to_block(20);
assert_eq!(Quota::spend_quota(2, 0), 0);
assert_eq!(
Quota::quota(2),
Some(pallet_quota::Quota {
last_use: 20, // used at block 20
// maximum quota is reached
// 1000 × (20 - 2) / 10 = 1800
amount: 1000 // min(1000, 1800)
})
);
})
}
/// test that right amount of quota is spent
#[test]
fn test_spend_quota() {
new_test_ext(QuotaConfig {
identities: vec![1, 2, 3],
})
.execute_with(|| {
// at block 5, quota are half loaded (500)
run_to_block(5);
// spending less than available
assert_eq!(Quota::spend_quota(1, 200), 200);
assert_eq!(
Quota::quota(1),
Some(pallet_quota::Quota {
last_use: 5,
amount: 300 // 500 - 200
})
);
// spending all available
assert_eq!(Quota::spend_quota(2, 500), 500);
assert_eq!(
Quota::quota(2),
Some(pallet_quota::Quota {
last_use: 5,
amount: 0 // 500 - 500
})
);
// spending more than available
assert_eq!(Quota::spend_quota(3, 1000), 500);
assert_eq!(
Quota::quota(3),
Some(pallet_quota::Quota {
last_use: 5,
amount: 0 // 500 - 500
})
);
})
}
/// test complete scenario with queue and process refund queue
#[test]
fn test_process_refund_queue() {
new_test_ext(QuotaConfig {
identities: vec![1, 2],
})
.execute_with(|| {
run_to_block(5);
// give enough currency to accounts and treasury and double check
Balances::set_balance(&account(1), 1000);
Balances::set_balance(&account(2), 1000);
Balances::set_balance(&account(3), 1000);
Balances::set_balance(
&<Test as pallet_quota::Config>::RefundAccount::get(),
10_000,
);
assert_eq!(
Balances::free_balance(<Test as pallet_quota::Config>::RefundAccount::get()),
10_000
);
// fill in the refund queue
Quota::queue_refund(pallet_quota::Refund {
account: account(1),
identity: 1,
amount: 10,
});
Quota::queue_refund(pallet_quota::Refund {
account: account(2),
identity: 2,
amount: 1000,
});
Quota::queue_refund(pallet_quota::Refund {
account: account(3),
identity: 3,
amount: 666,
});
// process it
Quota::process_refund_queue(Weight::from(10));
// after processing, it should be empty
assert!(pallet_quota::RefundQueue::<Test>::get().is_empty());
// and we should observe the effects of refund
assert_eq!(Balances::free_balance(account(1)), 1010); // 1000 initial + 10 refunded
assert_eq!(Balances::free_balance(account(2)), 1500); // 1000 initial + 500 refunded
assert_eq!(Balances::free_balance(account(3)), 1000); // only initial because no available quota
assert_eq!(
Balances::free_balance(<Test as pallet_quota::Config>::RefundAccount::get()),
// initial minus refunds
10_000 - 500 - 10
);
// events
System::assert_has_event(RuntimeEvent::Quota(pallet_quota::Event::Refunded {
who: account(1),
identity: 1,
amount: 10,
}));
System::assert_has_event(RuntimeEvent::Quota(pallet_quota::Event::NoQuotaForIdty(3)));
})
}
/// test not enough currency in treasury
#[test]
fn test_not_enough_treasury() {
new_test_ext(QuotaConfig {
identities: vec![1],
})
.execute_with(|| {
run_to_block(5);
Balances::set_balance(&account(1), 1000);
Balances::set_balance(&<Test as pallet_quota::Config>::RefundAccount::get(), 1200);
Quota::queue_refund(pallet_quota::Refund {
account: account(1),
identity: 1,
amount: 500,
});
Quota::process_refund_queue(Weight::from(10));
// refund was not possible, would kill treasury
assert_eq!(Balances::free_balance(account(1)), 1000);
assert_eq!(
Balances::free_balance(<Test as pallet_quota::Config>::RefundAccount::get()),
1200
);
// event
System::assert_has_event(RuntimeEvent::Quota(
pallet_quota::Event::NoMoreCurrencyForRefund,
));
// quotas were spent anyway, there is no refund for quotas when refund account is empty
assert_eq!(
Quota::quota(1),
Some(pallet_quota::Quota {
last_use: 5,
amount: 0
})
);
})
}
/// test complete scenario with queue and process refund queue weight with available quotas
#[test]
fn test_process_refund_queue_weight_with_quotas() {
new_test_ext(QuotaConfig {
identities: vec![1, 2, 3],
})
.execute_with(|| {
run_to_block(15);
// give enough currency to accounts and treasury and double check
Balances::set_balance(&account(1), 1000);
Balances::set_balance(&account(2), 1000);
Balances::set_balance(&account(3), 1000);
Balances::set_balance(
&<Test as pallet_quota::Config>::RefundAccount::get(),
10_000,
);
assert_eq!(
Balances::free_balance(<Test as pallet_quota::Config>::RefundAccount::get()),
10_000
);
// fill in the refund queue
Quota::queue_refund(pallet_quota::Refund {
account: account(1),
identity: 10,
amount: 10,
});
Quota::queue_refund(pallet_quota::Refund {
account: account(2),
identity: 2,
amount: 500,
});
Quota::queue_refund(pallet_quota::Refund {
account: account(3),
identity: 3,
amount: 666,
});
// process it with only no weight
Quota::process_refund_queue(Weight::from(0));
// after processing, it should be of the same size
assert_eq!(pallet_quota::RefundQueue::<Test>::get().len(), 3);
// process it with only 200 allowed weight
Quota::process_refund_queue(Weight::from_parts(200u64, 0));
// after processing, it should be of size 1 because total_weight += 25*2 by iteration and
// limit is total_weight < 200-100 so 2 elements can be processed
assert_eq!(pallet_quota::RefundQueue::<Test>::get().len(), 1);
// and we should observe the effects of refund
assert_eq!(Balances::free_balance(account(3)), 1666); // 1000 initial + 666 refunded
assert_eq!(Balances::free_balance(account(2)), 1500); // 1000 initial + 1500 refunded
assert_eq!(Balances::free_balance(account(1)), 1000); // only initial because no available weight to process
assert_eq!(
Balances::free_balance(<Test as pallet_quota::Config>::RefundAccount::get()),
// initial minus refunds
10_000 - 666 - 500
);
// events
System::assert_has_event(RuntimeEvent::Quota(pallet_quota::Event::Refunded {
who: account(3),
identity: 3,
amount: 666,
}));
System::assert_has_event(RuntimeEvent::Quota(pallet_quota::Event::Refunded {
who: account(2),
identity: 2,
amount: 500,
}));
})
}
/// test complete scenario with queue and process refund queue weight with limited quotas
#[test]
fn test_process_refund_queue_weight_no_quotas() {
new_test_ext(QuotaConfig {
identities: vec![1, 2],
})
.execute_with(|| {
run_to_block(15);
// give enough currency to accounts and treasury and double check
Balances::set_balance(&account(1), 1000);
Balances::set_balance(&account(2), 1000);
Balances::set_balance(&account(3), 1000);
Balances::set_balance(
&<Test as pallet_quota::Config>::RefundAccount::get(),
10_000,
);
assert_eq!(
Balances::free_balance(<Test as pallet_quota::Config>::RefundAccount::get()),
10_000
);
// fill in the refund queue
Quota::queue_refund(pallet_quota::Refund {
account: account(1),
identity: 10,
amount: 10,
});
Quota::queue_refund(pallet_quota::Refund {
account: account(2),
identity: 2,
amount: 500,
});
Quota::queue_refund(pallet_quota::Refund {
account: account(3),
identity: 3,
amount: 666,
});
// process it with only no weight
Quota::process_refund_queue(Weight::from(0));
// after processing, it should be of the same size
assert_eq!(pallet_quota::RefundQueue::<Test>::get().len(), 3);
// process it with only 150 allowed weight
Quota::process_refund_queue(Weight::from_parts(150u64, 0));
// after processing, it should be of size 2 because try_refund weight is 25 (first in the queue with no quota) then 25*2 for the 2 other elements
// limit is total_weight < 150-100 so 2 elements can be processed
assert_eq!(pallet_quota::RefundQueue::<Test>::get().len(), 1);
// and we should observe the effects of refund
assert_eq!(Balances::free_balance(account(3)), 1000); // 1000 initial only because no quota available
assert_eq!(Balances::free_balance(account(2)), 1500); // 1000 initial + 500 refunded
assert_eq!(Balances::free_balance(account(1)), 1000); // only initial because no available weight to process
assert_eq!(
Balances::free_balance(<Test as pallet_quota::Config>::RefundAccount::get()),
// initial minus refunds
10_000 - 500
);
// events
System::assert_has_event(RuntimeEvent::Quota(pallet_quota::Event::Refunded {
who: account(2),
identity: 2,
amount: 500,
}));
})
}
// Copyright 2021 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/>.
use crate::*;
/// Trait for managing refund operations.
pub trait RefundFee<T: Config> {
/// Request a refund of a fee for a specific account and identity.
fn request_refund(account: T::AccountId, identity: IdtyId<T>, amount: BalanceOf<T>);
}
impl<T: Config> RefundFee<T> for () {
fn request_refund(_account: T::AccountId, _identity: IdtyId<T>, _amount: BalanceOf<T>) {}
}
// tmp
use frame_support::weights::Weight;
pub trait WeightInfo {
fn queue_refund() -> Weight;
fn spend_quota() -> Weight;
fn try_refund() -> Weight;
fn do_refund() -> Weight;
fn on_process_refund_queue() -> Weight;
fn on_process_refund_queue_elements(_i: u32) -> Weight;
}
impl WeightInfo for () {
fn queue_refund() -> Weight {
Weight::from_parts(100u64, 0)
}
fn spend_quota() -> Weight {
Weight::from_parts(25u64, 0)
}
fn try_refund() -> Weight {
Weight::from_parts(100u64, 0)
}
fn do_refund() -> Weight {
Weight::from_parts(25u64, 0)
}
fn on_process_refund_queue() -> Weight {
Weight::from_parts(1u64, 0)
}
fn on_process_refund_queue_elements(_i: u32) -> Weight {
Weight::from_parts(1u64, 0)
}
}
[package]
name = "pallet-session-benchmarking"
authors.workspace = true
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
description = "FRAME sessions pallet benchmarking"
version.workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true }
frame-benchmarking = { workspace = true, optional = true }
frame-system = { workspace = true }
pallet-session = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
sp-runtime = { workspace = true }
[features]
default = ["std"]
std = [
"codec/std",
"frame-benchmarking?/std",
"frame-system/std",
"pallet-session/std",
"sp-runtime/std",
]
try-runtime = [
"frame-system/try-runtime",
"pallet-session/try-runtime",
"sp-runtime/try-runtime",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
// Copyright 2023 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/>.
//! # Duniter Session Benchmarking Pallet
//!
//! This crate provides benchmarks specifically for the `pallet-session` within Duniter. Unlike traditional setups, this implementation is decoupled from the `staking-pallet`, which is not utilized in Duniter's architecture. Instead, session management functionalities are integrated into the `authority-members` pallet.
//!
//! ## Note
//!
//! This crate is separated from the main codebase due to cyclic dependency issues, focusing solely on session-related benchmarking independent of staking-related functionalities.
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg(feature = "runtime-benchmarks")]
use codec::Decode;
use frame_benchmarking::{benchmarks, whitelisted_caller};
use frame_system::RawOrigin;
use pallet_session::*;
use scale_info::prelude::{vec, vec::Vec};
pub struct Pallet<T: Config>(pallet_session::Pallet<T>);
pub trait Config: pallet_session::Config {}
benchmarks! {
set_keys {
let caller: T::AccountId = whitelisted_caller();
frame_system::Pallet::<T>::inc_providers(&caller);
let keys = T::Keys::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()).unwrap();
let proof: Vec<u8> = vec![0,1,2,3];
}: _(RawOrigin::Signed(caller), keys, proof)
purge_keys {
let caller: T::AccountId = whitelisted_caller();
frame_system::Pallet::<T>::inc_providers(&caller);
let keys = T::Keys::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()).unwrap();
let proof: Vec<u8> = vec![0,1,2,3];
let _t = pallet_session::Pallet::<T>::set_keys(RawOrigin::Signed(caller.clone()).into(), keys, proof);
}: _(RawOrigin::Signed(caller))
}
[package]
name = "pallet-smith-members"
authors.workspace = true
description = "duniter pallet to handle offences"
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
version.workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
duniter-primitives = { workspace = true }
codec = { workspace = true, features = ["derive"] }
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
log = { workspace = true }
pallet-authority-members = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
sp-runtime = { workspace = true }
sp-staking = { workspace = true }
[features]
default = ["std"]
std = [
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"log/std",
"pallet-authority-members/std",
"scale-info/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-staking/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-authority-members/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"sp-staking/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-authority-members/runtime-benchmarks",
"pallet-authority-members/try-runtime",
"sp-runtime/try-runtime",
]
[dev-dependencies]
maplit = { workspace = true, default-features = true }
sp-core = { workspace = true, default-features = true }
sp-io = { workspace = true, default-features = true }
// Copyright 2021-2023 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::v2::*;
use frame_system::RawOrigin;
use crate::Pallet;
#[benchmarks(
where
T::IdtyIndex: From<u32>
)]
mod benchmarks {
use super::*;
fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
frame_system::Pallet::<T>::assert_has_event(generic_event.into());
}
#[benchmark]
fn invite_smith() {
let issuer: T::IdtyIndex = 1.into();
let caller: T::AccountId = T::IdtyAttr::owner_key(issuer).unwrap();
Pallet::<T>::on_smith_goes_online(1.into());
// Should be the last identities from the local_testnet_config
let receiver: T::IdtyIndex = 6.into();
#[extrinsic_call]
_(RawOrigin::Signed(caller), receiver);
assert_has_event::<T>(Event::<T>::InvitationSent { receiver, issuer }.into());
}
#[benchmark]
fn accept_invitation() -> Result<(), BenchmarkError> {
let issuer: T::IdtyIndex = 1.into();
let caller: T::AccountId = T::IdtyAttr::owner_key(issuer).unwrap();
Pallet::<T>::on_smith_goes_online(1.into());
let caller_origin: <T as frame_system::Config>::RuntimeOrigin =
RawOrigin::Signed(caller.clone()).into();
// Should be the last identities from the local_testnet_config
let receiver: T::IdtyIndex = 6.into();
Pallet::<T>::invite_smith(caller_origin, receiver)?;
let issuer: T::IdtyIndex = 6.into();
let caller: T::AccountId = T::IdtyAttr::owner_key(issuer).unwrap();
#[extrinsic_call]
_(RawOrigin::Signed(caller));
assert_has_event::<T>(
Event::<T>::InvitationAccepted {
idty_index: receiver,
}
.into(),
);
Ok(())
}
#[benchmark]
fn certify_smith() -> Result<(), BenchmarkError> {
let issuer: T::IdtyIndex = 1.into();
let caller: T::AccountId = T::IdtyAttr::owner_key(issuer).unwrap();
Pallet::<T>::on_smith_goes_online(1.into());
let caller_origin: <T as frame_system::Config>::RuntimeOrigin =
RawOrigin::Signed(caller.clone()).into();
// Should be the last identities from the local_testnet_config
let receiver: T::IdtyIndex = 6.into();
Pallet::<T>::invite_smith(caller_origin, receiver)?;
let issuer: T::IdtyIndex = receiver;
let caller: T::AccountId = T::IdtyAttr::owner_key(issuer).unwrap();
let caller_origin: <T as frame_system::Config>::RuntimeOrigin =
RawOrigin::Signed(caller.clone()).into();
Pallet::<T>::accept_invitation(caller_origin)?;
let issuer: T::IdtyIndex = 1.into();
let caller: T::AccountId = T::IdtyAttr::owner_key(issuer).unwrap();
#[extrinsic_call]
_(RawOrigin::Signed(caller), receiver);
assert_has_event::<T>(Event::<T>::SmithCertAdded { receiver, issuer }.into());
Ok(())
}
#[benchmark]
fn on_removed_wot_member() {
let idty: T::IdtyIndex = 1.into();
assert!(Smiths::<T>::get(idty).is_some());
#[block]
{
Pallet::<T>::on_removed_wot_member(idty);
}
}
#[benchmark]
fn on_removed_wot_member_empty() {
let idty: T::IdtyIndex = 100.into();
assert!(Smiths::<T>::get(idty).is_none());
#[block]
{
Pallet::<T>::on_removed_wot_member(idty);
}
}
impl_benchmark_test_suite!(
Pallet,
crate::mock::new_test_ext(crate::GenesisConfig {
initial_smiths: maplit::btreemap![
1 => (false, vec![2, 3]),
2 => (false, vec![1, 3]),
3 => (false, vec![1, 2]),
],
}),
crate::mock::Runtime
);
}