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
  • distance
  • elois-ci-binary-release
  • elois-compose-metrics
  • elois-duniter-storage
  • elois-fix-85
  • elois-opti-cert
  • elois-remove-renewable-period
  • elois-rework-certs
  • elois-smoldot
  • elois-substrate-v0.9.23
  • elois-technical-commitee
  • hugo-cucumber-identity
  • master
  • no-bootnodes
  • poc-oneshot-accounts
  • release/runtime-100
  • release/runtime-200
  • ts-types
  • ud-time-64
  • runtime-100
  • runtime-101
  • runtime-102
  • runtime-103
  • runtime-104
  • runtime-105
  • runtime-200
  • runtime-201
  • v0.1.0
28 results
Show changes
Showing
with 2002 additions and 695 deletions
// 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)]
#![allow(clippy::unused_unit)]
pub use pallet::*;
/*#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;*/
use sp_std::prelude::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
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 {}
// STORAGE //
#[pallet::storage]
#[pallet::getter(fn ud_accounts)]
pub type UdAccounts<T: Config> =
CountedStorageMap<_, Blake2_128Concat, T::AccountId, (), ValueQuery>;
// GENESIS //
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub ud_accounts: sp_std::collections::btree_set::BTreeSet<T::AccountId>,
}
#[cfg(feature = "std")]
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
Self {
ud_accounts: Default::default(),
}
}
}
#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
fn build(&self) {
for account in &self.ud_accounts {
<UdAccounts<T>>::insert(account, ());
}
}
}
// PUBLIC FUNCTIONS //
impl<T: Config> Pallet<T> {
pub fn accounts_len() -> u32 {
<UdAccounts<T>>::count()
}
pub fn accounts_list() -> Vec<T::AccountId> {
<UdAccounts<T>>::iter_keys().collect()
}
pub fn replace_account(
old_account_opt: Option<T::AccountId>,
new_account_opt: Option<T::AccountId>,
) -> Weight {
if let Some(old_account) = old_account_opt {
if let Some(new_account) = new_account_opt {
Self::replace_account_inner(old_account, new_account)
} else {
Self::del_account(old_account)
}
} else if let Some(new_account) = new_account_opt {
Self::add_account(new_account)
} else {
0
}
}
pub fn remove_account(account_id: T::AccountId) -> Weight {
Self::del_account(account_id)
}
fn replace_account_inner(old_account: T::AccountId, new_account: T::AccountId) -> Weight {
if <UdAccounts<T>>::contains_key(&old_account) {
if !<UdAccounts<T>>::contains_key(&new_account) {
<UdAccounts<T>>::remove(&old_account);
<UdAccounts<T>>::insert(&new_account, ());
} else {
frame_support::runtime_print!(
"ERROR: replace_account(): new_account {:?} already added",
new_account
);
}
} else {
frame_support::runtime_print!(
"ERROR: replace_account(): old_account {:?} already deleted",
old_account
);
}
0
}
fn add_account(account: T::AccountId) -> Weight {
if !<UdAccounts<T>>::contains_key(&account) {
<UdAccounts<T>>::insert(&account, ());
} else {
frame_support::runtime_print!(
"ERROR: add_account(): account {:?} already added",
account
);
}
0
}
fn del_account(account: T::AccountId) -> Weight {
if <UdAccounts<T>>::contains_key(&account) {
<UdAccounts<T>>::remove(&account);
} else {
frame_support::runtime_print!(
"ERROR: del_account(): account {:?} already deleted",
account
);
}
0
}
}
}
[package] [package]
authors = ['librelois <c@elo.tf>'] authors.workspace = true
description = 'FRAME pallet universal dividend.' description = "duniter pallet universal dividend"
edition = '2018' edition.workspace = true
homepage = 'https://substrate.dev' homepage.workspace = true
license = 'AGPL-3.0' license.workspace = true
name = 'pallet-universal-dividend' name = "pallet-universal-dividend"
repository = 'https://git.duniter.org/nodes/rust/duniter-v2s' repository.workspace = true
version = '3.0.0' version.workspace = true
[features] [features]
default = ['std'] default = ["std"]
runtime-benchmarks = ['frame-benchmarking'] runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-timestamp/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-timestamp/runtime-benchmarks",
"pallet-timestamp/try-runtime",
"sp-runtime/try-runtime",
]
std = [ std = [
'codec/std', "codec/std",
'frame-support/std', "frame-benchmarking?/std",
'frame-system/std', "frame-support/std",
'frame-benchmarking/std', "frame-system/std",
"pallet-balances/std",
"pallet-timestamp/std",
"scale-info/std",
"serde/std",
"sp-api/std",
"sp-arithmetic/std", "sp-arithmetic/std",
"sp-core/std",
"sp-io/std", "sp-io/std",
"sp-std/std", "sp-runtime/std",
] ]
try-runtime = ['frame-support/try-runtime']
[dependencies]
# substrate
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
[dependencies.codec]
default-features = false
features = ['derive']
package = 'parity-scale-codec'
version = '2.3.1'
[dependencies.frame-benchmarking]
default-features = false
git = 'https://github.com/librelois/substrate.git'
optional = true
branch = 'duniter-monthly-2022-02'
[dependencies.frame-support]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.frame-system]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-arithmetic]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-io]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-std]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-runtime]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
### DOC ###
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ['x86_64-unknown-linux-gnu'] default-features = false
targets = ["x86_64-unknown-linux-gnu"]
### DEV ###
[dev-dependencies.pallet-balances]
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dev-dependencies.serde]
features = ["derive"]
version = '1.0.119'
[dev-dependencies.sp-core]
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dev-dependencies.sp-io]
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dev-dependencies.sp-runtime] [dependencies]
git = 'https://github.com/librelois/substrate.git' duniter-primitives = { workspace = true }
branch = 'duniter-monthly-2022-02' codec = { workspace = true, features = ["derive", "max-encoded-len"] }
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
pallet-balances = { workspace = true }
pallet-timestamp = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
serde = { workspace = true, features = ["derive"] }
sp-api = { workspace = true }
sp-arithmetic = { workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
[dev-dependencies]
sp-core = { workspace = true, default-features = true }
// Copyright 2021 Axiom-Team // Copyright 2021-2022 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
//! Benchmarking setup for pallet-universal-dividend #![cfg(feature = "runtime-benchmarks")]
#![allow(clippy::multiple_bound_locations)]
use super::*; use super::*;
use core::num::NonZeroU16;
#[allow(unused)] use frame_benchmarking::{account, v2::*, whitelisted_caller};
use crate::Pallet as UniversalDividend; use frame_support::{pallet_prelude::IsType, traits::StoredMap};
use frame_benchmarking::{benchmarks, impl_benchmark_test_suite, whitelisted_caller};
use frame_system::RawOrigin; use frame_system::RawOrigin;
use pallet_balances::Pallet as Balances;
use crate::Pallet;
const ED_MULTIPLIER: u32 = 10;
#[benchmarks(
where
T: pallet_balances::Config, T::Balance: From<u64>,
BalanceOf<T>: IsType<T::Balance>
)]
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());
}
// Create state for use in `on_initialize`. #[benchmark]
fn create_state<T: Config>(n: u32) -> Result<(), &'static str> { fn claim_uds(i: Linear<1, { T::MaxPastReeval::get() }>) -> Result<(), BenchmarkError> {
<LastReevalStorage<T>>::put(LastReeval { // Benchmark `transfer_ud` extrinsic with the worst possible conditions:
members_count: T::MembersCount::get(), // * Transfer will kill the sender account.
monetary_mass: T::Currency::total_issuance(), // * Transfer will create the recipient account.
ud_amount: new_ud_amount, let caller: T::AccountId = T::IdtyAttr::owner_key(1).unwrap();
}); CurrentUdIndex::<T>::put(2054u16);
T::MembersStorage::insert(
&caller,
FirstEligibleUd(Some(
NonZeroU16::new(CurrentUdIndex::<T>::get() - i as u16).unwrap(),
)),
)?;
let (_, uds_total) = compute_claim_uds::compute_claim_uds(
CurrentUdIndex::<T>::get(),
CurrentUdIndex::<T>::get() - i as u16,
PastReevals::<T>::get().into_iter(),
);
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()));
assert_has_event::<T>(
Event::<T>::UdsClaimed {
count: i as u16,
total: uds_total,
who: caller,
}
.into(),
);
Ok(()) Ok(())
} }
benchmarks! { #[benchmark]
create_ud { fn transfer_ud() {
run_to_block(2); let existential_deposit = T::ExistentialDeposit::get();
}: UniversalDividend::on_initialize() let caller = whitelisted_caller();
verify { let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
assert_eq!(System::events().len(), 7); let _ = T::Currency::set_balance(&caller, balance.into());
// Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account and reap this user.
let recipient: T::AccountId = account("recipient", 0, 1);
let recipient_lookup: <T::Lookup as StaticLookup>::Source =
T::Lookup::unlookup(recipient.clone());
let transfer_amount =
existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into();
let transfer_amount_ud =
transfer_amount.saturating_mul(1_000.into()) / Pallet::<T>::current_ud().into();
#[extrinsic_call]
_(
RawOrigin::Signed(caller.clone()),
recipient_lookup,
transfer_amount_ud.into(),
);
assert_eq!(Balances::<T>::free_balance(&caller), Zero::zero());
assert_eq!(Balances::<T>::free_balance(&recipient), transfer_amount);
}
#[benchmark]
fn transfer_ud_keep_alive() {
// Benchmark `transfer_ud_keep_alive` with the worst possible condition:
// * The recipient account is created.
let caller = whitelisted_caller();
let recipient: T::AccountId = account("recipient", 0, 1);
let recipient_lookup: <T::Lookup as StaticLookup>::Source =
T::Lookup::unlookup(recipient.clone());
// Give the sender account max funds, thus a transfer will not kill account.
let _ = T::Currency::set_balance(&caller, u32::MAX.into());
let existential_deposit = T::ExistentialDeposit::get();
let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
let transfer_amount_ud =
transfer_amount.saturating_mul(1_000.into()) / Pallet::<T>::current_ud().into();
#[extrinsic_call]
_(
RawOrigin::Signed(caller.clone()),
recipient_lookup,
transfer_amount_ud.into(),
);
assert!(!Balances::<T>::free_balance(&caller).is_zero());
assert_eq!(Balances::<T>::free_balance(&recipient), transfer_amount);
} }
#[benchmark]
fn on_removed_member(i: Linear<1, { T::MaxPastReeval::get() }>) -> Result<(), BenchmarkError> {
let caller: T::AccountId = T::IdtyAttr::owner_key(1).unwrap();
CurrentUdIndex::<T>::put(2054u16);
T::MembersStorage::insert(
&caller,
FirstEligibleUd(Some(
NonZeroU16::new(CurrentUdIndex::<T>::get() - i as u16).unwrap(),
)),
)?;
let (_, uds_total) = compute_claim_uds::compute_claim_uds(
CurrentUdIndex::<T>::get(),
CurrentUdIndex::<T>::get() - i as u16,
PastReevals::<T>::get().into_iter(),
);
#[block]
{
Pallet::<T>::on_removed_member(CurrentUdIndex::<T>::get() - i as u16, &caller);
}
if i != 0 {
assert_has_event::<T>(
Event::<T>::UdsAutoPaid {
count: i as u16,
total: uds_total,
who: caller,
}
.into(),
);
}
Ok(())
} }
impl_benchmark_test_suite!( impl_benchmark_test_suite!(
UniversalDividend, Pallet,
crate::mock::new_test_ext(UniversalDividendConfig { crate::mock::new_test_ext(crate::mock::UniversalDividendConfig {
first_ud: 1_000, first_reeval: Some(48_000),
first_ud: Some(6_000),
initial_monetary_mass: 0, initial_monetary_mass: 0,
initial_members: vec![1],
ud: 10,
}), }),
crate::mock::Test, crate::mock::Test
); );
}
// 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 super::UdIndex;
use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero};
pub(super) fn compute_claim_uds<Balance: AtLeast32BitUnsigned>(
mut current_ud_index: UdIndex,
first_ud_index: UdIndex,
past_reevals: impl DoubleEndedIterator<Item = (UdIndex, Balance)>,
) -> (UdIndex, Balance) {
let mut total_amount = Zero::zero();
let mut total_count = 0;
// We start in reverse order, i.e. the most recent reeval first
for (reeval_index, ud_amount) in past_reevals.rev() {
// Therefore, if our first UD is above the current reeval index, we have reached our final useful reeval and must break
if reeval_index <= first_ud_index {
let count = current_ud_index - first_ud_index;
total_amount += Balance::from(count) * ud_amount;
total_count += count;
// First unclaimed UD is reached; stop counting now.
break;
}
// Otherwise, we consume the full reeval contained UDs
else {
let count = current_ud_index - reeval_index;
total_amount += Balance::from(count) * ud_amount;
total_count += count;
current_ud_index = reeval_index;
}
}
(total_count, total_amount)
}
#[cfg(test)]
#[allow(clippy::unnecessary_cast)]
mod tests {
use super::*;
type Balance = u64;
#[test]
fn empty_case() {
let past_reevals = Vec::<(UdIndex, Balance)>::new();
assert_eq!(compute_claim_uds(11, 1, past_reevals.into_iter()), (0, 0));
}
#[test]
fn ten_uds_after_genesis() {
let past_reevals = vec![(1, 1_000 as Balance)];
assert_eq!(
compute_claim_uds(11, 1, past_reevals.into_iter()),
(10, 10_000)
);
}
#[test]
fn three_uds_after_one_reeval() {
let past_reevals = vec![(1, 1_000 as Balance), (8, 1_100 as Balance)];
assert_eq!(
compute_claim_uds(11, 1, past_reevals.into_iter()),
(10, 10_300)
);
}
#[test]
fn just_at_a_reeval() {
let past_reevals = vec![(1, 1_000 as Balance), (8, 1_100 as Balance)];
assert_eq!(
compute_claim_uds(9, 1, past_reevals.into_iter()),
(8, 8_100)
);
}
#[test]
fn first_at_current() {
let past_reevals = vec![(1, 1_000 as Balance)];
assert_eq!(compute_claim_uds(1, 1, past_reevals.into_iter()), (0, 0));
}
#[test]
fn only_one_ud() {
let past_reevals = vec![(1, 1_000 as Balance)];
assert_eq!(
compute_claim_uds(2, 1, past_reevals.into_iter()),
(1, 1_000)
);
}
#[test]
fn ud_for_joiner_after_reeval() {
let past_reevals = vec![
(1, 1_000 as Balance),
(2, 10_000 as Balance),
(3, 100_000 as Balance),
];
assert_eq!(
compute_claim_uds(4, 2, past_reevals.into_iter()),
(2, 110_000)
);
}
#[test]
fn very_old_unclaimed_ud_out_of_reevals() {
let past_reevals = vec![
// (3, 100 as Balance), "old" reeval which has gone out of reevals window.
(4, 1_000 as Balance),
(5, 10_000 as Balance),
(6, 100_000 as Balance),
];
// All the UDs out of the reeval window must produce 0 money units
assert_eq!(
compute_claim_uds(7, 1, past_reevals.into_iter()),
(3, 111_000)
);
}
}
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
//! # Duniter Universal Dividend Pallet
//!
//! One of Duniter's core features is the Universal Dividend (UD), which operates based on the Relative Theory of Money. The UD serves both as a daily monetary creation mechanism and a unit of measure within the Duniter ecosystem.
//!
//! ## Overview
//!
//! This pallet enables:
//! - Creation of Universal Dividends (UD) as a daily monetary issuance and measure unit.
//! - Transfer of currency denominated in UD between accounts.
//!
//! **Note**: The UD is not automatically created daily for every account due to resource constraints. Instead, members must claim their UD using a specific extrinsic.
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
pub use pallet::*; mod benchmarking;
mod compute_claim_uds;
mod runtime_api;
mod types;
mod weights;
#[cfg(test)] #[cfg(test)]
mod mock; mod mock;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
#[cfg(feature = "runtime-benchmarks")] #[cfg(feature = "runtime-benchmarks")]
mod benchmarking; use duniter_primitives::Idty;
use frame_support::traits::{tokens::ExistenceRequirement, Currency}; pub use pallet::*;
pub use runtime_api::*;
pub use types::*;
pub use weights::WeightInfo;
use frame_support::traits::{
fungible::{self, Balanced, Inspect, Mutate},
tokens::{Fortitude, Precision, Preservation},
OnTimestampSet, ReservableCurrency,
};
use sp_arithmetic::{ use sp_arithmetic::{
per_things::Perbill, per_things::Perbill,
traits::{One, Saturating, Zero}, traits::{EnsureMul, One, Saturating, Zero},
}; };
use sp_runtime::traits::StaticLookup; use sp_runtime::traits::{Get, MaybeSerializeDeserialize, StaticLookup};
use sp_std::prelude::*;
const OFFCHAIN_PREFIX_UD_HISTORY: &[u8] = b"ud::history::";
#[allow(unreachable_patterns)]
#[frame_support::pallet] #[frame_support::pallet]
pub mod pallet { pub mod pallet {
use super::*; use super::*;
use frame_support::pallet_prelude::*; use frame_support::{
use frame_support::traits::StorageVersion; pallet_prelude::*,
traits::{StorageVersion, StoredMap},
};
use frame_system::pallet_prelude::*; use frame_system::pallet_prelude::*;
use sp_runtime::traits::Convert; use sp_runtime::traits::Convert;
pub type BalanceOf<T> = type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance; pub type BalanceOf<T> = <<T as Config>::Currency as fungible::Inspect<AccountIdOf<T>>>::Balance;
/// The current storage version. /// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet] #[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::storage_version(STORAGE_VERSION)] #[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info]
pub struct Pallet<T>(_); pub struct Pallet<T>(_);
#[pallet::config] #[pallet::config]
pub trait Config: frame_system::Config { pub trait Config: frame_system::Config + pallet_timestamp::Config {
// BlockNumber into Balance converter /// Something that convert a Moment inot a Balance.
type BlockNumberIntoBalance: Convert<Self::BlockNumber, BalanceOf<Self>>; type MomentIntoBalance: Convert<Self::Moment, BalanceOf<Self>>;
// The currency
type Currency: Currency<Self::AccountId>; /// The currency type used in this pallet.
/// Because this pallet emits events, it depends on the runtime's definition of an event. type Currency: fungible::Balanced<Self::AccountId>
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>; + fungible::Mutate<Self::AccountId>
/// Somethings that must provide the number of accounts allowed to create the universal dividend + fungible::Inspect<Self::AccountId>
+ ReservableCurrency<Self::AccountId>;
/// Maximum number of past UD revaluations to keep in storage.
#[pallet::constant]
type MaxPastReeval: Get<u32>;
/// Provides the number of accounts allowed to create the universal dividend.
type MembersCount: Get<BalanceOf<Self>>; type MembersCount: Get<BalanceOf<Self>>;
/// Somethings that must provide the list of accounts ids allowed to create the universal dividend
type MembersIds: Get<Vec<<Self as frame_system::Config>::AccountId>>; /// Storage for mapping AccountId to their first eligible UD creation time.
type MembersStorage: frame_support::traits::StoredMap<Self::AccountId, FirstEligibleUd>;
/// The overarching event type for this pallet.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Square of the money growth rate per UD reevaluation period.
#[pallet::constant] #[pallet::constant]
/// Square of the money growth rate per ud reevaluation period
type SquareMoneyGrowthRate: Get<Perbill>; type SquareMoneyGrowthRate: Get<Perbill>;
/// Universal dividend creation period in milliseconds.
#[pallet::constant] #[pallet::constant]
/// Universal dividend creation period type UdCreationPeriod: Get<Self::Moment>;
type UdCreationPeriod: Get<Self::BlockNumber>;
#[pallet::constant] /// Universal dividend reevaluation period in milliseconds.
/// Universal dividend reevaluation period (in number of blocks)
type UdReevalPeriod: Get<Self::BlockNumber>;
#[pallet::constant] #[pallet::constant]
/// The number of units to divide the amounts expressed in number of UDs type UdReevalPeriod: Get<Self::Moment>;
/// Example: If you wish to express the UD amounts with a maximum precision of the order
/// of the milliUD, choose 1000 /// Type representing the weight of this pallet.
type UnitsPerUd: Get<BalanceOf<Self>>; type WeightInfo: WeightInfo;
/// Something that gives the IdtyIndex of an AccountId and reverse, used for benchmarks.
#[cfg(feature = "runtime-benchmarks")]
type IdtyAttr: duniter_primitives::Idty<u32, Self::AccountId>;
} }
// STORAGE // // STORAGE //
/// Current UD amount /// The current Universal Dividend value.
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn current_ud)] #[pallet::getter(fn current_ud)]
pub type CurrentUd<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>; pub type CurrentUd<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
/// Total quantity of money created by universal dividend (does not take into account the possible destruction of money) /// The default index for the current Universal Dividend.
#[pallet::type_value]
pub fn DefaultForCurrentUdIndex() -> UdIndex {
1
}
/// The current Universal Dividend index.
#[pallet::storage]
#[pallet::getter(fn ud_index)]
pub type CurrentUdIndex<T: Config> =
StorageValue<_, UdIndex, ValueQuery, DefaultForCurrentUdIndex>;
#[cfg(test)]
#[pallet::storage]
// UD should be linked to idtyid instead of accountid
// if it is convenient in test, why not have it in runtime also?
// storing it in idty_value.data is strange
pub type TestMembers<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
FirstEligibleUd,
ValueQuery,
GetDefault,
ConstU32<300_000>,
>;
/// The total quantity of money created by Universal Dividend, excluding potential money destruction.
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn total_money_created)] #[pallet::getter(fn total_money_created)]
pub type MonetaryMass<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>; pub type MonetaryMass<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
/// Next UD reevaluation /// The next Universal Dividend re-evaluation.
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn next_reeval)] #[pallet::getter(fn next_reeval)]
pub type NextReeval<T: Config> = StorageValue<_, T::BlockNumber, ValueQuery>; pub type NextReeval<T: Config> = StorageValue<_, T::Moment, OptionQuery>;
/// The next Universal Dividend creation.
#[pallet::storage]
#[pallet::getter(fn next_ud)]
pub type NextUd<T: Config> = StorageValue<_, T::Moment, OptionQuery>;
/// The past Universal Dividend re-evaluations.
#[pallet::storage]
#[pallet::getter(fn past_reevals)]
pub type PastReevals<T: Config> =
StorageValue<_, BoundedVec<(UdIndex, BalanceOf<T>), T::MaxPastReeval>, ValueQuery>;
// GENESIS // GENESIS
#[pallet::genesis_config] #[pallet::genesis_config]
pub struct GenesisConfig<T: Config> { pub struct GenesisConfig<T: Config>
pub first_reeval: T::BlockNumber, where
pub first_ud: BalanceOf<T>, <T as pallet_timestamp::Config>::Moment: MaybeSerializeDeserialize,
{
/// moment of the first UD reeval
// If None, it will be set to one period after the first block with a timestamp
pub first_reeval: Option<T::Moment>,
/// moment of the first UD generation
// If None, it will be set to one period after the first block with a timestamp
pub first_ud: Option<T::Moment>,
/// initial monetary mass (should match total issuance)
pub initial_monetary_mass: BalanceOf<T>, pub initial_monetary_mass: BalanceOf<T>,
/// accounts of initial members
// (only for test purpose)
#[cfg(test)]
pub initial_members: Vec<T::AccountId>,
/// value of the first UD
/// expressed in amount of currency
pub ud: BalanceOf<T>,
} }
#[cfg(feature = "std")] impl<T: Config> Default for GenesisConfig<T>
impl<T: Config> Default for GenesisConfig<T> { where
<T as pallet_timestamp::Config>::Moment: MaybeSerializeDeserialize,
{
fn default() -> Self { fn default() -> Self {
Self { Self {
first_reeval: Default::default(), first_reeval: None,
first_ud: Default::default(), first_ud: None,
initial_monetary_mass: Default::default(), initial_monetary_mass: Default::default(),
#[cfg(test)]
initial_members: Default::default(),
ud: BalanceOf::<T>::one(),
} }
} }
} }
#[pallet::genesis_build] #[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> { impl<T: Config> BuildGenesisConfig for GenesisConfig<T>
where
<T as pallet_timestamp::Config>::Moment: MaybeSerializeDeserialize,
{
fn build(&self) { fn build(&self) {
assert!(!self.first_ud.is_zero()); assert!(!self.ud.is_zero());
assert!(self.initial_monetary_mass >= T::Currency::total_issuance());
<CurrentUd<T>>::put(self.first_ud); <CurrentUd<T>>::put(self.ud);
// totalissuance should be updated to the same amount
<MonetaryMass<T>>::put(self.initial_monetary_mass); <MonetaryMass<T>>::put(self.initial_monetary_mass);
NextReeval::<T>::put(self.first_reeval);
}
}
// HOOKS // NextReeval::<T>::set(self.first_reeval);
NextUd::<T>::set(self.first_ud);
let mut past_reevals = BoundedVec::default();
past_reevals
.try_push((1, self.ud))
.expect("MaxPastReeval should be greather than zero");
PastReevals::<T>::put(past_reevals);
#[pallet::hooks] #[cfg(test)]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { {
fn on_initialize(n: T::BlockNumber) -> Weight { for member in &self.initial_members {
if (n % T::UdCreationPeriod::get()).is_zero() { TestMembers::<T>::insert(member, FirstEligibleUd::min());
let current_members_count = T::MembersCount::get();
let next_reeval = NextReeval::<T>::get();
if n >= next_reeval {
NextReeval::<T>::put(next_reeval.saturating_add(T::UdReevalPeriod::get()));
Self::reeval_ud(current_members_count)
+ Self::create_ud(current_members_count, n)
+ T::DbWeight::get().reads_writes(2, 1)
} else {
Self::create_ud(current_members_count, n) + T::DbWeight::get().reads(2)
} }
} else {
0
} }
} }
} }
...@@ -163,91 +254,159 @@ pub mod pallet { ...@@ -163,91 +254,159 @@ pub mod pallet {
#[pallet::event] #[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)] #[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> { pub enum Event<T: Config> {
/// A new universal dividend is created /// A new universal dividend is created.
/// [amout, members_count]
NewUdCreated { NewUdCreated {
amount: BalanceOf<T>, amount: BalanceOf<T>,
index: UdIndex,
monetary_mass: BalanceOf<T>, monetary_mass: BalanceOf<T>,
members_count: BalanceOf<T>, members_count: BalanceOf<T>,
}, },
/// The universal dividend has been re-evaluated /// The universal dividend has been re-evaluated.
/// [new_ud_amount, monetary_mass, members_count]
UdReevalued { UdReevalued {
new_ud_amount: BalanceOf<T>, new_ud_amount: BalanceOf<T>,
monetary_mass: BalanceOf<T>, monetary_mass: BalanceOf<T>,
members_count: BalanceOf<T>, members_count: BalanceOf<T>,
}, },
/// DUs were automatically transferred as part of a member removal.
UdsAutoPaid {
count: UdIndex,
total: BalanceOf<T>,
who: T::AccountId,
},
/// A member claimed his UDs.
UdsClaimed {
count: UdIndex,
total: BalanceOf<T>,
who: T::AccountId,
},
} }
// INTERNAL FUNCTIONS // // ERRORS //
impl<T: Config> Pallet<T> {
fn create_ud(members_count: BalanceOf<T>, n: T::BlockNumber) -> Weight {
let total_weight: Weight = 0;
let ud_amount = <CurrentUd<T>>::try_get().expect("corrupted storage");
let monetary_mass = <MonetaryMass<T>>::try_get().expect("corrupted storage");
for account_id in T::MembersIds::get() { #[pallet::error]
T::Currency::deposit_creating(&account_id, ud_amount); pub enum Error<T> {
Self::write_ud_history(n, account_id, ud_amount); /// This account is not allowed to claim UDs.
AccountNotAllowedToClaimUds,
} }
// INTERNAL FUNCTIONS //
impl<T: Config> Pallet<T> {
/// create universal dividend
pub(crate) fn create_ud(members_count: BalanceOf<T>) {
// get current value of UD and monetary mass
let ud_amount = <CurrentUd<T>>::get();
let monetary_mass = <MonetaryMass<T>>::get();
// Increment ud index
let ud_index = CurrentUdIndex::<T>::mutate(|next_ud_index| {
core::mem::replace(next_ud_index, next_ud_index.saturating_add(1))
});
// compute the new monetary mass
let new_monetary_mass = let new_monetary_mass =
monetary_mass.saturating_add(ud_amount.saturating_mul(members_count)); monetary_mass.saturating_add(ud_amount.saturating_mul(members_count));
// update the storage value of the monetary mass
MonetaryMass::<T>::put(new_monetary_mass); MonetaryMass::<T>::put(new_monetary_mass);
// emit an event to inform blockchain users that the holy UNIVERSAL DIVIDEND was created
Self::deposit_event(Event::NewUdCreated { Self::deposit_event(Event::NewUdCreated {
amount: ud_amount, amount: ud_amount,
index: ud_index,
members_count, members_count,
monetary_mass: new_monetary_mass, monetary_mass: new_monetary_mass,
}); });
}
total_weight /// claim all due universal dividend at a time
fn do_claim_uds(who: &T::AccountId) -> DispatchResultWithPostInfo {
T::MembersStorage::try_mutate_exists(who, |maybe_first_eligible_ud| {
if let Some(FirstEligibleUd(Some(ref mut first_ud_index))) = maybe_first_eligible_ud
{
let current_ud_index = CurrentUdIndex::<T>::get();
if first_ud_index.get() >= current_ud_index {
DispatchResultWithPostInfo::Ok(().into())
} else {
let (uds_count, uds_total) = compute_claim_uds::compute_claim_uds(
current_ud_index,
first_ud_index.get(),
PastReevals::<T>::get().into_iter(),
);
let _ = core::mem::replace(
first_ud_index,
core::num::NonZeroU16::new(current_ud_index)
.expect("unreachable because current_ud_index is never zero."),
);
// Currency is issued here
let actual_total = T::Currency::mint_into(who, uds_total)?;
Self::deposit_event(Event::UdsClaimed {
count: uds_count,
total: actual_total,
who: who.clone(),
});
Ok(().into())
}
} else {
Err(Error::<T>::AccountNotAllowedToClaimUds.into())
}
})
} }
/// like balance.transfer, but give an amount in milliUD
fn do_transfer_ud( fn do_transfer_ud(
origin: OriginFor<T>, origin: OriginFor<T>,
dest: <T::Lookup as StaticLookup>::Source, dest: <T::Lookup as StaticLookup>::Source,
value: BalanceOf<T>, value: BalanceOf<T>,
existence_requirement: ExistenceRequirement, preservation: Preservation,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?; let who = ensure_signed(origin)?;
let dest = T::Lookup::lookup(dest)?; let dest = T::Lookup::lookup(dest)?;
let ud_amount = let ud_amount = <CurrentUd<T>>::get();
<CurrentUd<T>>::try_get().map_err(|_| DispatchError::Other("corrupted storage"))?;
T::Currency::transfer( T::Currency::transfer(
&who, &who,
&dest, &dest,
value.saturating_mul(ud_amount) / T::UnitsPerUd::get(), value.ensure_mul(ud_amount)? / 1_000u32.into(),
existence_requirement, preservation,
)?; )?;
Ok(().into()) Ok(().into())
} }
fn reeval_ud(members_count: BalanceOf<T>) -> Weight {
let total_weight: Weight = 0;
let ud_amount = <CurrentUd<T>>::try_get().expect("corrupted storage"); /// reevaluate the value of the universal dividend
pub(crate) fn reeval_ud(members_count: BalanceOf<T>) {
let monetary_mass = <MonetaryMass<T>>::try_get().expect("corrupted storage"); // get current value and monetary mass
let ud_amount = <CurrentUd<T>>::get();
let monetary_mass = <MonetaryMass<T>>::get();
// compute new value
let new_ud_amount = Self::reeval_ud_formula( let new_ud_amount = Self::reeval_ud_formula(
ud_amount, ud_amount,
T::SquareMoneyGrowthRate::get(), T::SquareMoneyGrowthRate::get(),
monetary_mass, monetary_mass,
members_count, members_count,
T::BlockNumberIntoBalance::convert( T::MomentIntoBalance::convert(
T::UdReevalPeriod::get() / T::UdCreationPeriod::get(), T::UdReevalPeriod::get() / T::UdCreationPeriod::get(),
), ),
); );
<CurrentUd<T>>::put(new_ud_amount); // update the storage value and the history of past reevals
CurrentUd::<T>::put(new_ud_amount);
PastReevals::<T>::mutate(|past_reevals| {
if past_reevals.len() == T::MaxPastReeval::get() as usize {
past_reevals.remove(0);
}
past_reevals
.try_push((CurrentUdIndex::<T>::get(), new_ud_amount))
.expect("Unreachable, because we removed an element just before.")
});
Self::deposit_event(Event::UdReevalued { Self::deposit_event(Event::UdReevalued {
new_ud_amount, new_ud_amount,
monetary_mass, monetary_mass,
members_count, members_count,
}); });
total_weight
} }
/// formula for Universal Dividend reevaluation
fn reeval_ud_formula( fn reeval_ud_formula(
ud_t: BalanceOf<T>, ud_t: BalanceOf<T>,
c_square: Perbill, c_square: Perbill,
...@@ -255,7 +414,7 @@ pub mod pallet { ...@@ -255,7 +414,7 @@ pub mod pallet {
mut members_count: BalanceOf<T>, mut members_count: BalanceOf<T>,
count_uds_beetween_two_reevals: BalanceOf<T>, // =(dt/udFrequency) count_uds_beetween_two_reevals: BalanceOf<T>, // =(dt/udFrequency)
) -> BalanceOf<T> { ) -> BalanceOf<T> {
// Ensure that we not divide by zero // Ensure that we do not divide by zero
if members_count.is_zero() { if members_count.is_zero() {
members_count = One::one(); members_count = One::one();
} }
...@@ -263,37 +422,134 @@ pub mod pallet { ...@@ -263,37 +422,134 @@ pub mod pallet {
// UD(t+1) = UD(t) + c² (M(t+1) / N(t+1)) / (dt/udFrequency) // UD(t+1) = UD(t) + c² (M(t+1) / N(t+1)) / (dt/udFrequency)
ud_t + (c_square * monetary_mass) / (members_count * count_uds_beetween_two_reevals) ud_t + (c_square * monetary_mass) / (members_count * count_uds_beetween_two_reevals)
} }
fn write_ud_history(n: T::BlockNumber, account_id: T::AccountId, ud_amount: BalanceOf<T>) {
let mut key = Vec::with_capacity(57);
key.extend_from_slice(OFFCHAIN_PREFIX_UD_HISTORY);
account_id.encode_to(&mut key);
n.encode_to(&mut key);
sp_io::offchain_index::set(key.as_ref(), ud_amount.encode().as_ref());
}
} }
// CALLS // // CALLS //
#[pallet::call] #[pallet::call]
impl<T: Config> Pallet<T> { impl<T: Config> Pallet<T> {
/// Claim Universal Dividends.
#[pallet::call_index(0)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::claim_uds(T::MaxPastReeval::get()))]
pub fn claim_uds(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
Self::do_claim_uds(&who)
}
/// Transfer some liquid free balance to another account, in milliUD. /// Transfer some liquid free balance to another account, in milliUD.
#[pallet::weight(1_000_000_000)] #[pallet::call_index(1)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::transfer_ud())]
pub fn transfer_ud( pub fn transfer_ud(
origin: OriginFor<T>, origin: OriginFor<T>,
dest: <T::Lookup as StaticLookup>::Source, dest: <T::Lookup as StaticLookup>::Source,
#[pallet::compact] value: BalanceOf<T>, #[pallet::compact] value: BalanceOf<T>,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
Self::do_transfer_ud(origin, dest, value, ExistenceRequirement::AllowDeath) Self::do_transfer_ud(origin, dest, value, Preservation::Expendable)
} }
/// Transfer some liquid free balance to another account, in milliUD. /// Transfer some liquid free balance to another account in milliUD and keep the account alive.
#[pallet::weight(1_000_000_000)] #[pallet::call_index(2)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::transfer_ud_keep_alive())]
pub fn transfer_ud_keep_alive( pub fn transfer_ud_keep_alive(
origin: OriginFor<T>, origin: OriginFor<T>,
dest: <T::Lookup as StaticLookup>::Source, dest: <T::Lookup as StaticLookup>::Source,
#[pallet::compact] value: BalanceOf<T>, #[pallet::compact] value: BalanceOf<T>,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
Self::do_transfer_ud(origin, dest, value, ExistenceRequirement::KeepAlive) Self::do_transfer_ud(origin, dest, value, Preservation::Preserve)
}
}
// PUBLIC FUNCTIONS
impl<T: Config> Pallet<T> {
/// Initialize the first eligible Universal Dividend index.
pub fn init_first_eligible_ud() -> FirstEligibleUd {
CurrentUdIndex::<T>::get().into()
}
/// Handle the removal of a member, which automatically claims Universal Dividends.
pub fn on_removed_member(first_ud_index: UdIndex, who: &T::AccountId) -> Weight {
let current_ud_index = CurrentUdIndex::<T>::get();
if first_ud_index < current_ud_index {
let (uds_count, uds_total) = compute_claim_uds::compute_claim_uds(
current_ud_index,
first_ud_index,
PastReevals::<T>::get().into_iter(),
);
let _ = T::Currency::deposit(who, uds_total, Precision::Exact);
Self::deposit_event(Event::UdsAutoPaid {
count: uds_count,
total: uds_total,
who: who.clone(),
});
<T as pallet::Config>::WeightInfo::on_removed_member(first_ud_index as u32)
} else {
<T as pallet::Config>::WeightInfo::on_removed_member(0)
}
}
/// Get the total balance information for an account
///
/// Returns an object with three fields:
/// - `transferable`: sum of free + unclaim_uds
/// - `reserved`: reserved balance
/// - `unclaim_uds`: amount of unclaimed UDs computed by compute_claim_uds
pub fn account_balances(who: &T::AccountId) -> crate::AccountBalances<BalanceOf<T>> {
let total_balance = T::Currency::total_balance(who);
let reducible_balance =
T::Currency::reducible_balance(who, Preservation::Preserve, Fortitude::Polite);
// Calculate unclaimed UDs
let current_ud_index = CurrentUdIndex::<T>::get();
let maybe_first_eligible_ud = T::MembersStorage::get(who);
let unclaim_uds =
if let FirstEligibleUd(Some(ref first_ud_index)) = maybe_first_eligible_ud {
let past_reevals = PastReevals::<T>::get();
compute_claim_uds::compute_claim_uds(
current_ud_index,
first_ud_index.get(),
past_reevals.into_iter(),
)
.1
} else {
Zero::zero()
};
crate::AccountBalances {
total: total_balance.saturating_add(unclaim_uds),
transferable: reducible_balance.saturating_add(unclaim_uds),
unclaim_uds,
}
}
}
}
impl<T: Config> OnTimestampSet<T::Moment> for Pallet<T>
where
<T as pallet_timestamp::Config>::Moment: MaybeSerializeDeserialize,
{
fn on_timestamp_set(moment: T::Moment) {
let next_ud = NextUd::<T>::get().unwrap_or_else(|| {
let next_ud = moment.saturating_add(T::UdCreationPeriod::get());
NextUd::<T>::put(next_ud);
next_ud
});
if moment >= next_ud {
let current_members_count = T::MembersCount::get();
let next_reeval = NextReeval::<T>::get().unwrap_or_else(|| {
let next_reeval = moment.saturating_add(T::UdReevalPeriod::get());
NextReeval::<T>::put(next_reeval);
next_reeval
});
// Reevaluation may happen later than expected, but this has no effect before a new UD
// is created. This is why we can check for reevaluation only when creating UD.
if moment >= next_reeval {
NextReeval::<T>::put(next_reeval.saturating_add(T::UdReevalPeriod::get()));
Self::reeval_ud(current_members_count);
}
Self::create_ud(current_members_count);
NextUd::<T>::put(next_ud.saturating_add(T::UdCreationPeriod::get()));
} }
} }
} }
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use super::*; use super::*;
use crate::{self as pallet_universal_dividend}; use crate::{self as pallet_universal_dividend};
use frame_support::{ use frame_support::{
parameter_types, derive_impl, parameter_types,
traits::{Everything, Get, OnFinalize, OnInitialize}, traits::{Everything, OnFinalize, OnInitialize},
}; };
use frame_system as system; use frame_system as system;
use sp_core::H256; use sp_core::{ConstU32, H256};
use sp_runtime::{ use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup}, traits::{BlakeTwo256, IdentityLookup},
BuildStorage, BuildStorage,
}; };
pub const BLOCK_TIME: u64 = 6_000;
type Balance = u64; type Balance = u64;
type BlockNumber = u64;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>; type Block = frame_system::mocking::MockBlock<Test>;
// Configure a mock runtime to test the pallet. // Configure a mock runtime to test the pallet.
frame_support::construct_runtime!( frame_support::construct_runtime!(
pub enum Test where pub enum Test
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{ {
System: frame_system::{Pallet, Call, Config, Storage, Event<T>}, System: frame_system,
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>}, Timestamp: pallet_timestamp,
UniversalDividend: pallet_universal_dividend::{Pallet, Storage, Config<T>, Event<T>}, Balances: pallet_balances,
UniversalDividend: pallet_universal_dividend,
} }
); );
...@@ -51,83 +48,108 @@ parameter_types! { ...@@ -51,83 +48,108 @@ parameter_types! {
pub const SS58Prefix: u8 = 42; pub const SS58Prefix: u8 = 42;
} }
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl system::Config for Test { impl system::Config for Test {
type AccountData = pallet_balances::AccountData<Balance>;
type AccountId = u32;
type BaseCallFilter = Everything; type BaseCallFilter = Everything;
type BlockWeights = (); type Block = Block;
type BlockLength = (); type BlockHashCount = BlockHashCount;
type DbWeight = ();
type Origin = Origin;
type Call = Call;
type Index = u64;
type BlockNumber = u64;
type Hash = H256; type Hash = H256;
type Hashing = BlakeTwo256; type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>; type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header; type MaxConsumers = frame_support::traits::ConstU32<16>;
type Event = Event; type Nonce = u64;
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo; type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<Balance>; type RuntimeCall = RuntimeCall;
type OnNewAccount = (); type RuntimeEvent = RuntimeEvent;
type OnKilledAccount = (); type RuntimeOrigin = RuntimeOrigin;
type SystemWeightInfo = ();
type SS58Prefix = SS58Prefix; type SS58Prefix = SS58Prefix;
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
} }
parameter_types! { parameter_types! {
pub const ExistentialDeposit: Balance = 1; pub const MinimumPeriod: u64 = 3_000;
}
impl pallet_timestamp::Config for Test {
type MinimumPeriod = MinimumPeriod;
type Moment = u64;
type OnTimestampSet = UniversalDividend;
type WeightInfo = ();
}
parameter_types! {
pub const ExistentialDeposit: Balance = 10;
pub const MaxLocks: u32 = 50; pub const MaxLocks: u32 = 50;
} }
impl pallet_balances::Config for Test { impl pallet_balances::Config for Test {
type AccountStore = System;
type Balance = Balance; type Balance = Balance;
type DoneSlashHandler = ();
type DustRemoval = (); type DustRemoval = ();
type Event = Event;
type ExistentialDeposit = ExistentialDeposit; type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System; type FreezeIdentifier = ();
type WeightInfo = pallet_balances::weights::SubstrateWeight<Test>; type MaxFreezes = ConstU32<0>;
type MaxLocks = MaxLocks; type MaxLocks = MaxLocks;
type MaxReserves = (); type MaxReserves = ();
type ReserveIdentifier = [u8; 8]; type ReserveIdentifier = [u8; 8];
type RuntimeEvent = RuntimeEvent;
type RuntimeFreezeReason = ();
type RuntimeHoldReason = ();
type WeightInfo = pallet_balances::weights::SubstrateWeight<Test>;
} }
parameter_types! { parameter_types! {
pub const MembersCount: u64 = 3; pub const MembersCount: u64 = 3;
pub const SquareMoneyGrowthRate: Perbill = Perbill::from_percent(10); pub const SquareMoneyGrowthRate: Perbill = Perbill::from_percent(10);
pub const UdCreationPeriod: BlockNumber = 2; pub const UdCreationPeriod: u64 = 12_000;
pub const UdReevalPeriod: BlockNumber = 8; pub const UdReevalPeriod: u64 = 48_000;
} }
pub struct FakeWot; pub struct TestMembersStorage;
impl Get<Vec<u64>> for FakeWot { impl frame_support::traits::StoredMap<u32, FirstEligibleUd> for TestMembersStorage {
fn get() -> Vec<u64> { fn get(key: &u32) -> FirstEligibleUd {
vec![1, 2, 3] crate::TestMembers::<Test>::get(key)
}
fn try_mutate_exists<R, E: From<sp_runtime::DispatchError>>(
key: &u32,
f: impl FnOnce(&mut Option<FirstEligibleUd>) -> Result<R, E>,
) -> Result<R, E> {
let mut value = Some(crate::TestMembers::<Test>::get(key));
let result = f(&mut value)?;
if let Some(value) = value {
crate::TestMembers::<Test>::insert(key, value)
}
Ok(result)
} }
} }
impl pallet_universal_dividend::Config for Test { impl pallet_universal_dividend::Config for Test {
type BlockNumberIntoBalance = sp_runtime::traits::ConvertInto;
type Currency = pallet_balances::Pallet<Test>; type Currency = pallet_balances::Pallet<Test>;
type Event = Event; #[cfg(feature = "runtime-benchmarks")]
type IdtyAttr = ();
type MaxPastReeval = frame_support::traits::ConstU32<2>;
type MembersCount = MembersCount; type MembersCount = MembersCount;
type MembersIds = FakeWot; type MembersStorage = TestMembersStorage;
type MomentIntoBalance = sp_runtime::traits::ConvertInto;
type RuntimeEvent = RuntimeEvent;
type SquareMoneyGrowthRate = SquareMoneyGrowthRate; type SquareMoneyGrowthRate = SquareMoneyGrowthRate;
type UdCreationPeriod = UdCreationPeriod; type UdCreationPeriod = UdCreationPeriod;
type UdReevalPeriod = UdReevalPeriod; type UdReevalPeriod = UdReevalPeriod;
type UnitsPerUd = frame_support::traits::ConstU64<1_000>; type WeightInfo = ();
} }
// Build genesis storage according to the mock runtime. // Build genesis storage according to the mock runtime.
pub fn new_test_ext( pub fn new_test_ext(
gen_conf: pallet_universal_dividend::GenesisConfig<Test>, gen_conf: pallet_universal_dividend::GenesisConfig<Test>,
) -> sp_io::TestExternalities { ) -> sp_io::TestExternalities {
GenesisConfig { RuntimeGenesisConfig {
system: SystemConfig::default(), system: SystemConfig::default(),
balances: BalancesConfig::default(), balances: BalancesConfig {
total_issuance: gen_conf.initial_monetary_mass,
},
universal_dividend: gen_conf, universal_dividend: gen_conf,
} }
.build_storage() .build_storage()
...@@ -139,8 +161,15 @@ pub fn run_to_block(n: u64) { ...@@ -139,8 +161,15 @@ pub fn run_to_block(n: u64) {
while System::block_number() < n { while System::block_number() < n {
UniversalDividend::on_finalize(System::block_number()); UniversalDividend::on_finalize(System::block_number());
System::on_finalize(System::block_number()); System::on_finalize(System::block_number());
System::reset_events();
System::set_block_number(System::block_number() + 1); System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number()); System::on_initialize(System::block_number());
UniversalDividend::on_initialize(System::block_number()); UniversalDividend::on_initialize(System::block_number());
Timestamp::set_timestamp(System::block_number() * BLOCK_TIME);
} }
} }
/// Helper function to mint tokens for testing purposes
pub fn mint_into(who: &u32, amount: Balance) -> Result<Balance, sp_runtime::DispatchError> {
<Test as pallet_universal_dividend::Config>::Currency::mint_into(who, amount)
}
// 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 codec::{Codec, Decode, Encode};
use scale_info::TypeInfo;
use sp_runtime::RuntimeDebug;
sp_api::decl_runtime_apis! {
/// Runtime API for Universal Dividend pallet
pub trait UniversalDividendApi<AccountId, Balance>
where
AccountId: Codec,
AccountBalances<Balance>: Codec,
{
/// Get the total balance information for an account
///
/// Returns an object with three fields:
/// - `total`: total balance
/// - `transferable`: sum of reducible + unclaim_uds
/// - `unclaim_uds`: amount of unclaimed UDs
fn account_balances(account: AccountId) -> AccountBalances<Balance>;
}
}
/// Account total balance information
#[derive(Encode, Decode, TypeInfo, Clone, PartialEq, RuntimeDebug)]
pub struct AccountBalances<Balance> {
/// The total amount of funds for which the user is the ultimate beneficial owner.
/// Includes funds that may not be transferable (e.g., reserved balance, existential deposit).
pub total: Balance,
/// The maximum amount of funds that can be successfully withdrawn or transferred
/// (includes unclaimed UDs).
pub transferable: Balance,
/// The total amount of unclaimed UDs (accounts for any re-evaluations of UDs).
pub unclaim_uds: Balance,
}
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use crate::mock::*; use crate::mock::*;
use frame_system::{EventRecord, Phase}; use frame_support::{assert_err, assert_ok, assert_storage_noop, traits::ReservableCurrency};
use sp_runtime::{ArithmeticError, DispatchError};
#[test] #[test]
fn test_ud_creation() { fn test_claim_uds() {
new_test_ext(UniversalDividendConfig { new_test_ext(UniversalDividendConfig {
first_reeval: 8, first_reeval: Some(48_000),
first_ud: 1_000, first_ud: Some(12_000),
initial_monetary_mass: 0, initial_monetary_mass: 0,
initial_members: vec![1, 2, 3],
ud: 1_000,
}) })
.execute_with(|| { .execute_with(|| {
// In the beginning there was no money // In the beginning there was no money
...@@ -32,82 +35,604 @@ fn test_ud_creation() { ...@@ -32,82 +35,604 @@ fn test_ud_creation() {
assert_eq!(Balances::free_balance(4), 0); assert_eq!(Balances::free_balance(4), 0);
assert_eq!(UniversalDividend::total_money_created(), 0); assert_eq!(UniversalDividend::total_money_created(), 0);
// The first UD must be created in block #2 // Alice can claim UDs, but this should be a no-op.
run_to_block(1);
assert_storage_noop!(assert_ok!(UniversalDividend::claim_uds(
RuntimeOrigin::signed(1)
)));
assert_eq!(Balances::free_balance(1), 0);
// Dave is not a member, he can't claim UDs
assert_err!(
UniversalDividend::claim_uds(RuntimeOrigin::signed(4)),
crate::Error::<Test>::AccountNotAllowedToClaimUds
);
// At block #2, the first UD must be created, but nobody should receive money
run_to_block(2); run_to_block(2);
assert_eq!(UniversalDividend::total_money_created(), 3_000);
assert_eq!(Balances::free_balance(1), 0);
assert_eq!(Balances::free_balance(2), 0);
assert_eq!(Balances::free_balance(3), 0);
assert_eq!(Balances::free_balance(4), 0);
// Alice can claim UDs, and this time she must receive exactly one UD
assert_ok!(UniversalDividend::claim_uds(RuntimeOrigin::signed(1)));
System::assert_has_event(RuntimeEvent::UniversalDividend(crate::Event::UdsClaimed {
count: 1,
total: 1_000,
who: 1,
}));
// the expected event from pallet balances is Minted
System::assert_has_event(RuntimeEvent::Balances(pallet_balances::Event::Minted {
who: 1,
amount: 1000,
}));
assert_eq!(Balances::free_balance(1), 1_000); assert_eq!(Balances::free_balance(1), 1_000);
assert_eq!(Balances::free_balance(2), 1_000); // Others members should not receive any UDs with Alice claim
assert_eq!(Balances::free_balance(3), 1_000); assert_eq!(Balances::free_balance(2), 0);
assert_eq!(Balances::free_balance(3), 0);
assert_eq!(Balances::free_balance(4), 0);
// At block #4, the second UD must be created, but nobody should receive money
run_to_block(4);
assert_eq!(UniversalDividend::total_money_created(), 6_000);
assert_eq!(Balances::free_balance(1), 1_000);
assert_eq!(Balances::free_balance(2), 0);
assert_eq!(Balances::free_balance(3), 0);
assert_eq!(Balances::free_balance(4), 0);
// Alice can claim UDs, And she must receive exactly one UD (the second one)
assert_ok!(UniversalDividend::claim_uds(RuntimeOrigin::signed(1)));
System::assert_has_event(RuntimeEvent::UniversalDividend(crate::Event::UdsClaimed {
count: 1,
total: 1_000,
who: 1,
}));
assert_eq!(Balances::free_balance(1), 2_000);
// Others members should not receive any UDs with Alice claim
assert_eq!(Balances::free_balance(2), 0);
assert_eq!(Balances::free_balance(3), 0);
assert_eq!(Balances::free_balance(4), 0);
// Bob can claim UDs, he must receive exactly two UDs
assert_ok!(UniversalDividend::claim_uds(RuntimeOrigin::signed(2)));
System::assert_has_event(RuntimeEvent::UniversalDividend(crate::Event::UdsClaimed {
count: 2,
total: 2_000,
who: 2,
}));
assert_eq!(Balances::free_balance(2), 2_000);
// Others members should not receive any UDs with Alice and Bob claims
assert_eq!(Balances::free_balance(3), 0);
assert_eq!(Balances::free_balance(4), 0); assert_eq!(Balances::free_balance(4), 0);
// Dave is still not a member, he still can't claim UDs.
assert_err!(
UniversalDividend::claim_uds(RuntimeOrigin::signed(4)),
crate::Error::<Test>::AccountNotAllowedToClaimUds
);
// At block #8, the first reevaluated UD should be created
run_to_block(8);
assert_eq!(UniversalDividend::total_money_created(), 12_225);
// Charlie can claim all his UDs at once, he must receive exactly four UDs
assert_ok!(UniversalDividend::claim_uds(RuntimeOrigin::signed(3)));
System::assert_has_event(RuntimeEvent::UniversalDividend(crate::Event::UdsClaimed {
count: 4,
total: 4_075,
who: 3,
}));
assert_eq!(Balances::free_balance(3), 4_075);
// At block #16, the second reevaluated UD should be created
run_to_block(16);
assert_eq!(UniversalDividend::total_money_created(), 25_671);
// Charlie can claim new UD, he must receive exactly four UDs
assert_ok!(UniversalDividend::claim_uds(RuntimeOrigin::signed(3)));
System::assert_has_event(RuntimeEvent::UniversalDividend(crate::Event::UdsClaimed {
count: 4,
total: 4_482,
who: 3,
}));
assert_eq!(Balances::free_balance(3), 8557);
});
}
#[test]
fn test_claim_uds_using_genesis_timestamp() {
new_test_ext(UniversalDividendConfig {
first_reeval: None,
first_ud: None,
initial_monetary_mass: 0,
initial_members: vec![1, 2, 3],
ud: 1_000,
})
.execute_with(|| {
// In the beginning there was no money
assert_eq!(Balances::free_balance(1), 0);
assert_eq!(Balances::free_balance(2), 0);
assert_eq!(Balances::free_balance(3), 0);
assert_eq!(Balances::free_balance(4), 0);
assert_eq!(UniversalDividend::total_money_created(), 0);
// Alice can claim UDs, but this should be a no-op.
run_to_block(1);
assert_storage_noop!(assert_ok!(UniversalDividend::claim_uds(
RuntimeOrigin::signed(1)
)));
assert_eq!(Balances::free_balance(1), 0);
// Dave is not a member, he can't claim UDs
assert_err!(
UniversalDividend::claim_uds(RuntimeOrigin::signed(4)),
crate::Error::<Test>::AccountNotAllowedToClaimUds
);
// At block #3, the first UD must be created, but nobody should receive money
run_to_block(3);
assert_eq!(UniversalDividend::total_money_created(), 3_000); assert_eq!(UniversalDividend::total_money_created(), 3_000);
assert_eq!(Balances::free_balance(1), 0);
assert_eq!(Balances::free_balance(2), 0);
assert_eq!(Balances::free_balance(3), 0);
assert_eq!(Balances::free_balance(4), 0);
// Block #2 must generate 7 events, 2 events per new account fed, plus 1 event for the creation of the UD. // Alice can claim UDs, and this time she must receive exactly one UD
let events = System::events(); assert_ok!(UniversalDividend::claim_uds(RuntimeOrigin::signed(1)));
println!("events: {:#?}", events); System::assert_has_event(RuntimeEvent::UniversalDividend(crate::Event::UdsClaimed {
assert_eq!(events.len(), 10); count: 1,
assert_eq!( total: 1_000,
events[9], who: 1,
EventRecord { }));
phase: Phase::Initialization, assert_eq!(Balances::free_balance(1), 1_000);
event: Event::UniversalDividend(crate::Event::NewUdCreated { // Others members should not receive any UDs with Alice claim
assert_eq!(Balances::free_balance(2), 0);
assert_eq!(Balances::free_balance(3), 0);
assert_eq!(Balances::free_balance(4), 0);
// At block #5, the second UD must be created, but nobody should receive money
run_to_block(5);
assert_eq!(UniversalDividend::total_money_created(), 6_000);
assert_eq!(Balances::free_balance(1), 1_000);
assert_eq!(Balances::free_balance(2), 0);
assert_eq!(Balances::free_balance(3), 0);
assert_eq!(Balances::free_balance(4), 0);
// Alice can claim UDs, And she must receive exactly one UD (the second one)
assert_ok!(UniversalDividend::claim_uds(RuntimeOrigin::signed(1)));
System::assert_has_event(RuntimeEvent::UniversalDividend(crate::Event::UdsClaimed {
count: 1,
total: 1_000,
who: 1,
}));
assert_eq!(Balances::free_balance(1), 2_000);
// Others members should not receive any UDs with Alice claim
assert_eq!(Balances::free_balance(2), 0);
assert_eq!(Balances::free_balance(3), 0);
assert_eq!(Balances::free_balance(4), 0);
// Bob can claim UDs, he must receive exactly two UDs
assert_ok!(UniversalDividend::claim_uds(RuntimeOrigin::signed(2)));
System::assert_has_event(RuntimeEvent::UniversalDividend(crate::Event::UdsClaimed {
count: 2,
total: 2_000,
who: 2,
}));
assert_eq!(Balances::free_balance(2), 2_000);
// Others members should not receive any UDs with Alice and Bob claims
assert_eq!(Balances::free_balance(3), 0);
assert_eq!(Balances::free_balance(4), 0);
// Dave is still not a member, he still can't claim UDs.
assert_err!(
UniversalDividend::claim_uds(RuntimeOrigin::signed(4)),
crate::Error::<Test>::AccountNotAllowedToClaimUds
);
// At block #11, the first reevaluated UD should be created
run_to_block(11);
assert_eq!(UniversalDividend::total_money_created(), 15_300);
});
}
#[test]
fn test_ud_creation() {
new_test_ext(UniversalDividendConfig {
first_reeval: Some(48_000),
first_ud: Some(12_000),
initial_monetary_mass: 0,
initial_members: vec![1, 2, 3],
ud: 1_000,
})
.execute_with(|| {
// In the beginning there was no money
assert_eq!(Balances::free_balance(1), 0);
assert_eq!(Balances::free_balance(2), 0);
assert_eq!(Balances::free_balance(3), 0);
assert_eq!(Balances::free_balance(4), 0);
assert_eq!(UniversalDividend::total_money_created(), 0);
// The first UD must be created in block #2
run_to_block(2);
System::assert_has_event(RuntimeEvent::UniversalDividend(
crate::Event::NewUdCreated {
amount: 1_000, amount: 1_000,
index: 1,
monetary_mass: 3_000, monetary_mass: 3_000,
members_count: 3, members_count: 3,
}), },
topics: vec![], ));
} assert_eq!(UniversalDividend::total_money_created(), 3_000);
); /*assert_eq!(Balances::free_balance(1), 1_000);
assert_eq!(Balances::free_balance(2), 1_000);
assert_eq!(Balances::free_balance(3), 1_000);
assert_eq!(Balances::free_balance(4), 0);*/
// The second UD must be created in block #4 // The second UD must be created in block #4
run_to_block(4); run_to_block(4);
assert_eq!(Balances::free_balance(1), 2_000); System::assert_has_event(RuntimeEvent::UniversalDividend(
crate::Event::NewUdCreated {
amount: 1_000,
index: 2,
monetary_mass: 6_000,
members_count: 3,
},
));
assert_eq!(UniversalDividend::total_money_created(), 6_000);
/*assert_eq!(Balances::free_balance(1), 2_000);
assert_eq!(Balances::free_balance(2), 2_000); assert_eq!(Balances::free_balance(2), 2_000);
assert_eq!(Balances::free_balance(3), 2_000); assert_eq!(Balances::free_balance(3), 2_000);
assert_eq!(Balances::free_balance(4), 0); assert_eq!(Balances::free_balance(4), 0);*/
assert_eq!(UniversalDividend::total_money_created(), 6_000);
/*// Block #4 must generate 4 events, 1 event per account fed, plus 1 event for the creation of the UD.
let events = System::events();
println!("{:?}", events);
assert_eq!(events.len(), 4);
assert_eq!(
events[3],
EventRecord {
phase: Phase::Initialization,
event: Event::UniversalDividend(crate::Event::NewUdCreated(1000, 3)),
topics: vec![],
}
);*/
// The third UD must be created in block #6 // The third UD must be created in block #6
run_to_block(6); run_to_block(6);
assert_eq!(Balances::free_balance(1), 3_000); System::assert_has_event(RuntimeEvent::UniversalDividend(
crate::Event::NewUdCreated {
amount: 1_000,
index: 3,
monetary_mass: 9_000,
members_count: 3,
},
));
assert_eq!(UniversalDividend::total_money_created(), 9_000);
/*assert_eq!(Balances::free_balance(1), 3_000);
assert_eq!(Balances::free_balance(2), 3_000); assert_eq!(Balances::free_balance(2), 3_000);
assert_eq!(Balances::free_balance(3), 3_000); assert_eq!(Balances::free_balance(3), 3_000);
assert_eq!(Balances::free_balance(4), 0); assert_eq!(Balances::free_balance(4), 0);*/
assert_eq!(UniversalDividend::total_money_created(), 9_000);
// Block #8 should cause a re-evaluation of UD // Block #8 should cause a re-evaluation of UD
run_to_block(8); run_to_block(8);
assert_eq!(Balances::free_balance(1), 4_075); System::assert_has_event(RuntimeEvent::UniversalDividend(crate::Event::UdReevalued {
new_ud_amount: 1_075,
monetary_mass: 9_000,
members_count: 3,
}));
// Then, the first reevalued UD should be created
System::assert_has_event(RuntimeEvent::UniversalDividend(
crate::Event::NewUdCreated {
amount: 1_075,
index: 4,
monetary_mass: 12_225,
members_count: 3,
},
));
assert_eq!(UniversalDividend::total_money_created(), 12_225);
/*assert_eq!(Balances::free_balance(1), 4_075);
assert_eq!(Balances::free_balance(2), 4_075); assert_eq!(Balances::free_balance(2), 4_075);
assert_eq!(Balances::free_balance(3), 4_075); assert_eq!(Balances::free_balance(3), 4_075);
assert_eq!(Balances::free_balance(4), 0); assert_eq!(Balances::free_balance(4), 0);*/
assert_eq!(UniversalDividend::total_money_created(), 12_225);
// Block #10 #12 and #14should creates the reevalued UD // Block #10 #12 and #14should creates the reevalued UD
run_to_block(14); run_to_block(14);
assert_eq!(Balances::free_balance(1), 7_300); System::assert_has_event(RuntimeEvent::UniversalDividend(
assert_eq!(Balances::free_balance(2), 7_300); crate::Event::NewUdCreated {
assert_eq!(Balances::free_balance(3), 7_300); amount: 1_075,
assert_eq!(Balances::free_balance(4), 0); index: 7,
monetary_mass: 21_900,
members_count: 3,
},
));
assert_eq!(UniversalDividend::total_money_created(), 21_900); assert_eq!(UniversalDividend::total_money_created(), 21_900);
// Block #16 should cause a second re-evaluation of UD // Block #16 should cause a second re-evaluation of UD
run_to_block(16); run_to_block(16);
assert_eq!(Balances::free_balance(1), 8_557); System::assert_has_event(RuntimeEvent::UniversalDividend(crate::Event::UdReevalued {
assert_eq!(Balances::free_balance(2), 8_557); new_ud_amount: 1_257,
assert_eq!(Balances::free_balance(3), 8_557); monetary_mass: 21_900,
assert_eq!(Balances::free_balance(4), 0); members_count: 3,
}));
// Then, the reevalued UD should be created
System::assert_has_event(RuntimeEvent::UniversalDividend(
crate::Event::NewUdCreated {
amount: 1_257,
index: 8,
monetary_mass: 25_671,
members_count: 3,
},
));
assert_eq!(UniversalDividend::total_money_created(), 25_671); assert_eq!(UniversalDividend::total_money_created(), 25_671);
}); });
} }
#[test]
fn test_account_balances() {
new_test_ext(UniversalDividendConfig {
first_reeval: Some(48_000),
first_ud: Some(12_000),
initial_monetary_mass: 0,
initial_members: vec![1, 2, 3],
ud: 1_000,
})
.execute_with(|| {
// Initially, all accounts have zero balance
let balance_info = UniversalDividend::account_balances(&1);
assert_eq!(balance_info.transferable, 0);
assert_eq!(balance_info.total, 0);
assert_eq!(balance_info.unclaim_uds, 0);
// Create some UDs and claim them
run_to_block(2);
assert_ok!(UniversalDividend::claim_uds(RuntimeOrigin::signed(1)));
// Check balance after claiming
let balance_info = UniversalDividend::account_balances(&1);
assert_eq!(balance_info.transferable, 1000 - 10); // free (1000) + unclaim_uds (0) - existantial deposit (10)
assert_eq!(balance_info.total, 1000); // transferable + reserved
assert_eq!(balance_info.unclaim_uds, 0);
// Create more UDs but don't claim them
run_to_block(4);
let balance_info = UniversalDividend::account_balances(&1);
assert_eq!(balance_info.transferable, 1000 + 1000 - 10); // free (1000) + unclaim_uds (1000) - existantial deposit (10)
assert_eq!(balance_info.total, 2000); // transferable + reserved
assert_eq!(balance_info.unclaim_uds, 1000);
// Test with reserved balance
assert_ok!(Balances::reserve(&1, 500));
let balance_info = UniversalDividend::account_balances(&1);
assert_eq!(balance_info.transferable, 500 + 1000 - 10); // free (500) + unclaim_uds (1000) - existantial deposit (10)
assert_eq!(balance_info.total, 2000); // transferable + reserved
assert_eq!(balance_info.unclaim_uds, 1000);
// Test non-member account
let balance_info = UniversalDividend::account_balances(&4);
assert_eq!(balance_info.transferable, 0);
assert_eq!(balance_info.total, 0);
assert_eq!(balance_info.unclaim_uds, 0);
});
}
#[test]
fn test_transfer_ud_overflow() {
new_test_ext(UniversalDividendConfig {
first_reeval: Some(48_000),
first_ud: Some(12_000),
initial_monetary_mass: 0,
initial_members: vec![1, 2, 3],
ud: 1_000,
})
.execute_with(|| {
// Give account 1 some balance to work with
let _ = mint_into(&1, 1_000_000);
assert_eq!(Balances::free_balance(1), 1_000_000);
// Test overflow scenario: try to transfer a very large value in milliUD
// that when multiplied by current_ud (1000) would overflow u64
let max_u64 = u64::MAX;
let overflow_value = max_u64 / 1000 + 1; // This will overflow when multiplied by 1000
assert_err!(
UniversalDividend::transfer_ud(RuntimeOrigin::signed(1), 2, overflow_value),
DispatchError::Arithmetic(ArithmeticError::Overflow),
);
});
}
#[test]
fn test_transfer_ud_keep_alive_overflow() {
new_test_ext(UniversalDividendConfig {
first_reeval: Some(48_000),
first_ud: Some(12_000),
initial_monetary_mass: 0,
initial_members: vec![1, 2, 3],
ud: 1_000,
})
.execute_with(|| {
// Give account 1 some balance to work with
let _ = mint_into(&1, 1_000_000);
assert_eq!(Balances::free_balance(1), 1_000_000);
// Test overflow scenario: try to transfer a very large value in milliUD
// that when multiplied by current_ud (1000) would overflow u64
let max_u64 = u64::MAX;
let overflow_value = max_u64 / 1000 + 1; // This will overflow when multiplied by 1000
assert_err!(
UniversalDividend::transfer_ud_keep_alive(RuntimeOrigin::signed(1), 2, overflow_value),
DispatchError::Arithmetic(ArithmeticError::Overflow),
);
});
}
#[test]
fn test_transfer_ud_underflow() {
new_test_ext(UniversalDividendConfig {
first_reeval: Some(48_000),
first_ud: Some(12_000),
initial_monetary_mass: 0,
initial_members: vec![1, 2, 3],
ud: 1_000,
})
.execute_with(|| {
// Give account 1 some balance to work with
let _ = mint_into(&1, 1_000_000);
assert_eq!(Balances::free_balance(1), 1_000_000);
// Test underflow scenario: try to transfer a value that when divided by 1000
// would result in 0 (which is not a valid transfer amount)
let underflow_value = 999; // 999 * 1000 / 1000 = 999, which is valid
// This should work because 999 milliUD = 999 actual units
assert_ok!(UniversalDividend::transfer_ud(
RuntimeOrigin::signed(1),
2,
underflow_value
));
assert_eq!(Balances::free_balance(2), 999);
// Test with minimum valid value (1000 milliUD = 1 UD)
assert_ok!(UniversalDividend::transfer_ud(
RuntimeOrigin::signed(1),
2,
1000
));
assert_eq!(Balances::free_balance(2), 1999); // 999 + 1000
});
}
#[test]
fn test_transfer_ud_keep_alive_underflow() {
new_test_ext(UniversalDividendConfig {
first_reeval: Some(48_000),
first_ud: Some(12_000),
initial_monetary_mass: 0,
initial_members: vec![1, 2, 3],
ud: 1_000,
})
.execute_with(|| {
// Give account 1 some balance to work with
let _ = mint_into(&1, 1_000_000);
assert_eq!(Balances::free_balance(1), 1_000_000);
// Test underflow scenario: try to transfer a value that when divided by 1000
// would result in 0 (which is not a valid transfer amount)
let underflow_value = 999; // 999 * 1000 / 1000 = 999, which is valid
// This should work because 999 milliUD = 999 actual units
assert_ok!(UniversalDividend::transfer_ud_keep_alive(
RuntimeOrigin::signed(1),
2,
underflow_value
));
assert_eq!(Balances::free_balance(2), 999);
// Test with minimum valid value (1000 milliUD = 1 UD)
assert_ok!(UniversalDividend::transfer_ud_keep_alive(
RuntimeOrigin::signed(1),
2,
1000
));
assert_eq!(Balances::free_balance(2), 1999); // 999 + 1000
});
}
#[test]
fn test_transfer_ud_edge_cases() {
new_test_ext(UniversalDividendConfig {
first_reeval: Some(48_000),
first_ud: Some(12_000),
initial_monetary_mass: 0,
initial_members: vec![1, 2, 3],
ud: 1_000,
})
.execute_with(|| {
// Give account 1 some balance to work with
let _ = mint_into(&1, 1_000_000);
assert_eq!(Balances::free_balance(1), 1_000_000);
// Test with zero value (should work - 0 * 1000 / 1000 = 0)
assert_ok!(UniversalDividend::transfer_ud(
RuntimeOrigin::signed(1),
2,
0
));
assert_eq!(Balances::free_balance(2), 0);
// Test with very small values that should work
assert_ok!(UniversalDividend::transfer_ud(
RuntimeOrigin::signed(1),
2,
1000
)); // 1 UD
assert_eq!(Balances::free_balance(2), 1000);
assert_ok!(UniversalDividend::transfer_ud(
RuntimeOrigin::signed(1),
2,
1500
)); // 1.5 UD
assert_eq!(Balances::free_balance(2), 2500);
});
}
#[test]
fn test_transfer_ud_keep_alive_edge_cases() {
new_test_ext(UniversalDividendConfig {
first_reeval: Some(48_000),
first_ud: Some(12_000),
initial_monetary_mass: 0,
initial_members: vec![1, 2, 3],
ud: 1_000,
})
.execute_with(|| {
// Give account 1 some balance to work with
let _ = mint_into(&1, 1_000_000);
assert_eq!(Balances::free_balance(1), 1_000_000);
// Test with zero value (should work - 0 * 1000 / 1000 = 0)
assert_ok!(UniversalDividend::transfer_ud_keep_alive(
RuntimeOrigin::signed(1),
2,
0
));
assert_eq!(Balances::free_balance(2), 0);
// Test with very small values that should work
assert_ok!(UniversalDividend::transfer_ud_keep_alive(
RuntimeOrigin::signed(1),
2,
1000
)); // 1 UD
assert_eq!(Balances::free_balance(2), 1000);
assert_ok!(UniversalDividend::transfer_ud_keep_alive(
RuntimeOrigin::signed(1),
2,
1500
)); // 1.5 UD
assert_eq!(Balances::free_balance(2), 2500);
});
}
#[test]
fn test_transfer_ud_insufficient_balance() {
new_test_ext(UniversalDividendConfig {
first_reeval: Some(48_000),
first_ud: Some(12_000),
initial_monetary_mass: 0,
initial_members: vec![1, 2, 3],
ud: 1_000,
})
.execute_with(|| {
// Give account 1 minimal balance
let _ = mint_into(&1, 100);
assert_eq!(Balances::free_balance(1), 100);
// Try to transfer more than available balance
assert_err!(
UniversalDividend::transfer_ud(RuntimeOrigin::signed(1), 2, 2000), // Would require 2000 balance
DispatchError::Arithmetic(ArithmeticError::Underflow),
);
// Try to transfer exactly the available balance
assert_ok!(UniversalDividend::transfer_ud(
RuntimeOrigin::signed(1),
2,
100
)); // 100 milliUD = 0.1 UD
assert_eq!(Balances::free_balance(2), 100);
assert_eq!(Balances::free_balance(1), 0);
});
}
// 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 codec::{Decode, DecodeWithMemTracking, Encode, Error, Input, MaxEncodedLen, Output};
use core::num::NonZeroU16;
use scale_info::prelude::vec::Vec;
use sp_runtime::RuntimeDebug;
pub type UdIndex = u16;
/// Represents the first eligible Universal Dividend.
#[derive(
Clone, Eq, DecodeWithMemTracking, PartialEq, RuntimeDebug, serde::Deserialize, serde::Serialize,
)]
pub struct FirstEligibleUd(pub Option<NonZeroU16>);
/// Default is not eligible
impl Default for FirstEligibleUd {
fn default() -> Self {
FirstEligibleUd(None)
}
}
impl FirstEligibleUd {
/// Eligible at the first UD index
pub fn min() -> Self {
Self(Some(NonZeroU16::new(1).expect("unreachable")))
}
}
impl From<UdIndex> for FirstEligibleUd {
fn from(ud_index: UdIndex) -> Self {
FirstEligibleUd(NonZeroU16::new(ud_index))
}
}
impl From<FirstEligibleUd> for Option<UdIndex> {
fn from(first_eligible_ud: FirstEligibleUd) -> Self {
first_eligible_ud.0.map(|ud_index| ud_index.get())
}
}
impl Encode for FirstEligibleUd {
fn size_hint(&self) -> usize {
self.as_u16().size_hint()
}
fn encode_to<W: Output + ?Sized>(&self, dest: &mut W) {
self.as_u16().encode_to(dest)
}
fn encode(&self) -> Vec<u8> {
self.as_u16().encode()
}
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
self.as_u16().using_encoded(f)
}
}
impl codec::EncodeLike for FirstEligibleUd {}
impl Decode for FirstEligibleUd {
fn decode<I: Input>(input: &mut I) -> Result<Self, Error> {
Ok(match NonZeroU16::new(Decode::decode(input)?) {
Some(non_zero_u16) => Self(Some(non_zero_u16)),
None => Self(None),
})
}
}
impl MaxEncodedLen for FirstEligibleUd {
fn max_encoded_len() -> usize {
u16::max_encoded_len()
}
}
impl scale_info::TypeInfo for FirstEligibleUd {
type Identity = UdIndex;
fn type_info() -> scale_info::Type {
Self::Identity::type_info()
}
}
impl FirstEligibleUd {
// private
#[inline(always)]
fn as_u16(&self) -> UdIndex {
self.0.map(|ud_index| ud_index.get()).unwrap_or_default()
}
}
// 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 claim_uds(i: u32) -> Weight;
fn transfer_ud() -> Weight;
fn transfer_ud_keep_alive() -> Weight;
fn on_removed_member(i: u32) -> Weight;
}
// Insecure weights implementation, use it for tests only!
impl WeightInfo for () {
fn claim_uds(i: u32) -> Weight {
Weight::from_parts(32_514_000, 0)
// Standard Error: 32_000
.saturating_add(Weight::from_parts(8_000, 0).saturating_mul(i as u64))
.saturating_add(RocksDbWeight::get().reads(4))
.saturating_add(RocksDbWeight::get().writes(1))
}
// Storage: UniversalDividend CurrentUd (r:1 w:0)
// Storage: System Account (r:1 w:1)
fn transfer_ud() -> Weight {
Weight::from_parts(53_401_000, 0)
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(2))
}
// Storage: UniversalDividend CurrentUd (r:1 w:0)
// Storage: System Account (r:1 w:1)
fn transfer_ud_keep_alive() -> Weight {
Weight::from_parts(33_420_000, 0)
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(2))
}
fn on_removed_member(i: u32) -> Weight {
Weight::from_parts(32_514_000, 0)
// Standard Error: 32_000
.saturating_add(Weight::from_parts(8_000, 0).saturating_mul(i as u64))
.saturating_add(RocksDbWeight::get().reads(4))
.saturating_add(RocksDbWeight::get().writes(1))
}
}
[package] [package]
authors = ['librelois <c@elo.tf>'] authors.workspace = true
description = 'FRAME pallet to upgrade specified origin to root.' description = "duniter pallet to upgrade specified origin to root"
edition = '2018' edition.workspace = true
homepage = 'https://substrate.dev' homepage.workspace = true
license = 'AGPL-3.0' license.workspace = true
name = 'pallet-upgrade-origin' name = "pallet-upgrade-origin"
repository = 'https://git.duniter.org/nodes/rust/duniter-v2s' repository.workspace = true
version = '3.0.0' version.workspace = true
[features] [features]
default = ['std'] default = ["std"]
runtime-benchmarks = ['frame-benchmarking'] runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"sp-staking/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"sp-runtime/try-runtime",
]
std = [ std = [
'codec/std', "codec/std",
'frame-support/std', "frame-benchmarking?/std",
'frame-system/std', "frame-support/std",
'frame-benchmarking/std', "frame-system/std",
"scale-info/std",
"sp-core/std",
"sp-io/std", "sp-io/std",
"sp-std/std", "sp-runtime/std",
"sp-staking/std",
] ]
try-runtime = ['frame-support/try-runtime']
[dependencies]
# substrate
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
[dependencies.codec]
default-features = false
features = ['derive']
package = 'parity-scale-codec'
version = '2.3.1'
[dependencies.frame-benchmarking]
default-features = false
git = 'https://github.com/librelois/substrate.git'
optional = true
branch = 'duniter-monthly-2022-02'
[dependencies.frame-support]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.frame-system] [package.metadata.docs.rs]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-io]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-std]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-runtime]
default-features = false default-features = false
git = 'https://github.com/librelois/substrate.git' targets = ["x86_64-unknown-linux-gnu"]
branch = 'duniter-monthly-2022-02'
### DOC ###
[package.metadata.docs.rs] [dependencies]
targets = ['x86_64-unknown-linux-gnu'] codec = { workspace = true, features = ["derive"] }
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
sp-staking = { workspace = true }
// 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 crate::Pallet;
use frame_benchmarking::v2::*;
use frame_support::traits::Get;
use scale_info::prelude::vec;
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn dispatch_as_root() {
let call = Box::new(frame_system::Call::remark { remark: vec![] }.into());
let origin: T::WorstCaseOriginType = T::WorstCaseOrigin::get();
#[extrinsic_call]
_(origin, call);
}
}
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::boxed_local)] #![allow(clippy::boxed_local)]
mod benchmarking;
mod weights;
pub use pallet::*;
pub use weights::WeightInfo;
use frame_support::{ use frame_support::{
dispatch::PostDispatchInfo, dispatch::{GetDispatchInfo, PostDispatchInfo},
traits::{IsSubType, UnfilteredDispatchable}, traits::{IsSubType, UnfilteredDispatchable},
weights::GetDispatchInfo,
}; };
use scale_info::prelude::boxed::Box;
use sp_runtime::traits::Dispatchable; use sp_runtime::traits::Dispatchable;
use sp_std::prelude::*;
pub use pallet::*; #[allow(unreachable_patterns)]
#[frame_support::pallet] #[frame_support::pallet]
pub mod pallet { pub mod pallet {
use super::*; use super::*;
...@@ -33,26 +38,36 @@ pub mod pallet { ...@@ -33,26 +38,36 @@ pub mod pallet {
use frame_system::pallet_prelude::*; use frame_system::pallet_prelude::*;
#[pallet::pallet] #[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_); pub struct Pallet<T>(_);
/// Configuration trait. /// Configuration trait.
#[pallet::config] #[pallet::config]
pub trait Config: frame_system::Config { pub trait Config: frame_system::Config {
/// The overarching event type.
type Event: From<Event> + IsType<<Self as frame_system::Config>::Event>;
/// The overarching call type. /// The overarching call type.
type Call: Parameter type Call: Parameter
+ Dispatchable<Origin = Self::Origin, PostInfo = PostDispatchInfo> + Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
+ GetDispatchInfo + GetDispatchInfo
+ From<frame_system::Call<Self>> + From<frame_system::Call<Self>>
+ UnfilteredDispatchable<Origin = Self::Origin> + UnfilteredDispatchable<RuntimeOrigin = Self::RuntimeOrigin>
+ IsSubType<Call<Self>> + IsSubType<Call<Self>>
+ IsType<<Self as frame_system::Config>::Call>; + IsType<<Self as frame_system::Config>::RuntimeCall>;
/// The overarching event type.
type RuntimeEvent: From<Event> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// The origin type required for performing upgradable operations.
type UpgradableOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// Type representing the weight of this pallet.
type WeightInfo: WeightInfo;
/// Type representing the worst case origin type used in weight benchmarks.
#[cfg(feature = "runtime-benchmarks")]
type WorstCaseOriginType: Into<Self::RuntimeOrigin>;
/// The upgradable origin /// Retrieves the worst case origin for use in weight benchmarks.
type UpgradableOrigin: EnsureOrigin<Self::Origin>; #[cfg(feature = "runtime-benchmarks")]
type WorstCaseOrigin: Get<Self::WorstCaseOriginType>;
} }
#[pallet::event] #[pallet::event]
...@@ -67,11 +82,18 @@ pub mod pallet { ...@@ -67,11 +82,18 @@ pub mod pallet {
/// Dispatches a function call from root origin. /// Dispatches a function call from root origin.
/// ///
/// The weight of this call is defined by the caller. /// The weight of this call is defined by the caller.
#[pallet::weight(*_weight)] #[pallet::call_index(0)]
#[pallet::weight({
let dispatch_info = call.get_dispatch_info();
(
T::WeightInfo::dispatch_as_root()
.saturating_add(dispatch_info.call_weight).saturating_add(dispatch_info.extension_weight),
dispatch_info.class,
)
})]
pub fn dispatch_as_root( pub fn dispatch_as_root(
origin: OriginFor<T>, origin: OriginFor<T>,
call: Box<<T as Config>::Call>, call: Box<<T as Config>::Call>,
_weight: Weight,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
T::UpgradableOrigin::ensure_origin(origin)?; T::UpgradableOrigin::ensure_origin(origin)?;
...@@ -82,5 +104,28 @@ pub mod pallet { ...@@ -82,5 +104,28 @@ pub mod pallet {
}); });
Ok(Pays::No.into()) Ok(Pays::No.into())
} }
/// Dispatches a function call from root origin.
/// This function does not check the weight of the call, and instead allows the
/// caller to specify the weight of the call.
///
/// The weight of this call is defined by the caller.
#[pallet::call_index(1)]
#[pallet::weight((*weight, call.get_dispatch_info().class))]
pub fn dispatch_as_root_unchecked_weight(
origin: OriginFor<T>,
call: Box<<T as Config>::Call>,
weight: Weight,
) -> DispatchResultWithPostInfo {
let _ = weight; // We dont need to check the weight witness.
T::UpgradableOrigin::ensure_origin(origin)?;
let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into());
Self::deposit_event(Event::DispatchedAsRoot {
result: res.map(|_| ()).map_err(|e| e.error),
});
Ok(Pays::No.into())
}
} }
} }
// 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::Weight;
/// Weight functions needed for pallet_upgrade_origin.
pub trait WeightInfo {
fn dispatch_as_root() -> Weight;
}
// Insecure weights implementation, use it for tests only!
impl WeightInfo for () {
fn dispatch_as_root() -> Weight {
Weight::from_parts(8_000, 0)
}
}
# Primitives
TODO
\ No newline at end of file
[package]
authors.workspace = true
description = "primitives for pallet distance"
edition.workspace = true
homepage.workspace = true
license.workspace = true
name = "sp-distance"
readme = "README.md"
repository.workspace = true
version.workspace = true
[package.metadata.docs.rs]
default-features = false
targets = ["x86_64-unknown-linux-gnu"]
[features]
default = ["std"]
std = [
"async-trait",
"codec/std",
"frame-support/std",
"scale-info/std",
"serde/std",
"sp-inherents/std",
"sp-runtime/std",
"thiserror",
]
try-runtime = ["frame-support/try-runtime", "sp-runtime/try-runtime"]
runtime-benchmarks = []
[dependencies]
async-trait = { workspace = true, optional = true }
codec = { workspace = true, features = ["derive"] }
frame-support = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
serde = { workspace = true, features = ["derive"] }
sp-inherents = { workspace = true }
sp-runtime = { workspace = true }
thiserror = { workspace = true, optional = true }
// 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/>.
//! Defines types and traits for users of pallet distance.
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::type_complexity)]
use codec::{Decode, DecodeWithMemTracking, Encode};
use frame_support::pallet_prelude::RuntimeDebug;
use scale_info::TypeInfo;
use sp_inherents::{InherentIdentifier, IsFatalError};
use sp_runtime::Perbill;
#[cfg(feature = "std")]
use std::marker::PhantomData;
pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"distanc0";
/// Represents the result of a distance computation.
#[derive(Clone, DecodeWithMemTracking, Decode, Encode, PartialEq, RuntimeDebug, TypeInfo)]
pub struct ComputationResult {
pub distances: scale_info::prelude::vec::Vec<Perbill>,
}
/// Errors that can occur while checking the inherent data in `ProvideInherent::check_inherent` from pallet-distance.
#[derive(Encode, sp_runtime::RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Decode, thiserror::Error))]
pub enum InherentError {}
impl IsFatalError for InherentError {
fn is_fatal_error(&self) -> bool {
false
}
}
#[cfg(feature = "std")]
pub struct InherentDataProvider<IdtyIndex: Decode + Encode + PartialEq + TypeInfo> {
computation_result: Option<ComputationResult>,
_p: PhantomData<IdtyIndex>,
}
#[cfg(feature = "std")]
impl<IdtyIndex: Decode + Encode + PartialEq + TypeInfo> InherentDataProvider<IdtyIndex> {
pub fn new(computation_result: Option<ComputationResult>) -> Self {
Self {
computation_result,
_p: PhantomData,
}
}
}
#[cfg(feature = "std")]
#[async_trait::async_trait]
impl<IdtyIndex: Decode + Encode + PartialEq + TypeInfo + Send + Sync>
sp_inherents::InherentDataProvider for InherentDataProvider<IdtyIndex>
{
async fn provide_inherent_data(
&self,
inherent_data: &mut sp_inherents::InherentData,
) -> Result<(), sp_inherents::Error> {
if let Some(computation_result) = &self.computation_result {
inherent_data.put_data(INHERENT_IDENTIFIER, computation_result)?;
}
Ok(())
}
async fn try_handle_error(
&self,
_identifier: &InherentIdentifier,
_error: &[u8],
) -> Option<Result<(), sp_inherents::Error>> {
// No errors occur here.
// Errors handled here are emitted in the `ProvideInherent::check_inherent`
// (from pallet-distance) which is not implemented.
None
}
}
[package] [package]
authors = ['librelois <c@elo.tf>'] authors.workspace = true
description = 'primitives for duniter runtime.' description = "primitives for duniter runtime"
edition = '2018' edition.workspace = true
homepage = 'https://substrate.dev' homepage.workspace = true
license = 'AGPL-3.0' license.workspace = true
name = 'duniter-primitives' name = "duniter-primitives"
readme = 'README.md' readme = "README.md"
repository = 'https://git.duniter.org/nodes/rust/duniter-v2s' repository.workspace = true
version = '3.0.0' version.workspace = true
[features] [package.metadata.docs.rs]
default = ['std']
std = [
'codec/std',
'frame-support/std',
'sp-runtime/std',
'sp-std/std',
]
try-runtime = ['frame-support/try-runtime']
[dependencies]
# substrate
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
[dependencies.codec]
default-features = false
features = ['derive']
package = 'parity-scale-codec'
version = '2.3.1'
[dependencies.frame-support]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-runtime]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-std]
default-features = false default-features = false
git = 'https://github.com/librelois/substrate.git' targets = ["x86_64-unknown-linux-gnu"]
branch = 'duniter-monthly-2022-02'
### DOC ### [dependencies]
sp-runtime = { workspace = true }
[package.metadata.docs.rs] [features]
targets = ['x86_64-unknown-linux-gnu'] default = ["std"]
std = [
"sp-runtime/std",
]
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
/// Bound length; forbid trailing or double spaces; accept only ascii alphanumeric or punctuation or space /// Checks rules for valid identity names
/// - Limit length to 42
/// - Accept only ascii alphanumeric or `-` or `_`
pub fn validate_idty_name(idty_name: &[u8]) -> bool { pub fn validate_idty_name(idty_name: &[u8]) -> bool {
idty_name.len() >= 3 idty_name.len() >= 3
&& idty_name.len() <= 64 && idty_name.len() <= 42
&& idty_name[0] != 32
&& idty_name[idty_name.len() - 1] != 32
&& idty_name && idty_name
.iter() .iter()
.all(|c| c.is_ascii_alphanumeric() || c.is_ascii_punctuation() || *c == 32) .all(|c| c.is_ascii_alphanumeric() || *c == b'-' || *c == b'_')
&& idty_name }
.iter()
.zip(idty_name.iter().skip(1)) pub trait GetSigner<Lookup: sp_runtime::traits::StaticLookup> {
.all(|(c1, c2)| *c1 != 32 || *c2 != 32) fn get_signer(&self) -> Option<Lookup::Target>;
}
impl<Address: Clone, Lookup, Call, Signature, Extension> GetSigner<Lookup>
for sp_runtime::generic::UncheckedExtrinsic<Address, Call, Signature, Extension>
where
Lookup: sp_runtime::traits::StaticLookup<Source = Address>,
{
fn get_signer(&self) -> Option<Lookup::Target> {
match &self.preamble {
sp_runtime::generic::Preamble::Signed(signer, _, _) => {
Lookup::lookup(signer.clone()).ok()
}
_ => None,
}
}
}
/// trait used to go from index to owner key and reverse
// replaces less explicit "Convert" implementations
pub trait Idty<IdtyIndex, AccountId> {
fn owner_key(index: IdtyIndex) -> Option<AccountId>;
fn idty_index(owner_key: AccountId) -> Option<IdtyIndex>;
}
// mock implementation for any type
impl<T> Idty<T, T> for () {
fn owner_key(t: T) -> Option<T> {
Some(t)
}
fn idty_index(t: T) -> Option<T> {
Some(t)
}
} }
#[cfg(test)] #[cfg(test)]
...@@ -37,12 +70,19 @@ mod tests { ...@@ -37,12 +70,19 @@ mod tests {
#[test] #[test]
fn test_validate_idty_name() { fn test_validate_idty_name() {
// --- allow
assert!(validate_idty_name(b"B0b")); assert!(validate_idty_name(b"B0b"));
assert!(validate_idty_name(b"lorem ipsum dolor-sit_amet.")); assert!(validate_idty_name(b"lorem_ipsum-dolor-sit_amet"));
assert!(!validate_idty_name(b" space")); assert!(validate_idty_name(
assert!(!validate_idty_name(b"space ")); b"1_______10________20________30________40_-"
assert!(!validate_idty_name(b"double space")); ));
// --- disallow
assert!(!validate_idty_name(
b"1_______10________20________30________40_-_"
));
assert!(!validate_idty_name(b"with space"));
assert!(!validate_idty_name("non-ascii🌵".as_bytes())); assert!(!validate_idty_name("non-ascii🌵".as_bytes()));
assert!(!validate_idty_name("ğune".as_bytes())); assert!(!validate_idty_name("ğune".as_bytes()));
assert!(!validate_idty_name("toto!".as_bytes()));
} }
} }
[package] [package]
authors = ['librelois <c@elo.tf>'] authors.workspace = true
description = 'primitives for pallet membership.' description = "primitives for pallet membership"
edition = '2018' edition.workspace = true
homepage = 'https://substrate.dev' homepage.workspace = true
license = 'AGPL-3.0' license.workspace = true
name = 'sp-membership' name = "sp-membership"
readme = 'README.md' readme = "README.md"
repository = 'https://git.duniter.org/nodes/rust/duniter-v2s' repository.workspace = true
version = '3.0.0' version.workspace = true
[package.metadata.docs.rs]
default-features = false
targets = ["x86_64-unknown-linux-gnu"]
[features] [features]
default = ['std'] default = ["std"]
std = [ std = [
'codec/std', "codec/std",
'frame-support/std', "frame-support/std",
'serde', "scale-info/std",
'sp-runtime/std', "serde/std",
'sp-std/std', "sp-runtime/std",
] ]
try-runtime = ['frame-support/try-runtime'] try-runtime = ["frame-support/try-runtime", "sp-runtime/try-runtime"]
runtime-benchmarks = []
[dependencies] [dependencies]
codec = { workspace = true, features = ["derive"] }
# substrate frame-support = { workspace = true }
scale-info = { version = "1.0", default-features = false, features = ["derive"] } scale-info = { workspace = true, features = ["derive"] }
serde = { workspace = true }
[dependencies.codec] sp-runtime = { workspace = true }
default-features = false
features = ['derive']
package = 'parity-scale-codec'
version = '2.3.1'
[dependencies.frame-support]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.serde]
version = "1.0.101"
optional = true
features = ["derive"]
[dependencies.sp-runtime]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-std]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
### DOC ###
[package.metadata.docs.rs]
targets = ['x86_64-unknown-linux-gnu']