From 52ebb5ef47cf29536c156b6b93151c1421cd6657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lo=C3=AFs?= <c@elo.tf> Date: Tue, 5 Jul 2022 14:33:14 +0200 Subject: [PATCH] =?UTF-8?q?feat(runtime):=C2=A0create=20UDs=20manually=20w?= =?UTF-8?q?ith=20new=20call=20universalDividend.claim=5Fuds=20(!83)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ref: move IdtyDataIter in common runtime * bench real weights for pallet ud * bench claim_uds * weights constants * weights: ParametersStorage is whitelisted * pallet ud on_initialize weights * manual ud: not emit event if 0 UDs claimed * tests(gdev): add int test test_remove_identity_after_one_ud * auto claim uds at member removal * migrate pallet id integration tests to manual ud * remove all try_get in pallet ud * write PastReevals * add call claim_uds * remove pallet ud_accounts_storage * update pallet ud interface * create type FirstEligibleUd * add custom IdtyData --- .cargo/config | 1 + .vscode/launch.json | 19 -- Cargo.lock | 21 -- Cargo.toml | 1 - node/src/chain_spec/gdev.rs | 9 +- node/src/chain_spec/gen_genesis_data.rs | 6 +- pallets/duniter-wot/src/mock.rs | 2 + pallets/duniter-wot/src/tests.rs | 1 + pallets/identity/src/lib.rs | 64 ++++- pallets/identity/src/mock.rs | 1 + pallets/identity/src/tests.rs | 3 +- pallets/identity/src/types.rs | 3 +- pallets/ud-accounts-storage/Cargo.toml | 83 ------- pallets/ud-accounts-storage/src/lib.rs | 155 ------------- pallets/universal-dividend/Cargo.toml | 21 +- .../universal-dividend/src/benchmarking.rs | 90 ++++++-- .../src/compute_claim_uds.rs | 96 ++++++++ pallets/universal-dividend/src/lib.rs | 218 ++++++++++++++---- pallets/universal-dividend/src/mock.rs | 43 +++- pallets/universal-dividend/src/tests.rs | 215 +++++++++++++---- pallets/universal-dividend/src/types.rs | 97 ++++++++ pallets/universal-dividend/src/weights.rs | 35 +++ runtime/common/Cargo.toml | 3 - runtime/common/src/constants.rs | 24 +- runtime/common/src/handlers.rs | 37 +-- runtime/common/src/lib.rs | 9 +- runtime/common/src/pallets_config.rs | 16 +- runtime/common/src/providers.rs | 40 +++- .../src/weights/pallet_universal_dividend.rs | 38 ++- runtime/g1/Cargo.toml | 1 - runtime/g1/src/lib.rs | 6 +- runtime/gdev/Cargo.toml | 1 - runtime/gdev/src/lib.rs | 9 +- runtime/gdev/tests/common/mod.rs | 7 +- runtime/gdev/tests/integration_tests.rs | 62 ++++- runtime/gtest/Cargo.toml | 1 - runtime/gtest/src/lib.rs | 6 +- 37 files changed, 947 insertions(+), 497 deletions(-) delete mode 100644 pallets/ud-accounts-storage/Cargo.toml delete mode 100644 pallets/ud-accounts-storage/src/lib.rs create mode 100644 pallets/universal-dividend/src/compute_claim_uds.rs create mode 100644 pallets/universal-dividend/src/types.rs diff --git a/.cargo/config b/.cargo/config index e07889471..3a03e9a4f 100644 --- a/.cargo/config +++ b/.cargo/config @@ -2,5 +2,6 @@ cucumber = "test -p duniter-end2end-tests --test cucumber_tests --" sanity-gdev = "test -p duniter-live-tests --test sanity_gdev -- --nocapture" tu = "test --workspace --exclude duniter-end2end-tests" +tb = "test --features runtime-benchmarks -p" xtask = "run --package xtask --" diff --git a/.vscode/launch.json b/.vscode/launch.json index 23b696a3d..3fb767dac 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -88,25 +88,6 @@ "args": [], "cwd": "${workspaceFolder}" }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'pallet-ud-accounts-storage'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=pallet-ud-accounts-storage" - ], - "filter": { - "name": "pallet-ud-accounts-storage", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, { "type": "lldb", "request": "launch", diff --git a/Cargo.lock b/Cargo.lock index fb1b1b03e..2bb67beb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -895,7 +895,6 @@ dependencies = [ "pallet-session", "pallet-timestamp", "pallet-treasury", - "pallet-ud-accounts-storage", "pallet-universal-dividend", "pallet-upgrade-origin", "parity-scale-codec", @@ -2325,7 +2324,6 @@ dependencies = [ "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-treasury", - "pallet-ud-accounts-storage", "pallet-universal-dividend", "pallet-upgrade-origin", "pallet-utility", @@ -2394,7 +2392,6 @@ dependencies = [ "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-treasury", - "pallet-ud-accounts-storage", "pallet-universal-dividend", "pallet-upgrade-origin", "pallet-utility", @@ -2666,7 +2663,6 @@ dependencies = [ "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-treasury", - "pallet-ud-accounts-storage", "pallet-universal-dividend", "pallet-upgrade-origin", "pallet-utility", @@ -5501,23 +5497,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "pallet-ud-accounts-storage" -version = "3.0.0" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "pallet-balances", - "parity-scale-codec", - "scale-info", - "serde", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", -] - [[package]] name = "pallet-universal-dividend" version = "3.0.0" diff --git a/Cargo.toml b/Cargo.toml index b054d2a4e..ae17782bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -131,7 +131,6 @@ members = [ 'pallets/identity', 'pallets/membership', 'pallets/authority-members', - 'pallets/ud-accounts-storage', 'pallets/universal-dividend', 'pallets/upgrade-origin', 'primitives/membership', diff --git a/node/src/chain_spec/gdev.rs b/node/src/chain_spec/gdev.rs index 18695102a..52bea218a 100644 --- a/node/src/chain_spec/gdev.rs +++ b/node/src/chain_spec/gdev.rs @@ -21,7 +21,7 @@ use gdev_runtime::{ opaque::SessionKeys, AccountConfig, AccountId, AuthorityMembersConfig, BabeConfig, BalancesConfig, CertConfig, GenesisConfig, IdentityConfig, ImOnlineId, MembershipConfig, ParametersConfig, SessionConfig, SmithsCertConfig, SmithsMembershipConfig, SudoConfig, - SystemConfig, UdAccountsStorageConfig, UniversalDividendConfig, WASM_BINARY, + SystemConfig, UniversalDividendConfig, WASM_BINARY, }; use sc_service::ChainType; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; @@ -377,6 +377,7 @@ fn gen_genesis_for_local_chain( owner_key: owner_key.clone(), removable_on: 0, status: IdtyStatus::Validated, + data: IdtyData::min(), }, }) .collect(), @@ -416,9 +417,6 @@ fn gen_genesis_for_local_chain( certs_by_issuer: clique_wot(initial_smiths_len, smith_cert_validity_period), }, smiths_collective: Default::default(), - ud_accounts_storage: UdAccountsStorageConfig { - ud_accounts: initial_identities.values().cloned().collect(), - }, universal_dividend: UniversalDividendConfig { first_reeval: 100, first_ud, @@ -466,7 +464,6 @@ fn genesis_data_to_gdev_genesis_conf( smiths_certs_by_issuer, smiths_memberships, sudo_key, - ud_accounts, } = genesis_data; gdev_runtime::GenesisConfig { @@ -506,6 +503,7 @@ fn genesis_data_to_gdev_genesis_conf( owner_key: pubkey, removable_on: 0, status: IdtyStatus::Validated, + data: IdtyData::min(), }, }) .collect(), @@ -523,7 +521,6 @@ fn genesis_data_to_gdev_genesis_conf( memberships: smiths_memberships, }, smiths_collective: Default::default(), - ud_accounts_storage: UdAccountsStorageConfig { ud_accounts }, universal_dividend: UniversalDividendConfig { first_reeval: first_ud_reeval, first_ud, diff --git a/node/src/chain_spec/gen_genesis_data.rs b/node/src/chain_spec/gen_genesis_data.rs index 437f691fe..d9c66c76a 100644 --- a/node/src/chain_spec/gen_genesis_data.rs +++ b/node/src/chain_spec/gen_genesis_data.rs @@ -17,7 +17,7 @@ use common_runtime::*; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sp_core::{blake2_256, Decode, Encode, H256}; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeMap; type MembershipData = sp_membership::MembershipData<u32>; @@ -38,7 +38,6 @@ pub struct GenesisData<Parameters: DeserializeOwned, SessionKeys: Decode> { pub smiths_certs_by_issuer: BTreeMap<u32, BTreeMap<u32, u32>>, pub smiths_memberships: BTreeMap<u32, MembershipData>, pub sudo_key: Option<AccountId>, - pub ud_accounts: BTreeSet<AccountId>, } #[derive(Default)] @@ -151,7 +150,6 @@ where let mut initial_monetary_mass = 0; let mut memberships = BTreeMap::new(); //let mut total_dust = 0; - let mut ud_accounts = BTreeSet::new(); // SIMPLE WALLETS // @@ -194,7 +192,6 @@ where // We must count the money under the existential deposit because what we count is // the monetary mass created (for the revaluation of the DU) initial_monetary_mass += identity.balance; - ud_accounts.insert(identity.pubkey.clone()); // Wot identities_.push((idty_name.clone(), identity.pubkey.clone())); @@ -329,7 +326,6 @@ where smiths_certs_by_issuer, smiths_memberships, sudo_key, - ud_accounts, }; Ok(f(genesis_data)) diff --git a/pallets/duniter-wot/src/mock.rs b/pallets/duniter-wot/src/mock.rs index ae08de65f..5646650d9 100644 --- a/pallets/duniter-wot/src/mock.rs +++ b/pallets/duniter-wot/src/mock.rs @@ -113,6 +113,7 @@ impl pallet_identity::Config for Test { type Event = Event; type EnsureIdtyCallAllowed = DuniterWot; type IdtyCreationPeriod = IdtyCreationPeriod; + type IdtyData = (); type IdtyNameValidator = IdtyNameValidatorTestImpl; type IdtyIndex = IdtyIndex; type IdtyValidationOrigin = system::EnsureRoot<AccountId>; @@ -248,6 +249,7 @@ pub fn new_test_ext( owner_key: i as u64, removable_on: 0, status: pallet_identity::IdtyStatus::Validated, + data: (), }, }) .collect(), diff --git a/pallets/duniter-wot/src/tests.rs b/pallets/duniter-wot/src/tests.rs index 3e237c609..870f29ccd 100644 --- a/pallets/duniter-wot/src/tests.rs +++ b/pallets/duniter-wot/src/tests.rs @@ -217,6 +217,7 @@ fn test_new_idty_validation() { owner_key: 6, removable_on: 0, status: IdtyStatus::Validated, + data: () }) ); }); diff --git a/pallets/identity/src/lib.rs b/pallets/identity/src/lib.rs index 79c16b06e..0cc9fc089 100644 --- a/pallets/identity/src/lib.rs +++ b/pallets/identity/src/lib.rs @@ -68,6 +68,14 @@ pub mod pallet { type EnsureIdtyCallAllowed: EnsureIdtyCallAllowed<Self>; /// Minimum duration between the creation of 2 identities by the same creator type IdtyCreationPeriod: Get<Self::BlockNumber>; + /// Custom data to store in each identity + type IdtyData: Clone + + Codec + + Default + + Eq + + TypeInfo + + MaybeSerializeDeserialize + + MaxEncodedLen; /// A short identity index. type IdtyIndex: Parameter + Member @@ -102,7 +110,7 @@ pub mod pallet { pub struct GenesisIdty<T: Config> { pub index: T::IdtyIndex, pub name: IdtyName, - pub value: IdtyValue<T::BlockNumber, T::AccountId>, + pub value: IdtyValue<T::BlockNumber, T::AccountId, T::IdtyData>, } #[pallet::genesis_config] @@ -159,7 +167,7 @@ pub mod pallet { _, Twox64Concat, T::IdtyIndex, - IdtyValue<T::BlockNumber, T::AccountId>, + IdtyValue<T::BlockNumber, T::AccountId, T::IdtyData>, OptionQuery, >; @@ -280,6 +288,7 @@ pub mod pallet { owner_key: owner_key.clone(), removable_on, status: IdtyStatus::Created, + data: Default::default(), }, ); IdentitiesRemovableOn::<T>::append(removable_on, (idty_index, IdtyStatus::Created)); @@ -560,3 +569,54 @@ impl<T: Config> sp_runtime::traits::Convert<T::AccountId, Option<T::IdtyIndex>> Self::identity_index_of(account_id) } } + +impl<T> frame_support::traits::StoredMap<T::AccountId, T::IdtyData> for Pallet<T> +where + T: Config, +{ + fn get(key: &T::AccountId) -> T::IdtyData { + if let Some(idty_index) = Self::identity_index_of(key) { + if let Some(idty_val) = Identities::<T>::get(idty_index) { + idty_val.data + } else { + Default::default() + } + } else { + Default::default() + } + } + fn try_mutate_exists<R, E: From<sp_runtime::DispatchError>>( + key: &T::AccountId, + f: impl FnOnce(&mut Option<T::IdtyData>) -> Result<R, E>, + ) -> Result<R, E> { + let maybe_idty_index = Self::identity_index_of(key); + let mut maybe_idty_data = if let Some(idty_index) = maybe_idty_index { + if let Some(idty_val) = Identities::<T>::get(idty_index) { + Some(idty_val.data) + } else { + None + } + } else { + None + }; + let result = f(&mut maybe_idty_data)?; + if let Some(idty_index) = maybe_idty_index { + Identities::<T>::mutate_exists(idty_index, |idty_val_opt| { + if let Some(ref mut idty_val) = idty_val_opt { + idty_val.data = maybe_idty_data.unwrap_or_default(); + } else if maybe_idty_data.is_some() { + return Err(sp_runtime::DispatchError::Other( + "Tring to set IdtyData for a non-existing identity!", + )); + } + Ok(()) + })?; + } else if maybe_idty_data.is_some() { + return Err(sp_runtime::DispatchError::Other( + "Tring to set IdtyData for a non-existing identity!", + ) + .into()); + } + Ok(result) + } +} diff --git a/pallets/identity/src/mock.rs b/pallets/identity/src/mock.rs index 420bfa10b..36e24dca7 100644 --- a/pallets/identity/src/mock.rs +++ b/pallets/identity/src/mock.rs @@ -102,6 +102,7 @@ impl pallet_identity::Config for Test { type Event = Event; type EnsureIdtyCallAllowed = (); type IdtyCreationPeriod = IdtyCreationPeriod; + type IdtyData = (); type IdtyNameValidator = IdtyNameValidatorTestImpl; type IdtyIndex = u64; type IdtyValidationOrigin = system::EnsureRoot<AccountId>; diff --git a/pallets/identity/src/tests.rs b/pallets/identity/src/tests.rs index 3a25afc57..f2f50efb1 100644 --- a/pallets/identity/src/tests.rs +++ b/pallets/identity/src/tests.rs @@ -22,7 +22,7 @@ use frame_support::assert_ok; use frame_system::{EventRecord, Phase}; use sp_runtime::testing::TestSignature; -type IdtyVal = IdtyValue<u64, u64>; +type IdtyVal = IdtyValue<u64, u64, ()>; fn alice() -> GenesisIdty<Test> { GenesisIdty { @@ -33,6 +33,7 @@ fn alice() -> GenesisIdty<Test> { owner_key: 1, removable_on: 0, status: crate::IdtyStatus::Validated, + data: (), }, } } diff --git a/pallets/identity/src/types.rs b/pallets/identity/src/types.rs index 529fb0bab..708b2ed93 100644 --- a/pallets/identity/src/types.rs +++ b/pallets/identity/src/types.rs @@ -79,11 +79,12 @@ impl Default for IdtyStatus { #[cfg_attr(feature = "std", derive(Debug, Deserialize, Serialize))] #[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] -pub struct IdtyValue<BlockNumber, AccountId> { +pub struct IdtyValue<BlockNumber, AccountId, IdtyData> { pub next_creatable_identity_on: BlockNumber, pub owner_key: AccountId, pub removable_on: BlockNumber, pub status: IdtyStatus, + pub data: IdtyData, } #[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo, RuntimeDebug)] diff --git a/pallets/ud-accounts-storage/Cargo.toml b/pallets/ud-accounts-storage/Cargo.toml deleted file mode 100644 index 3a7dc7135..000000000 --- a/pallets/ud-accounts-storage/Cargo.toml +++ /dev/null @@ -1,83 +0,0 @@ -[package] -authors = ['librelois <c@elo.tf>'] -description = 'FRAME pallet universal dividend accounts storage.' -edition = '2018' -homepage = 'https://substrate.dev' -license = 'AGPL-3.0' -name = 'pallet-ud-accounts-storage' -readme = 'README.md' -repository = 'https://git.duniter.org/nodes/rust/duniter-v2s' -version = '3.0.0' - -[features] -default = ['std'] -runtime-benchmarks = ['frame-benchmarking'] -std = [ - 'codec/std', - 'frame-support/std', - 'frame-system/std', - 'frame-benchmarking/std', - "sp-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-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-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'] -[dev-dependencies.serde] -version = '1.0.119' - -### DEV ### - -[dev-dependencies.pallet-balances] -default-features = false -git = 'https://github.com/librelois/substrate.git' -branch = 'duniter-monthly-2022-02' - -[dev-dependencies.sp-core] -default-features = false -git = 'https://github.com/librelois/substrate.git' -branch = 'duniter-monthly-2022-02' - -[dev-dependencies.sp-io] -default-features = false -git = 'https://github.com/librelois/substrate.git' -branch = 'duniter-monthly-2022-02' - -[dev-dependencies.sp-runtime] -default-features = false -git = 'https://github.com/librelois/substrate.git' -branch = 'duniter-monthly-2022-02' diff --git a/pallets/ud-accounts-storage/src/lib.rs b/pallets/ud-accounts-storage/src/lib.rs deleted file mode 100644 index d73c89fed..000000000 --- a/pallets/ud-accounts-storage/src/lib.rs +++ /dev/null @@ -1,155 +0,0 @@ -// 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 - } - } -} diff --git a/pallets/universal-dividend/Cargo.toml b/pallets/universal-dividend/Cargo.toml index 8b939e5f7..f6ab06ac8 100644 --- a/pallets/universal-dividend/Cargo.toml +++ b/pallets/universal-dividend/Cargo.toml @@ -12,13 +12,14 @@ version = '3.0.0' default = ['std'] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", - "pallet-balances" + "pallet-balances", ] std = [ 'codec/std', 'frame-support/std', 'frame-system/std', 'frame-benchmarking/std', + "serde", "sp-arithmetic/std", "sp-io/std", "sp-std/std", @@ -26,20 +27,15 @@ std = [ try-runtime = ['frame-support/try-runtime'] [dependencies] - -# substrate +# crates.io +codec = { package = 'parity-scale-codec', version = '2.3.1', default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "1.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.101", features = ["derive"], optional = true } # substrate bencharks frame-benchmarking = { git = 'https://github.com/librelois/substrate.git', branch = 'duniter-monthly-2022-02', optional = true, default-features = false } pallet-balances = { git = 'https://github.com/librelois/substrate.git', branch = 'duniter-monthly-2022-02', optional = true, default-features = false } -[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' @@ -78,14 +74,13 @@ targets = ['x86_64-unknown-linux-gnu'] ### DEV ### +[dev-dependencies] +serde = { version = "1.0.101", features = ["derive"] } + [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' diff --git a/pallets/universal-dividend/src/benchmarking.rs b/pallets/universal-dividend/src/benchmarking.rs index 530065d03..0dd6b662d 100644 --- a/pallets/universal-dividend/src/benchmarking.rs +++ b/pallets/universal-dividend/src/benchmarking.rs @@ -18,8 +18,8 @@ use super::*; -use frame_benchmarking::{account, benchmarks, whitelisted_caller}; -use frame_support::pallet_prelude::IsType; +use frame_benchmarking::{account, benchmarks, whitelist_account, whitelisted_caller}; +use frame_support::pallet_prelude::{BoundedVec, IsType}; use frame_support::traits::{Get, OnInitialize}; use frame_system::RawOrigin; use pallet_balances::Pallet as Balances; @@ -27,28 +27,89 @@ use sp_runtime::traits::Bounded; use crate::Pallet; -const BLOCK_NUMBER: u32 = 2; const ED_MULTIPLIER: u32 = 10; const SEED: u32 = 0; -/*fn fill_storage<T: Config>( - members_count: u32, -) -> Result<(), &'static str> { - Ok(()) -}*/ - benchmarks! { - // TODO add on_initialize_ud_created and on_initialize_ud_reevalued (after manual UD impl) + where_clause { + where + T: pallet_balances::Config, T::Balance: From<u64>, + <T::Currency as Currency<T::AccountId>>::Balance: IsType<T::Balance> + } on_initialize { - //let n in 1 .. 10_000; - //fill_storage::<T>(n)?; - }: { Pallet::<T>::on_initialize(BLOCK_NUMBER.into()); } + let total_money_created = Pallet::<T>::total_money_created(); + }: { Pallet::<T>::on_initialize(1_u32.into()); } + verify { + assert_eq!(Pallet::<T>::total_money_created(), total_money_created); + } + where_clause { + where + T: pallet_balances::Config, T::Balance: From<u64>, + <T::Currency as Currency<T::AccountId>>::Balance: IsType<T::Balance> + } + on_initialize_ud_created { + let block_number = T::UdCreationPeriod::get(); + let block_number_plus_one: T::BlockNumber = block_number + One::one(); + NextReeval::<T>::put(block_number_plus_one); + }: { Pallet::<T>::on_initialize(block_number.into()); } + verify { + } + where_clause { + where + T: pallet_balances::Config, T::Balance: From<u64>, + <T::Currency as Currency<T::AccountId>>::Balance: IsType<T::Balance> + } + on_initialize_ud_reevalued { + let block_number = T::UdCreationPeriod::get(); + let block_number_plus_one: T::BlockNumber = block_number + One::one(); + NextReeval::<T>::put(block_number_plus_one); + Pallet::<T>::on_initialize(block_number.into()); + NextReeval::<T>::put(block_number); + }: { Pallet::<T>::on_initialize(block_number.into()); } + verify { + } + // Benchmark `claim_uds` extrinsic with the worst possible conditions: + // * UDs have never been claimed + // * The maximum number of revaluations has taken place since + where_clause { + where + T: pallet_balances::Config, T::Balance: From<u64>, + <T::Currency as Currency<T::AccountId>>::Balance: IsType<T::Balance> + } + claim_uds { + let n in 1 .. T::MaxPastReeval::get(); + + // Caller should be a member + let caller: T::AccountId = T::MembersStorageIter::from(None) + .next() + .expect("we need at least one member") + .0; + + // Simulate n reevals + let mut past_reevals = BoundedVec::default(); + for i in 0..n { + past_reevals + .try_push((((3 * i) + 1) as u16, ((1_000 + (100 * i)) as u32).into())) + .expect("unreachable"); + } + PastReevals::<T>::put(past_reevals); + + // Simulate 3n+2 UDs + CurrentUdIndex::<T>::put(((3 * n) + 2) as u16); + + whitelist_account!(caller); + }: claim_uds(RawOrigin::Signed(caller)) verify { } + // Benchmark `transfer_ud` extrinsic with the worst possible conditions: // * Transfer will kill the sender account. // * Transfer will create the recipient account. - where_clause { where T: pallet_balances::Config, T::Balance: From<u64>, <T::Currency as Currency<T::AccountId>>::Balance: IsType<T::Balance> } + where_clause { + where + T: pallet_balances::Config, T::Balance: From<u64>, + <T::Currency as Currency<T::AccountId>>::Balance: IsType<T::Balance> + } transfer_ud { let existential_deposit = T::ExistentialDeposit::get(); let caller = whitelisted_caller(); @@ -94,6 +155,7 @@ benchmarks! { first_reeval: 8, first_ud: 1_000, initial_monetary_mass: 0, + initial_members: vec![1], }), crate::mock::Test ); diff --git a/pallets/universal-dividend/src/compute_claim_uds.rs b/pallets/universal-dividend/src/compute_claim_uds.rs new file mode 100644 index 000000000..5e214b977 --- /dev/null +++ b/pallets/universal-dividend/src/compute_claim_uds.rs @@ -0,0 +1,96 @@ +// Copyright 2021 Axiom-Team +// +// This file is part of Substrate-Libre-Currency. +// +// Substrate-Libre-Currency is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Substrate-Libre-Currency is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. + +use super::UdIndex; +use core::iter::DoubleEndedIterator; +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; + for (ud_index, ud_amount) in past_reevals.rev() { + let count = current_ud_index - core::cmp::max(ud_index, first_ud_index); + total_amount += Balance::from(count) * ud_amount; + total_count += count; + if ud_index <= first_ud_index { + break; + } else { + current_ud_index = ud_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) + ); + } +} diff --git a/pallets/universal-dividend/src/lib.rs b/pallets/universal-dividend/src/lib.rs index ab6fa5361..faf5749d2 100644 --- a/pallets/universal-dividend/src/lib.rs +++ b/pallets/universal-dividend/src/lib.rs @@ -17,13 +17,16 @@ #![cfg_attr(not(feature = "std"), no_std)] mod benchmarking; +mod compute_claim_uds; #[cfg(test)] mod mock; #[cfg(test)] mod tests; +mod types; mod weights; pub use pallet::*; +pub use types::*; pub use weights::WeightInfo; use frame_support::traits::{tokens::ExistenceRequirement, Currency}; @@ -32,17 +35,15 @@ use sp_arithmetic::{ traits::{One, Saturating, Zero}, }; use sp_runtime::traits::StaticLookup; -use sp_std::prelude::*; - -const OFFCHAIN_PREFIX_UD_HISTORY: &[u8] = b"ud::history::"; #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; - use frame_support::traits::StorageVersion; + use frame_support::traits::{StorageVersion, StoredMap}; use frame_system::pallet_prelude::*; use sp_runtime::traits::Convert; + use sp_std::vec::Vec; pub type BalanceOf<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance; @@ -53,7 +54,7 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] - #[pallet::without_storage_info] + //#[pallet::without_storage_info] pub struct Pallet<T>(_); #[pallet::config] @@ -64,10 +65,16 @@ pub mod pallet { type Currency: Currency<Self::AccountId>; /// Because this pallet emits events, it depends on the runtime's definition of an event. type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>; + #[pallet::constant] + /// Maximum number of past UD revaluations to keep in storage. + type MaxPastReeval: Get<u32>; /// Somethings that must provide the number of accounts allowed to create the universal dividend 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>>; + type MembersStorage: frame_support::traits::StoredMap<Self::AccountId, FirstEligibleUd>; + /// An iterator over all members + type MembersStorageIter: From<Option<Vec<u8>>> + + Iterator<Item = (Self::AccountId, FirstEligibleUd)>; #[pallet::constant] /// Square of the money growth rate per ud reevaluation period type SquareMoneyGrowthRate: Get<Perbill>; @@ -93,6 +100,29 @@ pub mod pallet { #[pallet::getter(fn current_ud)] pub type CurrentUd<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>; + #[pallet::type_value] + pub fn DefaultForCurrentUdIndex() -> UdIndex { + 1 + } + + /// Current UD index + #[pallet::storage] + #[pallet::getter(fn ud_index)] + pub type CurrentUdIndex<T: Config> = + StorageValue<_, UdIndex, ValueQuery, DefaultForCurrentUdIndex>; + + #[cfg(test)] + #[pallet::storage] + pub type TestMembers<T: Config> = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + FirstEligibleUd, + ValueQuery, + GetDefault, + ConstU32<300_000>, + >; + /// Total quantity of money created by universal dividend (does not take into account the possible destruction of money) #[pallet::storage] #[pallet::getter(fn total_money_created)] @@ -103,6 +133,12 @@ pub mod pallet { #[pallet::getter(fn next_reeval)] pub type NextReeval<T: Config> = StorageValue<_, T::BlockNumber, ValueQuery>; + /// Past UD reevaluations + #[pallet::storage] + #[pallet::getter(fn past_reevals)] + pub type PastReevals<T: Config> = + StorageValue<_, BoundedVec<(UdIndex, BalanceOf<T>), T::MaxPastReeval>, ValueQuery>; + // GENESIS #[pallet::genesis_config] @@ -110,6 +146,8 @@ pub mod pallet { pub first_reeval: T::BlockNumber, pub first_ud: BalanceOf<T>, pub initial_monetary_mass: BalanceOf<T>, + #[cfg(test)] + pub initial_members: Vec<T::AccountId>, } #[cfg(feature = "std")] @@ -119,6 +157,8 @@ pub mod pallet { first_reeval: Default::default(), first_ud: Default::default(), initial_monetary_mass: Default::default(), + #[cfg(test)] + initial_members: Default::default(), } } } @@ -132,6 +172,18 @@ pub mod pallet { <CurrentUd<T>>::put(self.first_ud); <MonetaryMass<T>>::put(self.initial_monetary_mass); NextReeval::<T>::put(self.first_reeval); + let mut past_reevals = BoundedVec::default(); + past_reevals + .try_push((1, self.first_ud)) + .expect("MaxPastReeval should be greather than zero"); + PastReevals::<T>::put(past_reevals); + + #[cfg(test)] + { + for member in &self.initial_members { + TestMembers::<T>::insert(member, FirstEligibleUd::min()); + } + } } } @@ -140,21 +192,21 @@ pub mod pallet { #[pallet::hooks] impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { fn on_initialize(n: T::BlockNumber) -> Weight { - let mut total_weight = T::WeightInfo::on_initialize(); if (n % T::UdCreationPeriod::get()).is_zero() { 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())); - total_weight += Self::reeval_ud(current_members_count) - + Self::create_ud(current_members_count, n) - + T::DbWeight::get().reads_writes(2, 1); + Self::reeval_ud(current_members_count); + Self::create_ud(current_members_count); + T::WeightInfo::on_initialize_ud_reevalued() } else { - total_weight += - Self::create_ud(current_members_count, n) + T::DbWeight::get().reads(2); + Self::create_ud(current_members_count); + T::WeightInfo::on_initialize_ud_created() } + } else { + T::WeightInfo::on_initialize() } - total_weight } } @@ -165,45 +217,92 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event<T: Config> { - /// A new universal dividend is created - /// [amout, members_count] + /// A new universal dividend is created. NewUdCreated { amount: BalanceOf<T>, + index: UdIndex, monetary_mass: BalanceOf<T>, members_count: BalanceOf<T>, }, - /// The universal dividend has been re-evaluated - /// [new_ud_amount, monetary_mass, members_count] + /// The universal dividend has been re-evaluated. UdReevalued { new_ud_amount: BalanceOf<T>, monetary_mass: BalanceOf<T>, members_count: BalanceOf<T>, }, + /// DUs were automatically transferred as part of a member removal. + UdsAutoPaidAtRemoval { + count: UdIndex, + total: BalanceOf<T>, + who: T::AccountId, + }, + /// A member claimed his UDs. + UdsClaimed { + count: UdIndex, + total: BalanceOf<T>, + who: T::AccountId, + }, + } + + // ERRORS // + + #[pallet::error] + pub enum Error<T> { + /// This account is not allowed to claim UDs. + AccountNotAllowedToClaimUds, } // INTERNAL FUNCTIONS // impl<T: Config> Pallet<T> { - fn create_ud(members_count: BalanceOf<T>, n: T::BlockNumber) -> Weight { - let total_weight: Weight = 0; + fn create_ud(members_count: BalanceOf<T>) { + let ud_amount = <CurrentUd<T>>::get(); + let monetary_mass = <MonetaryMass<T>>::get(); - 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() { - T::Currency::deposit_creating(&account_id, ud_amount); - Self::write_ud_history(n, account_id, ud_amount); - } + // Increment ud index + let ud_index = CurrentUdIndex::<T>::mutate(|next_ud_index| { + core::mem::replace(next_ud_index, next_ud_index.saturating_add(1)) + }); let new_monetary_mass = monetary_mass.saturating_add(ud_amount.saturating_mul(members_count)); MonetaryMass::<T>::put(new_monetary_mass); Self::deposit_event(Event::NewUdCreated { amount: ud_amount, + index: ud_index, members_count, monetary_mass: new_monetary_mass, }); - - total_weight + } + 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("unrechable because current_ud_index is never zero."), + ); + T::Currency::deposit_creating(who, uds_total); + Self::deposit_event(Event::UdsClaimed { + count: uds_count, + total: uds_total, + who: who.clone(), + }); + Ok(().into()) + } + } else { + Err(Error::<T>::AccountNotAllowedToClaimUds.into()) + } + }) } fn do_transfer_ud( origin: OriginFor<T>, @@ -213,8 +312,7 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let dest = T::Lookup::lookup(dest)?; - let ud_amount = - <CurrentUd<T>>::try_get().map_err(|_| DispatchError::Other("corrupted storage"))?; + let ud_amount = <CurrentUd<T>>::get(); T::Currency::transfer( &who, &dest, @@ -223,12 +321,10 @@ pub mod pallet { )?; Ok(().into()) } - fn reeval_ud(members_count: BalanceOf<T>) -> Weight { - let total_weight: Weight = 0; + fn reeval_ud(members_count: BalanceOf<T>) { + let ud_amount = <CurrentUd<T>>::get(); - let ud_amount = <CurrentUd<T>>::try_get().expect("corrupted storage"); - - let monetary_mass = <MonetaryMass<T>>::try_get().expect("corrupted storage"); + let monetary_mass = <MonetaryMass<T>>::get(); let new_ud_amount = Self::reeval_ud_formula( ud_amount, @@ -240,15 +336,21 @@ pub mod pallet { ), ); - <CurrentUd<T>>::put(new_ud_amount); + 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 { new_ud_amount, monetary_mass, members_count, }); - - total_weight } fn reeval_ud_formula( ud_t: BalanceOf<T>, @@ -265,19 +367,18 @@ pub mod pallet { // 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) } - 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 // #[pallet::call] impl<T: Config> Pallet<T> { + /// Claim Universal Dividends + #[pallet::weight(T::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. #[pallet::weight(T::WeightInfo::transfer_ud())] pub fn transfer_ud( @@ -298,4 +399,31 @@ pub mod pallet { Self::do_transfer_ud(origin, dest, value, ExistenceRequirement::KeepAlive) } } + + // PUBLIC FUNCTIONS + + impl<T: Config> Pallet<T> { + pub fn init_first_eligible_ud() -> FirstEligibleUd { + CurrentUdIndex::<T>::get().into() + } + 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(), + ); + T::Currency::deposit_creating(who, uds_total); + Self::deposit_event(Event::UdsAutoPaidAtRemoval { + count: uds_count, + total: uds_total, + who: who.clone(), + }); + T::DbWeight::get().reads_writes(2, 1) + } else { + T::DbWeight::get().reads(1) + } + } + } } diff --git a/pallets/universal-dividend/src/mock.rs b/pallets/universal-dividend/src/mock.rs index 668069164..a53a0fe96 100644 --- a/pallets/universal-dividend/src/mock.rs +++ b/pallets/universal-dividend/src/mock.rs @@ -18,7 +18,7 @@ use super::*; use crate::{self as pallet_universal_dividend}; use frame_support::{ parameter_types, - traits::{Everything, Get, OnFinalize, OnInitialize}, + traits::{Everything, OnFinalize, OnInitialize}, }; use frame_system as system; use sp_core::H256; @@ -102,10 +102,38 @@ parameter_types! { pub const UdReevalPeriod: BlockNumber = 8; } -pub struct FakeWot; -impl Get<Vec<u64>> for FakeWot { - fn get() -> Vec<u64> { - vec![1, 2, 3] +pub struct TestMembersStorage; +impl frame_support::traits::StoredMap<u64, FirstEligibleUd> for TestMembersStorage { + fn get(key: &u64) -> FirstEligibleUd { + crate::TestMembers::<Test>::get(key) + } + fn try_mutate_exists<R, E: From<sp_runtime::DispatchError>>( + key: &u64, + 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) + } +} +pub struct TestMembersStorageIter(frame_support::storage::PrefixIterator<(u64, FirstEligibleUd)>); +impl From<Option<Vec<u8>>> for TestMembersStorageIter { + fn from(maybe_key: Option<Vec<u8>>) -> Self { + let mut iter = crate::TestMembers::<Test>::iter(); + if let Some(key) = maybe_key { + iter.set_last_raw_key(key); + } + Self(iter) + } +} +impl Iterator for TestMembersStorageIter { + type Item = (u64, FirstEligibleUd); + + fn next(&mut self) -> Option<Self::Item> { + self.0.next() } } @@ -113,8 +141,10 @@ impl pallet_universal_dividend::Config for Test { type BlockNumberIntoBalance = sp_runtime::traits::ConvertInto; type Currency = pallet_balances::Pallet<Test>; type Event = Event; + type MaxPastReeval = frame_support::traits::ConstU32<2>; type MembersCount = MembersCount; - type MembersIds = FakeWot; + type MembersStorage = TestMembersStorage; + type MembersStorageIter = TestMembersStorageIter; type SquareMoneyGrowthRate = SquareMoneyGrowthRate; type UdCreationPeriod = UdCreationPeriod; type UdReevalPeriod = UdReevalPeriod; @@ -140,6 +170,7 @@ pub fn run_to_block(n: u64) { while System::block_number() < n { UniversalDividend::on_finalize(System::block_number()); System::on_finalize(System::block_number()); + System::reset_events(); System::set_block_number(System::block_number() + 1); System::on_initialize(System::block_number()); UniversalDividend::on_initialize(System::block_number()); diff --git a/pallets/universal-dividend/src/tests.rs b/pallets/universal-dividend/src/tests.rs index 2b48bef4c..9cfbfe1a7 100644 --- a/pallets/universal-dividend/src/tests.rs +++ b/pallets/universal-dividend/src/tests.rs @@ -15,14 +15,15 @@ // along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. use crate::mock::*; -use frame_system::{EventRecord, Phase}; +use frame_support::{assert_err, assert_ok, assert_storage_noop}; #[test] -fn test_ud_creation() { +fn test_claim_uds() { new_test_ext(UniversalDividendConfig { first_reeval: 8, first_ud: 1_000, initial_monetary_mass: 0, + initial_members: vec![1, 2, 3], }) .execute_with(|| { // In the beginning there was no money @@ -32,82 +33,194 @@ fn test_ud_creation() { assert_eq!(Balances::free_balance(4), 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(Origin::signed(1)))); + assert_eq!(Balances::free_balance(1), 0); + + // Dave is not a member, he can't claim UDs + assert_err!( + UniversalDividend::claim_uds(Origin::signed(4)), + crate::Error::<Test>::AccountNotAllowedToClaimUds + ); + + // At block #2, the first UD must be created, but nobody should receive money 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(Origin::signed(1))); + System::assert_has_event(Event::UniversalDividend(crate::Event::UdsClaimed { + count: 1, + total: 1_000, + who: 1, + })); assert_eq!(Balances::free_balance(1), 1_000); - assert_eq!(Balances::free_balance(2), 1_000); - assert_eq!(Balances::free_balance(3), 1_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); - assert_eq!(UniversalDividend::total_money_created(), 3_000); - // Block #2 must generate 7 events, 2 events per new account fed, plus 1 event for the creation of the UD. - let events = System::events(); - println!("events: {:#?}", events); - assert_eq!(events.len(), 10); - assert_eq!( - events[9], - EventRecord { - phase: Phase::Initialization, - event: Event::UniversalDividend(crate::Event::NewUdCreated { - amount: 1_000, - monetary_mass: 3_000, - members_count: 3, - }), - topics: vec![], - } + // 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(Origin::signed(1))); + System::assert_has_event(Event::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(Origin::signed(2))); + System::assert_has_event(Event::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(Origin::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(Origin::signed(3))); + System::assert_has_event(Event::UniversalDividend(crate::Event::UdsClaimed { + count: 4, + total: 4_075, + who: 3, + })); + assert_eq!(Balances::free_balance(3), 4_075); + }); +} + +#[test] +fn test_ud_creation() { + new_test_ext(UniversalDividendConfig { + first_reeval: 8, + first_ud: 1_000, + initial_monetary_mass: 0, + initial_members: vec![1, 2, 3], + }) + .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(Event::UniversalDividend(crate::Event::NewUdCreated { + amount: 1_000, + index: 1, + monetary_mass: 3_000, + members_count: 3, + })); + 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 run_to_block(4); - assert_eq!(Balances::free_balance(1), 2_000); + System::assert_has_event(Event::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(3), 2_000); - 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![], - } - );*/ + assert_eq!(Balances::free_balance(4), 0);*/ // The third UD must be created in block #6 run_to_block(6); - assert_eq!(Balances::free_balance(1), 3_000); + System::assert_has_event(Event::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(3), 3_000); - assert_eq!(Balances::free_balance(4), 0); - assert_eq!(UniversalDividend::total_money_created(), 9_000); + assert_eq!(Balances::free_balance(4), 0);*/ // Block #8 should cause a re-evaluation of UD run_to_block(8); - assert_eq!(Balances::free_balance(1), 4_075); + System::assert_has_event(Event::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(Event::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(3), 4_075); - assert_eq!(Balances::free_balance(4), 0); - assert_eq!(UniversalDividend::total_money_created(), 12_225); + assert_eq!(Balances::free_balance(4), 0);*/ // Block #10 #12 and #14should creates the reevalued UD run_to_block(14); - assert_eq!(Balances::free_balance(1), 7_300); - assert_eq!(Balances::free_balance(2), 7_300); - assert_eq!(Balances::free_balance(3), 7_300); - assert_eq!(Balances::free_balance(4), 0); + System::assert_has_event(Event::UniversalDividend(crate::Event::NewUdCreated { + amount: 1_075, + index: 7, + monetary_mass: 21_900, + members_count: 3, + })); assert_eq!(UniversalDividend::total_money_created(), 21_900); // Block #16 should cause a second re-evaluation of UD run_to_block(16); - assert_eq!(Balances::free_balance(1), 8_557); - assert_eq!(Balances::free_balance(2), 8_557); - assert_eq!(Balances::free_balance(3), 8_557); - assert_eq!(Balances::free_balance(4), 0); + System::assert_has_event(Event::UniversalDividend(crate::Event::UdReevalued { + new_ud_amount: 1_257, + monetary_mass: 21_900, + members_count: 3, + })); + // Then, the reevalued UD should be created + System::assert_has_event(Event::UniversalDividend(crate::Event::NewUdCreated { + amount: 1_257, + index: 8, + monetary_mass: 25_671, + members_count: 3, + })); assert_eq!(UniversalDividend::total_money_created(), 25_671); }); } diff --git a/pallets/universal-dividend/src/types.rs b/pallets/universal-dividend/src/types.rs new file mode 100644 index 000000000..837efa9de --- /dev/null +++ b/pallets/universal-dividend/src/types.rs @@ -0,0 +1,97 @@ +// Copyright 2021 Axiom-Team +// +// This file is part of Substrate-Libre-Currency. +// +// Substrate-Libre-Currency is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Substrate-Libre-Currency is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. + +use codec::{Decode, Encode, Error, Input, MaxEncodedLen, Output}; +use core::num::NonZeroU16; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; +use sp_std::vec::Vec; + +pub type UdIndex = u16; + +#[cfg_attr(feature = "std", derive(Deserialize, Serialize))] +#[derive(Clone, Copy, Default, Eq, PartialEq)] +pub struct FirstEligibleUd(pub Option<NonZeroU16>); + +#[cfg(feature = "std")] +impl FirstEligibleUd { + 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() + } +} diff --git a/pallets/universal-dividend/src/weights.rs b/pallets/universal-dividend/src/weights.rs index 6dfab2bbd..e3b860880 100644 --- a/pallets/universal-dividend/src/weights.rs +++ b/pallets/universal-dividend/src/weights.rs @@ -21,6 +21,9 @@ use frame_support::weights::{constants::RocksDbWeight, Weight}; /// Weight functions needed for pallet_universal_dividend. pub trait WeightInfo { fn on_initialize() -> Weight; + fn on_initialize_ud_created() -> Weight; + fn on_initialize_ud_reevalued() -> Weight; + fn claim_uds(n: u32) -> Weight; fn transfer_ud() -> Weight; fn transfer_ud_keep_alive() -> Weight; } @@ -31,6 +34,38 @@ impl WeightInfo for () { fn on_initialize() -> Weight { 2_260_000 as Weight } + // Storage: Membership CounterForMembership (r:1 w:0) + // Storage: UniversalDividend NextReeval (r:1 w:0) + // Storage: UniversalDividend CurrentUd (r:1 w:0) + // Storage: UniversalDividend MonetaryMass (r:1 w:1) + // Storage: UniversalDividend CurrentUdIndex (r:1 w:1) + fn on_initialize_ud_created() -> Weight { + (20_160_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Membership CounterForMembership (r:1 w:0) + // Storage: UniversalDividend NextReeval (r:1 w:1) + // Storage: UniversalDividend CurrentUd (r:1 w:1) + // Storage: UniversalDividend MonetaryMass (r:1 w:1) + // Storage: UniversalDividend PastReevals (r:1 w:1) + // Storage: UniversalDividend CurrentUdIndex (r:1 w:1) + fn on_initialize_ud_reevalued() -> Weight { + (32_770_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(6 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } + // Storage: Identity IdentityIndexOf (r:1 w:0) + // Storage: Identity Identities (r:1 w:1) + // Storage: UniversalDividend CurrentUdIndex (r:1 w:0) + // Storage: UniversalDividend PastReevals (r:1 w:0) + fn claim_uds(n: u32) -> Weight { + (32_514_000 as Weight) + // Standard Error: 32_000 + .saturating_add((8_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } // Storage: UniversalDividend CurrentUd (r:1 w:0) // Storage: System Account (r:1 w:1) // Storage: Account PendingNewAccounts (r:0 w:1) diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index 9c2f22584..18152fd8c 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -21,7 +21,6 @@ runtime-benchmarks = [ 'pallet-multisig/runtime-benchmarks', 'pallet-proxy/runtime-benchmarks', 'pallet-treasury/runtime-benchmarks', - 'pallet-ud-accounts-storage/runtime-benchmarks', 'sp-runtime/runtime-benchmarks', ] std = [ @@ -46,7 +45,6 @@ std = [ 'pallet-timestamp/std', 'pallet-treasury/std', 'pallet-universal-dividend/std', - 'pallet-ud-accounts-storage/std', "serde/std", "serde_derive", 'sp-arithmetic/std', @@ -71,7 +69,6 @@ pallet-duniter-wot = { path = '../../pallets/duniter-wot', default-features = fa pallet-identity = { path = '../../pallets/identity', default-features = false } pallet-membership = { path = '../../pallets/membership', default-features = false } pallet-provide-randomness = { path = '../../pallets/provide-randomness', default-features = false } -pallet-ud-accounts-storage = { path = '../../pallets/ud-accounts-storage', default-features = false } pallet-upgrade-origin = { path = '../../pallets/upgrade-origin', default-features = false } pallet-universal-dividend = { path = '../../pallets/universal-dividend', default-features = false } sp-membership = { path = '../../primitives/membership', default-features = false } diff --git a/runtime/common/src/constants.rs b/runtime/common/src/constants.rs index b3c5e22fc..5eeb8b20c 100644 --- a/runtime/common/src/constants.rs +++ b/runtime/common/src/constants.rs @@ -15,6 +15,8 @@ // along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. use crate::{Balance, BlockNumber}; +use frame_support::weights::constants::WEIGHT_PER_MICROS; +use frame_support::weights::Weight; use sp_runtime::Perbill; /// This determines the average expected block time that we are targeting. @@ -56,26 +58,32 @@ pub const fn deposit(items: u32, bytes: u32) -> Balance { items as Balance * DEPOSIT_PER_ITEM + (bytes as Balance * DEPOSIT_PER_BYTE) } -// WEIGHTS COMSTANTS // +// Maximal weight proportion of normal extrinsics per block +pub const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); + +// WEIGHTS CONSTANTS // + +// Read DB weights +pub const READ_WEIGHTS: Weight = 250 * WEIGHT_PER_MICROS; // ~250 µs + +// Write DB weights +pub const WRITE_WEIGHTS: Weight = 1_000 * WEIGHT_PER_MICROS; // ~1000 µs // Execution cost of everything outside of the call itself: // signature verification, pre_dispatch and post_dispatch -pub const EXTRINSIC_BASE_WEIGHTS: frame_support::weights::Weight = 1_000_000_000; - -// Maximal weight proportion of normal extrinsics per block -pub const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); +pub const EXTRINSIC_BASE_WEIGHTS: Weight = READ_WEIGHTS + WRITE_WEIGHTS; // DB weights frame_support::parameter_types! { pub const DbWeight: frame_support::weights::RuntimeDbWeight = frame_support::weights::RuntimeDbWeight { - read: 250 * frame_support::weights::constants::WEIGHT_PER_MICROS, // ~25 µs - write: 1_000 * frame_support::weights::constants::WEIGHT_PER_MICROS, // ~100 µs + read: READ_WEIGHTS, + write: WRITE_WEIGHTS, }; } // Block weights limits pub fn block_weights( - expected_block_weight: frame_support::weights::Weight, + expected_block_weight: Weight, normal_ratio: sp_arithmetic::Perbill, ) -> frame_system::limits::BlockWeights { let normal_weight = normal_ratio * expected_block_weight; diff --git a/runtime/common/src/handlers.rs b/runtime/common/src/handlers.rs index 7e5587f55..b5f407057 100644 --- a/runtime/common/src/handlers.rs +++ b/runtime/common/src/handlers.rs @@ -15,12 +15,13 @@ // along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. use super::entities::*; -use super::{AccountId, IdtyIndex}; +use super::{AccountId, IdtyData, IdtyIndex}; use frame_support::dispatch::UnfilteredDispatchable; use frame_support::instances::{Instance1, Instance2}; use frame_support::pallet_prelude::Weight; +use frame_support::traits::Get; use frame_support::Parameter; -use sp_runtime::traits::{Convert, IsMember}; +use sp_runtime::traits::IsMember; pub struct OnNewSessionHandler<Runtime>(core::marker::PhantomData<Runtime>); impl<Runtime> pallet_authority_members::traits::OnNewSession for OnNewSessionHandler<Runtime> @@ -40,27 +41,35 @@ type MembershipMetaData = pallet_duniter_wot::MembershipMetaData<AccountId>; impl< Inner: sp_membership::traits::OnEvent<IdtyIndex, MembershipMetaData>, Runtime: frame_system::Config<AccountId = AccountId> - + pallet_identity::Config<IdtyIndex = IdtyIndex> + + pallet_identity::Config<IdtyData = IdtyData, IdtyIndex = IdtyIndex> + pallet_membership::Config<Instance1, MetaData = MembershipMetaData> - + pallet_ud_accounts_storage::Config, + + pallet_universal_dividend::Config, > sp_membership::traits::OnEvent<IdtyIndex, MembershipMetaData> for OnMembershipEventHandler<Inner, Runtime> { fn on_event(membership_event: &sp_membership::Event<IdtyIndex, MembershipMetaData>) -> Weight { (match membership_event { - sp_membership::Event::MembershipAcquired(_idty_index, owner_key) => { - pallet_ud_accounts_storage::Pallet::<Runtime>::replace_account( - None, - Some(owner_key.0.clone()), - ) + sp_membership::Event::MembershipAcquired(idty_index, _owner_key) => { + pallet_identity::Identities::<Runtime>::mutate_exists(idty_index, |idty_val_opt| { + if let Some(ref mut idty_val) = idty_val_opt { + idty_val.data = + pallet_universal_dividend::Pallet::<Runtime>::init_first_eligible_ud(); + } + }); + Runtime::DbWeight::get().reads_writes(1, 1) } sp_membership::Event::MembershipRevoked(idty_index) => { - if let Some(account_id) = - crate::providers::IdentityAccountIdProvider::<Runtime>::convert(*idty_index) - { - pallet_ud_accounts_storage::Pallet::<Runtime>::remove_account(account_id) + if let Some(idty_value) = pallet_identity::Identities::<Runtime>::get(idty_index) { + if let Some(first_ud_index) = idty_value.data.into() { + pallet_universal_dividend::Pallet::<Runtime>::on_removed_member( + first_ud_index, + &idty_value.owner_key, + ) + } else { + Runtime::DbWeight::get().reads(1) + } } else { - 0 + Runtime::DbWeight::get().reads(1) } } _ => 0, diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 2763a8c2a..96daf2186 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -54,15 +54,18 @@ pub type Hash = sp_core::H256; /// Block header type pub type Header = sp_runtime::generic::Header<BlockNumber, sp_runtime::traits::BlakeTwo256>; -/// Index of an identity -pub type IdtyIndex = u32; - /// Index of a transaction in the chain. pub type Index = u32; /// Alias to 512-bit hash when used in the context of a transaction signature on the chain. pub type Signature = sp_runtime::MultiSignature; +/// Index of an identity +pub type IdtyIndex = u32; + +/// Identity data +pub type IdtyData = pallet_universal_dividend::FirstEligibleUd; + pub struct FullIdentificationOfImpl; impl sp_runtime::traits::Convert<AccountId, Option<entities::ValidatorFullIdentification>> for FullIdentificationOfImpl diff --git a/runtime/common/src/pallets_config.rs b/runtime/common/src/pallets_config.rs index e7b880f1f..eb599c40e 100644 --- a/runtime/common/src/pallets_config.rs +++ b/runtime/common/src/pallets_config.rs @@ -386,12 +386,21 @@ macro_rules! pallets_config { // UNIVERSAL DIVIDEND // + pub struct MembersCount; + impl frame_support::pallet_prelude::Get<Balance> for MembersCount { + fn get() -> Balance { + <Membership as sp_membership::traits::MembersCount>::members_count() as Balance + } + } + impl pallet_universal_dividend::Config for Runtime { type BlockNumberIntoBalance = sp_runtime::traits::ConvertInto; type Currency = pallet_balances::Pallet<Runtime>; type Event = Event; - type MembersCount = common_runtime::providers::UdAccountsProvider<Runtime>; - type MembersIds = common_runtime::providers::UdAccountsProvider<Runtime>; + type MaxPastReeval = frame_support::traits::ConstU32<4>; + type MembersCount = MembersCount; + type MembersStorage = Identity; + type MembersStorageIter = common_runtime::providers::IdtyDataIter<Runtime>; type SquareMoneyGrowthRate = SquareMoneyGrowthRate; type UdCreationPeriod = UdCreationPeriod; type UdReevalPeriod = UdReevalPeriod; @@ -399,8 +408,6 @@ macro_rules! pallets_config { type WeightInfo = common_runtime::weights::pallet_universal_dividend::WeightInfo<Runtime>; } - impl pallet_ud_accounts_storage::Config for Runtime {} - // WEB OF TRUST // use frame_support::instances::Instance1; @@ -416,6 +423,7 @@ macro_rules! pallets_config { type Event = Event; type EnsureIdtyCallAllowed = Wot; type IdtyCreationPeriod = IdtyCreationPeriod; + type IdtyData = IdtyData; type IdtyIndex = IdtyIndex; type IdtyNameValidator = IdtyNameValidatorImpl; type IdtyValidationOrigin = EnsureRoot<Self::AccountId>; diff --git a/runtime/common/src/providers.rs b/runtime/common/src/providers.rs index 5c87d9773..193a9bd98 100644 --- a/runtime/common/src/providers.rs +++ b/runtime/common/src/providers.rs @@ -15,10 +15,11 @@ // along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. use crate::{AccountId, IdtyIndex}; -use frame_support::traits::Get; +use core::marker::PhantomData; +use sp_std::boxed::Box; use sp_std::vec::Vec; -pub struct IdentityAccountIdProvider<Runtime>(core::marker::PhantomData<Runtime>); +pub struct IdentityAccountIdProvider<Runtime>(PhantomData<Runtime>); impl< Runtime: frame_system::Config<AccountId = AccountId> @@ -31,16 +32,33 @@ impl< } } -pub struct UdAccountsProvider<Runtime>(core::marker::PhantomData<Runtime>); -impl<Runtime: pallet_ud_accounts_storage::Config> Get<u64> for UdAccountsProvider<Runtime> { - fn get() -> u64 { - <pallet_ud_accounts_storage::Pallet<Runtime>>::accounts_len() as u64 +#[allow(clippy::type_complexity)] +pub struct IdtyDataIter<T: pallet_identity::Config>( + Box<dyn Iterator<Item = pallet_identity::IdtyValue<T::BlockNumber, T::AccountId, T::IdtyData>>>, + PhantomData<T>, +); + +impl<T: pallet_identity::Config> From<Option<Vec<u8>>> for IdtyDataIter<T> { + fn from(maybe_key: Option<Vec<u8>>) -> Self { + let mut iter = pallet_identity::Identities::<T>::iter_values(); + if let Some(key) = maybe_key { + iter.set_last_raw_key(key); + } + Self(Box::new(iter), PhantomData) } } -impl<Runtime: frame_system::Config<AccountId = AccountId> + pallet_ud_accounts_storage::Config> - Get<Vec<AccountId>> for UdAccountsProvider<Runtime> -{ - fn get() -> Vec<AccountId> { - <pallet_ud_accounts_storage::Pallet<Runtime>>::accounts_list() + +impl<T: pallet_identity::Config> Iterator for IdtyDataIter<T> { + type Item = (T::AccountId, T::IdtyData); + + fn next(&mut self) -> Option<Self::Item> { + if let Some(pallet_identity::IdtyValue { + owner_key, data, .. + }) = self.0.next() + { + Some((owner_key, data)) + } else { + None + } } } diff --git a/runtime/common/src/weights/pallet_universal_dividend.rs b/runtime/common/src/weights/pallet_universal_dividend.rs index 1aab206a0..9a1b5c980 100644 --- a/runtime/common/src/weights/pallet_universal_dividend.rs +++ b/runtime/common/src/weights/pallet_universal_dividend.rs @@ -17,7 +17,7 @@ //! Autogenerated weights for `pallet_universal_dividend` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-06-12, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-07-03, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Interpreted, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -45,10 +45,40 @@ use sp_std::marker::PhantomData; /// Weight functions for `pallet_universal_dividend`. pub struct WeightInfo<T>(PhantomData<T>); impl<T: frame_system::Config> pallet_universal_dividend::WeightInfo for WeightInfo<T> { - // Storage: Parameters ParametersStorage (r:1 w:0) fn on_initialize() -> Weight { - (104_055_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) + 111_220_000 as Weight + } + // Storage: Membership CounterForMembership (r:1 w:0) + // Storage: UniversalDividend NextReeval (r:1 w:0) + // Storage: UniversalDividend CurrentUd (r:1 w:0) + // Storage: UniversalDividend MonetaryMass (r:1 w:1) + // Storage: UniversalDividend CurrentUdIndex (r:1 w:1) + fn on_initialize_ud_created() -> Weight { + (525_382_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Membership CounterForMembership (r:1 w:0) + // Storage: UniversalDividend NextReeval (r:1 w:1) + // Storage: UniversalDividend CurrentUd (r:1 w:1) + // Storage: UniversalDividend MonetaryMass (r:1 w:1) + // Storage: UniversalDividend PastReevals (r:1 w:1) + // Storage: UniversalDividend CurrentUdIndex (r:1 w:1) + fn on_initialize_ud_reevalued() -> Weight { + (1_161_595_000 as Weight) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) + } + // Storage: Identity IdentityIndexOf (r:1 w:0) + // Storage: Identity Identities (r:1 w:1) + // Storage: UniversalDividend CurrentUdIndex (r:1 w:0) + // Storage: UniversalDividend PastReevals (r:1 w:0) + fn claim_uds(n: u32) -> Weight { + (1_221_776_000 as Weight) + // Standard Error: 958_000 + .saturating_add((7_709_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: UniversalDividend CurrentUd (r:1 w:0) // Storage: System Account (r:1 w:1) diff --git a/runtime/g1/Cargo.toml b/runtime/g1/Cargo.toml index 7fd344106..a4479f456 100644 --- a/runtime/g1/Cargo.toml +++ b/runtime/g1/Cargo.toml @@ -118,7 +118,6 @@ pallet-duniter-wot = { path = '../../pallets/duniter-wot', default-features = fa pallet-identity = { path = '../../pallets/identity', default-features = false } pallet-membership = { path = '../../pallets/membership', default-features = false } pallet-provide-randomness = { path = '../../pallets/provide-randomness', default-features = false } -pallet-ud-accounts-storage = { path = '../../pallets/ud-accounts-storage', default-features = false } pallet-universal-dividend = { path = '../../pallets/universal-dividend', default-features = false } pallet-upgrade-origin = { path = '../../pallets/upgrade-origin', default-features = false } sp-membership = { path = '../../primitives/membership', default-features = false } diff --git a/runtime/g1/src/lib.rs b/runtime/g1/src/lib.rs index c29189666..75f12540d 100644 --- a/runtime/g1/src/lib.rs +++ b/runtime/g1/src/lib.rs @@ -27,7 +27,8 @@ pub mod parameters; pub use self::parameters::*; pub use common_runtime::{ constants::*, entities::*, handlers::*, AccountId, Address, Balance, BlockNumber, - FullIdentificationOfImpl, GetCurrentEpochIndex, Hash, Header, IdtyIndex, Index, Signature, + FullIdentificationOfImpl, GetCurrentEpochIndex, Hash, Header, IdtyData, IdtyIndex, Index, + Signature, }; pub use pallet_balances::Call as BalancesCall; pub use pallet_identity::{IdtyStatus, IdtyValue}; @@ -227,8 +228,7 @@ construct_runtime!( Preimage: pallet_preimage::{Pallet, Call, Storage, Event<T>} = 22, // Universal dividend - UdAccountsStorage: pallet_ud_accounts_storage::{Pallet, Config<T>, Storage} = 30, - UniversalDividend: pallet_universal_dividend::{Pallet, Call, Config<T>, Storage, Event<T>} = 31, + UniversalDividend: pallet_universal_dividend::{Pallet, Call, Config<T>, Storage, Event<T>} = 30, // Web Of Trust Wot: pallet_duniter_wot::<Instance1>::{Pallet} = 40, diff --git a/runtime/gdev/Cargo.toml b/runtime/gdev/Cargo.toml index d400bdf3d..236aadec2 100644 --- a/runtime/gdev/Cargo.toml +++ b/runtime/gdev/Cargo.toml @@ -140,7 +140,6 @@ pallet-duniter-wot = { path = '../../pallets/duniter-wot', default-features = fa pallet-identity = { path = '../../pallets/identity', default-features = false } pallet-membership = { path = '../../pallets/membership', default-features = false } pallet-provide-randomness = { path = '../../pallets/provide-randomness', default-features = false } -pallet-ud-accounts-storage = { path = '../../pallets/ud-accounts-storage', default-features = false } pallet-universal-dividend = { path = '../../pallets/universal-dividend', default-features = false } pallet-upgrade-origin = { path = '../../pallets/upgrade-origin', default-features = false } sp-membership = { path = '../../primitives/membership', default-features = false } diff --git a/runtime/gdev/src/lib.rs b/runtime/gdev/src/lib.rs index 594091a2a..6e574292b 100644 --- a/runtime/gdev/src/lib.rs +++ b/runtime/gdev/src/lib.rs @@ -31,7 +31,8 @@ pub mod parameters; pub use self::parameters::*; pub use common_runtime::{ constants::*, entities::*, handlers::*, AccountId, Address, Balance, BlockNumber, - FullIdentificationOfImpl, GetCurrentEpochIndex, Hash, Header, IdtyIndex, Index, Signature, + FullIdentificationOfImpl, GetCurrentEpochIndex, Hash, Header, IdtyData, IdtyIndex, Index, + Signature, }; pub use pallet_balances::Call as BalancesCall; pub use pallet_duniter_test_parameters::Parameters as GenesisParameters; @@ -228,7 +229,10 @@ common_runtime::pallets_config! { pub type MembershipPeriod = pallet_duniter_test_parameters::MembershipPeriod<Runtime>; pub type RenewablePeriod = pallet_duniter_test_parameters::MembershipRenewablePeriod<Runtime>; pub type PendingMembershipPeriod = pallet_duniter_test_parameters::PendingMembershipPeriod<Runtime>; + #[cfg(not(test))] pub type UdCreationPeriod = pallet_duniter_test_parameters::UdCreationPeriod<Runtime>; + #[cfg(test)] + pub type UdCreationPeriod = frame_support::traits::ConstU32<10>; pub type UdReevalPeriod = pallet_duniter_test_parameters::UdReevalPeriod<Runtime>; pub type WotFirstCertIssuableOn = pallet_duniter_test_parameters::WotFirstCertIssuableOn<Runtime>; pub type WotMinCertForMembership = pallet_duniter_test_parameters::WotMinCertForMembership<Runtime>; @@ -301,8 +305,7 @@ construct_runtime!( Preimage: pallet_preimage::{Pallet, Call, Storage, Event<T>} = 22, // Universal dividend - UdAccountsStorage: pallet_ud_accounts_storage::{Pallet, Config<T>, Storage} = 30, - UniversalDividend: pallet_universal_dividend::{Pallet, Call, Config<T>, Storage, Event<T>} = 31, + UniversalDividend: pallet_universal_dividend::{Pallet, Call, Config<T>, Storage, Event<T>} = 30, // Web Of Trust Wot: pallet_duniter_wot::<Instance1>::{Pallet} = 40, diff --git a/runtime/gdev/tests/common/mod.rs b/runtime/gdev/tests/common/mod.rs index f17c23b4c..762e921fb 100644 --- a/runtime/gdev/tests/common/mod.rs +++ b/runtime/gdev/tests/common/mod.rs @@ -210,6 +210,7 @@ impl ExtBuilder { owner_key: owner_key.clone(), removable_on: 0, status: IdtyStatus::Validated, + data: IdtyData::min(), }, }) .collect(), @@ -266,12 +267,6 @@ impl ExtBuilder { .assimilate_storage(&mut t) .unwrap(); - pallet_ud_accounts_storage::GenesisConfig::<Runtime> { - ud_accounts: initial_identities.values().cloned().collect(), - } - .assimilate_storage(&mut t) - .unwrap(); - pallet_universal_dividend::GenesisConfig::<Runtime> { first_reeval: 100, first_ud: 1_000, diff --git a/runtime/gdev/tests/integration_tests.rs b/runtime/gdev/tests/integration_tests.rs index 5505560b4..3cad420ec 100644 --- a/runtime/gdev/tests/integration_tests.rs +++ b/runtime/gdev/tests/integration_tests.rs @@ -17,7 +17,7 @@ mod common; use common::*; -use frame_support::traits::{PalletInfo, StorageInfo, StorageInfoTrait}; +use frame_support::traits::{Get, PalletInfo, StorageInfo, StorageInfoTrait}; use frame_support::{assert_noop, assert_ok}; use frame_support::{StorageHasher, Twox128}; use gdev_runtime::*; @@ -107,16 +107,60 @@ fn test_remove_identity() { System::events()[2].event, Event::Identity(pallet_identity::Event::IdtyRemoved { idty_index: 4 }) ); + }); +} +#[test] +fn test_remove_identity_after_one_ud() { + ExtBuilder::new(1, 3, 4).build().execute_with(|| { + //println!("UdCreationPeriod={}", <Runtime as pallet_universal_dividend::Config>::UdCreationPeriod::get()); + run_to_block(<Runtime as pallet_universal_dividend::Config>::UdCreationPeriod::get() + 1); + + assert_ok!(Identity::remove_identity( + frame_system::RawOrigin::Root.into(), + 4, + None + )); + + // Verify events + let events = System::events(); + //println!("{:?}", events); + assert_eq!(events.len(), 5); + assert_eq!( + System::events()[0].event, + Event::Membership(pallet_membership::Event::MembershipRevoked(4)) + ); + assert_eq!( + System::events()[1].event, + Event::Balances(pallet_balances::Event::Deposit { + who: AccountKeyring::Dave.to_account_id(), + amount: 1_000 + }) + ); + assert_eq!( + System::events()[2].event, + Event::Balances(pallet_balances::Event::Endowed { + account: AccountKeyring::Dave.to_account_id(), + free_balance: 1_000 + }) + ); + assert_eq!( + System::events()[3].event, + Event::UniversalDividend(pallet_universal_dividend::Event::UdsAutoPaidAtRemoval { + count: 1, + total: 1_000, + who: AccountKeyring::Dave.to_account_id(), + }) + ); + assert_eq!( + System::events()[4].event, + Event::Identity(pallet_identity::Event::IdtyRemoved { idty_index: 4 }) + ); - // The identity should be removed from UdAccountsStorage - assert_eq!(UdAccountsStorage::accounts_len(), 3); + // Verify state + assert!(Identity::identity(4).is_none()); assert_eq!( - UdAccountsStorage::accounts_list(), - vec![ - AccountKeyring::Bob.to_account_id(), - AccountKeyring::Charlie.to_account_id(), - AccountKeyring::Alice.to_account_id(), - ] + Balances::free_balance(AccountKeyring::Dave.to_account_id()), + 1_000 ); }); } diff --git a/runtime/gtest/Cargo.toml b/runtime/gtest/Cargo.toml index 68915d05b..cefd2396d 100644 --- a/runtime/gtest/Cargo.toml +++ b/runtime/gtest/Cargo.toml @@ -118,7 +118,6 @@ pallet-duniter-wot = { path = '../../pallets/duniter-wot', default-features = fa pallet-identity = { path = '../../pallets/identity', default-features = false } pallet-membership = { path = '../../pallets/membership', default-features = false } pallet-provide-randomness = { path = '../../pallets/provide-randomness', default-features = false } -pallet-ud-accounts-storage = { path = '../../pallets/ud-accounts-storage', default-features = false } pallet-universal-dividend = { path = '../../pallets/universal-dividend', default-features = false } pallet-upgrade-origin = { path = '../../pallets/upgrade-origin', default-features = false } sp-membership = { path = '../../primitives/membership', default-features = false } diff --git a/runtime/gtest/src/lib.rs b/runtime/gtest/src/lib.rs index e134dc461..43c84ab9e 100644 --- a/runtime/gtest/src/lib.rs +++ b/runtime/gtest/src/lib.rs @@ -27,7 +27,8 @@ pub mod parameters; pub use self::parameters::*; pub use common_runtime::{ constants::*, entities::*, handlers::*, AccountId, Address, Balance, BlockNumber, - FullIdentificationOfImpl, GetCurrentEpochIndex, Hash, Header, IdtyIndex, Index, Signature, + FullIdentificationOfImpl, GetCurrentEpochIndex, Hash, Header, IdtyData, IdtyIndex, Index, + Signature, }; pub use pallet_balances::Call as BalancesCall; pub use pallet_identity::{IdtyStatus, IdtyValue}; @@ -228,8 +229,7 @@ construct_runtime!( Preimage: pallet_preimage::{Pallet, Call, Storage, Event<T>} = 22, // Universal dividend - UdAccountsStorage: pallet_ud_accounts_storage::{Pallet, Config<T>, Storage} = 30, - UniversalDividend: pallet_universal_dividend::{Pallet, Call, Config<T>, Storage, Event<T>} = 31, + UniversalDividend: pallet_universal_dividend::{Pallet, Call, Config<T>, Storage, Event<T>} = 30, // Web Of Trust Wot: pallet_duniter_wot::<Instance1>::{Pallet} = 40, -- GitLab