diff --git a/Cargo.lock b/Cargo.lock index d00e4ff660ceb428492319cc6bd2b3f36760fbb7..ca42803f51c2bfcf7572cb79fe08e322b48a14d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2603,6 +2603,7 @@ dependencies = [ "sp-authority-discovery", "sp-block-builder", "sp-consensus-babe", + "sp-consensus-slots", "sp-consensus-vrf", "sp-core", "sp-finality-grandpa", @@ -5545,6 +5546,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", + "pallet-timestamp", "parity-scale-codec", "scale-info", "serde", diff --git a/end2end-tests/cucumber-features/monetary_mass.feature b/end2end-tests/cucumber-features/monetary_mass.feature index 8679d2c2326c48360581d19dc119c97865d3ef22..0b5675d8ffca9ebce2533dd30c9ebda1050c73ad 100644 --- a/end2end-tests/cucumber-features/monetary_mass.feature +++ b/end2end-tests/cucumber-features/monetary_mass.feature @@ -3,7 +3,7 @@ Feature: Balance transfer Scenario: After 10 blocks, the monetary mass should be 60 ÄžD Then Monetary mass should be 30.00 ÄžD Then Current UD amount should be 10.00 ÄžD - When 10 blocks later + When 15 blocks later Then Monetary mass should be 60.00 ÄžD When 10 blocks later Then Monetary mass should be 90.00 ÄžD diff --git a/end2end-tests/cucumber-genesis/default.json b/end2end-tests/cucumber-genesis/default.json index 90f83b57578a71797970c5e9b2256cd35c0bee3d..4186cef469d19fe1c6a66945b37fd534d94c89ec 100644 --- a/end2end-tests/cucumber-genesis/default.json +++ b/end2end-tests/cucumber-genesis/default.json @@ -1,6 +1,6 @@ { - "first_ud": 1000, - "first_ud_reeval": 100, + "first_ud": null, + "first_ud_reeval": null, "genesis_parameters": { "genesis_certs_expire_on": 1000, "genesis_certs_min_received": 2, @@ -36,8 +36,8 @@ "idty_creation_period": 50, "membership_period": 1000, "pending_membership_period": 500, - "ud_creation_period": 10, - "ud_reeval_period": 100, + "ud_creation_period": 60000, + "ud_reeval_period": 600000, "smith_cert_period": 15, "smith_cert_max_by_issuer": 8, "smith_cert_min_received_cert_to_issue_cert": 2, @@ -62,5 +62,6 @@ } }, "sudo_key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", - "technical_committee": ["Alice", "Bob", "Charlie"] + "technical_committee": ["Alice", "Bob", "Charlie"], + "ud": 1000 } diff --git a/end2end-tests/cucumber-genesis/wot.json b/end2end-tests/cucumber-genesis/wot.json index e49e2ed633e11be6def1bbffe2046a4c74e8b83f..969d3f02075053cdb02c812fabd1848bc76d3129 100644 --- a/end2end-tests/cucumber-genesis/wot.json +++ b/end2end-tests/cucumber-genesis/wot.json @@ -1,6 +1,6 @@ { - "first_ud": 1000, - "first_ud_reeval": 100, + "first_ud": null, + "first_ud_reeval": null, "genesis_parameters": { "genesis_certs_min_received": 2, "genesis_memberships_expire_on": 100000, @@ -39,8 +39,8 @@ "idty_creation_period": 50, "membership_period": 1000, "pending_membership_period": 500, - "ud_creation_period": 10, - "ud_reeval_period": 100, + "ud_creation_period": 60000, + "ud_reeval_period": 600000, "smith_cert_period": 15, "smith_cert_max_by_issuer": 8, "smith_cert_min_received_cert_to_issue_cert": 2, @@ -65,5 +65,6 @@ } }, "sudo_key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", - "technical_committee": ["Alice", "Bob", "Charlie"] + "technical_committee": ["Alice", "Bob", "Charlie"], + "ud": 1000 } diff --git a/node/src/chain_spec/gdev.rs b/node/src/chain_spec/gdev.rs index d2e91b2764fff306a7959f20c57a12d0ca761976..adb4a3eb801dcf349ed662e0dd3409794e2e4843 100644 --- a/node/src/chain_spec/gdev.rs +++ b/node/src/chain_spec/gdev.rs @@ -72,10 +72,10 @@ fn get_session_keys_from_seed(s: &str) -> SessionKeys { } /// get environment variable -fn get_env_u32(env_var_name: &'static str, default_value: u32) -> u32 { +fn get_env<T: std::str::FromStr>(env_var_name: &'static str, default_value: T) -> T { std::env::var(env_var_name) .map_or(Ok(default_value), |s| s.parse()) - .unwrap_or_else(|_| panic!("{} must be a number", env_var_name)) + .unwrap_or_else(|_| panic!("{} must be a {}", env_var_name, std::any::type_name::<T>())) } /// make session keys struct @@ -339,14 +339,14 @@ fn gen_genesis_for_local_chain( assert!(initial_smiths_len <= initial_identities_len); assert!(initial_authorities_len <= initial_smiths_len); - let babe_epoch_duration = get_env_u32("DUNITER_BABE_EPOCH_DURATION", 30) as u64; - let cert_validity_period = get_env_u32("DUNITER_CERT_VALIDITY_PERIOD", 1_000); - let first_ud = 1_000; - let membership_period = get_env_u32("DUNITER_MEMBERSHIP_PERIOD", 1_000); - let smith_cert_validity_period = get_env_u32("DUNITER_SMITH_CERT_VALIDITY_PERIOD", 1_000); - let smith_membership_period = get_env_u32("DUNITER_SMITH_MEMBERSHIP_PERIOD", 1_000); - let ud_creation_period = get_env_u32("DUNITER_UD_CREATION_PERIOD", 10); - let ud_reeval_period = get_env_u32("DUNITER_UD_REEEVAL_PERIOD", 200); + let babe_epoch_duration = get_env("DUNITER_BABE_EPOCH_DURATION", 30); + let cert_validity_period = get_env("DUNITER_CERT_VALIDITY_PERIOD", 1_000); + let membership_period = get_env("DUNITER_MEMBERSHIP_PERIOD", 1_000); + let smith_cert_validity_period = get_env("DUNITER_SMITH_CERT_VALIDITY_PERIOD", 1_000); + let smith_membership_period = get_env("DUNITER_SMITH_MEMBERSHIP_PERIOD", 1_000); + let ud_creation_period = get_env("DUNITER_UD_CREATION_PERIOD", 60_000); + let ud_reeval_period = get_env("DUNITER_UD_REEEVAL_PERIOD", 1_200_000); + let ud = 1_000; let initial_smiths = (0..initial_smiths_len) .map(|i| get_authority_keys_from_seed(NAMES[i])) @@ -374,7 +374,7 @@ fn gen_genesis_for_local_chain( owner_key.clone(), GenesisAccountData { random_id: H256(blake2_256(&(i as u32, owner_key).encode())), - balance: first_ud, + balance: ud, is_identity: true, }, ) @@ -482,9 +482,10 @@ fn gen_genesis_for_local_chain( certs_by_receiver: clique_wot(initial_smiths_len), }, universal_dividend: UniversalDividendConfig { - first_reeval: 100, - first_ud, - initial_monetary_mass: initial_identities_len as u64 * first_ud, + first_reeval: None, + first_ud: None, + initial_monetary_mass: initial_identities_len as u64 * ud, + ud, }, treasury: Default::default(), } @@ -504,14 +505,14 @@ fn gen_genesis_for_benchmark_chain( assert!(initial_smiths_len <= initial_identities_len); assert!(initial_authorities_len <= initial_smiths_len); - let babe_epoch_duration = get_env_u32("DUNITER_BABE_EPOCH_DURATION", 30) as u64; - let cert_validity_period = get_env_u32("DUNITER_CERT_VALIDITY_PERIOD", 1_000); - let first_ud = 1_000; - let membership_period = get_env_u32("DUNITER_MEMBERSHIP_PERIOD", 1_000); - let smith_cert_validity_period = get_env_u32("DUNITER_SMITH_CERT_VALIDITY_PERIOD", 1_000); - let smith_membership_period = get_env_u32("DUNITER_SMITH_MEMBERSHIP_PERIOD", 1_000); - let ud_creation_period = get_env_u32("DUNITER_UD_CREATION_PERIOD", 10); - let ud_reeval_period = get_env_u32("DUNITER_UD_REEEVAL_PERIOD", 200); + let babe_epoch_duration = get_env("DUNITER_BABE_EPOCH_DURATION", 30); + let cert_validity_period = get_env("DUNITER_CERT_VALIDITY_PERIOD", 1_000); + let membership_period = get_env("DUNITER_MEMBERSHIP_PERIOD", 1_000); + let smith_cert_validity_period = get_env("DUNITER_SMITH_CERT_VALIDITY_PERIOD", 1_000); + let smith_membership_period = get_env("DUNITER_SMITH_MEMBERSHIP_PERIOD", 1_000); + let ud_creation_period = get_env("DUNITER_UD_CREATION_PERIOD", 60_000); + let ud_reeval_period = get_env("DUNITER_UD_REEEVAL_PERIOD", 1_200_000); + let ud = 1_000; let initial_smiths = (0..initial_smiths_len) .map(|i| get_authority_keys_from_seed(NAMES[i])) @@ -539,7 +540,7 @@ fn gen_genesis_for_benchmark_chain( owner_key.clone(), GenesisAccountData { random_id: H256(blake2_256(&(i as u32, owner_key).encode())), - balance: first_ud, + balance: ud, is_identity: true, }, ) @@ -670,9 +671,10 @@ fn gen_genesis_for_benchmark_chain( certs_by_receiver: clique_wot(initial_smiths_len), }, universal_dividend: UniversalDividendConfig { - first_reeval: 100, - first_ud, - initial_monetary_mass: initial_identities_len as u64 * first_ud, + first_reeval: None, + first_ud: None, + initial_monetary_mass: initial_identities_len as u64 * ud, + ud, }, treasury: Default::default(), } @@ -698,6 +700,7 @@ fn genesis_data_to_gdev_genesis_conf( smith_memberships, sudo_key, technical_committee_members, + ud, } = genesis_data; gdev_runtime::GenesisConfig { @@ -763,6 +766,7 @@ fn genesis_data_to_gdev_genesis_conf( first_reeval: first_ud_reeval, first_ud, initial_monetary_mass, + ud, }, treasury: Default::default(), } diff --git a/node/src/chain_spec/gen_genesis_data.rs b/node/src/chain_spec/gen_genesis_data.rs index b26ebe3ad6b4a0c5c837e42c2ed7f2d30509525f..2fdd6cef08caa4a40d06c1c9b40e6ec197f44a14 100644 --- a/node/src/chain_spec/gen_genesis_data.rs +++ b/node/src/chain_spec/gen_genesis_data.rs @@ -27,8 +27,8 @@ const EXISTENTIAL_DEPOSIT: u64 = 200; pub struct GenesisData<Parameters: DeserializeOwned, SessionKeys: Decode> { pub accounts: BTreeMap<AccountId, GenesisAccountData<u64>>, pub certs_by_receiver: BTreeMap<u32, BTreeMap<u32, Option<u32>>>, - pub first_ud: u64, - pub first_ud_reeval: u32, + pub first_ud: Option<u64>, + pub first_ud_reeval: Option<u64>, pub identities: Vec<(String, AccountId)>, pub initial_authorities: BTreeMap<u32, (AccountId, bool)>, pub initial_monetary_mass: u64, @@ -39,6 +39,7 @@ pub struct GenesisData<Parameters: DeserializeOwned, SessionKeys: Decode> { pub smith_memberships: BTreeMap<u32, MembershipData>, pub sudo_key: Option<AccountId>, pub technical_committee_members: Vec<AccountId>, + pub ud: u64, } #[derive(Default, Deserialize, Serialize)] @@ -51,8 +52,8 @@ pub struct ParamsAppliedAtGenesis { #[derive(Deserialize, Serialize)] struct GenesisConfig<Parameters> { - first_ud: u64, - first_ud_reeval: u32, + first_ud: Option<u64>, + first_ud_reeval: Option<u64>, genesis_parameters: ParamsAppliedAtGenesis, identities: BTreeMap<String, Idty>, #[serde(default)] @@ -61,6 +62,7 @@ struct GenesisConfig<Parameters> { smith_identities: BTreeMap<String, SmithData>, sudo_key: Option<AccountId>, technical_committee: Vec<String>, + ud: u64, #[serde(default)] wallets: BTreeMap<AccountId, u64>, } @@ -155,6 +157,7 @@ where identities, smith_identities, technical_committee, + ud, wallets, } = genesis_config; @@ -387,6 +390,7 @@ where smith_memberships, sudo_key, technical_committee_members, + ud, }; Ok(f(genesis_data)) diff --git a/node/src/chain_spec/gtest.rs b/node/src/chain_spec/gtest.rs index 023aef9f39e0ec298e9953a216cbb5bf9e7c11c8..0eb25e3db83b09843193cbfe47a1bf0b66c175db 100644 --- a/node/src/chain_spec/gtest.rs +++ b/node/src/chain_spec/gtest.rs @@ -271,7 +271,7 @@ fn generate_genesis( assert!(initial_smiths_len <= initial_identities_len); assert!(initial_authorities_len <= initial_smiths_len); - let first_ud = 1_000; + let ud = 1_000; let initial_smiths = (0..initial_smiths_len) .map(|i| get_authority_keys_from_seed(NAMES[i])) @@ -299,7 +299,7 @@ fn generate_genesis( owner_key.clone(), GenesisAccountData { random_id: H256(blake2_256(&(i as u32, owner_key).encode())), - balance: first_ud, + balance: ud, is_identity: true, }, ) @@ -395,9 +395,10 @@ fn generate_genesis( certs_by_receiver: clique_wot(initial_smiths_len), }, universal_dividend: UniversalDividendConfig { - first_reeval: 100, - first_ud: 1_000, + first_reeval: 600_000, + first_ud: 6_000, initial_monetary_mass: 0, + ud, }, treasury: Default::default(), } diff --git a/pallets/duniter-test-parameters/src/lib.rs b/pallets/duniter-test-parameters/src/lib.rs index 908bc7fe7e7f33087940d784a60061e88b7aa20b..cd688fd913b98d5d1ea6623aa37fbc768b05de4b 100644 --- a/pallets/duniter-test-parameters/src/lib.rs +++ b/pallets/duniter-test-parameters/src/lib.rs @@ -45,8 +45,8 @@ pub mod types { pub idty_creation_period: BlockNumber, pub membership_period: BlockNumber, pub pending_membership_period: BlockNumber, - pub ud_creation_period: BlockNumber, - pub ud_reeval_period: BlockNumber, + pub ud_creation_period: PeriodCount, + pub ud_reeval_period: PeriodCount, pub smith_cert_period: BlockNumber, pub smith_cert_max_by_issuer: CertCount, pub smith_cert_min_received_cert_to_issue_cert: CertCount, diff --git a/pallets/universal-dividend/Cargo.toml b/pallets/universal-dividend/Cargo.toml index a4c167c13631fde767da1a25c44728088c2ffa02..91eb775c66ab0732c5731c9e9a2158db28a3eae3 100644 --- a/pallets/universal-dividend/Cargo.toml +++ b/pallets/universal-dividend/Cargo.toml @@ -19,6 +19,7 @@ std = [ 'frame-support/std', 'frame-system/std', 'frame-benchmarking/std', + 'pallet-timestamp/std', "serde", "sp-arithmetic/std", "sp-io/std", @@ -46,6 +47,11 @@ default-features = false git = 'https://github.com/duniter/substrate' branch = 'duniter-substrate-v0.9.32' +[dependencies.pallet-timestamp] +default-features = false +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.32' + [dependencies.sp-arithmetic] default-features = false git = 'https://github.com/duniter/substrate' diff --git a/pallets/universal-dividend/src/benchmarking.rs b/pallets/universal-dividend/src/benchmarking.rs index 7d54bb0173de37757d8e84cd62c9ebcf3a2fb243..4edfc2356afd5060e4e25378d6fbc6b4403439dc 100644 --- a/pallets/universal-dividend/src/benchmarking.rs +++ b/pallets/universal-dividend/src/benchmarking.rs @@ -152,10 +152,11 @@ benchmarks! { impl_benchmark_test_suite!( Pallet, crate::mock::new_test_ext(crate::mock::UniversalDividendConfig { - first_reeval: 8, - first_ud: 1_000, + first_reeval: 48_000, + first_ud: 6_000, initial_monetary_mass: 0, initial_members: vec![1], + ud, }), crate::mock::Test ); diff --git a/pallets/universal-dividend/src/compute_claim_uds.rs b/pallets/universal-dividend/src/compute_claim_uds.rs index 19415e594c9f065527ccd9b59c6bf9af4ff5f7d7..e932e8482d33441a5d3bc63c6df1ce38d16535fd 100644 --- a/pallets/universal-dividend/src/compute_claim_uds.rs +++ b/pallets/universal-dividend/src/compute_claim_uds.rs @@ -30,6 +30,7 @@ pub(super) fn compute_claim_uds<Balance: AtLeast32BitUnsigned>( 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; } else { let count = current_ud_index - ud_index; diff --git a/pallets/universal-dividend/src/lib.rs b/pallets/universal-dividend/src/lib.rs index 73d5af1eb6efd2ffbcca68d64ede43f9c04d68d2..cb3dc5128f643e8934d8ff2c671f700573b09162 100644 --- a/pallets/universal-dividend/src/lib.rs +++ b/pallets/universal-dividend/src/lib.rs @@ -29,12 +29,12 @@ pub use pallet::*; pub use types::*; pub use weights::WeightInfo; -use frame_support::traits::{tokens::ExistenceRequirement, Currency}; +use frame_support::traits::{tokens::ExistenceRequirement, Currency, OnTimestampSet}; use sp_arithmetic::{ per_things::Perbill, traits::{One, Saturating, Zero}, }; -use sp_runtime::traits::StaticLookup; +use sp_runtime::traits::{Get, MaybeSerializeDeserialize, StaticLookup}; #[frame_support::pallet] pub mod pallet { @@ -58,9 +58,9 @@ pub mod pallet { pub struct Pallet<T>(_); #[pallet::config] - pub trait Config: frame_system::Config { - // BlockNumber into Balance converter - type BlockNumberIntoBalance: Convert<Self::BlockNumber, BalanceOf<Self>>; + pub trait Config: frame_system::Config + pallet_timestamp::Config { + // Moment into Balance converter + type MomentIntoBalance: Convert<Self::Moment, BalanceOf<Self>>; // The currency type Currency: Currency<Self::AccountId>; #[pallet::constant] @@ -79,11 +79,11 @@ pub mod pallet { /// Square of the money growth rate per ud reevaluation period type SquareMoneyGrowthRate: Get<Perbill>; #[pallet::constant] - /// Universal dividend creation period - type UdCreationPeriod: Get<Self::BlockNumber>; + /// Universal dividend creation period (ms) + type UdCreationPeriod: Get<Self::Moment>; #[pallet::constant] - /// Universal dividend reevaluation period (in number of blocks) - type UdReevalPeriod: Get<Self::BlockNumber>; + /// Universal dividend reevaluation period (ms) + type UdReevalPeriod: Get<Self::Moment>; #[pallet::constant] /// The number of units to divide the amounts expressed in number of UDs /// Example: If you wish to express the UD amounts with a maximum precision of the order @@ -131,7 +131,12 @@ pub mod pallet { /// Next UD reevaluation #[pallet::storage] #[pallet::getter(fn next_reeval)] - pub type NextReeval<T: Config> = StorageValue<_, T::BlockNumber, ValueQuery>; + pub type NextReeval<T: Config> = StorageValue<_, T::Moment, OptionQuery>; + + /// Next UD creation + #[pallet::storage] + #[pallet::getter(fn next_ud)] + pub type NextUd<T: Config> = StorageValue<_, T::Moment, OptionQuery>; /// Past UD reevaluations #[pallet::storage] @@ -142,39 +147,53 @@ pub mod pallet { // GENESIS #[pallet::genesis_config] - pub struct GenesisConfig<T: Config> { - pub first_reeval: T::BlockNumber, - pub first_ud: BalanceOf<T>, + pub struct GenesisConfig<T: Config> + where + <T as pallet_timestamp::Config>::Moment: MaybeSerializeDeserialize, + { + /// If None, it will be set to one period after the first block with a timestamp + pub first_reeval: Option<T::Moment>, + /// If None, it will be set to one period after the first block with a timestamp + pub first_ud: Option<T::Moment>, pub initial_monetary_mass: BalanceOf<T>, #[cfg(test)] pub initial_members: Vec<T::AccountId>, + 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 { Self { - first_reeval: Default::default(), - first_ud: Default::default(), + first_reeval: None, + first_ud: None, initial_monetary_mass: Default::default(), #[cfg(test)] initial_members: Default::default(), + ud: Default::default(), } } } #[pallet::genesis_build] - impl<T: Config> GenesisBuild<T> for GenesisConfig<T> { + impl<T: Config> GenesisBuild<T> for GenesisConfig<T> + where + <T as pallet_timestamp::Config>::Moment: MaybeSerializeDeserialize, + { 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); <MonetaryMass<T>>::put(self.initial_monetary_mass); - NextReeval::<T>::put(self.first_reeval); + NextReeval::<T>::set(self.first_reeval); + NextUd::<T>::set(self.first_ud); let mut past_reevals = BoundedVec::default(); past_reevals - .try_push((1, self.first_ud)) + .try_push((1, self.ud)) .expect("MaxPastReeval should be greather than zero"); PastReevals::<T>::put(past_reevals); @@ -187,29 +206,6 @@ pub mod pallet { } } - // HOOKS // - - #[pallet::hooks] - impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { - fn on_initialize(n: T::BlockNumber) -> Weight { - 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())); - Self::reeval_ud(current_members_count); - Self::create_ud(current_members_count); - T::WeightInfo::on_initialize_ud_reevalued() - } else { - Self::create_ud(current_members_count); - T::WeightInfo::on_initialize_ud_created() - } - } else { - T::WeightInfo::on_initialize() - } - } - } - // EVENTS // // Pallets use events to inform users when important changes are made. @@ -255,7 +251,7 @@ pub mod pallet { // INTERNAL FUNCTIONS // impl<T: Config> Pallet<T> { /// create universal dividend - fn create_ud(members_count: BalanceOf<T>) { + 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(); @@ -334,7 +330,7 @@ pub mod pallet { } /// reevaluate the value of the universal dividend - fn reeval_ud(members_count: BalanceOf<T>) { + pub(crate) fn reeval_ud(members_count: BalanceOf<T>) { // get current value and monetary mass let ud_amount = <CurrentUd<T>>::get(); let monetary_mass = <MonetaryMass<T>>::get(); @@ -345,7 +341,7 @@ pub mod pallet { T::SquareMoneyGrowthRate::get(), monetary_mass, members_count, - T::BlockNumberIntoBalance::convert( + T::MomentIntoBalance::convert( T::UdReevalPeriod::get() / T::UdCreationPeriod::get(), ), ); @@ -391,13 +387,13 @@ pub mod pallet { #[pallet::call] impl<T: Config> Pallet<T> { /// Claim Universal Dividends - #[pallet::weight(T::WeightInfo::claim_uds(T::MaxPastReeval::get()))] + #[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. - #[pallet::weight(T::WeightInfo::transfer_ud())] + #[pallet::weight(<T as pallet::Config>::WeightInfo::transfer_ud())] pub fn transfer_ud( origin: OriginFor<T>, dest: <T::Lookup as StaticLookup>::Source, @@ -407,7 +403,7 @@ pub mod pallet { } /// Transfer some liquid free balance to another account, in milliUD. - #[pallet::weight(T::WeightInfo::transfer_ud_keep_alive())] + #[pallet::weight(<T as pallet::Config>::WeightInfo::transfer_ud_keep_alive())] pub fn transfer_ud_keep_alive( origin: OriginFor<T>, dest: <T::Lookup as StaticLookup>::Source, @@ -446,3 +442,32 @@ pub mod pallet { } } } + +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())); + } + } +} diff --git a/pallets/universal-dividend/src/mock.rs b/pallets/universal-dividend/src/mock.rs index bb5a4b20d63cb6e5447eaffb95e78e8e84b63a2a..54284cedfbf04d299463fb4ab9df411124cf350a 100644 --- a/pallets/universal-dividend/src/mock.rs +++ b/pallets/universal-dividend/src/mock.rs @@ -28,8 +28,9 @@ use sp_runtime::{ BuildStorage, }; +pub const BLOCK_TIME: u64 = 6_000; + type Balance = u64; -type BlockNumber = u64; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>; type Block = frame_system::mocking::MockBlock<Test>; @@ -41,6 +42,7 @@ frame_support::construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event<T>}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>}, UniversalDividend: pallet_universal_dividend::{Pallet, Storage, Config<T>, Event<T>}, } @@ -78,6 +80,17 @@ impl system::Config for Test { type MaxConsumers = frame_support::traits::ConstU32<16>; } +parameter_types! { + pub const MinimumPeriod: u64 = 3_000; +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = UniversalDividend; + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + parameter_types! { pub const ExistentialDeposit: Balance = 10; pub const MaxLocks: u32 = 50; @@ -98,8 +111,8 @@ impl pallet_balances::Config for Test { parameter_types! { pub const MembersCount: u64 = 3; pub const SquareMoneyGrowthRate: Perbill = Perbill::from_percent(10); - pub const UdCreationPeriod: BlockNumber = 2; - pub const UdReevalPeriod: BlockNumber = 8; + pub const UdCreationPeriod: u64 = 12_000; + pub const UdReevalPeriod: u64 = 48_000; } pub struct TestMembersStorage; @@ -138,7 +151,7 @@ impl Iterator for TestMembersStorageIter { } impl pallet_universal_dividend::Config for Test { - type BlockNumberIntoBalance = sp_runtime::traits::ConvertInto; + type MomentIntoBalance = sp_runtime::traits::ConvertInto; type Currency = pallet_balances::Pallet<Test>; type MaxPastReeval = frame_support::traits::ConstU32<2>; type MembersCount = MembersCount; @@ -174,5 +187,6 @@ pub fn run_to_block(n: u64) { System::set_block_number(System::block_number() + 1); System::on_initialize(System::block_number()); UniversalDividend::on_initialize(System::block_number()); + Timestamp::set_timestamp(System::block_number() * BLOCK_TIME); } } diff --git a/pallets/universal-dividend/src/tests.rs b/pallets/universal-dividend/src/tests.rs index 3a4f83bf20d5828353115a210a2dab90f1c2e4c2..3240e9b5fdc713dedd381870e57e6ad22c2e0e39 100644 --- a/pallets/universal-dividend/src/tests.rs +++ b/pallets/universal-dividend/src/tests.rs @@ -20,10 +20,11 @@ use frame_support::{assert_err, assert_ok, assert_storage_noop}; #[test] fn test_claim_uds() { new_test_ext(UniversalDividendConfig { - first_reeval: 8, - first_ud: 1_000, + 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 @@ -118,16 +119,126 @@ fn test_claim_uds() { 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!(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, + })); + assert_eq!(Balances::free_balance(1), 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); + + // 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: 8, - first_ud: 1_000, + 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 diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index d03759d4cbd890271f299281e500552a5c5c5ae0..589b4f3812f87b937d04f309518eda1fa5742b63 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -63,6 +63,9 @@ pub type Signature = sp_runtime::MultiSignature; /// Index of an identity pub type IdtyIndex = u32; +/// Time in milliseconds +pub type Moment = u64; + 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 f289dfac14c0f74e508242470ac5750a0b3cfa97..55fcb8bca93bc8c396d5a05af0ad0206560fac6e 100644 --- a/runtime/common/src/pallets_config.rs +++ b/runtime/common/src/pallets_config.rs @@ -141,7 +141,7 @@ macro_rules! pallets_config { impl pallet_timestamp::Config for Runtime { type Moment = u64; - type OnTimestampSet = Babe; + type OnTimestampSet = (Babe, UniversalDividend); type MinimumPeriod = MinimumPeriod; type WeightInfo = common_runtime::weights::pallet_timestamp::WeightInfo<Runtime>; } @@ -413,10 +413,10 @@ macro_rules! pallets_config { } impl pallet_universal_dividend::Config for Runtime { - type BlockNumberIntoBalance = sp_runtime::traits::ConvertInto; + type MomentIntoBalance = sp_runtime::traits::ConvertInto; type Currency = pallet_balances::Pallet<Runtime>; type RuntimeEvent = RuntimeEvent; - type MaxPastReeval = frame_support::traits::ConstU32<4>; + type MaxPastReeval = frame_support::traits::ConstU32<160>; type MembersCount = MembersCount; type MembersStorage = common_runtime::providers::UdMembersStorage<Runtime>; type MembersStorageIter = common_runtime::providers::UdMembersStorageIter<Runtime>; diff --git a/runtime/g1/src/parameters.rs b/runtime/g1/src/parameters.rs index 354f52a437ae1599cf397e6d2dddc5454363713d..ec911e29e24322e3c270ae7a62c30b49cec6dc96 100644 --- a/runtime/g1/src/parameters.rs +++ b/runtime/g1/src/parameters.rs @@ -16,7 +16,7 @@ use crate::*; use common_runtime::constants::*; -use common_runtime::{Balance, BlockNumber}; +use common_runtime::{Balance, BlockNumber, Moment}; use frame_support::parameter_types; use frame_support::weights::constants::WEIGHT_PER_SECOND; use sp_arithmetic::Perbill; @@ -80,8 +80,8 @@ frame_support::parameter_types! { parameter_types! { // 0.002_381_440 = 0.0488^2 pub const SquareMoneyGrowthRate: Perbill = Perbill::from_parts(2_381_440); - pub const UdCreationPeriod: BlockNumber = DAYS; - pub const UdReevalPeriod: BlockNumber = 2_620_800; // 86400 * 182 / 6 + pub const UdCreationPeriod: Moment = 86_400_000; // 1 day + pub const UdReevalPeriod: Moment = 15_778_800_000; // 1/2 year } /*******/ diff --git a/runtime/gdev/Cargo.toml b/runtime/gdev/Cargo.toml index 8c8fd77889789dff328a3647e5fb4565127594a6..b5c742c89a7a2d57969662343280f4fda1f8662d 100644 --- a/runtime/gdev/Cargo.toml +++ b/runtime/gdev/Cargo.toml @@ -128,6 +128,7 @@ try-runtime = [ ] [dev-dependencies] +sp-consensus-slots = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32' } sp-consensus-vrf = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32' } sp-finality-grandpa = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32' } sp-io = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.32' } diff --git a/runtime/gdev/src/lib.rs b/runtime/gdev/src/lib.rs index 770191a20d9addbc65e0625b25eb2a73f5dfd63d..dff62b247ed47614a3d3a9419493013910994b3a 100644 --- a/runtime/gdev/src/lib.rs +++ b/runtime/gdev/src/lib.rs @@ -255,10 +255,7 @@ common_runtime::pallets_config! { pub type IdtyCreationPeriod = pallet_duniter_test_parameters::IdtyCreationPeriod<Runtime>; pub type MembershipPeriod = pallet_duniter_test_parameters::MembershipPeriod<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>; diff --git a/runtime/gdev/tests/common/mod.rs b/runtime/gdev/tests/common/mod.rs index 438ffa81fad84b4e9ced32960beeef81171b283b..4debb039c8c5e28fd57271171869b8ee947a4258 100644 --- a/runtime/gdev/tests/common/mod.rs +++ b/runtime/gdev/tests/common/mod.rs @@ -44,6 +44,7 @@ pub type AuthorityKeys = ( AuthorityDiscoveryId, ); +pub const BLOCK_TIME: u64 = 6_000; pub const NAMES: [&str; 6] = ["Alice", "Bob", "Charlie", "Dave", "Eve", "Ferdie"]; pub struct ExtBuilder { @@ -104,8 +105,8 @@ impl ExtBuilder { idty_creation_period: 50, membership_period: 100, pending_membership_period: 500, - ud_creation_period: 10, - ud_reeval_period: 10 * 20, + ud_creation_period: 60_000, + ud_reeval_period: 60_000 * 20, smith_cert_period: 15, smith_cert_max_by_issuer: 8, smith_cert_min_received_cert_to_issue_cert: 2, @@ -266,9 +267,10 @@ impl ExtBuilder { .unwrap(); pallet_universal_dividend::GenesisConfig::<Runtime> { - first_reeval: 100, - first_ud: 1_000, + first_reeval: Some(600_000), + first_ud: Some(24_000), initial_monetary_mass: 0, + ud: 1_000, } .assimilate_storage(&mut t) .unwrap(); @@ -309,6 +311,11 @@ pub fn get_authority_keys_from_seed(s: &str) -> AuthorityKeys { ) } +fn mock_babe_initialize(n: u32) { + let slot: sp_consensus_slots::Slot = (n as u64).into(); + pallet_babe::CurrentSlot::<Runtime>::put(slot); +} + pub fn run_to_block(n: u32) { while System::block_number() < n { // Finalize the previous block @@ -325,7 +332,7 @@ pub fn run_to_block(n: u32) { // Initialize the new block Account::on_initialize(System::block_number()); Scheduler::on_initialize(System::block_number()); - //Babe::on_initialize(System::block_number()); + mock_babe_initialize(System::block_number()); Authorship::on_initialize(System::block_number()); Session::on_initialize(System::block_number()); @@ -337,6 +344,8 @@ pub fn run_to_block(n: u32) { SmithSubWot::on_initialize(System::block_number()); SmithMembership::on_initialize(System::block_number()); SmithCert::on_initialize(System::block_number()); + + Timestamp::set_timestamp(System::block_number() as u64 * BLOCK_TIME); } } diff --git a/runtime/gdev/tests/integration_tests.rs b/runtime/gdev/tests/integration_tests.rs index 22d1fb05626ce146ea8fde5e57fe23c0b0d0af4f..0bb497e1e00e58faf4f813ad65418adfc993ec86 100644 --- a/runtime/gdev/tests/integration_tests.rs +++ b/runtime/gdev/tests/integration_tests.rs @@ -206,7 +206,11 @@ fn test_membership_renewal() { 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); + run_to_block( + (<Runtime as pallet_universal_dividend::Config>::UdCreationPeriod::get() + / <Runtime as pallet_babe::Config>::ExpectedBlockTime::get() + + 1) as u32, + ); assert_ok!(Identity::remove_identity( frame_system::RawOrigin::Root.into(), @@ -251,8 +255,8 @@ fn test_remove_identity_after_one_ud() { #[test] fn test_ud_claimed_membership_on_and_off() { ExtBuilder::new(1, 3, 4).build().execute_with(|| { - // UD are created every 10 blocks from block 0 - run_to_block(10); + // UD are created every 10 blocks from block 4 + run_to_block(4); System::assert_has_event(RuntimeEvent::UniversalDividend( pallet_universal_dividend::Event::NewUdCreated { amount: 1000, @@ -267,7 +271,7 @@ fn test_ud_claimed_membership_on_and_off() { 0 ); - run_to_block(11); + run_to_block(13); // alice identity expires assert_ok!(Membership::force_expire_membership(1)); System::assert_has_event(RuntimeEvent::UniversalDividend( @@ -284,7 +288,7 @@ fn test_ud_claimed_membership_on_and_off() { ); // UD number 2 - run_to_block(20); + run_to_block(14); System::assert_has_event(RuntimeEvent::UniversalDividend( pallet_universal_dividend::Event::NewUdCreated { amount: 1000, @@ -303,7 +307,7 @@ fn test_ud_claimed_membership_on_and_off() { )); // UD number 3 - run_to_block(30); + run_to_block(24); System::assert_has_event(RuntimeEvent::UniversalDividend( pallet_universal_dividend::Event::NewUdCreated { amount: 1000, @@ -314,7 +318,7 @@ fn test_ud_claimed_membership_on_and_off() { )); // one block later, alice claims her new UD - run_to_block(31); + run_to_block(25); assert_ok!(UniversalDividend::claim_uds( frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into() )); diff --git a/runtime/gtest/src/parameters.rs b/runtime/gtest/src/parameters.rs index 86b66d53a19d638f20297d1ca95b206f09d65f6f..063c2c5a01bd83e9d6e1fc2c984b74e38677670f 100644 --- a/runtime/gtest/src/parameters.rs +++ b/runtime/gtest/src/parameters.rs @@ -80,9 +80,8 @@ frame_support::parameter_types! { parameter_types! { // 0.002_381_440 = 0.0488^2 pub const SquareMoneyGrowthRate: Perbill = Perbill::from_parts(2_381_440); - pub const UdCreationPeriod: BlockNumber = DAYS; - pub const UdFirstReeval: BlockNumber = 2 * DAYS; - pub const UdReevalPeriod: BlockNumber = 100_800; // 86400 * 7 / 6 + pub const UdCreationPeriod: BlockNumber = 86_400_000; // 1 day + pub const UdReevalPeriod: BlockNumber = 7 * 86_400_000; // 7 days } /*******/