diff --git a/Cargo.lock b/Cargo.lock index 2a705cb3e4..adff9e8b77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6200,6 +6200,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", + "sp-api", "sp-consensus-aura", "sp-core", "sp-io", diff --git a/pallets/parachain-staking/Cargo.toml b/pallets/parachain-staking/Cargo.toml index d93e7d581d..69bb2699ed 100644 --- a/pallets/parachain-staking/Cargo.toml +++ b/pallets/parachain-staking/Cargo.toml @@ -24,6 +24,7 @@ pallet-authorship = {git = "https://github.com/paritytech/substrate", branch = " pallet-balances = {git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false} pallet-session = {git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false} sp-runtime = {git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false} +sp-api = {git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false} sp-staking = {git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false} sp-std = {git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false} @@ -47,6 +48,7 @@ std = [ "parity-scale-codec/std", "scale-info/std", "serde", + "sp-api/std", "sp-runtime/std", "sp-staking/std", "sp-std/std", diff --git a/pallets/parachain-staking/src/benchmarking.rs b/pallets/parachain-staking/src/benchmarking.rs index 6da094caa7..1a6c10f763 100644 --- a/pallets/parachain-staking/src/benchmarking.rs +++ b/pallets/parachain-staking/src/benchmarking.rs @@ -48,11 +48,11 @@ fn setup_collator_candidates( for acc in collators.iter() { T::Currency::make_free_balance_be(acc, amount); - assert_ok!(>::join_candidates( + assert_ok!(Pallet::::join_candidates( T::Origin::from(Some(acc.clone()).into()), amount, )); - assert_eq!(>::get(acc).unwrap().stake, amount); + assert_eq!(CandidatePool::::get(acc).unwrap().stake, amount); } TopCandidates::::get() @@ -64,7 +64,7 @@ fn setup_collator_candidates( } fn fill_delegators(num_delegators: u32, collator: T::AccountId, collator_seed: u32) -> Vec { - let state = >::get(&collator).unwrap(); + let state = CandidatePool::::get(&collator).unwrap(); let current_delegators = state.delegators.len().saturated_into::(); let delegators: Vec = (current_delegators..num_delegators) @@ -79,7 +79,7 @@ fn fill_delegators(num_delegators: u32, collator: T::AccountId, colla for acc in delegators.iter() { T::Currency::make_free_balance_be(acc, T::MinDelegatorStake::get()); - assert_ok!(>::join_delegators( + assert_ok!(Pallet::::join_delegators( T::Origin::from(Some(acc.clone()).into()), T::Lookup::unlookup(collator.clone()), T::MinDelegatorStake::get(), @@ -95,56 +95,42 @@ where u64: Into<::BlockNumber>, { let who = delegator.unwrap_or(collator); - assert_eq!(>::get(who).len(), 0); + assert_eq!(Unstaking::::get(who).len(), 0); while System::::block_number() < unstaked.into() { if let Some(delegator) = delegator { - assert_ok!(>::delegator_stake_less( + assert_ok!(Pallet::::delegator_stake_less( RawOrigin::Signed(delegator.clone()).into(), - T::Lookup::unlookup(collator.clone()), T::CurrencyBalance::one() )); } else { - assert_ok!(>::candidate_stake_less( + assert_ok!(Pallet::::candidate_stake_less( RawOrigin::Signed(collator.clone()).into(), T::CurrencyBalance::one() )); } System::::set_block_number(System::::block_number() + T::BlockNumber::one()); } - assert_eq!(>::get(who).len() as u64, unstaked); - assert!(>::get(who).len() <= T::MaxUnstakeRequests::get().try_into().unwrap()); + assert_eq!(Unstaking::::get(who).len() as u64, unstaked); + assert!(Unstaking::::get(who).len() <= T::MaxUnstakeRequests::get().try_into().unwrap()); } benchmarks! { where_clause { where u64: Into<::BlockNumber> } on_initialize_no_action { - assert_eq!(>::get().current, 0u32); + assert_eq!(Round::::get().current, 0u32); let block = T::BlockNumber::one(); }: { Pallet::::on_initialize(block) } verify { - assert_eq!(>::get().current, 0u32); + assert_eq!(Round::::get().current, 0u32); } on_initialize_round_update { - let round = >::get(); + let round = Round::::get(); assert_eq!(round.current, 0u32); }: { Pallet::::on_initialize(round.length) } verify { - assert_eq!(>::get().current, 1u32); - } - - on_initialize_new_year { - let old = >::get(); - assert_eq!(>::get(), T::BlockNumber::zero()); - let block = (T::BLOCKS_PER_YEAR + 1u32.into()).saturated_into::(); - }: { Pallet::::on_initialize(block) } - verify { - let new = >::get(); - assert_eq!(>::get(), T::BlockNumber::one()); - assert_eq!(new.collator.max_rate, old.collator.max_rate); - assert_eq!(new.delegator.max_rate, old.delegator.max_rate); - assert!(new.collator.reward_rate.annual < old.collator.reward_rate.annual); + assert_eq!(Round::::get().current, 1u32); } on_initialize_network_rewards { @@ -161,14 +147,14 @@ benchmarks! { } force_new_round { - let round = >::get(); + let round = Round::::get(); let now = System::::block_number(); assert_eq!(round.current, 0); assert_eq!(Session::::current_index(), 0); - assert!(!>::get()); + assert!(!ForceNewRound::::get()); }: _(RawOrigin::Root) verify { - assert!(>::get()); + assert!(ForceNewRound::::get()); assert_eq!(Session::::current_index(), 0); // jump to next block to trigger new round @@ -176,15 +162,24 @@ benchmarks! { System::::set_block_number(now); Session::::on_initialize(now); assert_eq!(Session::::current_index(), 1); - assert_eq!(>::get(), RoundInfo { + assert_eq!(Round::::get(), RoundInfo { current: 1, first: now, length: round.length, }); - assert!(!>::get()); + assert!(!ForceNewRound::::get()); } set_inflation { + let n in 0 .. T::MaxTopCandidates::get(); + let m in 0 .. T::MaxDelegatorsPerCollator::get(); + + let candidates = setup_collator_candidates::(n, None); + for (i, c) in candidates.iter().enumerate() { + fill_delegators::(m, c.clone(), i.saturated_into::()); + Rewards::::insert(&c, T::CurrencyBalance::one()); + } + let inflation = InflationInfo::new( T::BLOCKS_PER_YEAR.saturated_into(), Perquintill::from_percent(10), @@ -194,7 +189,10 @@ benchmarks! { ); }: _(RawOrigin::Root, inflation.collator.max_rate, inflation.collator.reward_rate.annual, inflation.delegator.max_rate, inflation.delegator.reward_rate.annual) verify { - assert_eq!(>::get(), inflation); + assert_eq!(InflationConfig::::get(), inflation); + candidates.into_iter().for_each(|candidate| { + assert!(!Rewards::::get(&candidate).is_zero()); + }); } set_max_selected_candidates { @@ -208,14 +206,14 @@ benchmarks! { let old_candidate = candidates[0].clone(); }: _(RawOrigin::Root, n) verify { - assert_eq!(>::get(), n); + assert_eq!(MaxSelectedCandidates::::get(), n); } set_blocks_per_round { let bpr: T::BlockNumber = T::MinBlocksPerRound::get() + T::BlockNumber::one(); }: _(RawOrigin::Root, bpr) verify { - assert_eq!(>::get().length, bpr); + assert_eq!(Round::::get().length, bpr); } force_remove_candidate { @@ -263,7 +261,7 @@ benchmarks! { fill_delegators::(m, c.clone(), i.saturated_into::()); } - let now = >::get().current; + let now = Round::::get().current; let candidate = candidates[0].clone(); let origin = RawOrigin::Signed(candidate.clone()); @@ -272,7 +270,7 @@ benchmarks! { let candidates = TopCandidates::::get(); assert!(!candidates.into_iter().any(|other| other.owner == candidate)); let unlocking_at = now.saturating_add(T::ExitQueueDelay::get()); - assert!(>::get(candidate).unwrap().can_exit(unlocking_at)); + assert!(CandidatePool::::get(candidate).unwrap().can_exit(unlocking_at)); } cancel_leave_candidates { @@ -285,7 +283,7 @@ benchmarks! { } let candidate = candidates[0].clone(); - assert_ok!(>::init_leave_candidates(RawOrigin::Signed(candidate.clone()).into())); + assert_ok!(Pallet::::init_leave_candidates(RawOrigin::Signed(candidate.clone()).into())); let origin = RawOrigin::Signed(candidate.clone()); }: _(origin) @@ -308,16 +306,16 @@ benchmarks! { // increase stake so we can unstake, because current stake is minimum let more_stake = T::MinCollatorCandidateStake::get(); T::Currency::make_free_balance_be(&candidate, T::CurrencyBalance::from(u128::MAX)); - assert_ok!(>::candidate_stake_more(RawOrigin::Signed(candidate.clone()).into(), more_stake)); + assert_ok!(Pallet::::candidate_stake_more(RawOrigin::Signed(candidate.clone()).into(), more_stake)); // fill unstake BTreeMap by unstaked many entries of 1 fill_unstaking::(&candidate, None, u as u64); // go to block in which we can exit - assert_ok!(>::init_leave_candidates(RawOrigin::Signed(candidate.clone()).into())); + assert_ok!(Pallet::::init_leave_candidates(RawOrigin::Signed(candidate.clone()).into())); for i in 1..=T::ExitQueueDelay::get() { - let round = >::get(); + let round = Round::::get(); let now = round.first + round.length; System::::set_block_number(now); Pallet::::on_initialize(now); @@ -328,7 +326,7 @@ benchmarks! { }: _(origin, unlookup_candidate) verify { // should have one more entry in Unstaking - assert_eq!(>::get(&candidate).len().saturated_into::(), u.saturating_add(1u32)); + assert_eq!(Unstaking::::get(&candidate).len().saturated_into::(), u.saturating_add(1u32)); } candidate_stake_more { @@ -342,12 +340,12 @@ benchmarks! { } let candidate = candidates[0].clone(); - let old_stake = >::get(&candidate).unwrap().stake; + let old_stake = CandidatePool::::get(&candidate).unwrap().stake; let more_stake = T::MinCollatorCandidateStake::get(); // increase stake so we can unstake, because current stake is minimum T::Currency::make_free_balance_be(&candidate, T::CurrencyBalance::from(u128::MAX)); - assert_ok!(>::candidate_stake_more(RawOrigin::Signed(candidate.clone()).into(), more_stake)); + assert_ok!(Pallet::::candidate_stake_more(RawOrigin::Signed(candidate.clone()).into(), more_stake)); // fill unstake BTreeMap by unstaked many entries of 1 fill_unstaking::(&candidate, None, u as u64); @@ -355,8 +353,8 @@ benchmarks! { let origin = RawOrigin::Signed(candidate.clone()); }: _(origin, more_stake) verify { - let new_stake = >::get(&candidate).unwrap().stake; - assert!(>::get(candidate).is_empty()); + let new_stake = CandidatePool::::get(&candidate).unwrap().stake; + assert!(Unstaking::::get(candidate).is_empty()); assert_eq!(new_stake, old_stake + more_stake + more_stake - T::CurrencyBalance::from(u as u64)); } @@ -371,19 +369,19 @@ benchmarks! { let candidate = candidates[0].clone(); // increase stake of candidate to later decrease it again - let old_stake = >::get(&candidate).unwrap().stake; + let old_stake = CandidatePool::::get(&candidate).unwrap().stake; let more_stake = T::MinCollatorCandidateStake::get(); T::Currency::make_free_balance_be(&candidate, T::CurrencyBalance::from(u128::MAX)); Pallet::::candidate_stake_more(RawOrigin::Signed(candidate.clone()).into(), more_stake).expect("should increase stake"); - let new_stake = >::get(&candidate).unwrap().stake; + let new_stake = CandidatePool::::get(&candidate).unwrap().stake; assert_eq!(new_stake, old_stake + more_stake); let origin = RawOrigin::Signed(candidate.clone()); }: _(origin, more_stake) verify { - let new_stake = >::get(&candidate).unwrap().stake; + let new_stake = CandidatePool::::get(&candidate).unwrap().stake; assert_eq!(new_stake, old_stake); } @@ -405,7 +403,7 @@ benchmarks! { let origin = RawOrigin::Signed(delegator.clone()); }: _(origin, unlookup_collator, amount) verify { - let state = >::get(&collator).unwrap(); + let state = CandidatePool::::get(&collator).unwrap(); assert!(state.delegators.into_iter().any(|x| x.owner == delegator)); } @@ -424,27 +422,26 @@ benchmarks! { let amount = T::MinDelegatorStake::get(); // make sure delegator collated to collator - let state = >::get(&collator).unwrap(); + let state = CandidatePool::::get(&collator).unwrap(); let delegator = state.delegators.into_bounded_vec()[0].owner.clone(); - assert_eq!(>::get(&delegator).unwrap().total, amount); + assert_eq!(DelegatorState::::get(&delegator).unwrap().amount, amount); // increase stake so we can unstake, because current stake is minimum T::Currency::make_free_balance_be(&delegator, T::CurrencyBalance::from(u128::MAX)); - assert_ok!(>::delegator_stake_more(RawOrigin::Signed(delegator.clone()).into(), T::Lookup::unlookup(collator.clone()), T::CurrencyBalance::from(u as u64))); - assert_eq!(>::get(&delegator).unwrap().total, amount + T::CurrencyBalance::from(u as u64)); + assert_ok!(Pallet::::delegator_stake_more(RawOrigin::Signed(delegator.clone()).into(), T::CurrencyBalance::from(u as u64))); + assert_eq!(DelegatorState::::get(&delegator).unwrap().amount, amount + T::CurrencyBalance::from(u as u64)); // fill unstake BTreeMap by unstaked many entries of 1 fill_unstaking::(&collator, Some(&delegator), u as u64); - assert_eq!(>::get(&delegator).unwrap().total, amount); - let unlookup_collator = T::Lookup::unlookup(collator.clone()); + assert_eq!(DelegatorState::::get(&delegator).unwrap().amount, amount); let origin = RawOrigin::Signed(delegator.clone()); - }: _(origin, unlookup_collator, amount) + }: _(origin, amount) verify { - let state = >::get(&collator).unwrap(); + let state = CandidatePool::::get(&collator).unwrap(); assert!(state.delegators.into_iter().any(|x| x.owner == delegator)); - assert_eq!(>::get(&delegator).unwrap().total, amount + amount); - assert!(>::get(&delegator).is_empty()); + assert_eq!(DelegatorState::::get(&delegator).unwrap().amount, amount + amount); + assert!(Unstaking::::get(&delegator).is_empty()); } delegator_stake_less { @@ -461,66 +458,27 @@ benchmarks! { let amount = T::CurrencyBalance::one(); // make sure delegator collated to collator - let state = >::get(&collator).unwrap(); + let state = CandidatePool::::get(&collator).unwrap(); let delegator = state.delegators.into_bounded_vec()[0].owner.clone(); - assert_eq!(>::get(&delegator).unwrap().total, T::MinDelegatorStake::get()); + assert_eq!(DelegatorState::::get(&delegator).unwrap().amount, T::MinDelegatorStake::get()); // increase stake so we can unstake, because current stake is minimum T::Currency::make_free_balance_be(&delegator, T::CurrencyBalance::from(u128::MAX)); - assert_ok!(>::delegator_stake_more(RawOrigin::Signed(delegator.clone()).into(), T::Lookup::unlookup(collator.clone()), amount + amount)); - assert_eq!(>::get(&delegator).unwrap().total, T::MinDelegatorStake::get() + amount + amount); + assert_ok!(Pallet::::delegator_stake_more(RawOrigin::Signed(delegator.clone()).into(), amount + amount)); + assert_eq!(DelegatorState::::get(&delegator).unwrap().amount, T::MinDelegatorStake::get() + amount + amount); // decrease stake once so we have an unstaking entry for this block - assert_ok!(>::delegator_stake_less(RawOrigin::Signed(delegator.clone()).into(), T::Lookup::unlookup(collator.clone()), amount)); - assert_eq!(>::get(&delegator).unwrap().total, T::MinDelegatorStake::get() + amount); - assert_eq!(>::get(&delegator).len(), 1); - let unlookup_collator = T::Lookup::unlookup(collator.clone()); + assert_ok!(Pallet::::delegator_stake_less(RawOrigin::Signed(delegator.clone()).into(), amount)); + assert_eq!(DelegatorState::::get(&delegator).unwrap().amount, T::MinDelegatorStake::get() + amount); + assert_eq!(Unstaking::::get(&delegator).len(), 1); let origin = RawOrigin::Signed(delegator.clone()); - }: _(origin, unlookup_collator, amount) + }: _(origin, amount) verify { - let state = >::get(&collator).unwrap(); + let state = CandidatePool::::get(&collator).unwrap(); assert!(state.delegators.into_iter().any(|x| x.owner == delegator)); - assert_eq!(>::get(&delegator).unwrap().total, T::MinDelegatorStake::get()); - assert_eq!(>::get(&delegator).len(), 2); - } - - revoke_delegation { - // we need at least 1 collators - let n in 1 .. T::MaxTopCandidates::get(); - // we need at least 1 delegator - let m in 1 .. T::MaxDelegatorsPerCollator::get() - 1; - - let candidates = setup_collator_candidates::(n, None); - for (i, c) in candidates.iter().enumerate() { - fill_delegators::(m, c.clone(), i.saturated_into::()); - } - let collator = candidates[0].clone(); - let amount = T::CurrencyBalance::one(); - - // make sure delegator collated to collator - let state = >::get(&collator).unwrap(); - let delegator = state.delegators.into_bounded_vec()[0].owner.clone(); - assert_eq!(>::get(&delegator).unwrap().total, T::MinDelegatorStake::get()); - - // increase stake so we can unstake, because current stake is minimum - T::Currency::make_free_balance_be(&delegator, T::CurrencyBalance::from(u128::MAX)); - assert_ok!(>::delegator_stake_more(RawOrigin::Signed(delegator.clone()).into(), T::Lookup::unlookup(collator.clone()), amount + amount)); - assert_eq!(>::get(&delegator).unwrap().total, T::MinDelegatorStake::get() + amount + amount); - - // decrease stake once so we have an unstaking entry for this block - assert_ok!(>::delegator_stake_less(RawOrigin::Signed(delegator.clone()).into(), T::Lookup::unlookup(collator.clone()), amount)); - assert_eq!(>::get(&delegator).unwrap().total, T::MinDelegatorStake::get() + amount); - assert_eq!(>::get(&delegator).len(), 1); - let unlookup_collator = T::Lookup::unlookup(collator.clone()); - - let origin = RawOrigin::Signed(delegator.clone()); - }: _(origin, unlookup_collator) - verify { - let state = >::get(&collator).unwrap(); - assert!(!state.delegators.into_iter().any(|x| x.owner == delegator)); - assert!(>::get(&delegator).is_none()); - assert_eq!(>::get(&delegator).len(), 2); + assert_eq!(DelegatorState::::get(&delegator).unwrap().amount, T::MinDelegatorStake::get()); + assert_eq!(Unstaking::::get(&delegator).len(), 2); } leave_delegators { @@ -537,27 +495,27 @@ benchmarks! { let amount = T::CurrencyBalance::one(); // make sure delegator collated to collator - let state = >::get(&collator).unwrap(); + let state = CandidatePool::::get(&collator).unwrap(); let delegator = state.delegators.into_bounded_vec()[0].owner.clone(); - assert_eq!(>::get(&delegator).unwrap().total, T::MinDelegatorStake::get()); + assert_eq!(DelegatorState::::get(&delegator).unwrap().amount, T::MinDelegatorStake::get()); // increase stake so we can unstake, because current stake is minimum T::Currency::make_free_balance_be(&delegator, T::CurrencyBalance::from(u128::MAX)); - assert_ok!(>::delegator_stake_more(RawOrigin::Signed(delegator.clone()).into(), T::Lookup::unlookup(collator.clone()), amount + amount)); - assert_eq!(>::get(&delegator).unwrap().total, T::MinDelegatorStake::get() + amount + amount); + assert_ok!(Pallet::::delegator_stake_more(RawOrigin::Signed(delegator.clone()).into(), amount + amount)); + assert_eq!(DelegatorState::::get(&delegator).unwrap().amount, T::MinDelegatorStake::get() + amount + amount); // decrease stake once so we have an unstaking entry for this block - assert_ok!(>::delegator_stake_less(RawOrigin::Signed(delegator.clone()).into(), T::Lookup::unlookup(collator.clone()), amount)); - assert_eq!(>::get(&delegator).unwrap().total, T::MinDelegatorStake::get() + amount); - assert_eq!(>::get(&delegator).len(), 1); + assert_ok!(Pallet::::delegator_stake_less(RawOrigin::Signed(delegator.clone()).into(), amount)); + assert_eq!(DelegatorState::::get(&delegator).unwrap().amount, T::MinDelegatorStake::get() + amount); + assert_eq!(Unstaking::::get(&delegator).len(), 1); let origin = RawOrigin::Signed(delegator.clone()); }: _(origin) verify { - let state = >::get(&collator).unwrap(); + let state = CandidatePool::::get(&collator).unwrap(); assert!(!state.delegators.into_iter().any(|x| x.owner == delegator)); - assert!(>::get(&delegator).is_none()); - assert_eq!(>::get(&delegator).len(), 2); + assert!(DelegatorState::::get(&delegator).is_none()); + assert_eq!(Unstaking::::get(&delegator).len(), 2); } unlock_unstaked { @@ -567,18 +525,18 @@ benchmarks! { let free_balance = T::CurrencyBalance::from(u128::MAX); let stake = T::MinCollatorCandidateStake::get(); T::Currency::make_free_balance_be(&candidate, free_balance); - assert_ok!(>::join_candidates( + assert_ok!(Pallet::::join_candidates( T::Origin::from(Some(candidate.clone()).into()), stake, )); assert_eq!(pallet_balances::Pallet::::usable_balance(&candidate), (free_balance - T::MinCollatorCandidateStake::get()).into()); // increase stake so we can unstake, because current stake is minimum - assert_ok!(>::candidate_stake_more(RawOrigin::Signed(candidate.clone()).into(), stake)); + assert_ok!(Pallet::::candidate_stake_more(RawOrigin::Signed(candidate.clone()).into(), stake)); // fill unstake BTreeMap by unstaked many entries of 1 fill_unstaking::(&candidate, None, u as u64); - assert_eq!(>::get(&candidate).unwrap().stake, stake + stake - T::CurrencyBalance::from(u as u64)); + assert_eq!(CandidatePool::::get(&candidate).unwrap().stake, stake + stake - T::CurrencyBalance::from(u as u64)); // roll to block in which first unstake can be unlocked System::::set_block_number(T::StakeDuration::get()); @@ -588,55 +546,86 @@ benchmarks! { let origin = RawOrigin::Signed(candidate.clone()); }: _(origin, unlookup_candidate) verify { - assert_eq!(>::get(&candidate).len().saturated_into::(), u.saturating_sub(1u32)); + assert_eq!(Unstaking::::get(&candidate).len().saturated_into::(), u.saturating_sub(1u32)); assert_eq!(pallet_balances::Pallet::::usable_balance(&candidate), (free_balance - stake - stake + T::CurrencyBalance::one()).into()); } set_max_candidate_stake { - let old = >::get(); - let new = >::get() + T::CurrencyBalance::from(10u128); + let old = MaxCollatorCandidateStake::::get(); + let new = MaxCollatorCandidateStake::::get() + T::CurrencyBalance::from(10u128); }: _(RawOrigin::Root, new) verify { - assert_eq!(>::get(), new); + assert_eq!(MaxCollatorCandidateStake::::get(), new); + } + + increment_delegator_rewards { + let collator = setup_collator_candidates::(1, None)[0].clone(); + let delegator = fill_delegators::(1, collator.clone(), COLLATOR_ACCOUNT_SEED)[0].clone(); + + // mock high values to compensate for tiny values in unit test env + let stake = T::CurrencyBalance::from(1_000_000_000_000_000_000u128); + DelegatorState::::insert(&delegator, crate::types::Delegator { owner: collator.clone(), amount: stake}); + BlocksAuthored::::insert(&collator, u64::MAX.into()); + + assert!(Rewards::::get(&delegator).is_zero()); + let origin = RawOrigin::Signed(delegator.clone()); + }: _(origin) + verify { + assert!(!Rewards::::get(&delegator).is_zero()); + assert_eq!(BlocksRewarded::::get(&delegator), u64::MAX.into()); + } + + increment_collator_rewards { + let collator = setup_collator_candidates::(1, None)[0].clone(); + + // mock high counter to compensate for tiny amounts in unit test env + BlocksAuthored::::insert(&collator, u64::MAX.into()); + assert!(Rewards::::get(&collator).is_zero(), "reward {:?}", Rewards::::get(&collator)); + let origin = RawOrigin::Signed(collator.clone()); + }: _(origin) + verify { + assert!(!Rewards::::get(&collator).is_zero()); + assert_eq!(BlocksRewarded::::get(&collator), u64::MAX.into()); + } + + claim_rewards { + let beneficiary = account("beneficiary", 0, 0); + let amount = T::MinCollatorCandidateStake::get(); + T::Currency::make_free_balance_be(&beneficiary, amount); + Rewards::::insert(&beneficiary, amount); + assert_eq!(pallet_balances::Pallet::::usable_balance(&beneficiary), amount.into()); + let origin = RawOrigin::Signed(beneficiary.clone()); + }: _(origin) + verify { + assert!(Rewards::::get(&beneficiary).is_zero()); + assert_eq!(pallet_balances::Pallet::::usable_balance(&beneficiary), (amount + amount).into()); + } + + execute_scheduled_reward_change { + // we need at least 1 collators + let n in 0 .. T::MaxTopCandidates::get(); + // we need at least 1 delegator + let m in 0 .. T::MaxDelegatorsPerCollator::get(); + + let candidates = setup_collator_candidates::(n, None); + for (i, c) in candidates.iter().enumerate() { + fill_delegators::(m, c.clone(), i.saturated_into::()); + } + let collator = candidates[0].clone(); + + let old = InflationConfig::::get(); + assert_eq!(LastRewardReduction::::get(), T::BlockNumber::zero()); + System::::set_block_number(T::BLOCKS_PER_YEAR + T::BlockNumber::one()); + }: _(RawOrigin::Signed(collator)) + verify { + let new = InflationConfig::::get(); + assert_eq!(LastRewardReduction::::get(), T::BlockNumber::one()); + assert_eq!(new.collator.max_rate, old.collator.max_rate); + assert_eq!(new.delegator.max_rate, old.delegator.max_rate); + assert!(new.collator.reward_rate.annual < old.collator.reward_rate.annual); + assert!(new.delegator.reward_rate.annual < old.delegator.reward_rate.annual); } - // [Post-launch TODO]: Activate after increasing MaxCollatorsPerDelegator to at least 2. Expected to throw otherwise. - // delegate_another_candidate { - // // we need at least 2 collators - // let n in 2 .. T::MaxTopCandidates::get(); - // // we need at least 1 delegator - // let m in 1 .. T::MaxDelegatorsPerCollator::get() - 1; - // let u in 0 .. (T::MaxUnstakeRequests::get().saturated_into::() - 1); - - // let candidates = setup_collator_candidates::(n, None); - // for (i, c) in candidates.iter().enumerate() { - // fill_delegators::(m, c.clone(), i.saturated_into::()); - // } - // let collator_delegated = candidates[0].clone(); - // let collator = candidates.last().unwrap().clone(); - // let amount = T::MinDelegatorStake::get(); - - // // make sure delegator collated to collator_delegated - // let state_delegated = >::get(&collator_delegated).unwrap(); - // let delegator = state_delegated.delegators.into_bounded_vec()[0].owner.clone(); - // assert!(>::get(&delegator).is_some()); - - // // should not have delegated to collator yet - // let state = >::get(&collator).unwrap(); - // assert!(!state.delegators.into_iter().any(|x| x.owner == delegator)); - - // // increase stake so we can unstake, because current stake is minimum - // T::Currency::make_free_balance_be(&delegator, T::CurrencyBalance::from(u128::MAX)); - // assert_ok!(>::delegator_stake_more(RawOrigin::Signed(delegator.clone()).into(), T::Lookup::unlookup(collator_delegated.clone()), T::CurrencyBalance::from(u as u64))); - - // // fill unstake BTreeMap by unstaked many entries of 1 - // fill_unstaking::(&collator_delegated, Some(&delegator), u as u64); - - // }: _(RawOrigin::Signed(delegator.clone()), T::Lookup::unlookup(collator.clone()), amount) - // verify { - // let state = >::get(&collator).unwrap(); - // assert!(state.delegators.into_iter().any(|x| x.owner == delegator); - // } } impl_benchmark_test_suite!( diff --git a/pallets/parachain-staking/src/default_weights.rs b/pallets/parachain-staking/src/default_weights.rs index 81af301c12..3aeb2e585d 100644 --- a/pallets/parachain-staking/src/default_weights.rs +++ b/pallets/parachain-staking/src/default_weights.rs @@ -48,10 +48,9 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn on_initialize_no_action() -> Weight; fn on_initialize_round_update() -> Weight; - fn on_initialize_new_year() -> Weight; fn on_initialize_network_rewards() -> Weight; fn force_new_round() -> Weight; - fn set_inflation() -> Weight; + fn set_inflation(n: u32, m:u32 ) -> Weight; fn set_max_selected_candidates(n: u32, m: u32, ) -> Weight; fn set_blocks_per_round() -> Weight; fn force_remove_candidate(n: u32, m: u32, ) -> Weight; @@ -64,18 +63,21 @@ pub trait WeightInfo { fn join_delegators(n: u32, m: u32, ) -> Weight; fn delegator_stake_more(n: u32, m: u32, u: u32, ) -> Weight; fn delegator_stake_less(n: u32, m: u32, ) -> Weight; - fn revoke_delegation(n: u32, m: u32, ) -> Weight; fn leave_delegators(n: u32, m: u32, ) -> Weight; fn unlock_unstaked(u: u32, ) -> Weight; fn set_max_candidate_stake() -> Weight; + fn increment_delegator_rewards() -> Weight; + fn increment_collator_rewards() -> Weight; + fn claim_rewards() -> Weight; + fn execute_scheduled_reward_change(n: u32, m: u32, ) -> Weight; } /// Weights for parachain_staking using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); -impl WeightInfo for SubstrateWeight { +impl WeightInfo for SubstrateWeight {// KILT Blockchain – https://botlabs.org // Storage: ParachainStaking Round (r:1 w:0) fn on_initialize_no_action() -> Weight { - Weight::from_ref_time(7_701_000 as u64) + Weight::from_ref_time(3_103_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) } // Storage: ParachainStaking Round (r:1 w:1) @@ -87,14 +89,6 @@ impl WeightInfo for SubstrateWeight { // Storage: ParachainStaking Round (r:1 w:1) // Storage: ParachainStaking LastRewardReduction (r:1 w:1) // Storage: ParachainStaking InflationConfig (r:1 w:1) - fn on_initialize_new_year() -> Weight { - Weight::from_ref_time(35_951_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - } - // Storage: ParachainStaking Round (r:1 w:1) - // Storage: ParachainStaking LastRewardReduction (r:1 w:1) - // Storage: ParachainStaking InflationConfig (r:1 w:1) // Storage: ParachainStaking MaxCollatorCandidateStake (r:1 w:0) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: System Account (r:1 w:1) @@ -108,10 +102,23 @@ impl WeightInfo for SubstrateWeight { Weight::from_ref_time(8_791_000 as u64) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: ParachainStaking InflationConfig (r:0 w:1) - fn set_inflation() -> Weight { - Weight::from_ref_time(24_163_000 as u64) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Storage: ParachainStaking CandidatePool (r:3 w:0) + // Storage: ParachainStaking RewardCount (r:72 w:72) + // Storage: ParachainStaking Rewards (r:2 w:2) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking InflationConfig (r:1 w:1) + /// The range of component `n` is `[0, 75]`. + /// The range of component `m` is `[0, 35]`. + fn set_inflation(n: u32, m: u32, ) -> Weight { + Weight::from_ref_time(0 as u64) + // Standard Error: 3_005_000 + .saturating_add(Weight::from_ref_time(216_364_000 as u64).saturating_mul(n as u64)) + // Standard Error: 6_440_000 + .saturating_add(Weight::from_ref_time(440_763_000 as u64).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads((37 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().reads((75 as u64).saturating_mul(m as u64))) + .saturating_add(T::DbWeight::get().writes((36 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().writes((75 as u64).saturating_mul(m as u64))) } // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:1) // Storage: ParachainStaking TopCandidates (r:1 w:0) @@ -136,12 +143,17 @@ impl WeightInfo for SubstrateWeight { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking Unstaking (r:36 w:36) // Storage: ParachainStaking DelegatorState (r:35 w:35) + // Storage: ParachainStaking RewardCount (r:36 w:36) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + // Storage: ParachainStaking InflationConfig (r:1 w:0) // Storage: Session Validators (r:1 w:0) // Storage: Session DisabledValidators (r:1 w:1) // Storage: System Digest (r:1 w:1) // Storage: ParachainStaking CounterForCandidatePool (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) - // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + /// The range of component `n` is `[17, 75]`. + /// The range of component `m` is `[0, 35]`. fn force_remove_candidate(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 169_000 @@ -163,6 +175,8 @@ impl WeightInfo for SubstrateWeight { // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) // Storage: ParachainStaking CounterForCandidatePool (r:1 w:1) + /// The range of component `n` is `[1, 74]`. + /// The range of component `m` is `[0, 35]`. fn join_candidates(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 148_000 @@ -177,6 +191,8 @@ impl WeightInfo for SubstrateWeight { // Storage: ParachainStaking Round (r:1 w:0) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + /// The range of component `n` is `[17, 74]`. + /// The range of component `m` is `[0, 35]`. fn init_leave_candidates(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 169_000 @@ -190,6 +206,8 @@ impl WeightInfo for SubstrateWeight { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + /// The range of component `n` is `[17, 74]`. + /// The range of component `m` is `[0, 35]`. fn cancel_leave_candidates(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 181_000 @@ -203,6 +221,10 @@ impl WeightInfo for SubstrateWeight { // Storage: ParachainStaking Round (r:1 w:0) // Storage: ParachainStaking Unstaking (r:36 w:36) // Storage: ParachainStaking DelegatorState (r:35 w:35) + // Storage: ParachainStaking RewardCount (r:36 w:36) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking InflationConfig (r:1 w:0) // Storage: Session Validators (r:1 w:0) // Storage: Session DisabledValidators (r:1 w:1) // Storage: System Digest (r:1 w:1) @@ -240,6 +262,11 @@ impl WeightInfo for SubstrateWeight { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + // Storage: ParachainStaking RewardCount (r:36 w:36) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking InflationConfig (r:1 w:0) + /// The range of component `n` is `[1, 74]`. + /// The range of component `m` is `[0, 35]`. fn candidate_stake_less(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 165_000 @@ -259,6 +286,9 @@ impl WeightInfo for SubstrateWeight { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + // Storage: ParachainStaking RewardCount (r:1 w:1) + /// The range of component `n` is `[1, 75]`. + /// The range of component `m` is `[1, 34]`. fn join_delegators(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 154_000 @@ -291,6 +321,9 @@ impl WeightInfo for SubstrateWeight { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + // Storage: ParachainStaking RewardCount (r:2 w:0) + /// The range of component `n` is `[1, 75]`. + /// The range of component `m` is `[1, 34]`. fn delegator_stake_less(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 153_000 @@ -302,25 +335,13 @@ impl WeightInfo for SubstrateWeight { } // Storage: ParachainStaking DelegatorState (r:1 w:1) // Storage: ParachainStaking CandidatePool (r:1 w:1) + // Storage: ParachainStaking RewardCount (r:2 w:0) // Storage: ParachainStaking Unstaking (r:1 w:1) // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) - fn revoke_delegation(n: u32, m: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 154_000 - .saturating_add(Weight::from_ref_time(17_790_000 as u64).saturating_mul(n as u64)) - // Standard Error: 342_000 - .saturating_add(Weight::from_ref_time(38_481_000 as u64).saturating_mul(m as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) - } - // Storage: ParachainStaking DelegatorState (r:1 w:1) - // Storage: ParachainStaking CandidatePool (r:1 w:1) - // Storage: ParachainStaking Unstaking (r:1 w:1) - // Storage: ParachainStaking TopCandidates (r:1 w:1) - // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) - // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + /// The range of component `n` is `[1, 75]`. + /// The range of component `m` is `[1, 34]`. fn leave_delegators(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 153_000 @@ -345,13 +366,61 @@ impl WeightInfo for SubstrateWeight { Weight::from_ref_time(23_094_000 as u64) .saturating_add(T::DbWeight::get().writes(1 as u64)) } + // Storage: ParachainStaking DelegatorState (r:1 w:0) + // Storage: ParachainStaking RewardCount (r:2 w:1) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking InflationConfig (r:1 w:0) + fn increment_delegator_rewards() -> Weight { + Weight::from_ref_time(25_796_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: ParachainStaking CandidatePool (r:1 w:0) + // Storage: ParachainStaking RewardCount (r:1 w:1) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking InflationConfig (r:1 w:0) + /// The range of component `m` is `[0, 35]`. + fn increment_collator_rewards() -> Weight { + Weight::from_ref_time(366_611_000 as u64) + .saturating_add(T::DbWeight::get().reads(75 as u64)) + .saturating_add(T::DbWeight::get().writes(72 as u64)) + } + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn claim_rewards() -> Weight { + Weight::from_ref_time(29_833_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: ParachainStaking LastRewardReduction (r:1 w:1) + // Storage: ParachainStaking InflationConfig (r:1 w:1) + // Storage: ParachainStaking CandidatePool (r:3 w:0) + // Storage: ParachainStaking RewardCount (r:72 w:72) + // Storage: ParachainStaking Rewards (r:2 w:2) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking CounterForCandidatePool (r:1 w:0) + /// The range of component `n` is `[0, 75]`. + /// The range of component `m` is `[0, 35]`. + fn execute_scheduled_reward_change(n: u32, m: u32, ) -> Weight { + Weight::from_ref_time(0 as u64) + // Standard Error: 5_730_000 + .saturating_add(Weight::from_ref_time(202_623_000 as u64).saturating_mul(n as u64)) + // Standard Error: 12_280_000 + .saturating_add(Weight::from_ref_time(415_436_000 as u64).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads((37 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().reads((75 as u64).saturating_mul(m as u64))) + .saturating_add(T::DbWeight::get().writes((36 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().writes((75 as u64).saturating_mul(m as u64))) + } } // For backwards compatibility and tests impl WeightInfo for () { // Storage: ParachainStaking Round (r:1 w:0) fn on_initialize_no_action() -> Weight { - Weight::from_ref_time(7_701_000 as u64) + Weight::from_ref_time(3_103_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) } // Storage: ParachainStaking Round (r:1 w:1) @@ -363,14 +432,6 @@ impl WeightInfo for () { // Storage: ParachainStaking Round (r:1 w:1) // Storage: ParachainStaking LastRewardReduction (r:1 w:1) // Storage: ParachainStaking InflationConfig (r:1 w:1) - fn on_initialize_new_year() -> Weight { - Weight::from_ref_time(35_951_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) - } - // Storage: ParachainStaking Round (r:1 w:1) - // Storage: ParachainStaking LastRewardReduction (r:1 w:1) - // Storage: ParachainStaking InflationConfig (r:1 w:1) // Storage: ParachainStaking MaxCollatorCandidateStake (r:1 w:0) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: System Account (r:1 w:1) @@ -384,10 +445,23 @@ impl WeightInfo for () { Weight::from_ref_time(8_791_000 as u64) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: ParachainStaking InflationConfig (r:0 w:1) - fn set_inflation() -> Weight { - Weight::from_ref_time(24_163_000 as u64) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Storage: ParachainStaking CandidatePool (r:3 w:0) + // Storage: ParachainStaking RewardCount (r:72 w:72) + // Storage: ParachainStaking Rewards (r:2 w:2) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking InflationConfig (r:1 w:1) + /// The range of component `n` is `[0, 75]`. + /// The range of component `m` is `[0, 35]`. + fn set_inflation(n: u32, m: u32, ) -> Weight { + Weight::from_ref_time(0 as u64) + // Standard Error: 3_005_000 + .saturating_add(Weight::from_ref_time(216_364_000 as u64).saturating_mul(n as u64)) + // Standard Error: 6_440_000 + .saturating_add(Weight::from_ref_time(440_763_000 as u64).saturating_mul(m as u64)) + .saturating_add(RocksDbWeight::get().reads((37 as u64).saturating_mul(n as u64))) + .saturating_add(RocksDbWeight::get().reads((75 as u64).saturating_mul(m as u64))) + .saturating_add(RocksDbWeight::get().writes((36 as u64).saturating_mul(n as u64))) + .saturating_add(RocksDbWeight::get().writes((75 as u64).saturating_mul(m as u64))) } // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:1) // Storage: ParachainStaking TopCandidates (r:1 w:0) @@ -412,12 +486,17 @@ impl WeightInfo for () { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking Unstaking (r:36 w:36) // Storage: ParachainStaking DelegatorState (r:35 w:35) + // Storage: ParachainStaking RewardCount (r:36 w:36) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + // Storage: ParachainStaking InflationConfig (r:1 w:0) // Storage: Session Validators (r:1 w:0) // Storage: Session DisabledValidators (r:1 w:1) // Storage: System Digest (r:1 w:1) // Storage: ParachainStaking CounterForCandidatePool (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) - // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + /// The range of component `n` is `[17, 75]`. + /// The range of component `m` is `[0, 35]`. fn force_remove_candidate(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 169_000 @@ -439,6 +518,8 @@ impl WeightInfo for () { // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) // Storage: ParachainStaking CounterForCandidatePool (r:1 w:1) + /// The range of component `n` is `[1, 74]`. + /// The range of component `m` is `[0, 35]`. fn join_candidates(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 148_000 @@ -453,6 +534,8 @@ impl WeightInfo for () { // Storage: ParachainStaking Round (r:1 w:0) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + /// The range of component `n` is `[17, 74]`. + /// The range of component `m` is `[0, 35]`. fn init_leave_candidates(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 169_000 @@ -466,6 +549,8 @@ impl WeightInfo for () { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + /// The range of component `n` is `[17, 74]`. + /// The range of component `m` is `[0, 35]`. fn cancel_leave_candidates(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 181_000 @@ -479,6 +564,10 @@ impl WeightInfo for () { // Storage: ParachainStaking Round (r:1 w:0) // Storage: ParachainStaking Unstaking (r:36 w:36) // Storage: ParachainStaking DelegatorState (r:35 w:35) + // Storage: ParachainStaking RewardCount (r:36 w:36) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking InflationConfig (r:1 w:0) // Storage: Session Validators (r:1 w:0) // Storage: Session DisabledValidators (r:1 w:1) // Storage: System Digest (r:1 w:1) @@ -516,6 +605,11 @@ impl WeightInfo for () { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + // Storage: ParachainStaking RewardCount (r:36 w:36) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking InflationConfig (r:1 w:0) + /// The range of component `n` is `[1, 74]`. + /// The range of component `m` is `[0, 35]`. fn candidate_stake_less(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 165_000 @@ -535,6 +629,9 @@ impl WeightInfo for () { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + // Storage: ParachainStaking RewardCount (r:1 w:1) + /// The range of component `n` is `[1, 75]`. + /// The range of component `m` is `[1, 34]`. fn join_delegators(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 154_000 @@ -567,6 +664,9 @@ impl WeightInfo for () { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + // Storage: ParachainStaking RewardCount (r:2 w:0) + /// The range of component `n` is `[1, 75]`. + /// The range of component `m` is `[1, 34]`. fn delegator_stake_less(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 153_000 @@ -578,25 +678,13 @@ impl WeightInfo for () { } // Storage: ParachainStaking DelegatorState (r:1 w:1) // Storage: ParachainStaking CandidatePool (r:1 w:1) + // Storage: ParachainStaking RewardCount (r:2 w:0) // Storage: ParachainStaking Unstaking (r:1 w:1) // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) - fn revoke_delegation(n: u32, m: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 154_000 - .saturating_add(Weight::from_ref_time(17_790_000 as u64).saturating_mul(n as u64)) - // Standard Error: 342_000 - .saturating_add(Weight::from_ref_time(38_481_000 as u64).saturating_mul(m as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) - } - // Storage: ParachainStaking DelegatorState (r:1 w:1) - // Storage: ParachainStaking CandidatePool (r:1 w:1) - // Storage: ParachainStaking Unstaking (r:1 w:1) - // Storage: ParachainStaking TopCandidates (r:1 w:1) - // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) - // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + /// The range of component `n` is `[1, 75]`. + /// The range of component `m` is `[1, 34]`. fn leave_delegators(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 153_000 @@ -621,4 +709,52 @@ impl WeightInfo for () { Weight::from_ref_time(23_094_000 as u64) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } + // Storage: ParachainStaking DelegatorState (r:1 w:0) + // Storage: ParachainStaking RewardCount (r:2 w:1) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking InflationConfig (r:1 w:0) + fn increment_delegator_rewards() -> Weight { + Weight::from_ref_time(25_796_000 as u64) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: ParachainStaking CandidatePool (r:1 w:0) + // Storage: ParachainStaking RewardCount (r:1 w:1) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking InflationConfig (r:1 w:0) + /// The range of component `m` is `[0, 35]`. + fn increment_collator_rewards() -> Weight { + Weight::from_ref_time(366_611_000 as u64) + .saturating_add(RocksDbWeight::get().reads(75 as u64)) + .saturating_add(RocksDbWeight::get().writes(72 as u64)) + } + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn claim_rewards() -> Weight { + Weight::from_ref_time(29_833_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: ParachainStaking LastRewardReduction (r:1 w:1) + // Storage: ParachainStaking InflationConfig (r:1 w:1) + // Storage: ParachainStaking CandidatePool (r:3 w:0) + // Storage: ParachainStaking RewardCount (r:72 w:72) + // Storage: ParachainStaking Rewards (r:2 w:2) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking CounterForCandidatePool (r:1 w:0) + /// The range of component `n` is `[0, 75]`. + /// The range of component `m` is `[0, 35]`. + fn execute_scheduled_reward_change(n: u32, m: u32, ) -> Weight { + Weight::from_ref_time(0 as u64) + // Standard Error: 5_730_000 + .saturating_add(Weight::from_ref_time(202_623_000 as u64).saturating_mul(n as u64)) + // Standard Error: 12_280_000 + .saturating_add(Weight::from_ref_time(415_436_000 as u64).saturating_mul(m as u64)) + .saturating_add(RocksDbWeight::get().reads((37 as u64).saturating_mul(n as u64))) + .saturating_add(RocksDbWeight::get().reads((75 as u64).saturating_mul(m as u64))) + .saturating_add(RocksDbWeight::get().writes((36 as u64).saturating_mul(n as u64))) + .saturating_add(RocksDbWeight::get().writes((75 as u64).saturating_mul(m as u64))) + } } diff --git a/pallets/parachain-staking/src/lib.rs b/pallets/parachain-staking/src/lib.rs index f26d860212..f8b82500ae 100644 --- a/pallets/parachain-staking/src/lib.rs +++ b/pallets/parachain-staking/src/lib.rs @@ -106,7 +106,7 @@ //! //! The ParachainStaking pallet depends on the [`GenesisConfig`]. //! -//! ## Assumptions+ +//! ## Assumptions //! //! - At the start of session s(i), the set of session ids for session s(i+1) //! are chosen. These equal the set of selected candidates. Thus, we cannot @@ -118,7 +118,9 @@ #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; pub mod default_weights; +pub mod runtime_api; +pub mod migration; #[cfg(test)] pub(crate) mod mock; #[cfg(test)] @@ -131,7 +133,6 @@ mod types; use frame_support::pallet; pub use crate::{default_weights::WeightInfo, pallet::*}; -use types::ReplacedDelegator; #[pallet] pub mod pallet { @@ -172,7 +173,7 @@ pub mod pallet { pub(crate) const STAKING_ID: LockIdentifier = *b"kiltpstk"; /// The current storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(7); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(8); /// Pallet for parachain staking. #[pallet::pallet] @@ -208,6 +209,7 @@ pub mod pallet { + From + Into<::Balance> + From<::Balance> + + From + TypeInfo + MaxEncodedLen; @@ -251,10 +253,6 @@ pub mod pallet { #[pallet::constant] type MaxDelegatorsPerCollator: Get + Debug + PartialEq; - /// Maximum number of collators a single delegator can delegate. - #[pallet::constant] - type MaxCollatorsPerDelegator: Get + Debug + PartialEq; - /// Maximum size of the top candidates set. #[pallet::constant] type MaxTopCandidates: Get + Debug + PartialEq; @@ -269,10 +267,6 @@ pub mod pallet { #[pallet::constant] type MinCollatorCandidateStake: Get>; - /// Minimum stake required for any account to be able to delegate. - #[pallet::constant] - type MinDelegation: Get>; - /// Minimum stake required for any account to become a delegator. #[pallet::constant] type MinDelegatorStake: Get>; @@ -328,8 +322,6 @@ pub mod pallet { ValStakeBelowMin, /// The account has already staked the maximum amount of funds possible. ValStakeAboveMax, - /// The account has not staked enough funds to become a delegator. - NomStakeBelowMin, /// The account has not staked enough funds to delegate a collator /// candidate. DelegationBelowMin, @@ -396,10 +388,15 @@ pub mod pallet { /// The staking reward being unlocked does not exist. /// Max unlocking requests reached. NoMoreUnstaking, + /// The reward rate cannot be adjusted yet as an entire year has not + /// passed. + TooEarly, /// Provided staked value is zero. Should never be thrown. StakeNotFound, /// Cannot unlock when Unstaked is empty. UnstakingIsEmpty, + /// Cannot claim rewards if empty. + RewardsNotFound, } #[pallet::event] @@ -494,26 +491,22 @@ pub mod pallet { impl Hooks> for Pallet { fn on_initialize(now: T::BlockNumber) -> frame_support::weights::Weight { let mut post_weight = ::WeightInfo::on_initialize_no_action(); - let mut round = >::get(); + let mut round = Round::::get(); // check for round update if round.should_update(now) { // mutate round round.update(now); - // start next round - >::put(round); + Round::::put(round); Self::deposit_event(Event::NewRound(round.first, round.current)); post_weight = ::WeightInfo::on_initialize_round_update(); } - // check for InflationInfo update - if now > T::BLOCKS_PER_YEAR.saturated_into::() { - post_weight = post_weight.saturating_add(Self::adjust_reward_rates(now)); - } - // check for network reward + // check for network reward and mint + // on success, mint each block if now > T::NetworkRewardStart::get() { - T::NetworkRewardBeneficiary::on_unbalanced(Self::get_network_reward()); + T::NetworkRewardBeneficiary::on_unbalanced(Self::issue_network_reward()); post_weight = post_weight.saturating_add(::WeightInfo::on_initialize_network_rewards()); } post_weight @@ -545,13 +538,8 @@ pub mod pallet { /// It maps from an account to its delegation details. #[pallet::storage] #[pallet::getter(fn delegator_state)] - pub(crate) type DelegatorState = StorageMap< - _, - Twox64Concat, - T::AccountId, - Delegator, T::MaxCollatorsPerDelegator>, - OptionQuery, - >; + pub(crate) type DelegatorState = + StorageMap<_, Twox64Concat, T::AccountId, Delegator>, OptionQuery>; /// The staking information for a candidate. /// @@ -624,6 +612,32 @@ pub mod pallet { #[pallet::getter(fn last_reward_reduction)] pub(crate) type LastRewardReduction = StorageValue<_, T::BlockNumber, ValueQuery>; + /// The number of authored blocks for collators. It is updated via the + /// `note_author` hook when authoring a block . + #[pallet::storage] + #[pallet::getter(fn blocks_authored)] + pub(crate) type BlocksAuthored = StorageMap<_, Twox64Concat, T::AccountId, T::BlockNumber, ValueQuery>; + + /// The number of blocks for which rewards have been claimed by an address. + /// + /// For collators, this can be at most BlocksAuthored. It is updated when + /// incrementing collator rewards, either when calling + /// `inc_collator_rewards` or updating the `InflationInfo`. + /// + /// For delegators, this can be at most BlocksAuthored of the collator.It is + /// updated when incrementing delegator rewards, either when calling + /// `inc_delegator_rewards` or updating the `InflationInfo`. + #[pallet::storage] + #[pallet::getter(fn blocks_rewarded)] + pub(crate) type BlocksRewarded = StorageMap<_, Twox64Concat, T::AccountId, T::BlockNumber, ValueQuery>; + + /// The accumulated rewards for collator candidates and delegators. + /// + /// It maps from accounts to their total rewards since the last payout. + #[pallet::storage] + #[pallet::getter(fn rewards)] + pub(crate) type Rewards = StorageMap<_, Twox64Concat, T::AccountId, BalanceOf, ValueQuery>; + pub type GenesisStaker = Vec<( ::AccountId, Option<::AccountId>, @@ -660,7 +674,7 @@ pub mod pallet { "Invalid inflation configuration" ); - >::put(self.inflation_config.clone()); + InflationConfig::::put(self.inflation_config.clone()); MaxCollatorCandidateStake::::put(self.max_candidate_stake); // Setup delegate & collators @@ -670,13 +684,13 @@ pub mod pallet { "Account does not have enough balance to stake." ); if let Some(delegated_val) = opt_val { - assert_ok!(>::join_delegators( + assert_ok!(Pallet::::join_delegators( T::Origin::from(Some(actor.clone()).into()), T::Lookup::unlookup(delegated_val.clone()), balance, )); } else { - assert_ok!(>::join_candidates( + assert_ok!(Pallet::::join_candidates( T::Origin::from(Some(actor.clone()).into()), balance )); @@ -685,11 +699,11 @@ pub mod pallet { // Set total selected candidates to minimum config MaxSelectedCandidates::::put(T::MinCollators::get()); - >::update_total_stake(); + Pallet::::update_total_stake(); // Start Round 0 at Block 0 let round: RoundInfo = RoundInfo::new(0u32, 0u32.into(), T::DefaultBlocksPerRound::get()); - >::put(round); + Round::::put(round); } } @@ -701,20 +715,14 @@ pub mod pallet { /// ShouldEndSession<_>>::should_end_session. /// /// The dispatch origin must be Root. - /// - /// # - /// Weight: O(1) - /// - Reads: [Origin Account] - /// - Writes: ForceNewRound - /// # - #[pallet::weight(::WeightInfo::set_inflation())] + #[pallet::weight(::WeightInfo::force_new_round())] pub fn force_new_round(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; // set force_new_round handle which, at the start of the next block, will // trigger `should_end_session` in `Session::on_initialize` and update the // current round - >::put(true); + ForceNewRound::::put(true); Ok(()) } @@ -727,45 +735,33 @@ pub mod pallet { /// /// The estimated average block time is twelve seconds. /// + /// NOTE: Iterates over CandidatePool and for each candidate over their + /// delegators to update their rewards before the reward rates change. + /// Needs to be improved when scaling up `MaxTopCandidates`. + /// /// The dispatch origin must be Root. /// /// Emits `RoundInflationSet`. - /// - /// # - /// Weight: O(1) - /// - Reads: [Origin Account] - /// - Writes: InflationConfig - /// # - #[pallet::weight(::WeightInfo::set_inflation())] + #[pallet::weight(::WeightInfo::set_inflation(T::MaxTopCandidates::get(), T::MaxDelegatorsPerCollator::get()))] pub fn set_inflation( origin: OriginFor, collator_max_rate_percentage: Perquintill, collator_annual_reward_rate_percentage: Perquintill, delegator_max_rate_percentage: Perquintill, delegator_annual_reward_rate_percentage: Perquintill, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { ensure_root(origin)?; - let inflation = InflationInfo::new( - T::BLOCKS_PER_YEAR.saturated_into(), + // Update inflation and increment rewards + let (num_col, num_del) = Self::do_set_inflation( + T::BLOCKS_PER_YEAR, collator_max_rate_percentage, collator_annual_reward_rate_percentage, delegator_max_rate_percentage, delegator_annual_reward_rate_percentage, - ); + )?; - ensure!( - inflation.is_valid(T::BLOCKS_PER_YEAR.saturated_into()), - Error::::InvalidSchedule - ); - Self::deposit_event(Event::RoundInflationSet( - inflation.collator.max_rate, - inflation.collator.reward_rate.per_block, - inflation.delegator.max_rate, - inflation.delegator.reward_rate.per_block, - )); - >::put(inflation); - Ok(()) + Ok(Some(::WeightInfo::set_inflation(num_col, num_del)).into()) } /// Set the maximum number of collator candidates that can be selected @@ -779,20 +775,6 @@ pub mod pallet { /// The dispatch origin must be Root. /// /// Emits `MaxSelectedCandidatesSet`. - /// - /// - /// # - /// - The transaction's complexity is mainly dependent on updating the - /// `SelectedCandidates` storage in `select_top_candidates` which in - /// return depends on the number of `MaxSelectedCandidates` (N). - /// - For each N, we read `CandidatePool` from the storage. - /// --------- - /// Weight: O(N + D) where N is `MaxSelectedCandidates` bounded by - /// `MaxTopCandidates` and D is the number of delegators of a - /// candidate bounded by `MaxDelegatorsPerCollator`. - /// - Reads: MaxSelectedCandidates, TopCandidates, N * CandidatePool - /// - Writes: MaxSelectedCandidates - /// # #[pallet::weight(::WeightInfo::set_max_selected_candidates( *new, T::MaxDelegatorsPerCollator::get() @@ -861,22 +843,16 @@ pub mod pallet { /// The dispatch origin must be Root. /// /// Emits `BlocksPerRoundSet`. - /// - /// # - /// Weight: O(1) - /// - Reads: [Origin Account], Round - /// - Writes: Round - /// # #[pallet::weight(::WeightInfo::set_blocks_per_round())] pub fn set_blocks_per_round(origin: OriginFor, new: T::BlockNumber) -> DispatchResult { ensure_root(origin)?; ensure!(new >= T::MinBlocksPerRound::get(), Error::::CannotSetBelowMin); - let old_round = >::get(); + let old_round = Round::::get(); // *** No Fail beyond this point *** - >::put(RoundInfo { + Round::::put(RoundInfo { length: new, ..old_round }); @@ -896,12 +872,6 @@ pub mod pallet { /// The dispatch origin must be Root. /// /// Emits `MaxCandidateStakeChanged`. - /// - /// # - /// Weight: O(1) - /// - Reads: [Origin Account], MaxCollatorCandidateStake - /// - Writes: Round - /// # #[pallet::weight(::WeightInfo::set_max_candidate_stake())] pub fn set_max_candidate_stake(origin: OriginFor, new: BalanceOf) -> DispatchResult { ensure_root(origin)?; @@ -924,26 +894,12 @@ pub mod pallet { /// /// Prepares unstaking of the candidates and their delegators stake /// which can be unlocked via `unlock_unstaked` after waiting at - /// least `StakeDuration` many blocks. + /// least `StakeDuration` many blocks. Also increments rewards for the + /// collator and their delegators. /// - /// Emits `CandidateRemoved`. + /// Increments rewards of candidate and their delegators. /// - /// # - /// - The transaction's complexity is mainly dependent on updating the - /// `SelectedCandidates` storage in `select_top_candidates` which in - /// return depends on the number of `MaxSelectedCandidates` (N). - /// - For each N, we read `CandidatePool` from the storage. - /// --------- - /// Weight: O(N + D) where N is `MaxSelectedCandidates` bounded by - /// `MaxTopCandidates` and D is the number of delegators of the - /// collator candidate bounded by `MaxDelegatorsPerCollator`. - /// - Reads: MaxCollatorCandidateStake, 2 * N * CandidatePool, - /// TopCandidates, BlockNumber, D * DelegatorState, D * Unstaking - /// - Writes: MaxCollatorCandidateStake, N * CandidatePool, D * - /// DelegatorState, (D + 1) * Unstaking - /// - Kills: CandidatePool, DelegatorState for all delegators which only - /// delegated to the candidate - /// # + /// Emits `CandidateRemoved`. #[pallet::weight(::WeightInfo::force_remove_candidate( T::MaxTopCandidates::get(), T::MaxDelegatorsPerCollator::get() @@ -965,6 +921,7 @@ pub mod pallet { // *** No Fail except during remove_candidate beyond this point *** + // remove candidate storage and increment rewards Self::remove_candidate(&collator, &state)?; let (num_collators, num_delegators) = if candidates @@ -1008,16 +965,6 @@ pub mod pallet { /// candidates nor of the delegators set. /// /// Emits `JoinedCollatorCandidates`. - /// - /// # - /// Weight: O(N + D) where N is `MaxSelectedCandidates` bounded by - /// `MaxTopCandidates` and D is the number of delegators for this - /// candidate bounded by `MaxDelegatorsPerCollator`. - /// - Reads: [Origin Account], DelegatorState, - /// MaxCollatorCandidateStake, Locks, TotalCollatorStake, - /// TopCandidates, MaxSelectedCandidates, CandidatePool, - /// - Writes: Locks, TotalCollatorStake, CandidatePool, TopCandidates, - /// # #[pallet::weight(::WeightInfo::join_candidates( T::MaxTopCandidates::get(), T::MaxDelegatorsPerCollator::get() @@ -1078,7 +1025,7 @@ pub mod pallet { /// updated even though the funds of the candidate who signaled to leave /// are still locked for `ExitDelay` + `StakeDuration` more blocks. /// - /// NOTE: Upon starting a new session_i in `new_session`, the current + /// NOTE 1: Upon starting a new session_i in `new_session`, the current /// top candidates are selected to be block authors for session_i+1. Any /// changes to the top candidates afterwards do not effect the set of /// authors for session_i+1. @@ -1086,21 +1033,11 @@ pub mod pallet { /// leave before session_i+1 ends by delaying their /// exit for `ExitDelay` many blocks. /// - /// Emits `CollatorScheduledExit`. + /// NOTE 2: We do not increment rewards in this extrinsic as the + /// candidate could still author blocks, and thus be eligible to receive + /// rewards, until the end of the next session. /// - /// # - /// - The transaction's complexity is mainly dependent on updating the - /// `SelectedCandidates` storage in `select_top_candidates` which in - /// return depends on the number of `MaxSelectedCandidates` (N). - /// - For each N, we read `CandidatePool` from the storage. - /// --------- - /// Weight: O(N + D) where N is `MaxSelectedCandidates` bounded by - /// `MaxTopCandidates` and D is the number of delegators for this - /// candidate bounded by `MaxDelegatorsPerCollator`. - /// - Reads: [Origin Account], TopCandidates, (N + 1) * CandidatePool, - /// TotalCollatorStake - /// - Writes: CandidatePool, TopCandidates, TotalCollatorStake - /// # + /// Emits `CollatorScheduledExit`. #[pallet::weight(::WeightInfo::init_leave_candidates( T::MaxTopCandidates::get(), T::MaxTopCandidates::get().saturating_mul(T::MaxDelegatorsPerCollator::get()) @@ -1115,7 +1052,7 @@ pub mod pallet { Error::::TooFewCollatorCandidates ); - let now = >::get().current; + let now = Round::::get().current; let when = now.saturating_add(T::ExitQueueDelay::get()); state.leave_candidates(when); @@ -1158,18 +1095,11 @@ pub mod pallet { /// The exit request can be reversed by calling /// `cancel_leave_candidates`. /// - /// Emits `CollatorLeft`. + /// NOTE: Iterates over CandidatePool for each candidate over their + /// delegators to set rewards. Needs to be improved when scaling up + /// `MaxTopCandidates`. /// - /// # - /// Weight: O(N + D + U) where where N is `MaxSelectedCandidates` - /// bounded by `MaxTopCandidates`, D is the number of delegators for - /// this candidate bounded by `MaxDelegatorsPerCollator` and U is the - /// number of locked unstaking requests bounded by `MaxUnstakeRequests`. - /// - Reads: CandidatePool, Round, D * DelegatorState, D - /// * BlockNumber, D * Unstaking - /// - Writes: D * Unstaking, D * DelegatorState, Total - /// - Kills: CandidatePool, DelegatorState - /// # + /// Emits `CollatorLeft`. #[pallet::weight(::WeightInfo::execute_leave_candidates( T::MaxTopCandidates::get(), T::MaxDelegatorsPerCollator::get(), @@ -1182,13 +1112,14 @@ pub mod pallet { let collator = T::Lookup::lookup(collator)?; let state = CandidatePool::::get(&collator).ok_or(Error::::CandidateNotFound)?; ensure!(state.is_leaving(), Error::::NotLeaving); - ensure!(state.can_exit(>::get().current), Error::::CannotLeaveYet); + ensure!(state.can_exit(Round::::get().current), Error::::CannotLeaveYet); let num_delegators = state.delegators.len().saturated_into::(); let total_amount = state.total; // *** No Fail except during remove_candidate beyond this point *** + // remove candidate storage and increment rewards Self::remove_candidate(&collator, &state)?; Self::deposit_event(Event::CandidateLeft(collator, total_amount)); @@ -1208,15 +1139,6 @@ pub mod pallet { /// `init_leave_candidates`. /// /// Emits `CollatorCanceledExit`. - /// - /// # - /// Weight: O(N + D) where N is `MaxSelectedCandidates` bounded by - /// `MaxTopCandidates` and D is the number of delegators for this - /// candidate bounded by `MaxDelegatorsPerCollator`. - /// - Reads: [Origin Account], TotalCollatorStake, TopCandidates, - /// CandidatePool - /// - Writes: TotalCollatorStake, CandidatePool, TopCandidates - /// # #[pallet::weight(::WeightInfo::cancel_leave_candidates( T::MaxTopCandidates::get(), T::MaxDelegatorsPerCollator::get(), @@ -1264,16 +1186,6 @@ pub mod pallet { /// allowed range as set in the pallet's configuration. /// /// Emits `CollatorStakedMore`. - /// - /// # - /// Weight: O(N + D + U) where where N is `MaxSelectedCandidates` - /// bounded by `MaxTopCandidates`, D is the number of delegators for - /// this candidate bounded by `MaxDelegatorsPerCollator` and U is the - /// number of locked unstaking requests bounded by `MaxUnstakeRequests`. - /// - Reads: [Origin Account], Locks, TotalCollatorStake, - /// MaxCollatorCandidateStake, TopCandidates, CandidatePool - /// - Writes: Locks, TotalCollatorStake, CandidatePool, TopCandidates - /// # #[pallet::weight(::WeightInfo::candidate_stake_more( T::MaxTopCandidates::get(), T::MaxDelegatorsPerCollator::get(), @@ -1316,6 +1228,9 @@ pub mod pallet { }; CandidatePool::::insert(&collator, state); + // increment rewards for collator and update number of rewarded blocks + Self::do_inc_collator_reward(&collator, before_stake); + Self::deposit_event(Event::CollatorStakedMore(collator, before_stake, after_stake)); Ok(Some(::WeightInfo::candidate_stake_more( n, @@ -1340,15 +1255,6 @@ pub mod pallet { /// allowed range as set in the pallet's configuration. /// /// Emits `CollatorStakedLess`. - /// - /// # - /// Weight: O(N + D) where N is `MaxSelectedCandidates` bounded by - /// `MaxTopCandidates` and D is the number of delegators for this - /// candidate bounded by `MaxDelegatorsPerCollator`. - /// - Reads: [Origin Account], Unstaking, TopCandidates, - /// MaxSelectedCandidates, CandidatePool - /// - Writes: Unstaking, CandidatePool, TotalCollatorStake - /// # #[pallet::weight(::WeightInfo::candidate_stake_less( T::MaxTopCandidates::get(), T::MaxDelegatorsPerCollator::get() @@ -1390,6 +1296,9 @@ pub mod pallet { }; CandidatePool::::insert(&collator, state); + // increment rewards and update number of rewarded blocks + Self::do_inc_collator_reward(&collator, before_stake); + Self::deposit_event(Event::CollatorStakedLess(collator, before_stake, after)); Ok(Some(::WeightInfo::candidate_stake_less( n, @@ -1403,8 +1312,8 @@ pub mod pallet { /// The account that wants to delegate cannot be part of the collator /// candidates set as well. /// - /// The caller must _not_ have delegated before. Otherwise, - /// `delegate_another_candidate` should be called. + /// The caller must _not_ have a delegation. If that is the case, they + /// are required to first remove the delegation. /// /// The amount staked must be larger than the minimum required to become /// a delegator as set in the pallet's configuration. @@ -1420,16 +1329,6 @@ pub mod pallet { /// Emits `DelegationReplaced` if the candidate has /// `MaxDelegatorsPerCollator` many delegations but this delegator /// staked more than one of the other delegators of this candidate. - /// - /// # - /// Weight: O(N + D) where N is `MaxSelectedCandidates` bounded by - /// `MaxTopCandidates` and D is the number of delegators for this - /// candidate bounded by `MaxDelegatorsPerCollator`. - /// - Reads: [Origin Account], DelegatorState, TopCandidates, - /// MaxSelectedCandidates, CandidatePool, LastDelegation, Round - /// - Writes: Locks, CandidatePool, DelegatorState, TotalCollatorStake, - /// LastDelegation - /// # #[pallet::weight(::WeightInfo::join_delegators( T::MaxTopCandidates::get(), T::MaxDelegatorsPerCollator::get() @@ -1450,7 +1349,7 @@ pub mod pallet { // first delegation ensure!(DelegatorState::::get(&acc).is_none(), Error::::AlreadyDelegating); - ensure!(amount >= T::MinDelegatorStake::get(), Error::::NomStakeBelowMin); + ensure!(amount >= T::MinDelegatorStake::get(), Error::::DelegationBelowMin); // cannot be a collator candidate and delegator with same AccountId ensure!(Self::is_active_candidate(&acc).is_none(), Error::::CandidateExists); @@ -1483,11 +1382,10 @@ pub mod pallet { // should never fail but let's be safe ensure!(insert_delegator, Error::::DelegatorExists); - // can only throw if MaxCollatorsPerDelegator is set to 0 which should never - // occur in practice, even if the delegator rewards are set to 0 - let delegator_state = Delegator::try_new(collator.clone(), amount) - .map_err(|_| Error::::MaxCollatorsPerDelegatorExceeded)?; - + let delegator_state = Delegator { + amount, + owner: collator.clone(), + }; let CandidateOf:: { stake: old_stake, total: old_total, @@ -1495,13 +1393,12 @@ pub mod pallet { } = state; // update state and potentially prepare kicking a delegator with less staked - // amount - let (state, maybe_kicked_delegator) = if num_delegations_pre_insertion == T::MaxDelegatorsPerCollator::get() - { + // amount (includes setting rewards for kicked delegator) + let state = if num_delegations_pre_insertion == T::MaxDelegatorsPerCollator::get() { Self::do_update_delegator(delegation, state)? } else { state.total = state.total.saturating_add(amount); - (state, None) + state }; let new_total = state.total; @@ -1527,10 +1424,11 @@ pub mod pallet { // update states CandidatePool::::insert(&collator, state); DelegatorState::::insert(&acc, delegator_state); - >::insert(&acc, delegation_counter); + LastDelegation::::insert(&acc, delegation_counter); - // update or clear storage of potentially kicked delegator - Self::update_kicked_delegator_storage(maybe_kicked_delegator); + // initiate rewarded counter to match the current authored counter of the + // candidate + BlocksRewarded::::insert(&acc, BlocksAuthored::::get(&collator)); Self::deposit_event(Event::Delegation(acc, amount, collator, new_total)); Ok(Some(::WeightInfo::join_delegators( @@ -1540,163 +1438,8 @@ pub mod pallet { .into()) } - /// Delegate another collator's candidate by staking some funds and - /// increasing the pallet's as well as the collator's total stake. - /// - /// The account that wants to delegate cannot be part of the collator - /// candidates set as well. - /// - /// The caller _must_ have delegated before. Otherwise, - /// `join_delegators` should be called. - /// - /// If the delegator has already delegated the maximum number of - /// collator candidates, this operation will fail. - /// - /// The amount staked must be larger than the minimum required to become - /// a delegator as set in the pallet's configuration. - /// - /// As only `MaxDelegatorsPerCollator` are allowed to delegate a given - /// collator, the amount staked must be larger than the lowest one in - /// the current set of delegator for the operation to be meaningful. - /// - /// The collator's total stake as well as the pallet's total stake are - /// increased accordingly. - /// - /// NOTE: This transaction is expected to throw until we increase - /// `MaxCollatorsPerDelegator` by at least one, since it is currently - /// set to one. - /// - /// Emits `Delegation`. - /// Emits `DelegationReplaced` if the candidate has - /// `MaxDelegatorsPerCollator` many delegations but this delegator - /// staked more than one of the other delegators of this candidate. - /// - /// # - /// Weight: O(N + D) where N is `MaxSelectedCandidates` bounded by - /// `MaxTopCandidates` and D is the number of delegators for this - /// candidate bounded by `MaxDelegatorsPerCollator`. - /// - Reads: [Origin Account], DelegatorState, TopCandidates, - /// MaxSelectedCandidates, CandidatePool, LastDelegation, Round - /// - Writes: Locks, CandidatePool, DelegatorState, TotalCollatorStake, - /// LastDelegation - /// # - // - // NOTE: We can't benchmark this extrinsic until we have increased `MaxCollatorsPerDelegator` by at least 1, - // thus we use the closest weight we can get. - #[pallet::weight(::WeightInfo::join_delegators( - T::MaxTopCandidates::get(), - T::MaxDelegatorsPerCollator::get() - ))] - pub fn delegate_another_candidate( - origin: OriginFor, - collator: ::Source, - amount: BalanceOf, - ) -> DispatchResultWithPostInfo { - let acc = ensure_signed(origin)?; - let collator = T::Lookup::lookup(collator)?; - let mut delegator = DelegatorState::::get(&acc).ok_or(Error::::NotYetDelegating)?; - - // check balance - ensure!( - pallet_balances::Pallet::::free_balance(acc.clone()) - >= delegator.total.saturating_add(amount).into(), - pallet_balances::Error::::InsufficientBalance - ); - - // delegation after first - ensure!(amount >= T::MinDelegation::get(), Error::::DelegationBelowMin); - ensure!( - (delegator.delegations.len().saturated_into::()) < T::MaxCollatorsPerDelegator::get(), - Error::::MaxCollatorsPerDelegatorExceeded - ); - // cannot delegate if number of delegations in this round exceeds - // MaxDelegationsPerRound - let delegation_counter = Self::get_delegation_counter(&acc)?; - - // prepare new collator state - let mut state = CandidatePool::::get(&collator).ok_or(Error::::CandidateNotFound)?; - let num_delegations_pre_insertion: u32 = state.delegators.len().saturated_into(); - ensure!(!state.is_leaving(), Error::::CannotDelegateIfLeaving); - - // attempt to insert delegation, check for uniqueness and update total delegated - // amount - // NOTE: excess is handled below because we support replacing a delegator - // with fewer stake - ensure!( - delegator - .add_delegation(Stake { - owner: collator.clone(), - amount - }) - .unwrap_or(true), - Error::::AlreadyDelegatedCollator - ); - let delegation = Stake { - owner: acc.clone(), - amount, - }; - - // throws if delegation insertion exceeds bounded vec limit which we will handle - // below in Self::do_update_delegator - ensure!( - state.delegators.try_insert(delegation.clone()).unwrap_or(true), - Error::::DelegatorExists - ); - - let CandidateOf:: { - stake: old_stake, - total: old_total, - .. - } = state; - - // update state and potentially prepare kicking a delegator with less staked - // amount - let (state, maybe_kicked_delegator) = if num_delegations_pre_insertion == T::MaxDelegatorsPerCollator::get() - { - Self::do_update_delegator(delegation, state)? - } else { - state.total = state.total.saturating_add(amount); - (state, None) - }; - let new_total = state.total; - - // *** No Fail except during increase_lock beyond this point *** - - // lock stake - Self::increase_lock(&acc, delegator.total, amount)?; - - // update top candidates and total amount at stake - let n = if state.is_active() { - Self::update_top_candidates( - collator.clone(), - old_stake, - // safe because total >= stake - old_total - old_stake, - state.stake, - state.total - state.stake, - ) - } else { - 0u32 - }; - - // Update states - CandidatePool::::insert(&collator, state); - DelegatorState::::insert(&acc, delegator); - >::insert(&acc, delegation_counter); - - // update or clear storage of potentially kicked delegator - Self::update_kicked_delegator_storage(maybe_kicked_delegator); - - Self::deposit_event(Event::Delegation(acc, amount, collator, new_total)); - Ok(Some(::WeightInfo::join_delegators( - n, - T::MaxDelegatorsPerCollator::get(), - )) - .into()) - } - - /// Leave the set of delegators and, by implication, revoke all ongoing - /// delegations. + /// Leave the set of delegators and, by implication, revoke the ongoing + /// delegation. /// /// All staked funds are not unlocked immediately, but they are added to /// the queue of pending unstaking, and will effectively be released @@ -1707,16 +1450,10 @@ pub mod pallet { /// their chances to be included in the set of candidates in the next /// rounds. /// - /// Emits `DelegatorLeft`. + /// Automatically increments the accumulated rewards of the origin of + /// the current delegation. /// - /// # - /// Weight: O(C) where C is the number of delegations for this delegator - /// which is bounded by by `MaxCollatorsPerDelegator`. - /// - Reads: [Origin Account], DelegatorState, BlockNumber, Unstaking, - /// TopCandidates, MaxSelectedCandidates, C * CandidatePool, - /// - Writes: Unstaking, CandidatePool, TotalCollatorStake, - /// - Kills: DelegatorState - /// # + /// Emits `DelegatorLeft`. #[pallet::weight(::WeightInfo::leave_delegators( T::MaxTopCandidates::get(), T::MaxDelegatorsPerCollator::get() @@ -1724,63 +1461,16 @@ pub mod pallet { pub fn leave_delegators(origin: OriginFor) -> DispatchResultWithPostInfo { let acc = ensure_signed(origin)?; let delegator = DelegatorState::::get(&acc).ok_or(Error::::DelegatorNotFound)?; - let num_delegations: u32 = delegator.delegations.len().saturated_into(); - for stake in delegator.delegations.into_iter() { - Self::delegator_leaves_collator(acc.clone(), stake.owner.clone())?; - } + let collator = delegator.owner; + Self::delegator_leaves_collator(acc.clone(), collator)?; // *** No Fail beyond this point *** DelegatorState::::remove(&acc); - Self::deposit_event(Event::DelegatorLeft(acc, delegator.total)); + Self::deposit_event(Event::DelegatorLeft(acc, delegator.amount)); Ok(Some(::WeightInfo::leave_delegators( - num_delegations, - T::MaxDelegatorsPerCollator::get(), - )) - .into()) - } - - /// Terminates an ongoing delegation for a given collator candidate. - /// - /// The staked funds are not unlocked immediately, but they are added to - /// the queue of pending unstaking, and will effectively be released - /// after `StakeDuration` blocks from the moment the delegation is - /// terminated. - /// - /// This operation reduces the total stake of the pallet as well as the - /// stakes of the collator involved, potentially affecting its chances - /// to be included in the set of candidates in the next rounds. - /// - /// Emits `DelegatorLeft`. - /// - /// # - /// Weight: O(C) where C is the number of delegations for this delegator - /// which is bounded by by `MaxCollatorsPerDelegator`. - /// - Reads: [Origin Account], DelegatorState, BlockNumber, Unstaking, - /// Locks, TopCandidates, CandidatePool, MaxSelectedCandidates - /// - Writes: Unstaking, Locks, DelegatorState, CandidatePool, - /// TotalCollatorStake - /// - Kills: DelegatorState if the delegator has not delegated to - /// another collator - /// # - #[pallet::weight(::WeightInfo::revoke_delegation( - T::MaxCollatorsPerDelegator::get(), - T::MaxDelegatorsPerCollator::get() - ))] - pub fn revoke_delegation( - origin: OriginFor, - collator: ::Source, - ) -> DispatchResultWithPostInfo { - let collator = T::Lookup::lookup(collator)?; - let delegator = ensure_signed(origin)?; - - // *** No Fail except during delegator_revokes_collator beyond this point *** - - let num_delegations = Self::delegator_revokes_collator(delegator, collator)?; - - Ok(Some(::WeightInfo::revoke_delegation( - num_delegations, + 1, T::MaxDelegatorsPerCollator::get(), )) .into()) @@ -1792,42 +1482,27 @@ pub mod pallet { /// collator candidate to be added to it. /// /// Emits `DelegatorStakedMore`. - /// - /// # - /// Weight: O(N) + O(D) where N is `MaxSelectedCandidates` bounded - /// by `MaxTopCandidates` and D the number of total delegators for - /// this collator bounded by `MaxCollatorsPerDelegator`. - /// bounded by `MaxUnstakeRequests`. - /// - Reads: [Origin Account], DelegatorState, BlockNumber, Unstaking, - /// Locks, TopCandidates, CandidatePool, MaxSelectedCandidates - /// - Writes: Unstaking, Locks, DelegatorState, CandidatePool, - /// TotalCollatorStake - /// # #[pallet::weight(::WeightInfo::delegator_stake_more( T::MaxTopCandidates::get(), T::MaxDelegatorsPerCollator::get(), T::MaxUnstakeRequests::get().saturated_into::()) )] - pub fn delegator_stake_more( - origin: OriginFor, - candidate: ::Source, - more: BalanceOf, - ) -> DispatchResultWithPostInfo { + pub fn delegator_stake_more(origin: OriginFor, more: BalanceOf) -> DispatchResultWithPostInfo { let delegator = ensure_signed(origin)?; ensure!(!more.is_zero(), Error::::ValStakeZero); - let candidate = T::Lookup::lookup(candidate)?; - let mut delegations = DelegatorState::::get(&delegator).ok_or(Error::::DelegatorNotFound)?; + let mut delegation = DelegatorState::::get(&delegator).ok_or(Error::::DelegatorNotFound)?; + let candidate = delegation.owner.clone(); let mut collator = CandidatePool::::get(&candidate).ok_or(Error::::CandidateNotFound)?; ensure!(!collator.is_leaving(), Error::::CannotDelegateIfLeaving); - let delegator_total = delegations - .inc_delegation(candidate.clone(), more) - .ok_or(Error::::DelegationNotFound)?; + let stake_after = delegation + .try_increment(candidate.clone(), more) + .map_err(|_| Error::::DelegationNotFound)?; // *** No Fail except during increase_lock beyond this point *** // update lock - let unstaking_len = Self::increase_lock(&delegator, delegator_total, more)?; + let unstaking_len = Self::increase_lock(&delegator, stake_after, more)?; let CandidateOf:: { stake: before_stake, @@ -1851,8 +1526,11 @@ pub mod pallet { 0u32 }; + // increment rewards and update number of rewarded blocks + Self::do_inc_delegator_reward(&delegator, stake_after.saturating_sub(more), &candidate); + CandidatePool::::insert(&candidate, collator); - DelegatorState::::insert(&delegator, delegations); + DelegatorState::::insert(&delegator, delegation); Self::deposit_event(Event::DelegatorStakedMore(delegator, candidate, before_total, after)); Ok(Some(::WeightInfo::delegator_stake_more( @@ -1879,39 +1557,26 @@ pub mod pallet { /// allowed range as set in the pallet's configuration. /// /// Emits `DelegatorStakedLess`. - /// - /// # - /// Weight: O(1) - /// - Reads: [Origin Account], DelegatorState, BlockNumber, Unstaking, - /// TopCandidates, CandidatePool, MaxSelectedCandidates - /// - Writes: Unstaking, DelegatorState, CandidatePool, - /// TotalCollatorStake - /// # #[pallet::weight(::WeightInfo::delegator_stake_less( T::MaxTopCandidates::get(), T::MaxDelegatorsPerCollator::get() ))] - pub fn delegator_stake_less( - origin: OriginFor, - candidate: ::Source, - less: BalanceOf, - ) -> DispatchResultWithPostInfo { + pub fn delegator_stake_less(origin: OriginFor, less: BalanceOf) -> DispatchResultWithPostInfo { let delegator = ensure_signed(origin)?; ensure!(!less.is_zero(), Error::::ValStakeZero); - let candidate = T::Lookup::lookup(candidate)?; - let mut delegations = DelegatorState::::get(&delegator).ok_or(Error::::DelegatorNotFound)?; + let mut delegation = DelegatorState::::get(&delegator).ok_or(Error::::DelegatorNotFound)?; + let candidate = delegation.owner.clone(); let mut collator = CandidatePool::::get(&candidate).ok_or(Error::::CandidateNotFound)?; ensure!(!collator.is_leaving(), Error::::CannotDelegateIfLeaving); - let remaining = delegations - .dec_delegation(candidate.clone(), less) - .ok_or(Error::::DelegationNotFound)? + let stake_after = delegation + .try_decrement(candidate.clone(), less) + .map_err(|_| Error::::DelegationNotFound)? .ok_or(Error::::Underflow)?; - ensure!(remaining >= T::MinDelegation::get(), Error::::DelegationBelowMin); ensure!( - delegations.total >= T::MinDelegatorStake::get(), - Error::::NomStakeBelowMin + stake_after >= T::MinDelegatorStake::get(), + Error::::DelegationBelowMin ); // *** No Fail except during prep_unstake beyond this point *** @@ -1939,8 +1604,12 @@ pub mod pallet { } else { 0u32 }; + + // increment rewards and update number of rewarded blocks + Self::do_inc_delegator_reward(&delegator, stake_after.saturating_add(less), &candidate); + CandidatePool::::insert(&candidate, collator); - DelegatorState::::insert(&delegator, delegations); + DelegatorState::::insert(&delegator, delegation); Self::deposit_event(Event::DelegatorStakedLess(delegator, candidate, before_total, after)); Ok(Some(::WeightInfo::delegator_stake_less( @@ -1974,15 +1643,128 @@ pub mod pallet { Ok(Some(::WeightInfo::unlock_unstaked(unstaking_len)).into()) } + + /// Claim block authoring rewards for the target address. + /// + /// Requires `Rewards` to be set beforehand, which can by triggered by + /// any of the following options + /// * Calling increment_{collator, delegator}_rewards (active) + /// * Altering your stake (active) + /// * Leaving the network as a collator (active) + /// * Revoking a delegation as a delegator (active) + /// * Being a delegator whose collator left the network, altered their + /// stake or incremented rewards (passive) + /// + /// The dispatch origin can be any signed one, e.g., anyone can claim + /// for anyone. + /// + /// Emits `Rewarded`. + #[pallet::weight(::WeightInfo::claim_rewards())] + pub fn claim_rewards(origin: OriginFor) -> DispatchResult { + let target = ensure_signed(origin)?; + + // reset rewards + let rewards = Rewards::::take(&target); + ensure!(!rewards.is_zero(), Error::::RewardsNotFound); + + // mint into target + let rewards = T::Currency::deposit_into_existing(&target, rewards)?; + + Self::deposit_event(Event::Rewarded(target, rewards.peek())); + + Ok(()) + } + + /// Actively increment the rewards of a collator. + /// + /// The same effect is triggered by changing the stake or leaving the + /// network. + /// + /// The dispatch origin must be a collator. + #[pallet::weight(::WeightInfo::increment_collator_rewards())] + pub fn increment_collator_rewards(origin: OriginFor) -> DispatchResult { + let collator = ensure_signed(origin)?; + let state = CandidatePool::::get(&collator).ok_or(Error::::CandidateNotFound)?; + + // increment rewards and update number of rewarded blocks + Self::do_inc_collator_reward(&collator, state.stake); + + Ok(()) + } + + /// Actively increment the rewards of a delegator. + /// + /// The same effect is triggered by changing the stake or revoking + /// delegations. + /// + /// The dispatch origin must be a delegator. + #[pallet::weight(::WeightInfo::increment_delegator_rewards())] + pub fn increment_delegator_rewards(origin: OriginFor) -> DispatchResult { + let delegator = ensure_signed(origin)?; + let delegation = DelegatorState::::get(&delegator).ok_or(Error::::DelegatorNotFound)?; + let collator = delegation.owner; + + // increment rewards and update number of rewarded blocks + Self::do_inc_delegator_reward(&delegator, delegation.amount, &collator); + + Ok(()) + } + + /// Executes the annual reduction of the reward rates for collators and + /// delegators. + /// + /// Moreover, sets rewards for all collators and delegators + /// before adjusting the inflation. + /// + /// The dispatch origin can be any signed one because we bail if called + /// too early. + /// + /// Emits `RoundInflationSet`. + #[pallet::weight(::WeightInfo::execute_scheduled_reward_change(T::MaxTopCandidates::get(), T::MaxDelegatorsPerCollator::get()))] + pub fn execute_scheduled_reward_change(origin: OriginFor) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + + let now = frame_system::Pallet::::block_number(); + let year = now / T::BLOCKS_PER_YEAR; + + // We can already mutate thanks to extrinsics being transactional + let last_update = LastRewardReduction::::mutate(|last_year| { + let old = *last_year; + *last_year = old.saturating_add(T::BlockNumber::one()); + old + }); + // Bail if less than a year (in terms of number of blocks) has passed since the + // last update + ensure!(year > last_update, Error::::TooEarly); + + // Calculate new inflation based on last year + let inflation = InflationConfig::::get(); + + // collator reward rate decreases by 2% p.a. of the previous one + let c_reward_rate = inflation.collator.reward_rate.annual * Perquintill::from_percent(98); + + // delegator reward rate should be 6% in 2nd year and 0% afterwards + let d_reward_rate = if year == T::BlockNumber::one() { + Perquintill::from_percent(6) + } else { + Perquintill::zero() + }; + + // Update inflation and increment rewards + let (num_col, num_del) = Self::do_set_inflation( + T::BLOCKS_PER_YEAR, + inflation.collator.max_rate, + c_reward_rate, + inflation.delegator.max_rate, + d_reward_rate, + )?; + + Ok(Some(::WeightInfo::set_inflation(num_col, num_del)).into()) + } } impl Pallet { /// Check whether an account is currently delegating. - /// - /// # - /// Weight: O(1) - /// - Reads: DelegatorState - /// # pub fn is_delegator(acc: &T::AccountId) -> bool { DelegatorState::::get(acc).is_some() } @@ -1990,10 +1772,7 @@ pub mod pallet { /// Check whether an account is currently a collator candidate and /// whether their state is CollatorStatus::Active. /// - /// # - /// Weight: O(1) - /// - Reads: CandidatePool - /// # + /// Returns Some(is_active) if the account is a candidate, else None. pub fn is_active_candidate(acc: &T::AccountId) -> Option { if let Some(state) = CandidatePool::::get(acc) { Some(state.status == CandidateStatus::Active) @@ -2001,18 +1780,69 @@ pub mod pallet { None } } + /// Set the annual inflation rate to derive per-round inflation. + /// + /// The inflation details are considered valid if the annual reward rate + /// is approximately the per-block reward rate multiplied by the + /// estimated* total number of blocks per year. + /// + /// The estimated average block time is twelve seconds. + /// + /// NOTE: Iterates over CandidatePool and for each candidate over their + /// delegators to update their rewards before the reward rates change. + /// Needs to be improved when scaling up `MaxTopCandidates`. + /// + /// Emits `RoundInflationSet`. + fn do_set_inflation( + blocks_per_year: T::BlockNumber, + col_max_rate: Perquintill, + col_reward_rate: Perquintill, + del_max_rate: Perquintill, + del_reward_rate: Perquintill, + ) -> Result<(u32, u32), DispatchError> { + // Check validity of new inflation + let inflation = InflationInfo::new( + blocks_per_year.saturated_into(), + col_max_rate, + col_reward_rate, + del_max_rate, + del_reward_rate, + ); + ensure!( + inflation.is_valid(T::BLOCKS_PER_YEAR.saturated_into()), + Error::::InvalidSchedule + ); + + // Increment rewards for all collators and delegators due to change of reward + // rates + let mut num_delegators = 0u32; + CandidatePool::::iter().for_each(|(id, state)| { + // increment collator rewards + Self::do_inc_collator_reward(&id, state.stake); + // increment delegator rewards + state.delegators.into_iter().for_each(|delegator_state| { + Self::do_inc_delegator_reward(&delegator_state.owner, delegator_state.amount, &id); + num_delegators = num_delegators.saturating_add(1u32); + }); + }); + + // Update inflation + InflationConfig::::put(inflation); + Self::deposit_event(Event::RoundInflationSet( + col_max_rate, + col_reward_rate, + del_max_rate, + del_reward_rate, + )); + + Ok((CandidatePool::::count(), num_delegators)) + } /// Update the top candidates and total amount at stake after mutating /// an active candidate's stake. /// /// NOTE: It is assumed that the calling context checks whether the /// collator candidate is currently active before calling this function. - /// - /// # - /// Weight: O(1) - /// - Reads: TopCandidates, CandidatePool, TotalCollatorStake - /// - Writes: TopCandidates, TotalCollatorStake - /// # fn update_top_candidates( candidate: T::AccountId, old_self: BalanceOf, @@ -2140,14 +1970,6 @@ pub mod pallet { /// guarantee a single candidate's stake has changed, e.g. on genesis or /// when a collator leaves. Otherwise, please use /// [update_total_stake_by]. - /// - /// # - /// Weight: O(N) where N is `MaxSelectedCandidates` bounded by - /// `MaxTopCandidates` - /// - Reads: TopCandidates, MaxSelectedCandidates, N * CandidatePool, - /// TotalCollatorStake - /// - Writes: TotalCollatorStake - /// # fn update_total_stake() -> (u32, u32) { let mut num_of_delegators = 0u32; let mut collator_stake = BalanceOf::::zero(); @@ -2179,53 +2001,11 @@ pub mod pallet { (collators.len().saturated_into(), num_of_delegators) } - /// Update the delegator's state by removing the collator candidate from - /// the set of ongoing delegations. - /// - /// # - /// Weight: O(D) where D is the number of total delegators for - /// this collator bounded by `MaxCollatorsPerDelegator`. - /// - Reads: [Origin Account], DelegatorState, BlockNumber, Unstaking, - /// Locks, TopCandidates, D * CandidatePool, MaxSelectedCandidates - /// - Writes: Unstaking, Locks, DelegatorState, CandidatePool, - /// TotalCollatorStake - /// - Kills: DelegatorState if the delegator has not delegated to - /// another collator - /// # - fn delegator_revokes_collator(acc: T::AccountId, collator: T::AccountId) -> Result { - let mut delegator = DelegatorState::::get(&acc).ok_or(Error::::DelegatorNotFound)?; - let old_total = delegator.total; - let num_delegations: u32 = delegator.delegations.len().saturated_into::(); - let remaining = delegator - .rm_delegation(&collator) - .ok_or(Error::::DelegationNotFound)?; - - // edge case; if no delegations remaining, leave set of delegators - if delegator.delegations.is_empty() { - // leave the set of delegators because no delegations left - Self::delegator_leaves_collator(acc.clone(), collator)?; - DelegatorState::::remove(&acc); - Self::deposit_event(Event::DelegatorLeft(acc, old_total)); - } else { - // can never fail iff MinDelegatorStake == MinDelegation - ensure!(remaining >= T::MinDelegatorStake::get(), Error::::NomStakeBelowMin); - Self::delegator_leaves_collator(acc.clone(), collator)?; - DelegatorState::::insert(&acc, delegator); - } - Ok(num_delegations) - } - /// Update the collator's state by removing the delegator's stake and - /// starting the process to unlock the delegator's staked funds. + /// starting the process to unlock the delegator's staked funds as well + /// as incrementing their accumulated rewards. /// /// This operation affects the pallet's total stake. - /// - /// # - /// Weight: O(D) where D is the number of delegators for this - /// collator bounded by `MaxDelegatorsPerCollator`. - /// - Reads: CandidatePool, BlockNumber, Unstaking - /// - Writes: Unstaking, TotalCollatorStake, CandidatePool - /// # fn delegator_leaves_collator(delegator: T::AccountId, collator: T::AccountId) -> DispatchResult { let mut state = CandidatePool::::get(&collator).ok_or(Error::::CandidateNotFound)?; @@ -2247,6 +2027,10 @@ pub mod pallet { state.total = state.total.saturating_sub(delegator_stake); let new_total = state.total; + // increment rewards and kill storage for number of rewarded blocks + Self::do_inc_delegator_reward(&delegator, delegator_stake, &collator); + BlocksRewarded::::remove(&delegator); + // we don't unlock immediately Self::prep_unstake(&delegator, delegator_stake, false)?; @@ -2272,61 +2056,11 @@ pub mod pallet { Ok(()) } - /// Check for remaining delegations of the delegator which has been - /// removed from the given collator. - /// - /// Returns the removed delegator's address and - /// * Either the updated delegator state if other delegations are still - /// remaining - /// * Or `None`, signalling the delegator state should be cleared once - /// the transaction cannot fail anymore. - fn prep_kick_delegator( - delegation: &StakeOf, - collator: &T::AccountId, - ) -> Result, DispatchError> { - let mut state = DelegatorState::::get(&delegation.owner).ok_or(Error::::DelegatorNotFound)?; - state.rm_delegation(collator); - - // we don't unlock immediately - Self::prep_unstake(&delegation.owner, delegation.amount, true)?; - - // return state if not empty for later removal after all checks have passed - if state.delegations.is_empty() { - Ok(ReplacedDelegator { - who: delegation.owner.clone(), - state: None, - }) - } else { - Ok(ReplacedDelegator { - who: delegation.owner.clone(), - state: Some(state), - }) - } - } - - /// Either clear the storage of a kicked delegator or update its - /// delegation state if it still contains other delegations. - fn update_kicked_delegator_storage(delegator: Option>) { - match delegator { - Some(ReplacedDelegator { - who, - state: Some(state), - }) => DelegatorState::::insert(who, state), - Some(ReplacedDelegator { who, .. }) => DelegatorState::::remove(who), - _ => (), - } - } - /// Return the best `MaxSelectedCandidates` many candidates. /// /// In case a collator from last round was replaced by a candidate with /// the same total stake during sorting, we revert this swap to /// prioritize collators over candidates. - /// - /// # - /// Weight: O(1) - /// - Reads: TopCandidates, MaxSelectedCandidates - /// # pub fn selected_candidates() -> BoundedVec { let candidates = TopCandidates::::get(); @@ -2352,29 +2086,19 @@ pub mod pallet { /// amount is at most the minimum staked value of the original delegator /// set, an error is returned. /// + /// Sets rewards for the removed delegator. + /// /// Returns a tuple which contains the updated candidate state as well /// as the potentially replaced delegation which will be used later when /// updating the storage of the replaced delegator. /// /// Emits `DelegationReplaced` if the stake exceeds one of the current /// delegations. - /// - /// # - /// Weight: O(D) where D is the number of delegators for this collator - /// bounded by `MaxDelegatorsPerCollator`. - /// - Reads/Writes: 0 - /// # #[allow(clippy::type_complexity)] fn do_update_delegator( stake: Stake>, mut state: Candidate, T::MaxDelegatorsPerCollator>, - ) -> Result< - ( - CandidateOf, - Option>, - ), - DispatchError, - > { + ) -> Result, DispatchError> { // attempt to replace the last element of the set let stake_to_remove = state .delegators @@ -2394,8 +2118,12 @@ pub mod pallet { // update total stake state.total = state.total.saturating_sub(stake_to_remove.amount); - // update storage of kicked delegator - let kicked_delegator = Self::prep_kick_delegator(&stake_to_remove, &state.id)?; + // update rewards for kicked delegator + Self::do_inc_delegator_reward(&stake_to_remove.owner, stake_to_remove.amount, &state.id); + // prepare unstaking for kicked delegator + Self::prep_unstake(&stake_to_remove.owner, stake_to_remove.amount, true)?; + // remove Delegator state for kicked delegator + DelegatorState::::remove(&stake_to_remove.owner); Self::deposit_event(Event::DelegationReplaced( stake.owner, @@ -2405,11 +2133,9 @@ pub mod pallet { state.id.clone(), state.total, )); - - Ok((state, Some(kicked_delegator))) - } else { - Ok((state, None)) } + + Ok(state) } /// Either set or increase the BalanceLock of target account to @@ -2417,13 +2143,6 @@ pub mod pallet { /// /// Consumes unstaked balance which can be unlocked in the future up to /// amount and updates `Unstaking` storage accordingly. - /// - /// # - /// Weight: O(U) where U is the number of locked unstaking requests - /// bounded by `MaxUnstakeRequests`. - /// - Reads: Unstaking, Locks - /// - Writes: Unstaking, Locks - /// # fn increase_lock(who: &T::AccountId, amount: BalanceOf, more: BalanceOf) -> Result { ensure!( pallet_balances::Pallet::::free_balance(who) >= amount.into(), @@ -2435,7 +2154,7 @@ pub mod pallet { // *** No Fail except during Unstaking mutation beyond this point *** // update Unstaking by consuming up to {amount | more} - >::try_mutate(who, |unstaking| -> DispatchResult { + Unstaking::::try_mutate(who, |unstaking| -> DispatchResult { // reduce {amount | more} by unstaking until either {amount | more} is zero or // no unstaking is left // if more is set, we only want to reduce by more to achieve 100 - 40 + 30 = 90 @@ -2476,19 +2195,13 @@ pub mod pallet { /// Throws if the amount is zero (unlikely) or if active unlocking /// requests exceed limit. The latter defends against stake reduction /// spamming. - /// - /// # - /// Weight: O(1) - /// - Reads: BlockNumber, Unstaking - /// - Writes: Unstaking - /// # fn prep_unstake(who: &T::AccountId, amount: BalanceOf, is_removal: bool) -> DispatchResult { // should never occur but let's be safe ensure!(!amount.is_zero(), Error::::StakeNotFound); - let now = >::block_number(); + let now = frame_system::Pallet::::block_number(); let unlock_block = now.saturating_add(T::StakeDuration::get()); - let mut unstaking = >::get(who); + let mut unstaking = Unstaking::::get(who); let allowed_unstakings = if is_removal { // the account was forcedly removed and we allow to fill all unstake requests @@ -2508,40 +2221,29 @@ pub mod pallet { unstaking .try_insert(unlock_block, amount) .map_err(|_| Error::::NoMoreUnstaking)?; - >::insert(who, unstaking); + Unstaking::::insert(who, unstaking); Ok(()) } /// Clear the CandidatePool of the candidate and remove all delegations /// to the candidate. Moreover, prepare unstaking for the candidate and /// their former delegations. - /// - /// # - /// Weight: O(D + U) where D is the number of delegators of the collator - /// candidate bounded by `MaxDelegatorsPerCollator` and U is the - /// number of locked unstaking requests bounded by `MaxUnstakeRequests`. - /// - Reads: BlockNumber, D * DelegatorState, D * Unstaking - /// - Writes: D * DelegatorState, (D + 1) * Unstaking - /// - Kills: CandidatePool, DelegatorState for all delegators which only - /// delegated to the candidate - /// # fn remove_candidate( collator: &T::AccountId, state: &CandidateOf, ) -> DispatchResult { // iterate over delegators for stake in &state.delegators[..] { + // increment rewards + Self::do_inc_delegator_reward(&stake.owner, stake.amount, collator); // prepare unstaking of delegator Self::prep_unstake(&stake.owner, stake.amount, true)?; // remove delegation from delegator state if let Some(mut delegator) = DelegatorState::::get(&stake.owner) { - if let Some(remaining) = delegator.rm_delegation(collator) { - if remaining.is_zero() { - DelegatorState::::remove(&stake.owner); - } else { - DelegatorState::::insert(&stake.owner, delegator); - } - } + delegator + .try_clear(collator.clone()) + .map_err(|_| Error::::DelegationNotFound)?; + DelegatorState::::remove(&stake.owner); } } // prepare unstaking of collator candidate @@ -2549,6 +2251,9 @@ pub mod pallet { // *** No Fail beyond this point *** + // increment rewards of collator + Self::do_inc_collator_reward(collator, state.stake); + // disable validator for next session if they were in the set of validators pallet_session::Pallet::::validators() .into_iter() @@ -2564,23 +2269,18 @@ pub mod pallet { // FIXME: Does not prevent the collator from being able to author a block in this (or potentially the next) session. See https://github.com/paritytech/substrate/issues/8004 .map(pallet_session::Pallet::::disable_index); + // Kill storage + BlocksAuthored::::remove(&collator); + BlocksRewarded::::remove(&collator); CandidatePool::::remove(&collator); Ok(()) } /// Withdraw all staked currency which was unstaked at least /// `StakeDuration` blocks ago. - /// - /// # - /// Weight: O(U) where U is the number of locked unstaking - /// requests bounded by `MaxUnstakeRequests`. - /// - Reads: Unstaking, Locks - /// - Writes: Unstaking, Locks - /// - Kills: Unstaking & Locks if no balance is locked anymore - /// # fn do_unlock(who: &T::AccountId) -> Result { - let now = >::block_number(); - let mut unstaking = >::get(who); + let now = frame_system::Pallet::::block_number(); + let mut unstaking = Unstaking::::get(who); let unstaking_len = unstaking.len().saturated_into::(); ensure!(!unstaking.is_empty(), Error::::UnstakingIsEmpty); @@ -2613,82 +2313,23 @@ pub mod pallet { if total_locked.is_zero() { T::Currency::remove_lock(STAKING_ID, who); - >::remove(who); + Unstaking::::remove(who); } else { T::Currency::set_lock(STAKING_ID, who, total_locked, WithdrawReasons::all()); - >::insert(who, unstaking); + Unstaking::::insert(who, unstaking); } Ok(unstaking_len) } - /// Process the coinbase rewards for the production of a new block. - /// - /// # - /// Weight: O(1) - /// - Reads: Balance - /// - Writes: Balance - /// # - fn do_reward(who: &T::AccountId, reward: BalanceOf) { - // mint - if let Ok(imb) = T::Currency::deposit_into_existing(who, reward) { - Self::deposit_event(Event::Rewarded(who.clone(), imb.peek())); - } - } - - /// Annually reduce the reward rates for collators and delegators. - /// - /// # - /// Weight: O(1) - /// - Reads: LastRewardReduction, InflationConfig - /// - Writes: LastRewardReduction, InflationConfig - /// # - fn adjust_reward_rates(now: T::BlockNumber) -> Weight { - let year = now / T::BLOCKS_PER_YEAR; - let last_update = >::get(); - if year > last_update { - let inflation = >::get(); - // collator reward rate decreases by 2% of the previous one per year - let c_reward_rate = inflation.collator.reward_rate.annual * Perquintill::from_percent(98); - // delegator reward rate should be 6% in 2nd year and 0% afterwards - let d_reward_rate = if year == T::BlockNumber::one() { - Perquintill::from_percent(6) - } else { - Perquintill::zero() - }; - - let new_inflation = InflationInfo::new( - T::BLOCKS_PER_YEAR.saturated_into(), - inflation.collator.max_rate, - c_reward_rate, - inflation.delegator.max_rate, - d_reward_rate, - ); - >::put(new_inflation.clone()); - >::put(year); - Self::deposit_event(Event::RoundInflationSet( - new_inflation.collator.max_rate, - new_inflation.collator.reward_rate.per_block, - new_inflation.delegator.max_rate, - new_inflation.delegator.reward_rate.per_block, - )); - ::WeightInfo::on_initialize_new_year(); - } - T::DbWeight::get().reads(1) - } - /// Checks whether a delegator can still delegate in this round, e.g., /// if they have not delegated MaxDelegationsPerRound many times /// already in this round. - /// - /// # - /// Weight: O(1) - /// - Reads: LastDelegation, Round - /// # fn get_delegation_counter(delegator: &T::AccountId) -> Result { - let last_delegation = >::get(delegator); - let round = >::get(); + let last_delegation = LastDelegation::::get(delegator); + let round = Round::::get(); + // reset counter if the round advanced since last delegation let counter = if last_delegation.round < round.current { 0u32 } else { @@ -2696,7 +2337,7 @@ pub mod pallet { }; ensure!( - T::MaxDelegationsPerRound::get() > counter, + counter < T::MaxDelegationsPerRound::get(), Error::::DelegationsPerRoundExceeded ); @@ -2711,19 +2352,19 @@ pub mod pallet { /// in `on_initialize` by adding it to the free balance of /// `NetworkRewardBeneficiary`. /// + /// Over the course of an entire year, the network rewards equal the + /// maximum annual collator staking rewards multiplied with the + /// NetworkRewardRate. E.g., assuming 10% annual collator reward rate, + /// 10% max staking rate, 200k KILT max collator stake and 30 collators: + /// NetworkRewards = NetworkRewardRate * 10% * 10% * 200_000 KILT * 30 + /// /// The expected rewards are the product of /// * the current total maximum collator rewards /// * and the configured NetworkRewardRate /// /// `col_reward_rate_per_block * col_max_stake * max_num_of_collators * /// NetworkRewardRate` - /// - /// # - /// Weight: O(1) - /// - Reads: InflationConfig, MaxCollatorCandidateStake, - /// MaxSelectedCandidates - /// # - fn get_network_reward() -> NegativeImbalanceOf { + fn issue_network_reward() -> NegativeImbalanceOf { // Multiplication with Perquintill cannot overflow let max_col_rewards = InflationConfig::::get().collator.reward_rate.per_block * MaxCollatorCandidateStake::::get() @@ -2733,95 +2374,161 @@ pub mod pallet { T::Currency::issue(network_reward) } - // [Post-launch TODO] Think about Collator stake or total stake? - // /// Attempts to add a collator candidate to the set of collator - // /// candidates which already reached its maximum size. On success, - // /// another collator with the minimum total stake is removed from the - // /// set. On failure, an error is returned. removing an already existing - // fn check_collator_candidate_inclusion( - // stake: Stake>, - // mut candidates: OrderedSet>, - // T::MaxTopCandidates>, ) -> Result<(), DispatchError> { - // todo!() - // } + /// Calculates the collator staking rewards for authoring `multiplier` + /// many blocks based on the given stake. + /// + /// Depends on the current total issuance and staking reward + /// configuration for collators. + fn calc_block_rewards_collator(stake: BalanceOf, multiplier: BalanceOf) -> BalanceOf { + let total_issuance = T::Currency::total_issuance(); + let TotalStake { + collators: total_collators, + .. + } = TotalCollatorStake::::get(); + let staking_rate = Perquintill::from_rational(total_collators, total_issuance); + + InflationConfig::::get() + .collator + .compute_reward::(stake, staking_rate, multiplier) + } + + /// Calculates the delegator staking rewards for `multiplier` many + /// blocks based on the given stake. + /// + /// Depends on the current total issuance and staking reward + /// configuration for delegators. + fn calc_block_rewards_delegator(stake: BalanceOf, multiplier: BalanceOf) -> BalanceOf { + let total_issuance = T::Currency::total_issuance(); + let TotalStake { + delegators: total_delegators, + .. + } = TotalCollatorStake::::get(); + let staking_rate = Perquintill::from_rational(total_delegators, total_issuance); + + InflationConfig::::get() + .delegator + .compute_reward::(stake, staking_rate, multiplier) + } + + /// Calculates the staking rewards for a given account address. + /// + /// Subtracts the number of rewarded blocks from the number of authored + /// blocks by the collator and multiplies that with the current stake + /// as well as reward rate. + /// + /// At least used in Runtime API. + pub fn get_unclaimed_staking_rewards(acc: &T::AccountId) -> BalanceOf { + let count_rewarded = BlocksRewarded::::get(acc); + let rewards = Rewards::::get(acc); + + // delegators and collators need to be handled differently + if let Some(delegator_state) = DelegatorState::::get(acc) { + // #blocks for unclaimed staking rewards equals + // #blocks_authored_by_collator - #blocks_claimed_by_delegator + let count_unclaimed = BlocksAuthored::::get(&delegator_state.owner).saturating_sub(count_rewarded); + let stake = delegator_state.amount; + // rewards += stake * reward_count * delegator_reward_rate + rewards.saturating_add(Self::calc_block_rewards_delegator(stake, count_unclaimed.into())) + } else if Self::is_active_candidate(acc).is_some() { + // #blocks for unclaimed staking rewards equals + // #blocks_authored_by_collator - #blocks_claimed_by_collator + let count_unclaimed = BlocksAuthored::::get(acc).saturating_sub(count_rewarded); + let stake = CandidatePool::::get(acc) + .map(|state| state.stake) + .unwrap_or_else(BalanceOf::::zero); + // rewards += stake * self_count * collator_reward_rate + rewards.saturating_add(Self::calc_block_rewards_collator(stake, count_unclaimed.into())) + } else { + BalanceOf::::zero() + } + } + + /// Increment the accumulated rewards of a collator. + /// + /// Updates Rewarded(col) and sets BlocksRewarded(col) to equal + /// BlocksAuthored(col). + fn do_inc_collator_reward(acc: &T::AccountId, stake: BalanceOf) { + let count_authored = BlocksAuthored::::get(acc); + // We can already mutate thanks to extrinsics being transactional + let count_rewarded = BlocksRewarded::::mutate(acc, |rewarded| { + let old = *rewarded; + *rewarded = count_authored; + old + }); + let unclaimed_blocks = count_authored.saturating_sub(count_rewarded); + + Rewards::::mutate(acc, |reward| { + *reward = reward.saturating_add(Self::calc_block_rewards_collator(stake, unclaimed_blocks.into())); + }); + } + + /// Increment the accumulated rewards of a delegator by checking the + /// number of authored blocks by the collator. + /// + /// Updates Rewarded(del) and sets BlocksRewarded(del) to equal + /// BlocksAuthored(col). + fn do_inc_delegator_reward(acc: &T::AccountId, stake: BalanceOf, col: &T::AccountId) { + let count_authored = BlocksAuthored::::get(col); + // We can already mutate thanks to extrinsics being transactional + let count_rewarded = BlocksRewarded::::mutate(acc, |rewarded| { + let old = *rewarded; + *rewarded = count_authored; + old + }); + let unclaimed_blocks = count_authored.saturating_sub(count_rewarded); + + Rewards::::mutate(acc, |reward| { + *reward = reward.saturating_add(Self::calc_block_rewards_delegator(stake, unclaimed_blocks.into())) + }); + } + + /// Calculates the current staking and reward rates for collators and + /// delegators. + /// + /// At least used in Runtime API. + pub fn get_staking_rates() -> runtime_api::StakingRates { + let total_issuance = T::Currency::total_issuance(); + let total_stake = TotalCollatorStake::::get(); + let inflation_config = InflationConfig::::get(); + let collator_staking_rate = Perquintill::from_rational(total_stake.collators, total_issuance); + let delegator_staking_rate = Perquintill::from_rational(total_stake.delegators, total_issuance); + let collator_reward_rate = Perquintill::from_rational( + inflation_config.collator.max_rate.deconstruct(), + collator_staking_rate.deconstruct(), + ) * inflation_config.collator.reward_rate.annual; + let delegator_reward_rate = Perquintill::from_rational( + inflation_config.delegator.max_rate.deconstruct(), + delegator_staking_rate.deconstruct(), + ) * inflation_config.delegator.reward_rate.annual; + + runtime_api::StakingRates { + collator_staking_rate, + collator_reward_rate, + delegator_staking_rate, + delegator_reward_rate, + } + } } impl pallet_authorship::EventHandler for Pallet where T: Config + pallet_authorship::Config + pallet_session::Config, { - /// Compute coinbase rewards for block production and distribute it to - /// collator's (block producer) and its delegators according to their - /// stake and the current InflationInfo. - /// - /// The rewards are split between collators and delegators with - /// different reward rates and maximum staking rates. The latter is - /// required to have at most our targeted inflation because rewards are - /// minted. Rewards are immediately available without any restrictions - /// after minting. - /// - /// If the current staking rate is below the maximum, each collator and - /// delegator receives the corresponding `reward_rate * stake / - /// blocks_per_year`. Since a collator can only author blocks every - /// `MaxSelectedCandidates` many rounds, we multiply the reward with - /// this number. As a result, a collator who has been in the set of - /// selected candidates, eventually receives `reward_rate * stake` after - /// one year. - /// - /// However, if the current staking rate exceeds the max staking rate, - /// the reward will be reduced by `max_rate / current_rate`. E.g., if - /// the current rate is at 50% and the max rate at 40%, the reward is - /// reduced by 20%. - /// - /// # - /// Weight: O(D) where D is the number of delegators of this collator - /// block author bounded by `MaxDelegatorsPerCollator`. - /// - Reads: CandidatePool, TotalCollatorStake, Balance, - /// InflationConfig, MaxSelectedCandidates, Validators, - /// DisabledValidators - /// - Writes: (D + 1) * Balance - /// # + /// Increments the reward counter of the block author by the current + /// number of collators in the session. fn note_author(author: T::AccountId) { - let mut reads = 1u64; - let mut writes = 0u64; // should always include state except if the collator has been forcedly removed // via `force_remove_candidate` in the current or previous round - if let Some(state) = CandidatePool::::get(author.clone()) { - let total_issuance = T::Currency::total_issuance(); - let TotalStake { - collators: total_collators, - delegators: total_delegators, - } = >::get(); - let c_staking_rate = Perquintill::from_rational(total_collators, total_issuance); - let d_staking_rate = Perquintill::from_rational(total_delegators, total_issuance); - let inflation_config = >::get(); + if CandidatePool::::get(&author).is_some() { + // necessary to compensate for a potentially fluctuating number of collators let authors = pallet_session::Pallet::::validators(); - let authors_per_round = >::from(authors.len().saturated_into::()); - - // Reward collator - let amt_due_collator = - inflation_config - .collator - .compute_reward::(state.stake, c_staking_rate, authors_per_round); - Self::do_reward(&author, amt_due_collator); - writes = writes.saturating_add(1u64); - - // Reward delegators - for Stake { owner, amount } in state.delegators { - if amount >= T::MinDelegatorStake::get() { - let due = - inflation_config - .delegator - .compute_reward::(amount, d_staking_rate, authors_per_round); - Self::do_reward(&owner, due); - writes = writes.saturating_add(1u64); - } - } - reads = reads.saturating_add(4); + BlocksAuthored::::mutate(&author, |count| { + *count = count.saturating_add(authors.len().saturated_into::()); + }); } frame_system::Pallet::::register_extra_weight_unchecked( - T::DbWeight::get().reads_writes(reads, writes), + T::DbWeight::get().reads_writes(2, 1), DispatchClass::Mandatory, ); } @@ -2843,7 +2550,7 @@ pub mod pallet { log::debug!( "assembling new collators for new session {} at #{:?}", new_index, - >::block_number(), + frame_system::Pallet::::block_number(), ); frame_system::Pallet::::register_extra_weight_unchecked( @@ -2877,19 +2584,19 @@ pub mod pallet { DispatchClass::Mandatory, ); - let mut round = >::get(); + let mut round = Round::::get(); // always update when a new round should start if round.should_update(now) { true - } else if >::get() { + } else if ForceNewRound::::get() { frame_system::Pallet::::register_extra_weight_unchecked( T::DbWeight::get().writes(2), DispatchClass::Mandatory, ); // check for forced new round - >::put(false); + ForceNewRound::::put(false); round.update(now); - >::put(round); + Round::::put(round); Self::deposit_event(Event::NewRound(round.first, round.current)); true } else { @@ -2900,11 +2607,11 @@ pub mod pallet { impl EstimateNextSessionRotation for Pallet { fn average_session_length() -> T::BlockNumber { - >::get().length + Round::::get().length } fn estimate_current_session_progress(now: T::BlockNumber) -> (Option, Weight) { - let round = >::get(); + let round = Round::::get(); let passed_blocks = now.saturating_sub(round.first); ( @@ -2915,7 +2622,7 @@ pub mod pallet { } fn estimate_next_session_rotation(_now: T::BlockNumber) -> (Option, Weight) { - let round = >::get(); + let round = Round::::get(); ( Some(round.first + round.length), diff --git a/pallets/parachain-staking/src/migration.rs b/pallets/parachain-staking/src/migration.rs new file mode 100644 index 0000000000..11761a32b6 --- /dev/null +++ b/pallets/parachain-staking/src/migration.rs @@ -0,0 +1,137 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2022 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain 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 General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use crate::{ + set::OrderedSet, + types::{BalanceOf, Delegator, Stake}, +}; + +use super::*; +use core::marker::PhantomData; +use frame_support::{ + dispatch::GetStorageVersion, + pallet_prelude::ValueQuery, + parameter_types, storage_alias, + traits::{Get, OnRuntimeUpgrade}, + weights::Weight, + RuntimeDebug, +}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +#[cfg(feature = "try-runtime")] +use sp_runtime::SaturatedConversion; + +// Old types +#[derive(Encode, Decode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] +#[scale_info(skip_type_params(MaxCollatorsPerDelegator))] +#[codec(mel_bound(AccountId: MaxEncodedLen, Balance: MaxEncodedLen))] +pub struct DelegatorOld> { + pub delegations: OrderedSet, MaxCollatorsPerDelegator>, + pub total: Balance, +} +parameter_types! { + const MaxCollatorsPerDelegator: u32 = 1; +} + +/// Number of delegators post migration +#[storage_alias] +type CounterForDelegators = StorageValue, u32, ValueQuery>; + +pub struct StakingPayoutRefactor(PhantomData); +impl OnRuntimeUpgrade for StakingPayoutRefactor { + fn on_runtime_upgrade() -> Weight { + let current = Pallet::::current_storage_version(); + let onchain = Pallet::::on_chain_storage_version(); + + log::info!( + "Running migration with current storage version {:?} / onchain {:?}", + current, + onchain + ); + + if current == 8 && onchain == 7 { + let num_delegators = migrate_delegators::(); + T::DbWeight::get().reads_writes(num_delegators, num_delegators) + } else { + log::info!("StakingPayoutRefactor did not execute. This probably should be removed"); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + use sp_runtime::traits::Zero; + + let current = Pallet::::current_storage_version(); + + assert_eq!( + current, 7, + "ParachainStaking StorageVersion is {:?} instead of 7", + current + ); + assert!( + CounterForDelegators::::get().is_zero(), + "CounterForDelegators already set." + ); + // store number of delegators before migration + CounterForDelegators::::put(DelegatorState::::iter_keys().count().saturated_into::()); + Ok(()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + // new version must be set. + let onchain = Pallet::::on_chain_storage_version(); + + assert_eq!( + onchain, 8, + "ParachainStaking StorageVersion post-migration is not 8, but {:?} instead.", + onchain + ); + + let old_num_delegators: u32 = CounterForDelegators::::get(); + let new_num_delegators: u32 = DelegatorState::::iter_keys().count().saturated_into::(); + assert_eq!( + old_num_delegators, new_num_delegators, + "Number of delegators changed during migration! Before {:?} vs. now {:?}", + old_num_delegators, new_num_delegators + ); + Ok(()) + } +} + +fn migrate_delegators() -> u64 { + let mut counter = 0; + DelegatorState::::translate_values::< + Option, MaxCollatorsPerDelegator>>, + _, + >(|maybe_old| { + counter += 1; + maybe_old + .map(|old| { + old.delegations.get(0).map(|stake| Delegator { + amount: old.total, + owner: stake.owner.clone(), + }) + }) + .unwrap_or(None) + }); + + counter +} diff --git a/pallets/parachain-staking/src/mock.rs b/pallets/parachain-staking/src/mock.rs index 8d4b1102a3..1a5281dbd1 100644 --- a/pallets/parachain-staking/src/mock.rs +++ b/pallets/parachain-staking/src/mock.rs @@ -22,7 +22,7 @@ use super::*; use crate::{self as stake, types::NegativeImbalanceOf}; use frame_support::{ - construct_runtime, parameter_types, + assert_ok, construct_runtime, parameter_types, traits::{Currency, GenesisBuild, OnFinalize, OnInitialize, OnUnbalanced}, }; use pallet_authorship::EventHandler; @@ -132,15 +132,13 @@ parameter_types! { pub const ExitQueueDelay: u32 = 2; pub const DefaultBlocksPerRound: BlockNumber = BLOCKS_PER_ROUND; pub const MinCollators: u32 = 2; + pub const MaxDelegationsPerRound: u32 = 2; #[derive(Debug, Eq, PartialEq)] pub const MaxDelegatorsPerCollator: u32 = 4; - #[derive(Debug, Eq, PartialEq)] - pub const MaxCollatorsPerDelegator: u32 = 4; pub const MinCollatorStake: Balance = 10; #[derive(Debug, Eq, PartialEq)] pub const MaxCollatorCandidates: u32 = 10; pub const MinDelegatorStake: Balance = 5; - pub const MinDelegation: Balance = 3; pub const MaxUnstakeRequests: u32 = 6; pub const NetworkRewardRate: Perquintill = Perquintill::from_percent(10); pub const NetworkRewardStart: BlockNumber = 5 * 5 * 60 * 24 * 36525 / 100; @@ -164,14 +162,12 @@ impl Config for Test { type ExitQueueDelay = ExitQueueDelay; type MinCollators = MinCollators; type MinRequiredCollators = MinCollators; - type MaxDelegationsPerRound = MaxDelegatorsPerCollator; + type MaxDelegationsPerRound = MaxDelegationsPerRound; type MaxDelegatorsPerCollator = MaxDelegatorsPerCollator; - type MaxCollatorsPerDelegator = MaxCollatorsPerDelegator; type MinCollatorStake = MinCollatorStake; type MinCollatorCandidateStake = MinCollatorStake; type MaxTopCandidates = MaxCollatorCandidates; type MinDelegatorStake = MinDelegatorStake; - type MinDelegation = MinDelegation; type MaxUnstakeRequests = MaxUnstakeRequests; type NetworkRewardRate = NetworkRewardRate; type NetworkRewardStart = NetworkRewardStart; @@ -357,6 +353,15 @@ pub(crate) fn almost_equal(left: Balance, right: Balance, precision: Perbill) -> left.max(right) - left.min(right) <= err } +/// Incrementelly traverses from the current block to the provided one and +/// potentially sets block authors. +/// +/// If for a block `i` the corresponding index of the authors input is set, this +/// account is regarded to be the block author and thus gets noted. +/// +/// NOTE: At most, this updates the RewardCount of the block author but does not +/// increment rewards or claim them. Please use `roll_to_claim_rewards` in that +/// case. pub(crate) fn roll_to(n: BlockNumber, authors: Vec>) { while System::block_number() < n { if let Some(Some(author)) = authors.get((System::block_number()) as usize) { @@ -368,6 +373,40 @@ pub(crate) fn roll_to(n: BlockNumber, authors: Vec>) { } } +#[allow(unused_must_use)] +/// Incrementelly traverses from the current block to the provided one and +/// potentially sets block authors. +/// +/// If existent, rewards of the block author and their delegators are +/// incremented and claimed. +/// +/// If for a block `i` the corresponding index of the authors input is set, this +/// account is regarded to be the block author and thus gets noted. +pub(crate) fn roll_to_claim_rewards(n: BlockNumber, authors: Vec>) { + while System::block_number() < n { + if let Some(Some(author)) = authors.get((System::block_number()) as usize) { + StakePallet::note_author(*author); + // author has to increment rewards before claiming + assert_ok!(StakePallet::increment_collator_rewards(Origin::signed(*author))); + // author claims rewards + assert_ok!(StakePallet::claim_rewards(Origin::signed(*author))); + + // claim rewards for delegators + let col_state = StakePallet::candidate_pool(author).expect("Block author must be candidate"); + for delegation in col_state.delegators { + // delegator has to increment rewards before claiming + StakePallet::increment_delegator_rewards(Origin::signed(delegation.owner)); + // NOTE: cannot use assert_ok! as we sometimes expect zero rewards for + // delegators such that the claiming would throw + StakePallet::claim_rewards(Origin::signed(delegation.owner)); + } + } + >::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + >::on_initialize(System::block_number()); + } +} + pub(crate) fn last_event() -> Event { System::events().pop().expect("Event expected").event } diff --git a/pallets/parachain-staking/src/runtime_api.rs b/pallets/parachain-staking/src/runtime_api.rs new file mode 100644 index 0000000000..26ced2a6b5 --- /dev/null +++ b/pallets/parachain-staking/src/runtime_api.rs @@ -0,0 +1,41 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2022 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain 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 General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use frame_support::dispatch::fmt::Debug; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::Perquintill; + +#[derive(Decode, Encode, TypeInfo, MaxEncodedLen, PartialEq, Eq, Debug)] +pub struct StakingRates { + pub collator_staking_rate: Perquintill, + pub collator_reward_rate: Perquintill, + pub delegator_staking_rate: Perquintill, + pub delegator_reward_rate: Perquintill, +} + +sp_api::decl_runtime_apis! { + pub trait ParachainStakingApi + where + AccountId: Eq + PartialEq + Debug + Encode + Decode + Clone, + Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq + { + fn get_unclaimed_staking_rewards(account: &AccountId) -> Balance; + fn get_staking_rates() -> StakingRates; + } +} diff --git a/pallets/parachain-staking/src/tests.rs b/pallets/parachain-staking/src/tests.rs index 20e651a4e4..a08805d1a4 100644 --- a/pallets/parachain-staking/src/tests.rs +++ b/pallets/parachain-staking/src/tests.rs @@ -24,15 +24,18 @@ use frame_support::{ assert_noop, assert_ok, storage::bounded_btree_map::BoundedBTreeMap, traits::EstimateNextSessionRotation, BoundedVec, }; +use pallet_authorship::EventHandler; use pallet_balances::{BalanceLock, Error as BalancesError, Reasons}; use pallet_session::{SessionManager, ShouldEndSession}; use sp_runtime::{traits::Zero, Perbill, Permill, Perquintill, SaturatedConversion}; use crate::{ mock::{ - almost_equal, events, last_event, roll_to, AccountId, Balance, Balances, BlockNumber, Event as MetaEvent, - ExtBuilder, Origin, Session, StakePallet, System, Test, BLOCKS_PER_ROUND, DECIMALS, TREASURY_ACC, + almost_equal, events, last_event, roll_to, roll_to_claim_rewards, AccountId, Balance, Balances, BlockNumber, + Event as MetaEvent, ExtBuilder, Origin, Session, StakePallet, System, Test, BLOCKS_PER_ROUND, DECIMALS, + TREASURY_ACC, }, + runtime_api::StakingRates, set::OrderedSet, types::{ BalanceOf, Candidate, CandidateStatus, DelegationCounter, Delegator, RoundInfo, Stake, StakeOf, TotalStake, @@ -347,6 +350,7 @@ fn collator_exit_executes_after_delay() { (7, 100), (8, 9), (9, 4), + (10, 10), ]) .with_collators(vec![(1, 500), (2, 200), (7, 100)]) .with_delegators(vec![(3, 1, 100), (4, 1, 100), (5, 2, 100), (6, 2, 100)]) @@ -380,7 +384,7 @@ fn collator_exit_executes_after_delay() { // Still three, candidate didn't leave yet assert_eq!(CandidatePool::::count(), 3); assert_noop!( - StakePallet::delegate_another_candidate(Origin::signed(3), 2, 10), + StakePallet::join_delegators(Origin::signed(10), 2, 10), Error::::CannotDelegateIfLeaving ); assert_eq!(StakePallet::selected_candidates().into_inner(), vec![1, 7]); @@ -711,47 +715,19 @@ fn execute_leave_candidates_with_delay() { } assert_eq!( StakePallet::delegator_state(11), - Some( - Delegator::::MaxCollatorsPerDelegator> { - delegations: OrderedSet::from( - vec![StakeOf:: { owner: 1, amount: 110 }].try_into().unwrap() - ), - total: 110 - } - ) + Some(Delegator:: { owner: 1, amount: 110 }) ); assert_eq!( StakePallet::delegator_state(12), - Some( - Delegator::::MaxCollatorsPerDelegator> { - delegations: OrderedSet::from( - vec![StakeOf:: { owner: 1, amount: 120 }].try_into().unwrap() - ), - total: 120 - } - ) + Some(Delegator:: { owner: 1, amount: 120 }) ); assert_eq!( StakePallet::delegator_state(13), - Some( - Delegator::::MaxCollatorsPerDelegator> { - delegations: OrderedSet::from( - vec![StakeOf:: { owner: 2, amount: 130 }].try_into().unwrap() - ), - total: 130 - } - ) + Some(Delegator:: { owner: 2, amount: 130 }) ); assert_eq!( StakePallet::delegator_state(14), - Some( - Delegator::::MaxCollatorsPerDelegator> { - delegations: OrderedSet::from( - vec![StakeOf:: { owner: 2, amount: 140 }].try_into().unwrap() - ), - total: 140 - } - ) + Some(Delegator:: { owner: 2, amount: 140 }) ); for delegator in 11u64..=14u64 { assert!(StakePallet::is_delegator(&delegator)); @@ -828,47 +804,19 @@ fn execute_leave_candidates_with_delay() { } assert_eq!( StakePallet::delegator_state(11), - Some( - Delegator::::MaxCollatorsPerDelegator> { - delegations: OrderedSet::from( - vec![StakeOf:: { owner: 1, amount: 110 }].try_into().unwrap() - ), - total: 110 - } - ) + Some(Delegator:: { owner: 1, amount: 110 }) ); assert_eq!( StakePallet::delegator_state(12), - Some( - Delegator::::MaxCollatorsPerDelegator> { - delegations: OrderedSet::from( - vec![StakeOf:: { owner: 1, amount: 120 }].try_into().unwrap() - ), - total: 120 - } - ) + Some(Delegator:: { owner: 1, amount: 120 }) ); assert_eq!( StakePallet::delegator_state(13), - Some( - Delegator::::MaxCollatorsPerDelegator> { - delegations: OrderedSet::from( - vec![StakeOf:: { owner: 2, amount: 130 }].try_into().unwrap() - ), - total: 130 - } - ) + Some(Delegator:: { owner: 2, amount: 130 }) ); assert_eq!( StakePallet::delegator_state(14), - Some( - Delegator::::MaxCollatorsPerDelegator> { - delegations: OrderedSet::from( - vec![StakeOf:: { owner: 2, amount: 140 }].try_into().unwrap() - ), - total: 140 - } - ) + Some(Delegator:: { owner: 2, amount: 140 }) ); for delegator in 11u64..=14u64 { assert!(StakePallet::is_delegator(&delegator)); @@ -911,6 +859,7 @@ fn execute_leave_candidates_with_delay() { }); } +// FIXME: Re-enable or potentially remove entirely #[test] fn multiple_delegations() { ExtBuilder::default() @@ -927,6 +876,14 @@ fn multiple_delegations() { (10, 100), (11, 100), (12, 100), + // new + (13, 100), + (14, 100), + (15, 100), + (16, 100), + (17, 100), + (18, 100), + (99, 1), ]) .with_collators(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 10)]) .with_delegators(vec![(6, 1, 10), (7, 1, 10), (8, 2, 10), (9, 2, 10), (10, 1, 10)]) @@ -934,58 +891,65 @@ fn multiple_delegations() { .build() .execute_with(|| { assert_ok!(StakePallet::set_max_selected_candidates(Origin::root(), 5)); - roll_to(8, vec![]); + roll_to( + 8, + vec![Some(1), Some(2), Some(3), Some(4), Some(5), Some(1), Some(2), Some(3)], + ); // chooses top MaxSelectedCandidates (5), in order assert_eq!(StakePallet::selected_candidates().into_inner(), vec![1, 2, 3, 4, 5]); let mut expected = vec![Event::MaxSelectedCandidatesSet(2, 5), Event::NewRound(5, 1)]; assert_eq!(events(), expected); assert_noop!( - StakePallet::delegate_another_candidate(Origin::signed(6), 1, 10), - Error::::AlreadyDelegatedCollator, - ); - assert_noop!( - StakePallet::delegate_another_candidate(Origin::signed(6), 2, 2), + StakePallet::join_delegators(Origin::signed(13), 2, 2), Error::::DelegationBelowMin, ); - assert_ok!(StakePallet::delegate_another_candidate(Origin::signed(6), 2, 10)); - assert_ok!(StakePallet::delegate_another_candidate(Origin::signed(6), 4, 10)); - assert_ok!(StakePallet::delegate_another_candidate(Origin::signed(6), 3, 10)); + assert_ok!(StakePallet::join_delegators(Origin::signed(13), 2, 10)); + assert_ok!(StakePallet::join_delegators(Origin::signed(14), 4, 10)); + assert_ok!(StakePallet::join_delegators(Origin::signed(15), 3, 10)); assert_eq!(StakePallet::selected_candidates().into_inner(), vec![1, 2, 4, 3, 5]); assert_noop!( - StakePallet::delegate_another_candidate(Origin::signed(6), 5, 10), - Error::::MaxCollatorsPerDelegatorExceeded, + StakePallet::join_delegators(Origin::signed(6), 5, 10), + Error::::AlreadyDelegating, ); - roll_to(16, vec![]); + roll_to( + 16, + vec![Some(1), Some(2), Some(3), Some(4), Some(5), Some(1), Some(2), Some(3)], + ); assert_eq!(StakePallet::selected_candidates().into_inner(), vec![1, 2, 4, 3, 5]); let mut new = vec![ - Event::Delegation(6, 10, 2, 50), - Event::Delegation(6, 10, 4, 30), - Event::Delegation(6, 10, 3, 30), + Event::Delegation(13, 10, 2, 50), + Event::Delegation(14, 10, 4, 30), + Event::Delegation(15, 10, 3, 30), Event::NewRound(10, 2), Event::NewRound(15, 3), ]; expected.append(&mut new); assert_eq!(events(), expected); - roll_to(21, vec![]); - assert_ok!(StakePallet::delegate_another_candidate(Origin::signed(7), 2, 80)); + roll_to(21, vec![Some(1), Some(2), Some(3), Some(4), Some(5)]); + assert_ok!(StakePallet::join_delegators(Origin::signed(16), 2, 80)); assert_noop!( - StakePallet::delegate_another_candidate(Origin::signed(7), 3, 11), + StakePallet::join_delegators(Origin::signed(99), 3, 11), BalancesError::::InsufficientBalance ); assert_noop!( - StakePallet::delegate_another_candidate(Origin::signed(10), 2, 10), + StakePallet::join_delegators(Origin::signed(17), 2, 10), Error::::TooManyDelegators ); - // kick 6 - assert!(StakePallet::unstaking(6).is_empty()); - assert_ok!(StakePallet::delegate_another_candidate(Origin::signed(10), 2, 11)); - assert!(StakePallet::delegator_state(6).is_some()); - assert_eq!(StakePallet::unstaking(6).get(&23), Some(&10u128)); - // kick 9 + // kick 13 by staking 1 more (11 > 10) + assert!(StakePallet::unstaking(13).is_empty()); + assert_ok!(StakePallet::join_delegators(Origin::signed(17), 2, 11)); + assert!(StakePallet::delegator_state(13).is_none()); + assert_eq!(StakePallet::unstaking(13).get(&23), Some(&10u128)); + // kick 9 by staking 1 more (11 > 10) assert!(StakePallet::unstaking(9).is_empty()); + assert!(StakePallet::rewards(9).is_zero()); assert_ok!(StakePallet::join_delegators(Origin::signed(11), 2, 11)); + // 11 should be initiated with the same rewarded counter as the authored counter + // by their collator 2 + assert_eq!(StakePallet::blocks_rewarded(2), StakePallet::blocks_authored(11)); + assert!(StakePallet::delegator_state(9).is_none()); assert_eq!(StakePallet::unstaking(9).get(&23), Some(&10u128)); assert!(!StakePallet::candidate_pool(2) @@ -993,13 +957,13 @@ fn multiple_delegations() { .delegators .contains(&StakeOf:: { owner: 9, amount: 10 })); - roll_to(26, vec![]); + roll_to(26, vec![Some(1), Some(2), Some(3), Some(4), Some(5)]); assert_eq!(StakePallet::selected_candidates().into_inner(), vec![2, 1, 4, 3, 5]); let mut new2 = vec![ Event::NewRound(20, 4), - Event::Delegation(7, 80, 2, 130), - Event::DelegationReplaced(10, 11, 6, 10, 2, 131), - Event::Delegation(10, 11, 2, 131), + Event::Delegation(16, 80, 2, 130), + Event::DelegationReplaced(17, 11, 13, 10, 2, 131), + Event::Delegation(17, 11, 2, 131), Event::DelegationReplaced(11, 11, 9, 10, 2, 132), Event::Delegation(11, 11, 2, 132), Event::NewRound(25, 5), @@ -1013,7 +977,7 @@ fn multiple_delegations() { MetaEvent::StakePallet(Event::CollatorScheduledExit(5, 2, 7)) ); - roll_to(31, vec![]); + roll_to(31, vec![Some(1), Some(2), Some(3), Some(4), Some(5)]); let mut new3 = vec![ Event::LeftTopCandidates(2), Event::CollatorScheduledExit(5, 2, 7), @@ -1023,53 +987,53 @@ fn multiple_delegations() { assert_eq!(events(), expected); // test join_delegator errors - assert_ok!(StakePallet::delegate_another_candidate(Origin::signed(8), 1, 10)); + assert_ok!(StakePallet::join_delegators(Origin::signed(18), 1, 10)); assert_noop!( StakePallet::join_delegators(Origin::signed(12), 1, 10), Error::::TooManyDelegators ); - assert_noop!( - StakePallet::delegate_another_candidate(Origin::signed(12), 1, 10), - Error::::NotYetDelegating - ); assert_ok!(StakePallet::join_delegators(Origin::signed(12), 1, 11)); // verify that delegations are removed after collator leaves, not before - assert_eq!(StakePallet::delegator_state(7).unwrap().total, 90); - assert_eq!(StakePallet::delegator_state(7).unwrap().delegations.len(), 2usize); - assert_eq!(StakePallet::delegator_state(11).unwrap().total, 11); - assert_eq!(StakePallet::delegator_state(11).unwrap().delegations.len(), 1usize); - // 6 already has 10 in - assert_eq!(Balances::usable_balance(&7), 10); - assert_eq!(Balances::usable_balance(&11), 89); - assert_eq!(Balances::free_balance(&7), 100); - assert_eq!(Balances::free_balance(&11), 100); - - roll_to(35, vec![]); + assert!(StakePallet::candidate_pool(2) + .unwrap() + .delegators + .contains(&StakeOf:: { owner: 8, amount: 10 })); + assert!(StakePallet::candidate_pool(2) + .unwrap() + .delegators + .contains(&StakeOf:: { owner: 17, amount: 11 })); + assert_eq!(StakePallet::delegator_state(8).unwrap().amount, 10); + assert_eq!(StakePallet::delegator_state(17).unwrap().amount, 11); + assert_eq!(Balances::usable_balance(&8), 90); + assert_eq!(Balances::usable_balance(&17), 89); + assert_eq!(Balances::free_balance(&8), 100); + assert_eq!(Balances::free_balance(&17), 100); + + roll_to(35, vec![Some(1), Some(2), Some(3), Some(4)]); assert_ok!(StakePallet::execute_leave_candidates(Origin::signed(2), 2)); - let mut unbonding_7: BoundedBTreeMap, ::MaxUnstakeRequests> = + let mut unbonding_8: BoundedBTreeMap, ::MaxUnstakeRequests> = BoundedBTreeMap::new(); - assert_ok!(unbonding_7.try_insert(35u64 + ::StakeDuration::get() as u64, 80)); - assert_eq!(StakePallet::unstaking(7), unbonding_7); - let mut unbonding_11: BoundedBTreeMap, ::MaxUnstakeRequests> = + assert_ok!(unbonding_8.try_insert(35u64 + ::StakeDuration::get() as u64, 10)); + assert_eq!(StakePallet::unstaking(8), unbonding_8); + let mut unbonding_17: BoundedBTreeMap, ::MaxUnstakeRequests> = BoundedBTreeMap::new(); - assert_ok!(unbonding_11.try_insert(35u64 + ::StakeDuration::get() as u64, 11)); - assert_eq!(StakePallet::unstaking(11), unbonding_11); - - roll_to(37, vec![]); - assert_eq!(StakePallet::delegator_state(7).unwrap().total, 10); - assert!(StakePallet::delegator_state(11).is_none()); - assert_eq!(StakePallet::delegator_state(7).unwrap().delegations.len(), 1usize); - assert_ok!(StakePallet::unlock_unstaked(Origin::signed(7), 7)); - assert_ok!(StakePallet::unlock_unstaked(Origin::signed(11), 11)); + assert_ok!(unbonding_17.try_insert(35u64 + ::StakeDuration::get() as u64, 11)); + assert_eq!(StakePallet::unstaking(17), unbonding_17); + + roll_to(37, vec![Some(1), Some(2)]); + assert!(StakePallet::delegator_state(8).is_none()); + assert!(StakePallet::delegator_state(17).is_none()); + assert_ok!(StakePallet::unlock_unstaked(Origin::signed(8), 8)); + assert_ok!(StakePallet::unlock_unstaked(Origin::signed(17), 17)); assert_noop!( StakePallet::unlock_unstaked(Origin::signed(12), 12), Error::::UnstakingIsEmpty ); - assert_eq!(Balances::usable_balance(&11), 100); - assert_eq!(Balances::usable_balance(&7), 90); - assert_eq!(Balances::free_balance(&11), 100); - assert_eq!(Balances::free_balance(&7), 100); + assert_eq!(Balances::usable_balance(&17), 100); + assert_eq!(Balances::usable_balance(&8), 100); + assert_eq!(Balances::free_balance(&17), 100); + assert_eq!(Balances::free_balance(&8), 100); }); } @@ -1122,13 +1086,13 @@ fn should_update_total_stake() { ); old_stake = StakePallet::total_collator_stake(); - assert_ok!(StakePallet::delegator_stake_more(Origin::signed(7), 1, 50)); + assert_ok!(StakePallet::delegator_stake_more(Origin::signed(7), 50)); assert_noop!( - StakePallet::delegator_stake_more(Origin::signed(7), 1, 0), + StakePallet::delegator_stake_more(Origin::signed(7), 0), Error::::ValStakeZero ); assert_noop!( - StakePallet::delegator_stake_less(Origin::signed(7), 1, 0), + StakePallet::delegator_stake_less(Origin::signed(7), 0), Error::::ValStakeZero ); assert_eq!( @@ -1140,7 +1104,7 @@ fn should_update_total_stake() { ); old_stake = StakePallet::total_collator_stake(); - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(7), 1, 50)); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(7), 50)); assert_eq!( StakePallet::total_collator_stake(), TotalStake { @@ -1160,29 +1124,19 @@ fn should_update_total_stake() { ); old_stake = StakePallet::total_collator_stake(); - assert_ok!(StakePallet::delegate_another_candidate(Origin::signed(11), 2, 150)); - assert_eq!( - StakePallet::total_collator_stake(), - TotalStake { - delegators: old_stake.delegators + 150, - ..old_stake - } - ); - - old_stake = StakePallet::total_collator_stake(); - assert_eq!(StakePallet::delegator_state(11).unwrap().total, 350); + assert_eq!(StakePallet::delegator_state(11).unwrap().amount, 200); assert_ok!(StakePallet::leave_delegators(Origin::signed(11))); assert_eq!( StakePallet::total_collator_stake(), TotalStake { - delegators: old_stake.delegators - 350, + delegators: old_stake.delegators - 200, ..old_stake } ); let old_stake = StakePallet::total_collator_stake(); - assert_eq!(StakePallet::delegator_state(8).unwrap().total, 10); - assert_ok!(StakePallet::revoke_delegation(Origin::signed(8), 2)); + assert_eq!(StakePallet::delegator_state(8).unwrap().amount, 10); + assert_ok!(StakePallet::leave_delegators(Origin::signed(8))); assert_eq!( StakePallet::total_collator_stake(), TotalStake { @@ -1249,10 +1203,6 @@ fn collators_bond() { StakePallet::candidate_stake_less(Origin::signed(6), 50), Error::::CandidateNotFound ); - assert_noop!( - StakePallet::delegate_another_candidate(Origin::signed(6), 50, 10), - Error::::CandidateNotFound - ); assert_ok!(StakePallet::candidate_stake_more(Origin::signed(1), 50)); assert_noop!( StakePallet::candidate_stake_more(Origin::signed(1), 40), @@ -1337,49 +1287,29 @@ fn delegators_bond() { Error::::AlreadyDelegating ); assert_noop!( - StakePallet::delegator_stake_more(Origin::signed(1), 2, 50), + StakePallet::delegator_stake_more(Origin::signed(1), 50), Error::::DelegatorNotFound ); assert_noop!( - StakePallet::delegator_stake_less(Origin::signed(1), 2, 50), + StakePallet::delegator_stake_less(Origin::signed(1), 50), Error::::DelegatorNotFound ); assert_noop!( - StakePallet::delegator_stake_more(Origin::signed(6), 2, 50), - Error::::DelegationNotFound - ); - assert_noop!( - StakePallet::delegator_stake_more(Origin::signed(7), 6, 50), - Error::::CandidateNotFound - ); - assert_noop!( - StakePallet::delegator_stake_less(Origin::signed(7), 6, 50), - Error::::CandidateNotFound - ); - assert_noop!( - StakePallet::delegator_stake_less(Origin::signed(6), 1, 11), + StakePallet::delegator_stake_less(Origin::signed(6), 11), Error::::Underflow ); assert_noop!( - StakePallet::delegator_stake_less(Origin::signed(6), 1, 8), + StakePallet::delegator_stake_less(Origin::signed(6), 8), Error::::DelegationBelowMin ); + assert_ok!(StakePallet::delegator_stake_more(Origin::signed(6), 10)); assert_noop!( - StakePallet::delegator_stake_less(Origin::signed(6), 1, 6), - Error::::NomStakeBelowMin - ); - assert_ok!(StakePallet::delegator_stake_more(Origin::signed(6), 1, 10)); - assert_noop!( - StakePallet::delegator_stake_less(Origin::signed(6), 2, 5), - Error::::DelegationNotFound - ); - assert_noop!( - StakePallet::delegator_stake_more(Origin::signed(6), 1, 81), + StakePallet::delegator_stake_more(Origin::signed(6), 81), BalancesError::::InsufficientBalance ); assert_noop!( StakePallet::join_delegators(Origin::signed(10), 1, 4), - Error::::NomStakeBelowMin + Error::::DelegationBelowMin ); roll_to(9, vec![]); @@ -1399,55 +1329,27 @@ fn delegators_bond() { } #[test] -fn revoke_delegation_or_leave_delegators() { +fn should_leave_delegators() { ExtBuilder::default() - .with_balances(vec![ - (1, 100), - (2, 100), - (3, 100), - (4, 100), - (5, 100), - (6, 100), - (7, 100), - (8, 100), - (9, 100), - (10, 100), - ]) - .with_collators(vec![(1, 20), (2, 20), (3, 20), (4, 20), (5, 10)]) - .with_delegators(vec![(6, 1, 10), (7, 1, 10), (8, 2, 10), (9, 2, 10), (10, 1, 10)]) - .set_blocks_per_round(5) + .with_balances(vec![(1, 100), (2, 100)]) + .with_collators(vec![(1, 100)]) + .with_delegators(vec![(2, 1, 100)]) .build() .execute_with(|| { - roll_to(4, vec![]); + assert_ok!(StakePallet::leave_delegators(Origin::signed(2))); + assert!(StakePallet::delegator_state(2).is_none()); + assert!(!StakePallet::candidate_pool(1) + .unwrap() + .delegators + .contains(&StakeOf:: { owner: 2, amount: 100 })); assert_noop!( - StakePallet::revoke_delegation(Origin::signed(1), 2), + StakePallet::leave_delegators(Origin::signed(2)), Error::::DelegatorNotFound ); - assert_noop!( - StakePallet::revoke_delegation(Origin::signed(6), 2), - Error::::DelegationNotFound - ); assert_noop!( StakePallet::leave_delegators(Origin::signed(1)), Error::::DelegatorNotFound ); - assert_ok!(StakePallet::delegate_another_candidate(Origin::signed(6), 2, 3)); - assert_ok!(StakePallet::delegate_another_candidate(Origin::signed(6), 3, 3)); - assert_ok!(StakePallet::revoke_delegation(Origin::signed(6), 1)); - // cannot revoke delegation because would leave remaining total below - // MinDelegatorStake - assert_noop!( - StakePallet::revoke_delegation(Origin::signed(6), 2), - Error::::NomStakeBelowMin - ); - assert_noop!( - StakePallet::revoke_delegation(Origin::signed(6), 3), - Error::::NomStakeBelowMin - ); - // can revoke both remaining by calling leave delegators - assert_ok!(StakePallet::leave_delegators(Origin::signed(6))); - // this leads to 8 leaving set of delegators - assert_ok!(StakePallet::revoke_delegation(Origin::signed(8), 2)); }); } @@ -1628,7 +1530,7 @@ fn coinbase_rewards_few_blocks_detailed_check() { assert_eq!(Balances::usable_balance(&5), user_5); // 1 is block author for 1st block - roll_to(2, authors.clone()); + roll_to_claim_rewards(2, authors.clone()); assert_eq!(Balances::usable_balance(&1), user_1 + c_rewards); assert_eq!(Balances::usable_balance(&2), user_2); assert_eq!(Balances::usable_balance(&3), user_3 + d_rewards / 2); @@ -1636,7 +1538,7 @@ fn coinbase_rewards_few_blocks_detailed_check() { assert_eq!(Balances::usable_balance(&5), user_5); // 1 is block author for 2nd block - roll_to(3, authors.clone()); + roll_to_claim_rewards(3, authors.clone()); assert_eq!(Balances::usable_balance(&1), user_1 + 2 * c_rewards); assert_eq!(Balances::usable_balance(&2), user_2); assert_eq!(Balances::usable_balance(&3), user_3 + d_rewards); @@ -1644,7 +1546,7 @@ fn coinbase_rewards_few_blocks_detailed_check() { assert_eq!(Balances::usable_balance(&5), user_5); // 1 is block author for 3rd block - roll_to(4, authors.clone()); + roll_to_claim_rewards(4, authors.clone()); assert_eq!(Balances::usable_balance(&1), user_1 + 3 * c_rewards); assert_eq!(Balances::usable_balance(&2), user_2); assert_eq!(Balances::usable_balance(&3), user_3 + d_rewards / 2 * 3); @@ -1652,16 +1554,16 @@ fn coinbase_rewards_few_blocks_detailed_check() { assert_eq!(Balances::usable_balance(&5), user_5); // 2 is block author for 4th block - roll_to(5, authors.clone()); + roll_to_claim_rewards(5, authors.clone()); assert_eq!(Balances::usable_balance(&1), user_1 + 3 * c_rewards); assert_eq!(Balances::usable_balance(&2), user_2 + c_rewards); assert_eq!(Balances::usable_balance(&3), user_3 + d_rewards / 2 * 3); assert_eq!(Balances::usable_balance(&4), user_4 + d_rewards / 4 * 3); assert_eq!(Balances::usable_balance(&5), user_5 + d_rewards / 4); - assert_ok!(StakePallet::revoke_delegation(Origin::signed(5), 2)); + assert_ok!(StakePallet::leave_delegators(Origin::signed(5))); // 2 is block author for 5th block - roll_to(6, authors); + roll_to_claim_rewards(6, authors); assert_eq!(Balances::usable_balance(&1), user_1 + 3 * c_rewards); assert_eq!(Balances::usable_balance(&2), user_2 + 2 * c_rewards); assert_eq!(Balances::usable_balance(&3), user_3 + d_rewards / 2 * 3); @@ -1681,11 +1583,11 @@ fn delegator_should_not_receive_rewards_after_revoking() { .with_inflation(10, 15, 40, 15, 5) .build() .execute_with(|| { - assert_ok!(StakePallet::revoke_delegation(Origin::signed(2), 1)); + assert_ok!(StakePallet::leave_delegators(Origin::signed(2))); let authors: Vec> = (1u64..100u64).map(|_| Some(1u64)).collect(); assert_eq!(Balances::usable_balance(&1), Balance::zero()); assert_eq!(Balances::usable_balance(&2), Balance::zero()); - roll_to(100, authors); + roll_to_claim_rewards(100, authors); assert!(Balances::usable_balance(&1) > Balance::zero()); assert_ok!(StakePallet::unlock_unstaked(Origin::signed(2), 2)); assert_eq!(Balances::usable_balance(&2), 10_000_000 * DECIMALS); @@ -1702,12 +1604,12 @@ fn delegator_should_not_receive_rewards_after_revoking() { .with_inflation(10, 15, 40, 15, 5) .build() .execute_with(|| { - assert_ok!(StakePallet::revoke_delegation(Origin::signed(3), 1)); + assert_ok!(StakePallet::leave_delegators(Origin::signed(3))); let authors: Vec> = (1u64..100u64).map(|_| Some(1u64)).collect(); assert_eq!(Balances::usable_balance(&1), Balance::zero()); assert_eq!(Balances::usable_balance(&2), Balance::zero()); assert_eq!(Balances::usable_balance(&3), Balance::zero()); - roll_to(100, authors); + roll_to_claim_rewards(100, authors); assert!(Balances::usable_balance(&1) > Balance::zero()); assert!(Balances::usable_balance(&2) > Balance::zero()); assert_ok!(StakePallet::unlock_unstaked(Origin::signed(3), 3)); @@ -1740,7 +1642,7 @@ fn coinbase_rewards_many_blocks_simple_check() { let end_block: BlockNumber = num_of_years * Test::BLOCKS_PER_YEAR as BlockNumber; // set round robin authoring let authors: Vec> = (0u64..=end_block).map(|i| Some(i % 2 + 1)).collect(); - roll_to(end_block, authors); + roll_to_claim_rewards(end_block, authors); let rewards_1 = Balances::free_balance(&1).saturating_sub(40_000_000 * DECIMALS); let rewards_2 = Balances::free_balance(&2).saturating_sub(40_000_000 * DECIMALS); @@ -1847,7 +1749,7 @@ fn should_not_reward_delegators_below_min_stake() { assert_eq!(Balances::usable_balance(&4), 5); // should only reward 1 - roll_to(4, authors); + roll_to_claim_rewards(4, authors); assert!(Balances::usable_balance(&1) > Balance::zero()); assert_eq!(Balances::usable_balance(&4), 5); assert_eq!(Balances::usable_balance(&2), Balance::zero()); @@ -2255,7 +2157,7 @@ fn unlock_unstaked() { .with_delegators(vec![(2, 1, 100)]) .build() .execute_with(|| { - assert_ok!(StakePallet::revoke_delegation(Origin::signed(2), 1)); + assert_ok!(StakePallet::leave_delegators(Origin::signed(2))); let mut unstaking: BoundedBTreeMap, ::MaxUnstakeRequests> = BoundedBTreeMap::new(); assert_ok!(unstaking.try_insert(3, 100)); @@ -2274,7 +2176,7 @@ fn unlock_unstaked() { // join delegators and revoke again --> consume unstaking at block 3 roll_to(2, vec![]); assert_ok!(StakePallet::join_delegators(Origin::signed(2), 1, 100)); - assert_ok!(StakePallet::revoke_delegation(Origin::signed(2), 1)); + assert_ok!(StakePallet::leave_delegators(Origin::signed(2))); unstaking.remove(&3); assert_ok!(unstaking.try_insert(4, 100)); assert_eq!(StakePallet::unstaking(2), unstaking); @@ -2313,7 +2215,7 @@ fn unlock_unstaked() { .with_delegators(vec![(2, 1, 10)]) .build() .execute_with(|| { - assert_ok!(StakePallet::revoke_delegation(Origin::signed(2), 1)); + assert_ok!(StakePallet::leave_delegators(Origin::signed(2))); let mut unstaking: BoundedBTreeMap, ::MaxUnstakeRequests> = BoundedBTreeMap::new(); assert_ok!(unstaking.try_insert(3, 10)); @@ -2332,7 +2234,7 @@ fn unlock_unstaked() { // join delegators and revoke again roll_to(2, vec![]); assert_ok!(StakePallet::join_delegators(Origin::signed(2), 1, 100)); - assert_ok!(StakePallet::revoke_delegation(Origin::signed(2), 1)); + assert_ok!(StakePallet::leave_delegators(Origin::signed(2))); unstaking.remove(&3); assert_ok!(unstaking.try_insert(4, 100)); lock.amount = 100; @@ -2373,7 +2275,7 @@ fn unlock_unstaked() { .with_delegators(vec![(2, 1, 100)]) .build() .execute_with(|| { - assert_ok!(StakePallet::revoke_delegation(Origin::signed(2), 1)); + assert_ok!(StakePallet::leave_delegators(Origin::signed(2))); let mut unstaking: BoundedBTreeMap, ::MaxUnstakeRequests> = BoundedBTreeMap::new(); assert_ok!(unstaking.try_insert(3, 100)); @@ -2392,7 +2294,7 @@ fn unlock_unstaked() { // join delegators and revoke again roll_to(2, vec![]); assert_ok!(StakePallet::join_delegators(Origin::signed(2), 1, 10)); - assert_ok!(StakePallet::revoke_delegation(Origin::signed(2), 1)); + assert_ok!(StakePallet::leave_delegators(Origin::signed(2))); assert_ok!(unstaking.try_insert(3, 90)); assert_ok!(unstaking.try_insert(4, 10)); assert_eq!(StakePallet::unstaking(2), unstaking); @@ -2444,12 +2346,12 @@ fn unlock_unstaked() { assert_ok!(StakePallet::candidate_stake_less(Origin::signed(1), 10)); assert_ok!(StakePallet::candidate_stake_less(Origin::signed(1), 10)); assert_ok!(StakePallet::candidate_stake_less(Origin::signed(1), 10),); - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 1, 10)); - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 1, 10)); - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 1, 10)); - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 1, 10)); - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 1, 10)); - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 1, 10),); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 10)); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 10)); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 10)); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 10)); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 10)); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 10),); let mut unstaking: BoundedBTreeMap, ::MaxUnstakeRequests> = BoundedBTreeMap::new(); assert_ok!(unstaking.try_insert(3, 60)); @@ -2472,7 +2374,7 @@ fn unlock_unstaked() { roll_to(2, vec![]); assert_ok!(StakePallet::candidate_stake_less(Origin::signed(1), 10),); - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 1, 10),); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 10),); assert_ok!(unstaking.try_insert(4, 10)); assert_eq!(Balances::locks(1), vec![lock.clone()]); assert_eq!(Balances::locks(2), vec![lock.clone()]); @@ -2488,7 +2390,7 @@ fn unlock_unstaked() { roll_to(3, vec![]); assert_ok!(StakePallet::candidate_stake_less(Origin::signed(1), 10),); - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 1, 10),); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 10),); assert_ok!(unstaking.try_insert(5, 10)); assert_ok!(unstaking.try_insert(5, 10)); assert_eq!(Balances::locks(1), vec![lock.clone()]); @@ -2508,13 +2410,13 @@ fn unlock_unstaked() { // reach MaxUnstakeRequests roll_to(4, vec![]); assert_ok!(StakePallet::candidate_stake_less(Origin::signed(1), 10)); - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 1, 10)); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 10)); roll_to(5, vec![]); assert_ok!(StakePallet::candidate_stake_less(Origin::signed(1), 10)); - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 1, 10)); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 10)); roll_to(6, vec![]); assert_ok!(StakePallet::candidate_stake_less(Origin::signed(1), 10)); - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 1, 10)); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 10)); assert_ok!(unstaking.try_insert(6, 10)); assert_ok!(unstaking.try_insert(7, 10)); assert_ok!(unstaking.try_insert(8, 10)); @@ -2529,7 +2431,7 @@ fn unlock_unstaked() { Error::::NoMoreUnstaking ); assert_noop!( - StakePallet::delegator_stake_less(Origin::signed(2), 1, 10), + StakePallet::delegator_stake_less(Origin::signed(2), 10), Error::::NoMoreUnstaking ); assert_ok!(StakePallet::unlock_unstaked(Origin::signed(1), 1)); @@ -2544,10 +2446,10 @@ fn unlock_unstaked() { assert_eq!(Balances::locks(1), vec![lock.clone()]); assert_eq!(Balances::locks(2), vec![lock.clone()]); assert_ok!(StakePallet::candidate_stake_less(Origin::signed(1), 40)); - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 1, 40)); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(2), 40)); assert_ok!(unstaking.try_insert(9, 40)); assert_ok!(StakePallet::candidate_stake_more(Origin::signed(1), 30)); - assert_ok!(StakePallet::delegator_stake_more(Origin::signed(2), 1, 30)); + assert_ok!(StakePallet::delegator_stake_more(Origin::signed(2), 30)); unstaking.remove(&8); assert_ok!(unstaking.try_insert(9, 20)); assert_eq!(StakePallet::unstaking(1), unstaking); @@ -2608,14 +2510,14 @@ fn kick_delegator_with_full_unstaking() { // Fill unstake requests for block in 1u64..1u64.saturating_add(max_unstake_reqs as u64) { System::set_block_number(block); - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(5), 1, 1)); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(5), 1)); } assert_eq!(StakePallet::unstaking(5).into_inner().len(), max_unstake_reqs); // Additional unstake should fail System::set_block_number(100); assert_noop!( - StakePallet::delegator_stake_less(Origin::signed(5), 1, 1), + StakePallet::delegator_stake_less(Origin::signed(5), 1), Error::::NoMoreUnstaking ); @@ -2683,11 +2585,11 @@ fn candidate_leaves() { Error::::CannotDelegateIfLeaving ); assert_noop!( - StakePallet::delegator_stake_more(Origin::signed(12), 1, 1), + StakePallet::delegator_stake_more(Origin::signed(12), 1), Error::::CannotDelegateIfLeaving ); assert_noop!( - StakePallet::delegator_stake_less(Origin::signed(12), 1, 1), + StakePallet::delegator_stake_less(Origin::signed(12), 1), Error::::CannotDelegateIfLeaving ); assert_noop!( @@ -2806,7 +2708,7 @@ fn adjust_reward_rates() { let authors: Vec> = (0u64..=num_of_years).map(|_| Some(1u64)).collect(); // reward once in first year - roll_to(2, authors.clone()); + roll_to_claim_rewards(2, authors.clone()); let c_rewards_0 = Balances::free_balance(&1).saturating_sub(10_000_000 * DECIMALS); let d_rewards_0 = Balances::free_balance(&2).saturating_sub(90_000_000 * DECIMALS); assert!(!c_rewards_0.is_zero()); @@ -2814,7 +2716,10 @@ fn adjust_reward_rates() { // finish first year System::set_block_number(::BLOCKS_PER_YEAR); - roll_to(::BLOCKS_PER_YEAR + 1, vec![]); + roll_to_claim_rewards(::BLOCKS_PER_YEAR + 1, vec![]); + // reward reduction should not happen automatically anymore + assert_eq!(StakePallet::last_reward_reduction(), 0u64); + assert_ok!(StakePallet::execute_scheduled_reward_change(Origin::signed(1))); assert_eq!(StakePallet::last_reward_reduction(), 1u64); let inflation_1 = InflationInfo::new( ::BLOCKS_PER_YEAR, @@ -2825,7 +2730,7 @@ fn adjust_reward_rates() { ); assert_eq!(StakePallet::inflation_config(), inflation_1); // reward once in 2nd year - roll_to(::BLOCKS_PER_YEAR + 2, authors.clone()); + roll_to_claim_rewards(::BLOCKS_PER_YEAR + 2, authors.clone()); let c_rewards_1 = Balances::free_balance(&1) .saturating_sub(10_000_000 * DECIMALS) .saturating_sub(c_rewards_0); @@ -2842,7 +2747,10 @@ fn adjust_reward_rates() { // finish 2nd year System::set_block_number(2 * ::BLOCKS_PER_YEAR); - roll_to(2 * ::BLOCKS_PER_YEAR + 1, vec![]); + roll_to_claim_rewards(2 * ::BLOCKS_PER_YEAR + 1, vec![]); + // reward reduction should not happen automatically anymore + assert_eq!(StakePallet::last_reward_reduction(), 1u64); + assert_ok!(StakePallet::execute_scheduled_reward_change(Origin::signed(1))); assert_eq!(StakePallet::last_reward_reduction(), 2u64); let inflation_2 = InflationInfo::new( ::BLOCKS_PER_YEAR, @@ -2853,16 +2761,17 @@ fn adjust_reward_rates() { ); assert_eq!(StakePallet::inflation_config(), inflation_2); // reward once in 3rd year - roll_to(2 * ::BLOCKS_PER_YEAR + 2, authors); + roll_to_claim_rewards(2 * ::BLOCKS_PER_YEAR + 2, authors); let c_rewards_2 = Balances::free_balance(&1) .saturating_sub(10_000_000 * DECIMALS) .saturating_sub(c_rewards_0) .saturating_sub(c_rewards_1); + assert!(c_rewards_1 > c_rewards_2); + // should be zero because we set reward rate to zero let d_rewards_2 = Balances::free_balance(&2) .saturating_sub(90_000_000 * DECIMALS) .saturating_sub(d_rewards_0) .saturating_sub(d_rewards_1); - assert!(c_rewards_1 > c_rewards_2); assert!(d_rewards_2.is_zero()); }); } @@ -2961,60 +2870,49 @@ fn decrease_max_candidate_stake() { #[test] fn exceed_delegations_per_round() { ExtBuilder::default() - .with_balances(vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100), (6, 100)]) - .with_collators(vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)]) - .with_delegators(vec![(6, 1, 10)]) + .with_balances(vec![(1, 100), (2, 100)]) + .with_collators(vec![(1, 100)]) + .with_delegators(vec![(2, 1, 100)]) .build() .execute_with(|| { - assert_ok!(StakePallet::delegate_another_candidate(Origin::signed(6), 2, 10)); - assert_ok!(StakePallet::delegate_another_candidate(Origin::signed(6), 3, 10)); - assert_ok!(StakePallet::delegate_another_candidate(Origin::signed(6), 4, 10)); - assert_noop!( - StakePallet::delegate_another_candidate(Origin::signed(6), 5, 10), - Error::::MaxCollatorsPerDelegatorExceeded - ); - - // revoke delegation to allow one more collator for this delegator - assert_ok!(StakePallet::revoke_delegation(Origin::signed(6), 4)); + // leave and re-join to set counter to 2 (= MaxDelegationsPerRound) + assert_ok!(StakePallet::leave_delegators(Origin::signed(2))); + assert_ok!(StakePallet::join_delegators(Origin::signed(2), 1, 100)); + assert_ok!(StakePallet::leave_delegators(Origin::signed(2))); // reached max delegations in this round assert_noop!( - StakePallet::delegate_another_candidate(Origin::signed(6), 5, 10), - Error::::DelegationsPerRoundExceeded - ); - - // revoke all delegations in the same round - assert_ok!(StakePallet::leave_delegators(Origin::signed(6))); - assert_noop!( - StakePallet::join_delegators(Origin::signed(6), 1, 10), + StakePallet::join_delegators(Origin::signed(2), 1, 100), Error::::DelegationsPerRoundExceeded ); // roll to next round to clear DelegationCounter roll_to(5, vec![]); assert_eq!( - StakePallet::last_delegation(6), - DelegationCounter { round: 0, counter: 4 } + StakePallet::last_delegation(2), + DelegationCounter { round: 0, counter: 2 } ); - assert_ok!(StakePallet::join_delegators(Origin::signed(6), 1, 10),); + assert_ok!(StakePallet::join_delegators(Origin::signed(2), 1, 100)); + // counter should be reset because the round changed assert_eq!( - StakePallet::last_delegation(6), + StakePallet::last_delegation(2), DelegationCounter { round: 1, counter: 1 } ); - assert_ok!(StakePallet::leave_delegators(Origin::signed(6))); - assert_ok!(StakePallet::join_delegators(Origin::signed(6), 1, 10),); - assert_ok!(StakePallet::leave_delegators(Origin::signed(6))); - assert_ok!(StakePallet::join_delegators(Origin::signed(6), 1, 10),); - assert_ok!(StakePallet::leave_delegators(Origin::signed(6))); - assert_ok!(StakePallet::join_delegators(Origin::signed(6), 1, 10),); - assert_ok!(StakePallet::leave_delegators(Origin::signed(6))); - assert_eq!( - StakePallet::last_delegation(6), - DelegationCounter { round: 1, counter: 4 } + // leave and re-join to set counter to 2 (= MaxDelegationsPerRound)) + assert_ok!(StakePallet::leave_delegators(Origin::signed(2))); + assert_ok!(StakePallet::join_delegators(Origin::signed(2), 1, 100)); + assert_noop!( + StakePallet::join_delegators(Origin::signed(2), 1, 100), + Error::::AlreadyDelegating ); + assert_ok!(StakePallet::leave_delegators(Origin::signed(2))); assert_noop!( - StakePallet::join_delegators(Origin::signed(6), 1, 10), + StakePallet::join_delegators(Origin::signed(2), 1, 100), Error::::DelegationsPerRoundExceeded ); + assert_eq!( + StakePallet::last_delegation(2), + DelegationCounter { round: 1, counter: 2 } + ); }); } @@ -3027,7 +2925,7 @@ fn force_remove_candidate() { .build() .execute_with(|| { assert_eq!(CandidatePool::::count(), 3); - assert_ok!(StakePallet::delegate_another_candidate(Origin::signed(4), 2, 50)); + assert_ok!(StakePallet::join_delegators(Origin::signed(6), 2, 50)); assert_eq!(StakePallet::selected_candidates().into_inner(), vec![1, 2]); assert!(StakePallet::unstaking(1).get(&3).is_none()); assert!(StakePallet::unstaking(2).get(&3).is_none()); @@ -3058,14 +2956,8 @@ fn force_remove_candidate() { assert_eq!(StakePallet::selected_candidates().into_inner(), vec![2, 3]); assert_eq!(CandidatePool::::count(), 2); assert!(StakePallet::candidate_pool(1).is_none()); + assert!(StakePallet::delegator_state(4).is_none()); assert!(StakePallet::delegator_state(5).is_none()); - assert_eq!( - StakePallet::delegator_state(4), - Some(Delegator { - delegations: OrderedSet::from(vec![StakeOf:: { owner: 2, amount: 50 }].try_into().unwrap()), - total: 50 - }) - ); assert_eq!(StakePallet::unstaking(1).get(&3), Some(&100)); assert_eq!(StakePallet::unstaking(4).get(&3), Some(&50)); assert_eq!(StakePallet::unstaking(5).get(&3), Some(&50)); @@ -3241,7 +3133,7 @@ fn prioritize_collators() { ); // 7 decreases delegation - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(7), 5, 10)); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(7), 10)); assert_eq!(StakePallet::selected_candidates().into_inner(), vec![5, 3]); assert_eq!( StakePallet::top_candidates(), @@ -3257,7 +3149,7 @@ fn prioritize_collators() { .unwrap() ) ); - assert_ok!(StakePallet::revoke_delegation(Origin::signed(7), 5)); + assert_ok!(StakePallet::leave_delegators(Origin::signed(7))); assert_eq!(StakePallet::selected_candidates().into_inner(), vec![3, 5]); assert_eq!( StakePallet::top_candidates(), @@ -3287,9 +3179,11 @@ fn prioritize_delegators() { (5, 1000), (6, 1000), (7, 1000), + (8, 1000), + (9, 1000), ]) .with_collators(vec![(1, 100), (2, 100), (3, 100)]) - .with_delegators(vec![(5, 1, 100), (4, 2, 100), (7, 2, 100), (6, 2, 100)]) + .with_delegators(vec![(4, 2, 100), (7, 2, 100), (6, 2, 100)]) .build() .execute_with(|| { assert_eq!(StakePallet::selected_candidates().into_inner(), vec![2, 1]); @@ -3305,7 +3199,7 @@ fn prioritize_delegators() { .unwrap() ) ); - assert_ok!(StakePallet::delegate_another_candidate(Origin::signed(5), 2, 110)); + assert_ok!(StakePallet::join_delegators(Origin::signed(5), 2, 110)); assert_eq!( StakePallet::candidate_pool(2).unwrap().delegators, OrderedSet::from_sorted_set( @@ -3319,20 +3213,9 @@ fn prioritize_delegators() { .unwrap() ) ); - assert_eq!( - StakePallet::delegator_state(5).unwrap().delegations, - OrderedSet::from_sorted_set( - vec![ - StakeOf:: { owner: 2, amount: 110 }, - StakeOf:: { owner: 1, amount: 100 } - ] - .try_into() - .unwrap() - ) - ); // delegate_less - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(5), 2, 10)); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(5), 10)); assert_eq!( StakePallet::candidate_pool(2).unwrap().delegators, OrderedSet::from_sorted_set( @@ -3346,20 +3229,9 @@ fn prioritize_delegators() { .unwrap() ) ); - assert_eq!( - StakePallet::delegator_state(5).unwrap().delegations, - OrderedSet::from_sorted_set( - vec![ - StakeOf:: { owner: 2, amount: 100 }, - StakeOf:: { owner: 1, amount: 100 } - ] - .try_into() - .unwrap() - ) - ); // delegate_more - assert_ok!(StakePallet::delegator_stake_more(Origin::signed(6), 2, 10)); + assert_ok!(StakePallet::delegator_stake_more(Origin::signed(6), 10)); assert_eq!( StakePallet::candidate_pool(2).unwrap().delegators, OrderedSet::from_sorted_set( @@ -3373,7 +3245,7 @@ fn prioritize_delegators() { .unwrap() ) ); - assert_ok!(StakePallet::delegator_stake_more(Origin::signed(7), 2, 10)); + assert_ok!(StakePallet::delegator_stake_more(Origin::signed(7), 10)); assert_eq!( StakePallet::candidate_pool(2).unwrap().delegators, OrderedSet::from_sorted_set( @@ -3418,7 +3290,7 @@ fn authorities_per_round() { let inflation = StakePallet::inflation_config(); // roll to last block of round 0 - roll_to(4, authors.clone()); + roll_to_claim_rewards(4, authors.clone()); let reward_0 = inflation.collator.reward_rate.per_block * stake * 2; assert_eq!(Balances::free_balance(1), stake + reward_0); // increase max selected candidates which will become effective in round 2 @@ -3427,19 +3299,19 @@ fn authorities_per_round() { // roll to last block of round 1 // should still multiply with 2 because the Authority set was chosen at start of // round 1 - roll_to(9, authors.clone()); + roll_to_claim_rewards(9, authors.clone()); let reward_1 = inflation.collator.reward_rate.per_block * stake * 2; assert_eq!(Balances::free_balance(1), stake + reward_0 + reward_1); // roll to last block of round 2 // should multiply with 4 because there are only 4 candidates - roll_to(14, authors.clone()); + roll_to_claim_rewards(14, authors.clone()); let reward_2 = inflation.collator.reward_rate.per_block * stake * 4; assert_eq!(Balances::free_balance(1), stake + reward_0 + reward_1 + reward_2); // roll to last block of round 3 // should multiply with 4 because there are only 4 candidates - roll_to(19, authors); + roll_to_claim_rewards(19, authors); let reward_3 = inflation.collator.reward_rate.per_block * stake * 4; assert_eq!( Balances::free_balance(1), @@ -3747,7 +3619,7 @@ fn update_total_stake_collators_stay() { delegators: 150 } ); - assert_ok!(StakePallet::delegator_stake_more(Origin::signed(3), 1, 10)); + assert_ok!(StakePallet::delegator_stake_more(Origin::signed(3), 10)); assert_eq!( StakePallet::total_collator_stake(), TotalStake { @@ -3755,7 +3627,7 @@ fn update_total_stake_collators_stay() { delegators: 160 } ); - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(4), 2, 5)); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(4), 5)); assert_eq!( StakePallet::total_collator_stake(), TotalStake { @@ -3801,10 +3673,10 @@ fn update_total_stake_displace_collators() { delegators: 105 } ); - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(8), 4, 45)); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(8), 45)); // 3 is pushed out by delegator staking less - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(7), 3, 45)); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(7), 45)); assert_eq!( StakePallet::total_collator_stake(), TotalStake { @@ -3900,7 +3772,7 @@ fn update_total_stake_no_collator_changes() { delegators: 110 } ); - assert_ok!(StakePallet::delegator_stake_more(Origin::signed(5), 1, 10)); + assert_ok!(StakePallet::delegator_stake_more(Origin::signed(5), 10)); assert_eq!( StakePallet::total_collator_stake(), TotalStake { @@ -3916,7 +3788,7 @@ fn update_total_stake_no_collator_changes() { delegators: 110 } ); - assert_ok!(StakePallet::delegator_stake_less(Origin::signed(6), 2, 10)); + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(6), 10)); assert_eq!( StakePallet::total_collator_stake(), TotalStake { @@ -3926,3 +3798,568 @@ fn update_total_stake_no_collator_changes() { ); }); } + +#[test] +fn rewards_candidate_stake_more() { + ExtBuilder::default() + .with_balances(vec![(1, 2 * DECIMALS), (2, DECIMALS), (3, DECIMALS)]) + .with_collators(vec![(1, DECIMALS)]) + .with_delegators(vec![(2, 1, DECIMALS), (3, 1, DECIMALS)]) + .build() + .execute_with(|| { + // note once to set counter to 1 + StakePallet::note_author(1); + assert_eq!(StakePallet::blocks_authored(1), 1); + assert!(StakePallet::blocks_authored(2).is_zero()); + assert!(StakePallet::blocks_authored(3).is_zero()); + (1..=3).for_each(|id| { + assert!(StakePallet::blocks_rewarded(id).is_zero()); + assert!(StakePallet::rewards(id).is_zero()); + }); + + // stake less to trigger reward incrementing for collator + assert_ok!(StakePallet::candidate_stake_more(Origin::signed(1), DECIMALS)); + assert!(!StakePallet::rewards(1).is_zero()); + assert!(!StakePallet::blocks_rewarded(1).is_zero()); + // delegator reward storage should be untouched + (2..=3).for_each(|id| { + assert!( + StakePallet::rewards(id).is_zero(), + "Rewards not zero for acc_id {:?}", + id + ); + assert!( + StakePallet::blocks_rewarded(id).is_zero(), + "BlocksRewaeded not zero for acc_id {:?}", + id + ); + }); + }); +} + +#[test] +fn rewards_candidate_stake_less() { + ExtBuilder::default() + .with_balances(vec![(1, 2 * DECIMALS), (2, DECIMALS), (3, DECIMALS)]) + .with_collators(vec![(1, 2 * DECIMALS)]) + .with_delegators(vec![(2, 1, DECIMALS), (3, 1, DECIMALS)]) + .build() + .execute_with(|| { + // note once to set counter to 1 + StakePallet::note_author(1); + assert_eq!(StakePallet::blocks_authored(1), 1); + assert!(StakePallet::blocks_authored(2).is_zero()); + assert!(StakePallet::blocks_authored(3).is_zero()); + (1..=3).for_each(|id| { + assert!(StakePallet::blocks_rewarded(id).is_zero()); + assert!(StakePallet::rewards(id).is_zero()); + }); + + // stake less to trigger reward incrementing for collator + assert_ok!(StakePallet::candidate_stake_less(Origin::signed(1), DECIMALS)); + assert!(!StakePallet::rewards(1).is_zero()); + assert!(!StakePallet::blocks_rewarded(1).is_zero()); + // delegator reward storage should be untouched + (2..=3).for_each(|id| { + assert!( + StakePallet::rewards(id).is_zero(), + "Rewards not zero for acc_id {:?}", + id + ); + assert!( + StakePallet::blocks_rewarded(id).is_zero(), + "BlocksRewaeded not zero for acc_id {:?}", + id + ); + }); + }); +} + +#[test] +fn rewards_candidate_leave_network() { + ExtBuilder::default() + .with_balances(vec![ + (1, 2 * DECIMALS), + (2, DECIMALS), + (3, DECIMALS), + (4, DECIMALS), + (5, DECIMALS), + ]) + .with_collators(vec![(1, 2 * DECIMALS), (4, DECIMALS), (5, DECIMALS)]) + .with_delegators(vec![(2, 1, DECIMALS), (3, 1, DECIMALS)]) + .build() + .execute_with(|| { + // init does not increment rewards + assert_ok!(StakePallet::init_leave_candidates(Origin::signed(1))); + + // advance two rounds to enable leaving + roll_to( + 10, + vec![ + // we're already in block 1, so cant note_author for block 1 + None, + Some(1), + Some(2), + Some(1), + Some(2), + Some(1), + Some(2), + Some(1), + Some(2), + ], + ); + // Only authored should be bumped for collator, not rewarded + assert_eq!(StakePallet::blocks_authored(1), 4 * 2); + assert!(StakePallet::blocks_rewarded(1).is_zero()); + + // count for delegators should not be incremented + assert!(StakePallet::blocks_rewarded(2).is_zero()); + assert!(StakePallet::blocks_rewarded(3).is_zero()); + + // rewards should not be incremented + (1..=3).for_each(|id| { + assert!(StakePallet::rewards(id).is_zero()); + }); + + // execute leave intent to trigger reward incrementing for collator and + // delegators + assert_ok!(StakePallet::execute_leave_candidates(Origin::signed(1), 1)); + + // reward counting storages should be killed for collator + assert!(StakePallet::blocks_authored(1).is_zero()); + assert!(StakePallet::blocks_rewarded(1).is_zero()); + assert!(!StakePallet::rewards(1).is_zero()); + + // reward counting storages should NOT be killed for delegators + (2..=3).for_each(|id| { + assert!(!StakePallet::rewards(id).is_zero(), "Zero rewards acc_id {:?}", id); + assert_eq!( + StakePallet::blocks_rewarded(id), + 4 * 2, + "Rewarded blocks Delegator {:?} do not match up with exited collator", + id + ); + }); + }); +} + +#[test] +fn rewards_force_remove_candidate() { + ExtBuilder::default() + .with_balances(vec![ + (1, DECIMALS), + (2, DECIMALS), + (3, DECIMALS), + (4, DECIMALS), + (5, DECIMALS), + ]) + .with_collators(vec![(1, DECIMALS), (4, DECIMALS), (5, DECIMALS)]) + .with_delegators(vec![(2, 1, DECIMALS), (3, 1, DECIMALS)]) + .build() + .execute_with(|| { + // init does not increment rewards + StakePallet::note_author(1); + StakePallet::note_author(2); + + // removing triggers reward increment for collator 1 and delegators 4, 5 + assert_ok!(StakePallet::force_remove_candidate(Origin::root(), 1)); + // rewarded counter storage should be killed for collator + assert!(StakePallet::blocks_authored(1).is_zero()); + assert!(StakePallet::blocks_rewarded(1).is_zero()); + (1..=3).for_each(|id| { + assert!(!StakePallet::rewards(id).is_zero(), "Zero rewards for acc_id {:?}", id); + if id > 1 { + assert_eq!( + StakePallet::blocks_rewarded(id), + 2, + "Rewarded counter does not match for delegator {:?}", + id + ); + } + }); + (4..=5).for_each(|id| { + assert!(StakePallet::rewards(id).is_zero(), "acc_id {:?}", id); + assert!(StakePallet::blocks_rewarded(id).is_zero(), "acc_id {:?}", id); + }); + }); +} + +#[test] +fn blocks_rewarded_join_delegators() { + ExtBuilder::default() + .with_balances(vec![(1, 100), (2, 100)]) + .with_collators(vec![(1, 100)]) + .build() + .execute_with(|| { + // note once to set counter to 1 + StakePallet::note_author(1); + assert_eq!(StakePallet::blocks_authored(1), 1); + assert!(StakePallet::blocks_rewarded(1).is_zero()); + assert_ok!(StakePallet::join_delegators(Origin::signed(2), 1, 100)); + // delegator's rewarded counter should equal of collator's authored counter upon + // joining + assert_eq!(StakePallet::blocks_rewarded(2), StakePallet::blocks_authored(1)); + }); +} + +#[test] +fn rewards_delegator_stake_more() { + ExtBuilder::default() + .with_balances(vec![(1, DECIMALS), (2, DECIMALS), (3, 2 * DECIMALS)]) + .with_collators(vec![(1, DECIMALS)]) + .with_delegators(vec![(2, 1, DECIMALS), (3, 1, DECIMALS)]) + .build() + .execute_with(|| { + // note once to set counter to 1 + StakePallet::note_author(1); + assert_eq!(StakePallet::blocks_authored(1), 1); + assert!(StakePallet::blocks_rewarded(2).is_zero()); + assert!(StakePallet::blocks_rewarded(3).is_zero()); + (1..=3).for_each(|id| { + assert!(StakePallet::rewards(id).is_zero(), "acc_id {:?}", id); + }); + + // stake less to trigger reward incrementing just for 3 + assert_ok!(StakePallet::delegator_stake_more(Origin::signed(3), DECIMALS)); + // 1 should still have counter 1 but no rewards + assert_eq!(StakePallet::blocks_authored(1), 1); + assert!(StakePallet::blocks_rewarded(1).is_zero()); + assert!(StakePallet::rewards(1).is_zero()); + // 2 should still have neither rewards nor counter + assert!(StakePallet::blocks_rewarded(2).is_zero()); + assert!(StakePallet::rewards(2).is_zero()); + // 3 should have rewards and the same counter as 1 + assert_eq!(StakePallet::blocks_rewarded(3), 1); + assert!(!StakePallet::rewards(3).is_zero()); + }); +} + +#[test] +fn rewards_delegator_stake_less() { + ExtBuilder::default() + .with_balances(vec![(1, DECIMALS), (2, DECIMALS), (3, 2 * DECIMALS)]) + .with_collators(vec![(1, DECIMALS)]) + .with_delegators(vec![(2, 1, DECIMALS), (3, 1, 2 * DECIMALS)]) + .build() + .execute_with(|| { + // note once to set counter to 1 + StakePallet::note_author(1); + assert_eq!(StakePallet::blocks_authored(1), 1); + assert!(StakePallet::blocks_rewarded(2).is_zero()); + assert!(StakePallet::blocks_rewarded(3).is_zero()); + (1..=3).for_each(|id| { + assert!(StakePallet::rewards(id).is_zero(), "acc_id {:?}", id); + }); + + // stake less to trigger reward incrementing just for 3 + assert_ok!(StakePallet::delegator_stake_less(Origin::signed(3), DECIMALS)); + // 1 should still have counter 1 but no rewards + assert_eq!(StakePallet::blocks_authored(1), 1); + assert!(StakePallet::blocks_rewarded(1).is_zero()); + assert!(StakePallet::rewards(1).is_zero()); + // 2 should still have neither rewards nor counter + assert!(StakePallet::blocks_rewarded(2).is_zero()); + assert!(StakePallet::rewards(2).is_zero()); + // 3 should have rewards and the same counter as 1 + assert_eq!(StakePallet::blocks_rewarded(3), 1); + assert!(!StakePallet::rewards(3).is_zero()); + }); +} + +#[test] +fn rewards_delegator_replaced() { + ExtBuilder::default() + .with_balances(vec![ + (1, 2 * DECIMALS), + (2, 2 * DECIMALS), + (3, 2 * DECIMALS), + (4, 2 * DECIMALS), + (5, 2 * DECIMALS), + (6, 2 * DECIMALS), + ]) + .with_collators(vec![(1, 2 * DECIMALS)]) + .with_delegators(vec![ + (2, 1, 2 * DECIMALS), + (3, 1, 2 * DECIMALS), + (4, 1, 2 * DECIMALS), + (5, 1, DECIMALS), + ]) + .build() + .execute_with(|| { + // note once to set counter to 1 + StakePallet::note_author(1); + assert_eq!(StakePallet::blocks_authored(1), 1); + + // 6 kicks 5 + assert_ok!(StakePallet::join_delegators(Origin::signed(6), 1, 2 * DECIMALS)); + // 5 should have rewards and counter updated + assert!(!StakePallet::rewards(5).is_zero()); + assert_eq!(StakePallet::blocks_rewarded(5), 1); + // 6 should not have rewards but same counter as former collator + assert!(StakePallet::rewards(6).is_zero()); + assert_eq!(StakePallet::blocks_rewarded(6), 1); + }); +} + +#[test] +fn rewards_delegator_leaves() { + ExtBuilder::default() + .with_balances(vec![(1, DECIMALS), (2, DECIMALS), (3, DECIMALS)]) + .with_collators(vec![(1, DECIMALS)]) + .with_delegators(vec![(2, 1, DECIMALS), (3, 1, DECIMALS)]) + .build() + .execute_with(|| { + // note collator once to set their counter to 1 + StakePallet::note_author(1); + assert_eq!(StakePallet::blocks_authored(1), 1); + assert!(StakePallet::blocks_rewarded(2).is_zero()); + assert!(StakePallet::blocks_rewarded(3).is_zero()); + (1..=3).for_each(|id| { + assert!(StakePallet::rewards(id).is_zero(), "acc_id {:?}", id); + }); + + // only 3 should have non-zero rewards + assert_ok!(StakePallet::leave_delegators(Origin::signed(3))); + assert!(StakePallet::blocks_rewarded(1).is_zero()); + assert!(StakePallet::rewards(1).is_zero()); + assert!(StakePallet::blocks_rewarded(2).is_zero()); + assert!(StakePallet::rewards(2).is_zero()); + assert!(!StakePallet::rewards(3).is_zero()); + // counter should be reset due to leaving + assert!(StakePallet::blocks_rewarded(3).is_zero()); + }); +} + +#[test] +fn rewards_set_inflation() { + let hundred = Perquintill::from_percent(100); + ExtBuilder::default() + .with_balances(vec![ + (1, DECIMALS), + (2, DECIMALS), + (3, DECIMALS), + (4, DECIMALS), + (5, DECIMALS), + ]) + .with_collators(vec![(1, DECIMALS), (2, DECIMALS)]) + .with_delegators(vec![(3, 1, DECIMALS), (4, 1, DECIMALS), (5, 2, DECIMALS)]) + .build() + .execute_with(|| { + // note collators + StakePallet::note_author(1); + StakePallet::note_author(1); + StakePallet::note_author(2); + + // set inflation to trigger reward setting + assert_ok!(StakePallet::set_inflation( + Origin::root(), + hundred, + hundred, + hundred, + hundred + )); + // rewards and counters should be set + (1..=5).for_each(|id| { + assert!(!StakePallet::blocks_rewarded(id).is_zero(), "acc_id {:?}", id); + assert!(!StakePallet::rewards(id).is_zero(), "acc_id {:?}", id); + }); + }); +} + +#[test] +fn rewards_yearly_inflation_adjustment() { + ExtBuilder::default() + .with_balances(vec![ + (1, DECIMALS), + (2, DECIMALS), + (3, DECIMALS), + (4, DECIMALS), + (5, DECIMALS), + ]) + .with_collators(vec![(1, DECIMALS), (2, DECIMALS)]) + .with_delegators(vec![(3, 1, DECIMALS), (4, 1, DECIMALS), (5, 2, DECIMALS)]) + .build() + .execute_with(|| { + // init counter and go to next year + StakePallet::note_author(1); + StakePallet::note_author(2); + System::set_block_number(::BLOCKS_PER_YEAR - 1); + roll_to_claim_rewards(::BLOCKS_PER_YEAR + 1, vec![]); + assert!(!StakePallet::blocks_authored(1).is_zero()); + assert!(!StakePallet::blocks_authored(2).is_zero()); + + // rewards should not be triggered before executing pending adjustment + (1..=5).for_each(|id| { + assert!(StakePallet::rewards(id).is_zero(), "acc_id {:?}", id); + }); + + // execute to trigger reward increment + assert_ok!(StakePallet::execute_scheduled_reward_change(Origin::signed(1))); + (1..=5).for_each(|id| { + assert!( + !StakePallet::blocks_rewarded(id).is_zero(), + "Zero rewarded blocks for acc_id {:?}", + id + ); + assert!(!StakePallet::rewards(id).is_zero(), "Zero rewards for acc_id {:?}", id); + }); + }); +} + +#[test] +fn rewards_incrementing_and_claiming() { + ExtBuilder::default() + .with_balances(vec![(1, DECIMALS), (2, DECIMALS), (3, DECIMALS)]) + .with_collators(vec![(1, DECIMALS)]) + .with_delegators(vec![(2, 1, DECIMALS), (3, 1, DECIMALS)]) + .build() + .execute_with(|| { + // claiming should not be possible with zero counters + (1..=3).for_each(|id| { + assert_noop!( + StakePallet::claim_rewards(Origin::signed(id)), + Error::::RewardsNotFound, + ); + }); + + // note once to set counter to 1 + StakePallet::note_author(1); + assert_eq!(StakePallet::blocks_authored(1), 1); + assert!(StakePallet::blocks_rewarded(2).is_zero()); + + // claiming should not be possible before incrementing rewards + (1..=3).for_each(|id| { + assert_noop!( + StakePallet::claim_rewards(Origin::signed(id)), + Error::::RewardsNotFound + ); + }); + + // increment rewards for 2 and match counter to collator + assert_ok!(StakePallet::increment_delegator_rewards(Origin::signed(2))); + assert_eq!(StakePallet::blocks_rewarded(2), 1); + let rewards_2 = StakePallet::rewards(2); + assert!(!rewards_2.is_zero()); + assert!(StakePallet::blocks_rewarded(3).is_zero()); + assert!(StakePallet::rewards(3).is_zero()); + + // should only update rewards for collator as well + assert_ok!(StakePallet::increment_collator_rewards(Origin::signed(1))); + assert_eq!(StakePallet::blocks_rewarded(1), StakePallet::blocks_authored(1)); + assert!(!StakePallet::rewards(1).is_zero()); + // rewards of 2 should not be changed + assert_eq!(StakePallet::rewards(2), rewards_2); + // 3 should still not have blocks rewarded bumped + assert!(StakePallet::blocks_rewarded(3).is_zero()); + + // claim for 1 to move rewards into balance + assert_ok!(StakePallet::claim_rewards(Origin::signed(1))); + assert!(StakePallet::rewards(1).is_zero()); + // delegator situation should be unchanged + assert!(Balances::free_balance(&1) > DECIMALS); + assert_eq!(Balances::free_balance(&2), DECIMALS); + assert_eq!(Balances::free_balance(&3), DECIMALS); + + // incrementing again should not change anything because collator has not + // authored blocks since last inc + assert_ok!(StakePallet::increment_delegator_rewards(Origin::signed(2))); + assert_eq!(StakePallet::blocks_rewarded(2), 1); + // claim for 2 to move rewards into balance + assert_ok!(StakePallet::claim_rewards(Origin::signed(2))); + assert!(Balances::free_balance(&2) > DECIMALS); + assert!(StakePallet::rewards(2).is_zero()); + assert_eq!(Balances::free_balance(&3), DECIMALS); + + // should not be able to claim for incorrect role + assert_noop!( + StakePallet::increment_collator_rewards(Origin::signed(2)), + Error::::CandidateNotFound + ); + assert_noop!( + StakePallet::increment_delegator_rewards(Origin::signed(1)), + Error::::DelegatorNotFound + ); + }); +} + +#[test] +fn api_get_unclaimed_staking_rewards() { + let stake = 100_000 * DECIMALS; + ExtBuilder::default() + .with_balances(vec![(1, stake), (2, stake), (3, 100 * stake)]) + .with_collators(vec![(1, stake), (3, 2 * stake)]) + .with_delegators(vec![(2, 1, stake)]) + .build() + .execute_with(|| { + let inflation_config = StakePallet::inflation_config(); + + // Increment rewards of 1 and 2 + roll_to(2, vec![None, Some(1)]); + assert_eq!( + StakePallet::get_unclaimed_staking_rewards(&1), + // Multiplying with 2 because there are two authors + inflation_config.collator.reward_rate.per_block * stake * 2 + ); + assert_eq!( + StakePallet::get_unclaimed_staking_rewards(&2), + inflation_config.delegator.reward_rate.per_block * stake * 2 + ); + assert!(StakePallet::get_unclaimed_staking_rewards(&3).is_zero()); + + // Should only increment rewards of 3 + roll_to(3, vec![None, None, Some(3)]); + let rewards_1 = StakePallet::get_unclaimed_staking_rewards(&1); + let rewards_2 = StakePallet::get_unclaimed_staking_rewards(&2); + let rewards_3 = StakePallet::get_unclaimed_staking_rewards(&3); + assert_eq!(2 * rewards_1, rewards_3,); + assert_eq!(rewards_2, inflation_config.delegator.reward_rate.per_block * stake * 2); + + // API and actual claiming should match + assert_ok!(StakePallet::increment_collator_rewards(Origin::signed(1))); + assert_ok!(StakePallet::claim_rewards(Origin::signed(1))); + assert_eq!(rewards_1, Balances::usable_balance(&1)); + assert!(StakePallet::get_unclaimed_staking_rewards(&1).is_zero()); + + assert_ok!(StakePallet::increment_delegator_rewards(Origin::signed(2))); + assert_ok!(StakePallet::claim_rewards(Origin::signed(2))); + assert_eq!(rewards_2, Balances::usable_balance(&2)); + assert!(StakePallet::get_unclaimed_staking_rewards(&2).is_zero()); + + assert_ok!(StakePallet::increment_collator_rewards(Origin::signed(3))); + assert_ok!(StakePallet::claim_rewards(Origin::signed(3))); + assert_eq!(rewards_3 + 98 * stake, Balances::usable_balance(&3)); + assert!(StakePallet::get_unclaimed_staking_rewards(&3).is_zero()); + }); +} + +#[test] +fn api_get_staking_rates() { + let stake = 100_000 * DECIMALS; + ExtBuilder::default() + .with_balances(vec![(1, stake), (2, stake), (3, 2 * stake)]) + .with_collators(vec![(1, stake), (2, stake)]) + .with_delegators(vec![(3, 1, stake)]) + .with_inflation(25, 10, 25, 8, ::BLOCKS_PER_YEAR) + .build() + .execute_with(|| { + let mut rates = StakingRates { + collator_staking_rate: Perquintill::from_percent(50), + collator_reward_rate: Perquintill::from_percent(5), + delegator_staking_rate: Perquintill::from_percent(25), + delegator_reward_rate: Perquintill::from_percent(8), + }; + // collators exceed max staking rate + assert_eq!(rates, StakePallet::get_staking_rates()); + + // candidates stake less to not exceed max staking rate + assert_ok!(StakePallet::candidate_stake_less(Origin::signed(1), stake / 2)); + assert_ok!(StakePallet::candidate_stake_less(Origin::signed(2), stake / 2)); + // delegator stakes more to exceed + assert_ok!(StakePallet::delegator_stake_more(Origin::signed(3), stake)); + rates.collator_staking_rate = Perquintill::from_percent(25); + rates.collator_reward_rate = Perquintill::from_percent(10); + rates.delegator_staking_rate = Perquintill::from_percent(50); + rates.delegator_reward_rate = Perquintill::from_percent(4); + assert_eq!(rates, StakePallet::get_staking_rates()); + }); +} diff --git a/pallets/parachain-staking/src/types.rs b/pallets/parachain-staking/src/types.rs index 3dd84bfe73..d5f5841ee8 100644 --- a/pallets/parachain-staking/src/types.rs +++ b/pallets/parachain-staking/src/types.rs @@ -20,16 +20,14 @@ use frame_support::traits::{Currency, Get}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ - traits::{AtLeast32BitUnsigned, Saturating, Zero}, + traits::{AtLeast32BitUnsigned, CheckedSub, Saturating, Zero}, RuntimeDebug, }; use sp_staking::SessionIndex; use sp_std::{ cmp::Ordering, - convert::TryInto, fmt::Debug, ops::{Add, Sub}, - vec, }; use crate::{set::OrderedSet, Config}; @@ -211,99 +209,44 @@ where } } -#[derive(Encode, Decode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] -#[scale_info(skip_type_params(MaxCollatorsPerDelegator))] -#[codec(mel_bound(AccountId: MaxEncodedLen, Balance: MaxEncodedLen))] -pub struct Delegator> { - pub delegations: OrderedSet, MaxCollatorsPerDelegator>, - pub total: Balance, -} - -impl Delegator +pub type Delegator = Stake; +impl Delegator where AccountId: Eq + Ord + Clone + Debug, - Balance: Copy + Add + Saturating + PartialOrd + Eq + Ord + Debug + Zero, - MaxCollatorsPerDelegator: Get + Debug + PartialEq, + Balance: Copy + Add + Saturating + PartialOrd + Eq + Ord + Debug + Zero + Default + CheckedSub, { - pub fn try_new(collator: AccountId, amount: Balance) -> Result { - Ok(Delegator { - delegations: OrderedSet::from( - vec![Stake { - owner: collator, - amount, - }] - .try_into()?, - ), - total: amount, - }) - } - - /// Adds a new delegation. - /// - /// If already delegating to the same account, this call returns false and - /// doesn't insert the new delegation. - pub fn add_delegation(&mut self, stake: Stake) -> Result { - let amt = stake.amount; - if self.delegations.try_insert(stake).map_err(|_| ())? { - self.total = self.total.saturating_add(amt); - Ok(true) - } else { - Ok(false) - } - } - - /// Returns Some(remaining stake for delegator) if the delegation for the - /// collator exists. Returns `None` otherwise. - pub fn rm_delegation(&mut self, collator: &AccountId) -> Option { - let amt = self.delegations.remove(&Stake:: { - owner: collator.clone(), - // amount is irrelevant for removal - amount: Balance::zero(), - }); - - if let Some(Stake:: { amount: balance, .. }) = amt { - self.total = self.total.saturating_sub(balance); - Some(self.total) + /// Returns Ok if the delegation for the + /// collator exists and `Err` otherwise. + pub fn try_clear(&mut self, collator: AccountId) -> Result<(), ()> { + if self.owner == collator { + self.amount = Balance::zero(); + Ok(()) } else { - None + Err(()) } } - /// Returns None if delegation was not found. - pub fn inc_delegation(&mut self, collator: AccountId, more: Balance) -> Option { - if let Ok(i) = self.delegations.linear_search(&Stake:: { - owner: collator, - amount: Balance::zero(), - }) { - self.delegations - .mutate(|vec| vec[i].amount = vec[i].amount.saturating_add(more)); - self.total = self.total.saturating_add(more); - self.delegations.sort_greatest_to_lowest(); - Some(self.delegations[i].amount) + /// Returns Ok(delegated_amount) if successful, `Err` if delegation was + /// not found. + pub fn try_increment(&mut self, collator: AccountId, more: Balance) -> Result { + if self.owner == collator { + self.amount = self.amount.saturating_add(more); + Ok(self.amount) } else { - None + Err(()) } } - /// Returns Some(Some(balance)) if successful, None if delegation was not - /// found and Some(None) if delegated stake would underflow. - pub fn dec_delegation(&mut self, collator: AccountId, less: Balance) -> Option> { - if let Ok(i) = self.delegations.linear_search(&Stake:: { - owner: collator, - amount: Balance::zero(), - }) { - if self.delegations[i].amount > less { - self.delegations - .mutate(|vec| vec[i].amount = vec[i].amount.saturating_sub(less)); - self.total = self.total.saturating_sub(less); - self.delegations.sort_greatest_to_lowest(); - Some(Some(self.delegations[i].amount)) - } else { - // underflow error; should rm entire delegation - Some(None) - } + /// Returns Ok(Some(delegated_amount)) if successful, `Err` if delegation + /// was not found and Ok(None) if delegated stake would underflow. + pub fn try_decrement(&mut self, collator: AccountId, less: Balance) -> Result, ()> { + if self.owner == collator { + Ok(self.amount.checked_sub(&less).map(|new| { + self.amount = new; + self.amount + })) } else { - None + Err(()) } } } @@ -371,13 +314,6 @@ pub struct DelegationCounter { pub counter: u32, } -/// Internal type which is only used when a delegator is replaced by another -/// one to delay the storage entry removal until failure cannot happen anymore. -pub(crate) struct ReplacedDelegator { - pub who: AccountIdOf, - pub state: Option, BalanceOf, T::MaxCollatorsPerDelegator>>, -} - pub type AccountIdOf = ::AccountId; pub type BalanceOf = <::Currency as Currency>>::Balance; pub type CandidateOf = Candidate, BalanceOf, S>; diff --git a/runtimes/common/src/constants.rs b/runtimes/common/src/constants.rs index 5f0cc327c8..eabca7a177 100644 --- a/runtimes/common/src/constants.rs +++ b/runtimes/common/src/constants.rs @@ -205,9 +205,6 @@ pub mod staking { /// Maximum 25 delegators per collator at launch, might be increased later #[derive(Debug, Eq, PartialEq)] pub const MaxDelegatorsPerCollator: u32 = MAX_DELEGATORS_PER_COLLATOR; - /// Maximum 1 collator per delegator at launch, will be increased later - #[derive(Debug, Eq, PartialEq)] - pub const MaxCollatorsPerDelegator: u32 = 1; /// Minimum stake required to be reserved to be a collator is 10_000 pub const MinCollatorStake: Balance = 10_000 * KILT; /// Minimum stake required to be reserved to be a delegator is 1000 diff --git a/runtimes/peregrine/src/lib.rs b/runtimes/peregrine/src/lib.rs index 07a0bdd6bd..c09213ee15 100644 --- a/runtimes/peregrine/src/lib.rs +++ b/runtimes/peregrine/src/lib.rs @@ -651,11 +651,9 @@ impl parachain_staking::Config for Runtime { type MinRequiredCollators = constants::staking::MinRequiredCollators; type MaxDelegationsPerRound = constants::staking::MaxDelegationsPerRound; type MaxDelegatorsPerCollator = constants::staking::MaxDelegatorsPerCollator; - type MaxCollatorsPerDelegator = constants::staking::MaxCollatorsPerDelegator; type MinCollatorStake = constants::staking::MinCollatorStake; type MinCollatorCandidateStake = constants::staking::MinCollatorStake; type MaxTopCandidates = constants::staking::MaxCollatorCandidates; - type MinDelegation = constants::staking::MinDelegatorStake; type MinDelegatorStake = constants::staking::MinDelegatorStake; type MaxUnstakeRequests = constants::staking::MaxUnstakeRequests; type NetworkRewardRate = constants::staking::NetworkRewardRate; @@ -1261,6 +1259,16 @@ impl_runtime_apis! { } } + impl parachain_staking::runtime_api::ParachainStakingApi for Runtime { + fn get_unclaimed_staking_rewards(account: &AccountId) -> Balance { + ParachainStaking::get_unclaimed_staking_rewards(account) + } + + fn get_staking_rates() -> parachain_staking::runtime_api::StakingRates { + ParachainStaking::get_staking_rates() + } + } + #[cfg(feature = "runtime-benchmarks")] impl frame_benchmarking::Benchmark for Runtime { fn benchmark_metadata(extra: bool) -> ( diff --git a/runtimes/peregrine/src/weights/parachain_staking.rs b/runtimes/peregrine/src/weights/parachain_staking.rs index 33b1a77259..9ff79ed390 100644 --- a/runtimes/peregrine/src/weights/parachain_staking.rs +++ b/runtimes/peregrine/src/weights/parachain_staking.rs @@ -50,7 +50,7 @@ pub struct WeightInfo(PhantomData); impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking Round (r:1 w:0) fn on_initialize_no_action() -> Weight { - Weight::from_ref_time(7_701_000 as u64) + Weight::from_ref_time(3_103_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) } // Storage: ParachainStaking Round (r:1 w:1) @@ -62,14 +62,6 @@ impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking Round (r:1 w:1) // Storage: ParachainStaking LastRewardReduction (r:1 w:1) // Storage: ParachainStaking InflationConfig (r:1 w:1) - fn on_initialize_new_year() -> Weight { - Weight::from_ref_time(35_951_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - } - // Storage: ParachainStaking Round (r:1 w:1) - // Storage: ParachainStaking LastRewardReduction (r:1 w:1) - // Storage: ParachainStaking InflationConfig (r:1 w:1) // Storage: ParachainStaking MaxCollatorCandidateStake (r:1 w:0) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: System Account (r:1 w:1) @@ -83,10 +75,23 @@ impl parachain_staking::WeightInfo for WeightInfo { Weight::from_ref_time(8_791_000 as u64) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: ParachainStaking InflationConfig (r:0 w:1) - fn set_inflation() -> Weight { - Weight::from_ref_time(24_163_000 as u64) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Storage: ParachainStaking CandidatePool (r:3 w:0) + // Storage: ParachainStaking RewardCount (r:72 w:72) + // Storage: ParachainStaking Rewards (r:2 w:2) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking InflationConfig (r:1 w:1) + /// The range of component `n` is `[0, 75]`. + /// The range of component `m` is `[0, 35]`. + fn set_inflation(n: u32, m: u32, ) -> Weight { + Weight::from_ref_time(0 as u64) + // Standard Error: 3_005_000 + .saturating_add(Weight::from_ref_time(216_364_000 as u64).saturating_mul(n as u64)) + // Standard Error: 6_440_000 + .saturating_add(Weight::from_ref_time(440_763_000 as u64).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads((37 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().reads((75 as u64).saturating_mul(m as u64))) + .saturating_add(T::DbWeight::get().writes((36 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().writes((75 as u64).saturating_mul(m as u64))) } // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:1) // Storage: ParachainStaking TopCandidates (r:1 w:0) @@ -111,12 +116,17 @@ impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking Unstaking (r:36 w:36) // Storage: ParachainStaking DelegatorState (r:35 w:35) + // Storage: ParachainStaking RewardCount (r:36 w:36) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + // Storage: ParachainStaking InflationConfig (r:1 w:0) // Storage: Session Validators (r:1 w:0) // Storage: Session DisabledValidators (r:1 w:1) // Storage: System Digest (r:1 w:1) // Storage: ParachainStaking CounterForCandidatePool (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) - // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + /// The range of component `n` is `[17, 75]`. + /// The range of component `m` is `[0, 35]`. fn force_remove_candidate(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 169_000 @@ -138,6 +148,8 @@ impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) // Storage: ParachainStaking CounterForCandidatePool (r:1 w:1) + /// The range of component `n` is `[1, 74]`. + /// The range of component `m` is `[0, 35]`. fn join_candidates(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 148_000 @@ -152,6 +164,8 @@ impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking Round (r:1 w:0) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + /// The range of component `n` is `[17, 74]`. + /// The range of component `m` is `[0, 35]`. fn init_leave_candidates(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 169_000 @@ -165,6 +179,8 @@ impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + /// The range of component `n` is `[17, 74]`. + /// The range of component `m` is `[0, 35]`. fn cancel_leave_candidates(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 181_000 @@ -178,6 +194,10 @@ impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking Round (r:1 w:0) // Storage: ParachainStaking Unstaking (r:36 w:36) // Storage: ParachainStaking DelegatorState (r:35 w:35) + // Storage: ParachainStaking RewardCount (r:36 w:36) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking InflationConfig (r:1 w:0) // Storage: Session Validators (r:1 w:0) // Storage: Session DisabledValidators (r:1 w:1) // Storage: System Digest (r:1 w:1) @@ -215,6 +235,11 @@ impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + // Storage: ParachainStaking RewardCount (r:36 w:36) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking InflationConfig (r:1 w:0) + /// The range of component `n` is `[1, 74]`. + /// The range of component `m` is `[0, 35]`. fn candidate_stake_less(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 165_000 @@ -234,6 +259,9 @@ impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + // Storage: ParachainStaking RewardCount (r:1 w:1) + /// The range of component `n` is `[1, 75]`. + /// The range of component `m` is `[1, 34]`. fn join_delegators(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 154_000 @@ -266,6 +294,9 @@ impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + // Storage: ParachainStaking RewardCount (r:2 w:0) + /// The range of component `n` is `[1, 75]`. + /// The range of component `m` is `[1, 34]`. fn delegator_stake_less(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 153_000 @@ -277,25 +308,13 @@ impl parachain_staking::WeightInfo for WeightInfo { } // Storage: ParachainStaking DelegatorState (r:1 w:1) // Storage: ParachainStaking CandidatePool (r:1 w:1) + // Storage: ParachainStaking RewardCount (r:2 w:0) // Storage: ParachainStaking Unstaking (r:1 w:1) // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) - fn revoke_delegation(n: u32, m: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 154_000 - .saturating_add(Weight::from_ref_time(17_790_000 as u64).saturating_mul(n as u64)) - // Standard Error: 342_000 - .saturating_add(Weight::from_ref_time(38_481_000 as u64).saturating_mul(m as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) - } - // Storage: ParachainStaking DelegatorState (r:1 w:1) - // Storage: ParachainStaking CandidatePool (r:1 w:1) - // Storage: ParachainStaking Unstaking (r:1 w:1) - // Storage: ParachainStaking TopCandidates (r:1 w:1) - // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) - // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + /// The range of component `n` is `[1, 75]`. + /// The range of component `m` is `[1, 34]`. fn leave_delegators(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 153_000 @@ -320,4 +339,52 @@ impl parachain_staking::WeightInfo for WeightInfo { Weight::from_ref_time(23_094_000 as u64) .saturating_add(T::DbWeight::get().writes(1 as u64)) } + // Storage: ParachainStaking DelegatorState (r:1 w:0) + // Storage: ParachainStaking RewardCount (r:2 w:1) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking InflationConfig (r:1 w:0) + fn increment_delegator_rewards() -> Weight { + Weight::from_ref_time(25_796_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: ParachainStaking CandidatePool (r:1 w:0) + // Storage: ParachainStaking RewardCount (r:1 w:1) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking InflationConfig (r:1 w:0) + /// The range of component `m` is `[0, 35]`. + fn increment_collator_rewards() -> Weight { + Weight::from_ref_time(366_611_000 as u64) + .saturating_add(T::DbWeight::get().reads(75 as u64)) + .saturating_add(T::DbWeight::get().writes(72 as u64)) + } + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn claim_rewards() -> Weight { + Weight::from_ref_time(29_833_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: ParachainStaking LastRewardReduction (r:1 w:1) + // Storage: ParachainStaking InflationConfig (r:1 w:1) + // Storage: ParachainStaking CandidatePool (r:3 w:0) + // Storage: ParachainStaking RewardCount (r:72 w:72) + // Storage: ParachainStaking Rewards (r:2 w:2) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking CounterForCandidatePool (r:1 w:0) + /// The range of component `n` is `[0, 75]`. + /// The range of component `m` is `[0, 35]`. + fn execute_scheduled_reward_change(n: u32, m: u32, ) -> Weight { + Weight::from_ref_time(0 as u64) + // Standard Error: 5_730_000 + .saturating_add(Weight::from_ref_time(202_623_000 as u64).saturating_mul(n as u64)) + // Standard Error: 12_280_000 + .saturating_add(Weight::from_ref_time(415_436_000 as u64).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads((37 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().reads((75 as u64).saturating_mul(m as u64))) + .saturating_add(T::DbWeight::get().writes((36 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().writes((75 as u64).saturating_mul(m as u64))) + } } diff --git a/runtimes/spiritnet/src/lib.rs b/runtimes/spiritnet/src/lib.rs index 74e0d95f94..e8cab780c9 100644 --- a/runtimes/spiritnet/src/lib.rs +++ b/runtimes/spiritnet/src/lib.rs @@ -648,11 +648,9 @@ impl parachain_staking::Config for Runtime { type MinRequiredCollators = constants::staking::MinRequiredCollators; type MaxDelegationsPerRound = constants::staking::MaxDelegationsPerRound; type MaxDelegatorsPerCollator = constants::staking::MaxDelegatorsPerCollator; - type MaxCollatorsPerDelegator = constants::staking::MaxCollatorsPerDelegator; type MinCollatorStake = constants::staking::MinCollatorStake; type MinCollatorCandidateStake = constants::staking::MinCollatorStake; type MaxTopCandidates = constants::staking::MaxCollatorCandidates; - type MinDelegation = constants::staking::MinDelegatorStake; type MinDelegatorStake = constants::staking::MinDelegatorStake; type MaxUnstakeRequests = constants::staking::MaxUnstakeRequests; type NetworkRewardRate = constants::staking::NetworkRewardRate; @@ -1255,6 +1253,16 @@ impl_runtime_apis! { } } + impl parachain_staking::runtime_api::ParachainStakingApi for Runtime { + fn get_unclaimed_staking_rewards(account: &AccountId) -> Balance { + ParachainStaking::get_unclaimed_staking_rewards(account) + } + + fn get_staking_rates() -> parachain_staking::runtime_api::StakingRates { + ParachainStaking::get_staking_rates() + } + } + #[cfg(feature = "runtime-benchmarks")] impl frame_benchmarking::Benchmark for Runtime { fn benchmark_metadata(extra: bool) -> ( diff --git a/runtimes/spiritnet/src/weights/parachain_staking.rs b/runtimes/spiritnet/src/weights/parachain_staking.rs index 56f94d42a5..9ff79ed390 100644 --- a/runtimes/spiritnet/src/weights/parachain_staking.rs +++ b/runtimes/spiritnet/src/weights/parachain_staking.rs @@ -26,7 +26,7 @@ // ./target/release/kilt-parachain // benchmark // pallet -// --chain=spiritnet-dev +// --chain=dev // --steps=50 // --repeat=20 // --pallet=parachain-staking @@ -34,7 +34,7 @@ // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --output=./runtimes/spiritnet/src/weights/parachain_staking.rs +// --output=./runtimes/peregrine/src/weights/parachain_staking.rs // --template=.maintain/runtime-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -50,7 +50,7 @@ pub struct WeightInfo(PhantomData); impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking Round (r:1 w:0) fn on_initialize_no_action() -> Weight { - Weight::from_ref_time(7_701_000 as u64) + Weight::from_ref_time(3_103_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) } // Storage: ParachainStaking Round (r:1 w:1) @@ -62,14 +62,6 @@ impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking Round (r:1 w:1) // Storage: ParachainStaking LastRewardReduction (r:1 w:1) // Storage: ParachainStaking InflationConfig (r:1 w:1) - fn on_initialize_new_year() -> Weight { - Weight::from_ref_time(35_951_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - } - // Storage: ParachainStaking Round (r:1 w:1) - // Storage: ParachainStaking LastRewardReduction (r:1 w:1) - // Storage: ParachainStaking InflationConfig (r:1 w:1) // Storage: ParachainStaking MaxCollatorCandidateStake (r:1 w:0) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: System Account (r:1 w:1) @@ -83,10 +75,23 @@ impl parachain_staking::WeightInfo for WeightInfo { Weight::from_ref_time(8_791_000 as u64) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: ParachainStaking InflationConfig (r:0 w:1) - fn set_inflation() -> Weight { - Weight::from_ref_time(24_163_000 as u64) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Storage: ParachainStaking CandidatePool (r:3 w:0) + // Storage: ParachainStaking RewardCount (r:72 w:72) + // Storage: ParachainStaking Rewards (r:2 w:2) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking InflationConfig (r:1 w:1) + /// The range of component `n` is `[0, 75]`. + /// The range of component `m` is `[0, 35]`. + fn set_inflation(n: u32, m: u32, ) -> Weight { + Weight::from_ref_time(0 as u64) + // Standard Error: 3_005_000 + .saturating_add(Weight::from_ref_time(216_364_000 as u64).saturating_mul(n as u64)) + // Standard Error: 6_440_000 + .saturating_add(Weight::from_ref_time(440_763_000 as u64).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads((37 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().reads((75 as u64).saturating_mul(m as u64))) + .saturating_add(T::DbWeight::get().writes((36 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().writes((75 as u64).saturating_mul(m as u64))) } // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:1) // Storage: ParachainStaking TopCandidates (r:1 w:0) @@ -111,12 +116,17 @@ impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking Unstaking (r:36 w:36) // Storage: ParachainStaking DelegatorState (r:35 w:35) + // Storage: ParachainStaking RewardCount (r:36 w:36) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + // Storage: ParachainStaking InflationConfig (r:1 w:0) // Storage: Session Validators (r:1 w:0) // Storage: Session DisabledValidators (r:1 w:1) // Storage: System Digest (r:1 w:1) // Storage: ParachainStaking CounterForCandidatePool (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) - // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + /// The range of component `n` is `[17, 75]`. + /// The range of component `m` is `[0, 35]`. fn force_remove_candidate(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 169_000 @@ -138,6 +148,8 @@ impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) // Storage: ParachainStaking CounterForCandidatePool (r:1 w:1) + /// The range of component `n` is `[1, 74]`. + /// The range of component `m` is `[0, 35]`. fn join_candidates(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 148_000 @@ -152,6 +164,8 @@ impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking Round (r:1 w:0) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + /// The range of component `n` is `[17, 74]`. + /// The range of component `m` is `[0, 35]`. fn init_leave_candidates(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 169_000 @@ -165,6 +179,8 @@ impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + /// The range of component `n` is `[17, 74]`. + /// The range of component `m` is `[0, 35]`. fn cancel_leave_candidates(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 181_000 @@ -178,6 +194,10 @@ impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking Round (r:1 w:0) // Storage: ParachainStaking Unstaking (r:36 w:36) // Storage: ParachainStaking DelegatorState (r:35 w:35) + // Storage: ParachainStaking RewardCount (r:36 w:36) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking InflationConfig (r:1 w:0) // Storage: Session Validators (r:1 w:0) // Storage: Session DisabledValidators (r:1 w:1) // Storage: System Digest (r:1 w:1) @@ -215,6 +235,11 @@ impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + // Storage: ParachainStaking RewardCount (r:36 w:36) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking InflationConfig (r:1 w:0) + /// The range of component `n` is `[1, 74]`. + /// The range of component `m` is `[0, 35]`. fn candidate_stake_less(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 165_000 @@ -234,6 +259,9 @@ impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + // Storage: ParachainStaking RewardCount (r:1 w:1) + /// The range of component `n` is `[1, 75]`. + /// The range of component `m` is `[1, 34]`. fn join_delegators(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 154_000 @@ -266,6 +294,9 @@ impl parachain_staking::WeightInfo for WeightInfo { // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + // Storage: ParachainStaking RewardCount (r:2 w:0) + /// The range of component `n` is `[1, 75]`. + /// The range of component `m` is `[1, 34]`. fn delegator_stake_less(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 153_000 @@ -277,25 +308,13 @@ impl parachain_staking::WeightInfo for WeightInfo { } // Storage: ParachainStaking DelegatorState (r:1 w:1) // Storage: ParachainStaking CandidatePool (r:1 w:1) + // Storage: ParachainStaking RewardCount (r:2 w:0) // Storage: ParachainStaking Unstaking (r:1 w:1) // Storage: ParachainStaking TopCandidates (r:1 w:1) // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) - fn revoke_delegation(n: u32, m: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 154_000 - .saturating_add(Weight::from_ref_time(17_790_000 as u64).saturating_mul(n as u64)) - // Standard Error: 342_000 - .saturating_add(Weight::from_ref_time(38_481_000 as u64).saturating_mul(m as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) - } - // Storage: ParachainStaking DelegatorState (r:1 w:1) - // Storage: ParachainStaking CandidatePool (r:1 w:1) - // Storage: ParachainStaking Unstaking (r:1 w:1) - // Storage: ParachainStaking TopCandidates (r:1 w:1) - // Storage: ParachainStaking MaxSelectedCandidates (r:1 w:0) - // Storage: ParachainStaking TotalCollatorStake (r:1 w:1) + /// The range of component `n` is `[1, 75]`. + /// The range of component `m` is `[1, 34]`. fn leave_delegators(n: u32, m: u32, ) -> Weight { Weight::from_ref_time(0 as u64) // Standard Error: 153_000 @@ -320,4 +339,52 @@ impl parachain_staking::WeightInfo for WeightInfo { Weight::from_ref_time(23_094_000 as u64) .saturating_add(T::DbWeight::get().writes(1 as u64)) } + // Storage: ParachainStaking DelegatorState (r:1 w:0) + // Storage: ParachainStaking RewardCount (r:2 w:1) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking InflationConfig (r:1 w:0) + fn increment_delegator_rewards() -> Weight { + Weight::from_ref_time(25_796_000 as u64) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: ParachainStaking CandidatePool (r:1 w:0) + // Storage: ParachainStaking RewardCount (r:1 w:1) + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking InflationConfig (r:1 w:0) + /// The range of component `m` is `[0, 35]`. + fn increment_collator_rewards() -> Weight { + Weight::from_ref_time(366_611_000 as u64) + .saturating_add(T::DbWeight::get().reads(75 as u64)) + .saturating_add(T::DbWeight::get().writes(72 as u64)) + } + // Storage: ParachainStaking Rewards (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn claim_rewards() -> Weight { + Weight::from_ref_time(29_833_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: ParachainStaking LastRewardReduction (r:1 w:1) + // Storage: ParachainStaking InflationConfig (r:1 w:1) + // Storage: ParachainStaking CandidatePool (r:3 w:0) + // Storage: ParachainStaking RewardCount (r:72 w:72) + // Storage: ParachainStaking Rewards (r:2 w:2) + // Storage: ParachainStaking TotalCollatorStake (r:1 w:0) + // Storage: ParachainStaking CounterForCandidatePool (r:1 w:0) + /// The range of component `n` is `[0, 75]`. + /// The range of component `m` is `[0, 35]`. + fn execute_scheduled_reward_change(n: u32, m: u32, ) -> Weight { + Weight::from_ref_time(0 as u64) + // Standard Error: 5_730_000 + .saturating_add(Weight::from_ref_time(202_623_000 as u64).saturating_mul(n as u64)) + // Standard Error: 12_280_000 + .saturating_add(Weight::from_ref_time(415_436_000 as u64).saturating_mul(m as u64)) + .saturating_add(T::DbWeight::get().reads((37 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().reads((75 as u64).saturating_mul(m as u64))) + .saturating_add(T::DbWeight::get().writes((36 as u64).saturating_mul(n as u64))) + .saturating_add(T::DbWeight::get().writes((75 as u64).saturating_mul(m as u64))) + } }