From 45b234cdafc41c99bac9c7d112edc3ea96e9e3c4 Mon Sep 17 00:00:00 2001 From: zqhxuyuan Date: Tue, 19 Mar 2024 15:38:14 +0800 Subject: [PATCH] fix kick lottery Signed-off-by: zqhxuyuan --- Cargo.lock | 16 +- Cargo.toml | 3 +- pallets/pallet-lottery/Cargo.toml | 3 +- pallets/pallet-lottery/src/mock.rs | 20 +- .../src/staking/deposit_strategies.rs | 33 + pallets/pallet-lottery/src/staking/mod.rs | 43 +- .../src/staking/withdraw_strategies.rs | 2 +- pallets/pallet-lottery/src/tests.rs | 772 +++++++++++++++++- runtime/manta/Cargo.toml | 2 +- runtime/manta/src/lib.rs | 2 +- 10 files changed, 885 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff32278b7..3a8039ab1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2790,6 +2790,19 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "env_logger" version = "0.10.1" @@ -2995,7 +3008,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84f2e425d9790201ba4af4630191feac6dcc98765b118d4d18e91d23c2353866" dependencies = [ - "env_logger", + "env_logger 0.10.1", "log", ] @@ -6992,6 +7005,7 @@ name = "pallet-lottery" version = "4.6.1" dependencies = [ "calamari-runtime", + "env_logger 0.8.4", "frame-benchmarking", "frame-support", "frame-system", diff --git a/Cargo.toml b/Cargo.toml index ee910a11e..d98fb099f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,8 @@ strip = "symbols" panic = "unwind" [profile.test] -debug = true +inherits = "release" +debug = 1 [workspace.dependencies] anyhow = { version = "1.0.55", default-features = false } diff --git a/pallets/pallet-lottery/Cargo.toml b/pallets/pallet-lottery/Cargo.toml index b2129d5a4..ec06548cf 100644 --- a/pallets/pallet-lottery/Cargo.toml +++ b/pallets/pallet-lottery/Cargo.toml @@ -53,6 +53,7 @@ rand = { workspace = true } similar-asserts = { workspace = true } sp-staking = { workspace = true, features = ["std"] } xcm = { workspace = true, features = ["std"] } +env_logger = "0.8.3" [features] default = ["std"] @@ -69,6 +70,7 @@ runtime-benchmarks = [ 'rand/std_rng', ] std = [ + "log/std", "manta-primitives/std", "pallet-parachain-staking/std", "pallet-randomness/std", @@ -87,7 +89,6 @@ std = [ "runtime-common/std", "codec/std", "scale-info/std", - "log/std", "sp-staking/std", ] try-runtime = [ diff --git a/pallets/pallet-lottery/src/mock.rs b/pallets/pallet-lottery/src/mock.rs index 0733fa9f9..507d30b3b 100644 --- a/pallets/pallet-lottery/src/mock.rs +++ b/pallets/pallet-lottery/src/mock.rs @@ -60,6 +60,14 @@ pub const BOB: AccountId = 2; pub const CHARLIE: AccountId = 3; pub const DAVE: AccountId = 4; pub const EVE: AccountId = 5; +pub const DELEGATOR1: AccountId = 6; +pub const DELEGATOR2: AccountId = 7; +pub const DELEGATOR3: AccountId = 8; +pub const DELEGATOR4: AccountId = 9; +pub const DELEGATOR5: AccountId = 11; +pub const DELEGATOR6: AccountId = 12; +pub const DELEGATOR7: AccountId = 13; +pub const DELEGATOR8: AccountId = 14; pub const TREASURY_ACCOUNT: AccountId = 10; pub const JUMBO: Balance = 1_000_000_000_000; @@ -287,9 +295,9 @@ impl pallet_parachain_staking::Config for Test { /// Minimum collators selected per round, default at genesis and minimum forever after type MinSelectedCandidates = ConstU32<5>; /// Maximum top delegations per candidate - type MaxTopDelegationsPerCandidate = ConstU32<100>; + type MaxTopDelegationsPerCandidate = ConstU32<4>; /// Maximum bottom delegations per candidate - type MaxBottomDelegationsPerCandidate = ConstU32<50>; + type MaxBottomDelegationsPerCandidate = ConstU32<4>; /// Maximum delegations per delegator type MaxDelegationsPerDelegator = ConstU32<25>; type DefaultCollatorCommission = DefaultCollatorCommission; @@ -607,6 +615,14 @@ impl ExtBuilder { self } + pub(crate) fn with_delegations( + mut self, + delegations: Vec<(AccountId, AccountId, Balance)>, + ) -> Self { + self.delegations = delegations; + self + } + #[allow(dead_code)] pub(crate) fn with_inflation(mut self, inflation: InflationInfo) -> Self { self.inflation = inflation; diff --git a/pallets/pallet-lottery/src/staking/deposit_strategies.rs b/pallets/pallet-lottery/src/staking/deposit_strategies.rs index c8d71d7f8..d716fd0ca 100644 --- a/pallets/pallet-lottery/src/staking/deposit_strategies.rs +++ b/pallets/pallet-lottery/src/staking/deposit_strategies.rs @@ -40,12 +40,45 @@ pub(super) fn reactivate_bottom_collators( let staked = StakedCollators::::get(collator.clone()); let info = pallet_parachain_staking::Pallet::::candidate_info(collator.clone()) .expect("is active collator, therefore it has collator info. qed"); + + // If collator not exist in delegatorState(PotAccount).delegations, ignore + if let Some(state) = + pallet_parachain_staking::Pallet::::delegator_state(crate::Pallet::::account_id()) + { + let mut is_kick = true; + for x in &state.delegations.0 { + if x.owner == collator { + is_kick = false; + break; + } + } + if is_kick { + log::debug!( + "Staked collator:{:?} is kicked off from delegator state, ignore.", + collator + ); + continue; + } + } + log::debug!( + "check collator:{:?} staked:{:?}, lowest top: {:?}", + collator, + staked, + info.lowest_top_delegation_amount + ); + if staked < info.lowest_top_delegation_amount { // TODO: Small optimization: sort collators ascending by missing amount so we get the largest amount of collators active before running out of funds let this_deposit = core::cmp::min( remaining_deposit, info.lowest_top_delegation_amount - staked + 1u32.into(), ); + log::debug!( + "remaining:{:?}, deposit:{:?}, diff:{:?}", + remaining_deposit, + this_deposit, + remaining_deposit - this_deposit + ); // Ensure we don't try to stake a smaller than allowed delegation to a collator if remaining_deposit.saturating_sub(this_deposit) < crate::Pallet::::min_deposit() { deposits.push((collator, remaining_deposit)); // put the full remaining balance in this collator diff --git a/pallets/pallet-lottery/src/staking/mod.rs b/pallets/pallet-lottery/src/staking/mod.rs index 283da502d..796b034bf 100644 --- a/pallets/pallet-lottery/src/staking/mod.rs +++ b/pallets/pallet-lottery/src/staking/mod.rs @@ -77,6 +77,10 @@ impl Pallet { .cloned() .collect::>(); + log::debug!( + "deposit_eligible_collators size: {:?}", + deposit_eligible_collators.len() + ); // first concern: If we fell out of the active set on one or more collators, we need to get back into it deposits.append(&mut deposit_strategies::reactivate_bottom_collators::( deposit_eligible_collators.as_slice(), @@ -89,21 +93,46 @@ impl Pallet { .reduce(|sum, elem| sum + elem) .unwrap_or_else(|| 0u32.into()); + log::debug!( + "after reactivate_bottom_collators deposits: {:?}, remaining: ${:?}", + deposits.len(), + remaining_deposit + ); // If we have re-activated any collators and have leftover funds, we just distribute all surplus tokens to them evenly and call it a day if !deposits.is_empty() { if !remaining_deposit.is_zero() { + log::debug!( + "deposits:{:?} not null and remaining:{:?} not zero", + deposits, + remaining_deposit + ); let deposit_per_collator = Percent::from_rational(1, deposits.len() as u32).mul_ceil(remaining_deposit); // this overshoots the amount if there's a remainder for deposit in &mut deposits { let add = remaining_deposit.saturating_sub(deposit_per_collator); // we correct the overshoot here + log::debug!( + "deposit_per_collator:{:?}, add:{:?}", + deposit_per_collator, + add + ); deposit.1 += add; remaining_deposit -= add; } } + log::debug!( + "deposits:{:?} not null and remaining:{:?}", + deposits, + remaining_deposit + ); return deposits; } // second concern: We want to maximize staking APY earned, so we want to balance the staking pools with our deposits while conserving gas + log::debug!( + "deposit_eligible_collators size:{:?}, remaining_deposit:{:?}", + deposit_eligible_collators.len(), + remaining_deposit + ); deposits.append( &mut deposit_strategies::split_to_underallocated_collators::( deposit_eligible_collators.as_slice(), @@ -115,6 +144,10 @@ impl Pallet { .map(|deposit| deposit.1) .reduce(|sum, elem| sum + elem) .unwrap_or_else(|| 0u32.into()); + log::debug!( + "after split_to_underallocated_collators, remain: ${:?}", + remaining_deposit + ); // fallback: just assign to a random active collator ( choose a different collator for each invocation ) if !remaining_deposit.is_zero() { log::warn!( @@ -238,8 +271,12 @@ impl Pallet { }; let delegation_count = StakedCollators::::iter_keys().count() as u32; - // If we're already delegated to this collator, we must call `delegate_more` + // If we're already delegated to this collator, we must call `delegator_bond_more`. if StakedCollators::::get(&collator).is_zero() { + log::debug!( + "delegator not staked on collator:{:?}, use delegate", + collator + ); // Ensure the pallet has enough gas to pay for this let fee_estimate: BalanceOf = T::EstimateCallFee::estimate_call_fee( &pallet_parachain_staking::Call::delegate { @@ -271,6 +308,10 @@ impl Pallet { e.error })?; } else { + log::debug!( + "delegator already staked on collator:{:?}, use delegator_bond_more", + collator + ); // Ensure the pallet has enough gas to pay for this let fee_estimate: BalanceOf = T::EstimateCallFee::estimate_call_fee( &pallet_parachain_staking::Call::delegator_bond_more { diff --git a/pallets/pallet-lottery/src/staking/withdraw_strategies.rs b/pallets/pallet-lottery/src/staking/withdraw_strategies.rs index 9c6602539..75a1ea93a 100644 --- a/pallets/pallet-lottery/src/staking/withdraw_strategies.rs +++ b/pallets/pallet-lottery/src/staking/withdraw_strategies.rs @@ -83,7 +83,7 @@ pub(super) fn unstake_least_apy_collators( binfo.total_counted.cmp(&ainfo.total_counted) }); log::debug!( - "Active collators: {:?}", + "Active collators size: {:?}", apy_ordered_active_collators_we_are_staked_with.len() ); for c in apy_ordered_active_collators_we_are_staked_with { diff --git a/pallets/pallet-lottery/src/tests.rs b/pallets/pallet-lottery/src/tests.rs index 24078450a..dcd7ee3ce 100644 --- a/pallets/pallet-lottery/src/tests.rs +++ b/pallets/pallet-lottery/src/tests.rs @@ -19,8 +19,9 @@ use crate::{ mock::{ roll_one_block, roll_to, roll_to_round_begin, roll_to_round_end, AccountId, Assets, Balance, Balances, ExtBuilder, Farming, Lottery, ParachainStaking, RuntimeOrigin as Origin, - System, Test, ALICE, BOB, CHARLIE, DAVE, EVE, INIT_JUMBO_AMOUNT, INIT_V_MANTA_AMOUNT, - JUMBO_ID, POOL_ID, V_MANTA_ID, + System, Test, ALICE, BOB, CHARLIE, DAVE, DELEGATOR1, DELEGATOR2, DELEGATOR3, DELEGATOR4, + DELEGATOR5, DELEGATOR6, DELEGATOR7, DELEGATOR8, EVE, INIT_JUMBO_AMOUNT, + INIT_V_MANTA_AMOUNT, JUMBO_ID, POOL_ID, V_MANTA_ID, }, Config, Error, FarmingParameters, }; @@ -865,6 +866,773 @@ fn multiround_withdraw_partial_with_two_collators_works() { }); } +#[test] +fn delegator_less_than_bottom_cannot_deposit() { + let reserve = 10_000 * UNIT; + let balance = 500_000_000 * UNIT; + let quarter_balance = 125_000_000 * UNIT; + let delegate_amt = 100_000_000 * UNIT; + let delegate_amt_1 = 10_000_000 * UNIT; + ExtBuilder::default() + .with_balances(vec![ + (ALICE, HIGH_BALANCE), + (BOB, HIGH_BALANCE), + (CHARLIE, HIGH_BALANCE), + (DAVE, HIGH_BALANCE), + (DELEGATOR1, HIGH_BALANCE), + (DELEGATOR2, HIGH_BALANCE), + (DELEGATOR3, HIGH_BALANCE), + (DELEGATOR4, HIGH_BALANCE), + (DELEGATOR5, HIGH_BALANCE), + (DELEGATOR6, HIGH_BALANCE), + (DELEGATOR7, HIGH_BALANCE), + (DELEGATOR8, HIGH_BALANCE), + ]) + .with_candidates(vec![(BOB, balance), (CHARLIE, balance)]) + .with_delegations(vec![ + (DELEGATOR1, BOB, delegate_amt), + (DELEGATOR2, BOB, delegate_amt), + (DELEGATOR3, BOB, delegate_amt), + (DELEGATOR4, BOB, delegate_amt), + (DELEGATOR5, BOB, delegate_amt), + (DELEGATOR6, BOB, delegate_amt), + (DELEGATOR7, BOB, delegate_amt), + (DELEGATOR8, BOB, delegate_amt), + (DELEGATOR1, CHARLIE, delegate_amt), + (DELEGATOR2, CHARLIE, delegate_amt), + (DELEGATOR3, CHARLIE, delegate_amt), + (DELEGATOR4, CHARLIE, delegate_amt), + (DELEGATOR5, CHARLIE, delegate_amt), + (DELEGATOR6, CHARLIE, delegate_amt), + (DELEGATOR7, CHARLIE, delegate_amt), + (DELEGATOR8, CHARLIE, delegate_amt), + ]) + .with_funded_lottery_account(reserve) + .build() + .execute_with(|| { + assert_noop!( + Lottery::deposit(Origin::signed(ALICE), delegate_amt_1), + pallet_parachain_staking::Error::::CannotDelegateLessThanOrEqualToLowestBottomWhenFull + ); + assert_eq!(crate::StakedCollators::::iter().count(), 0); + }); +} + +#[test] +fn delegator_more_than_bottom_can_deposit_twocollator_secondfailed() { + let reserve = 10_000 * UNIT; + let balance = 500_000_000 * UNIT; + let quarter_balance = 125_000_000 * UNIT; + let delegate_amt = 100_000_000 * UNIT; + let delegate_amt_1 = 10_000_000 * UNIT; + ExtBuilder::default() + .with_balances(vec![ + (ALICE, HIGH_BALANCE), + (BOB, HIGH_BALANCE), + (CHARLIE, HIGH_BALANCE), + (DAVE, HIGH_BALANCE), + (DELEGATOR1, HIGH_BALANCE), + (DELEGATOR2, HIGH_BALANCE), + (DELEGATOR3, HIGH_BALANCE), + (DELEGATOR4, HIGH_BALANCE), + (DELEGATOR5, HIGH_BALANCE), + (DELEGATOR6, HIGH_BALANCE), + (DELEGATOR7, HIGH_BALANCE), + (DELEGATOR8, HIGH_BALANCE), + ]) + .with_candidates(vec![(BOB, balance), (CHARLIE, balance)]) + .with_delegations(vec![ + (DELEGATOR1, BOB, delegate_amt), + (DELEGATOR2, BOB, delegate_amt), + (DELEGATOR3, BOB, delegate_amt), + (DELEGATOR4, BOB, delegate_amt), + (DELEGATOR5, BOB, delegate_amt), + (DELEGATOR6, BOB, delegate_amt), + (DELEGATOR7, BOB, delegate_amt), + (DELEGATOR8, BOB, delegate_amt), + (DELEGATOR1, CHARLIE, delegate_amt), + (DELEGATOR2, CHARLIE, delegate_amt), + (DELEGATOR3, CHARLIE, delegate_amt), + (DELEGATOR4, CHARLIE, delegate_amt), + (DELEGATOR5, CHARLIE, delegate_amt), + (DELEGATOR6, CHARLIE, delegate_amt), + (DELEGATOR7, CHARLIE, delegate_amt), + (DELEGATOR8, CHARLIE, delegate_amt), + ]) + .with_funded_lottery_account(reserve) + .build() + .execute_with(|| { + assert_ok!(Lottery::deposit(Origin::signed(ALICE), quarter_balance)); + assert_eq!(crate::StakedCollators::::iter().count(), 1); + // After deposit, DELEGATOR8 is kicked cause he is the last one on bottom. + + // staked:125, lowestTop=100 + // If first deposit choose Bob as collator, then second will chose Charlie as collator. + // deposit failed cause his deposit balance is less than smallest bottom + assert_noop!( + Lottery::deposit(Origin::signed(DELEGATOR8), delegate_amt), + pallet_parachain_staking::Error::::CannotDelegateLessThanOrEqualToLowestBottomWhenFull + ); + assert_eq!(crate::StakedCollators::::iter().count(), 1); + + assert_ok!(Lottery::deposit(Origin::signed(DELEGATOR8), delegate_amt+1)); + }); +} + +#[test] +fn delegator_more_than_bottom_can_deposit_onecollator_secondok() { + let reserve = 10_000 * UNIT; + let balance = 500_000_000 * UNIT; + let quarter_balance = 125_000_000 * UNIT; + let delegate_amt = 100_000_000 * UNIT; + let delegation_min = 5_000 * UNIT; + ExtBuilder::default() + .with_balances(vec![ + (ALICE, HIGH_BALANCE), + (BOB, HIGH_BALANCE), + (CHARLIE, HIGH_BALANCE), + (DAVE, HIGH_BALANCE), + (DELEGATOR1, HIGH_BALANCE), + (DELEGATOR2, HIGH_BALANCE), + (DELEGATOR3, HIGH_BALANCE), + (DELEGATOR4, HIGH_BALANCE), + (DELEGATOR5, HIGH_BALANCE), + (DELEGATOR6, HIGH_BALANCE), + (DELEGATOR7, HIGH_BALANCE), + (DELEGATOR8, HIGH_BALANCE), + ]) + .with_candidates(vec![(BOB, balance)]) + .with_delegations(vec![ + (DELEGATOR1, BOB, delegate_amt), + (DELEGATOR2, BOB, delegate_amt), + (DELEGATOR3, BOB, delegate_amt), + (DELEGATOR4, BOB, delegate_amt), + (DELEGATOR5, BOB, delegate_amt), + (DELEGATOR6, BOB, delegate_amt), + (DELEGATOR7, BOB, delegate_amt), + (DELEGATOR8, BOB, delegate_amt), + ]) + .with_funded_lottery_account(reserve) + .build() + .execute_with(|| { + assert_ok!(Lottery::deposit(Origin::signed(ALICE), quarter_balance)); + assert_eq!(crate::StakedCollators::::iter().count(), 1); + + assert_ok!(Lottery::deposit(Origin::signed(ALICE), delegate_amt)); + assert_eq!(crate::StakedCollators::::iter().count(), 1); + + assert_ok!(Lottery::deposit(Origin::signed(ALICE), delegation_min)); + assert_eq!(crate::StakedCollators::::iter().count(), 1); + }); +} + +#[test] +fn delegator_kicked_not_in_state_cannot_deposit() { + let reserve = 10_000 * UNIT; + let balance = 500_000_000 * UNIT; + let quarter_balance = 125_000_000 * UNIT; + ExtBuilder::default() + .with_balances(vec![ + (ALICE, HIGH_BALANCE), + (BOB, HIGH_BALANCE), + (CHARLIE, HIGH_BALANCE), + (DAVE, HIGH_BALANCE), + (DELEGATOR1, HIGH_BALANCE), + (DELEGATOR2, HIGH_BALANCE), + (DELEGATOR3, HIGH_BALANCE), + (DELEGATOR4, HIGH_BALANCE), + (DELEGATOR5, HIGH_BALANCE), + (DELEGATOR6, HIGH_BALANCE), + (DELEGATOR7, HIGH_BALANCE), + (DELEGATOR8, HIGH_BALANCE), + ]) + .with_candidates(vec![(BOB, balance)]) + .with_delegations(vec![ + (DELEGATOR1, BOB, 800_000_000 * UNIT), + (DELEGATOR2, BOB, 700_000_000 * UNIT), + (DELEGATOR3, BOB, 600_000_000 * UNIT), + (DELEGATOR4, BOB, 500_000_000 * UNIT), + (DELEGATOR5, BOB, 400_000_000 * UNIT), + (DELEGATOR6, BOB, 300_000_000 * UNIT), + (DELEGATOR7, BOB, 200_000_000 * UNIT), + (DELEGATOR8, BOB, 100_000_000 * UNIT) + ]) + .with_funded_lottery_account(reserve) + .build() + .execute_with(|| { + // Large than lowest bottom, kicked DELEGATOR8 + assert_ok!(Lottery::deposit(Origin::signed(ALICE), 150_000_000 * UNIT)); + assert_eq!(crate::StakedCollators::::iter().count(), 1); + + // Check PotAmount state, exist! + let pot_state1 = ParachainStaking::delegator_state(&crate::Pallet::::account_id()).expect("just delegated => exists"); + assert_eq!(pot_state1.delegations.0[0].owner, BOB); + assert_eq!(pot_state1.delegations.0[0].amount, 150_000_000 * UNIT); + + // If delegator less than lowest bottom: 150, failed + assert_noop!(ParachainStaking::delegate( + Origin::signed(CHARLIE), + BOB, + 100_000_000 * UNIT, + 8, + 0 + ),pallet_parachain_staking::Error::::CannotDelegateLessThanOrEqualToLowestBottomWhenFull); + + // If delegator large than lowest bottom: 150, ok + assert_ok!(ParachainStaking::delegate( + Origin::signed(CHARLIE), + BOB, + 160_000_000 * UNIT, + 8, + 0 + )); + // Check Charlie state, exist! + let charlie_state = ParachainStaking::delegator_state(CHARLIE).expect("just delegated => exists"); + assert_eq!(charlie_state.delegations.0[0].owner, BOB); + assert_eq!(charlie_state.delegations.0[0].amount, 160_000_000 * UNIT); + + // Check PotAmount state, not exist! + let _pot_state2 = ParachainStaking::delegator_state(&crate::Pallet::::account_id()); + assert!(_pot_state2.is_none()); + + // Delegator: Pot Account is not exist on DelegatorState. + // Now Pot account is kicked, try to deposit, failed! + // In this case, we should delegate instead bond more. + assert_noop!( + Lottery::deposit(Origin::signed(ALICE), 150_000_000 * UNIT), + pallet_parachain_staking::Error::::DelegatorDNE + ); + }); +} + +#[test] +fn delegator_state_eq_lowest_top_choose_diff_collator() { + let reserve = 10_000 * UNIT; + let balance = 500_000_000 * UNIT; + let quarter_balance = 125_000_000 * UNIT; + ExtBuilder::default() + .with_balances(vec![ + (ALICE, HIGH_BALANCE), + (BOB, HIGH_BALANCE), + (CHARLIE, HIGH_BALANCE), + (DAVE, HIGH_BALANCE), + (DELEGATOR1, HIGH_BALANCE), + (DELEGATOR2, HIGH_BALANCE), + (DELEGATOR3, HIGH_BALANCE), + (DELEGATOR4, HIGH_BALANCE), + (DELEGATOR5, HIGH_BALANCE), + (DELEGATOR6, HIGH_BALANCE), + (DELEGATOR7, HIGH_BALANCE), + (DELEGATOR8, HIGH_BALANCE), + ]) + .with_candidates(vec![(BOB, balance), (CHARLIE, balance)]) + .with_delegations(vec![ + (DELEGATOR1, BOB, 80_000_000 * UNIT), + (DELEGATOR2, BOB, 70_000_000 * UNIT), + (DELEGATOR3, BOB, 60_000_000 * UNIT), + (DELEGATOR4, BOB, 50_000_000 * UNIT), + (DELEGATOR5, BOB, 40_000_000 * UNIT), + (DELEGATOR6, BOB, 30_000_000 * UNIT), + (DELEGATOR7, BOB, 20_000_000 * UNIT), + (DELEGATOR1, CHARLIE, 80_000_000 * UNIT), + (DELEGATOR2, CHARLIE, 70_000_000 * UNIT), + (DELEGATOR3, CHARLIE, 60_000_000 * UNIT), + (DELEGATOR4, CHARLIE, 50_000_000 * UNIT), + (DELEGATOR5, CHARLIE, 40_000_000 * UNIT), + (DELEGATOR6, CHARLIE, 30_000_000 * UNIT), + (DELEGATOR7, CHARLIE, 20_000_000 * UNIT), + ]) + .with_funded_lottery_account(reserve) + .build() + .execute_with(|| { + // 51 large than top lowest 50, 50 will be top bottom now + assert_ok!(Lottery::deposit(Origin::signed(ALICE), 51_000_000 * UNIT)); + assert_eq!(crate::StakedCollators::::iter().count(), 1); + + // staked: 51 eq to lowest top: 51, choose another collator + assert_ok!(Lottery::deposit(Origin::signed(ALICE), 51_000_000 * UNIT)); + assert_eq!(crate::StakedCollators::::iter().count(), 2); + + // third deposit, both collator's staked and lowest top is 51, choose random one. + assert_ok!(Lottery::deposit(Origin::signed(ALICE), 51_000_000 * UNIT)); + assert_eq!(crate::StakedCollators::::iter().count(), 2); + }); +} + +#[test] +fn delegator_kicked_when_reactivate_bottom_should_ignored() { + let reserve = 10_000 * UNIT; + let balance = 500_000_000 * UNIT; + ExtBuilder::default() + .with_balances(vec![ + (ALICE, HIGH_BALANCE), + (BOB, HIGH_BALANCE), + (CHARLIE, HIGH_BALANCE), + (DAVE, HIGH_BALANCE), + (DELEGATOR1, HIGH_BALANCE), + (DELEGATOR2, HIGH_BALANCE), + (DELEGATOR3, HIGH_BALANCE), + (DELEGATOR4, HIGH_BALANCE), + (DELEGATOR5, HIGH_BALANCE), + (DELEGATOR6, HIGH_BALANCE), + (DELEGATOR7, HIGH_BALANCE), + (DELEGATOR8, HIGH_BALANCE), + ]) + .with_candidates(vec![(BOB, balance), (CHARLIE, balance)]) + .with_delegations(vec![ + (DELEGATOR1, BOB, 80_000_000 * UNIT), + (DELEGATOR2, BOB, 70_000_000 * UNIT), + (DELEGATOR3, BOB, 60_000_000 * UNIT), + (DELEGATOR4, BOB, 50_000_000 * UNIT), + (DELEGATOR1, CHARLIE, 80_000_000 * UNIT), + (DELEGATOR2, CHARLIE, 70_000_000 * UNIT), + (DELEGATOR3, CHARLIE, 60_000_000 * UNIT), + (DELEGATOR4, CHARLIE, 50_000_000 * UNIT), + ]) + .with_funded_lottery_account(reserve) + .build() + .execute_with(|| { + assert_ok!(Lottery::deposit(Origin::signed(ALICE), 51_000_000 * UNIT)); + assert_eq!(crate::StakedCollators::::iter().count(), 1); + + assert_ok!(Lottery::deposit(Origin::signed(ALICE), 51_000_000 * UNIT)); + assert_eq!(crate::StakedCollators::::iter().count(), 2); + + // Pot Account delegate to two collators. + let pot_state1 = + ParachainStaking::delegator_state(&crate::Pallet::::account_id()) + .expect("just delegated => exists"); + assert_eq!(pot_state1.delegations.len(), 2); + assert_eq!(pot_state1.delegations.0[0].amount, 51_000_000 * UNIT); + assert_eq!(pot_state1.delegations.0[1].amount, 51_000_000 * UNIT); + + assert_ok!(ParachainStaking::delegate( + Origin::signed(DELEGATOR5), + BOB, + 52_000_000 * UNIT, + 5, + 0 + )); + assert_ok!(ParachainStaking::delegate( + Origin::signed(DELEGATOR6), + BOB, + 52_000_000 * UNIT, + 6, + 0 + )); + assert_ok!(ParachainStaking::delegate( + Origin::signed(DELEGATOR7), + BOB, + 52_000_000 * UNIT, + 7, + 0 + )); + assert_ok!(ParachainStaking::delegate( + Origin::signed(DELEGATOR8), + BOB, + 52_000_000 * UNIT, + 8, + 0 + )); + assert_ok!(ParachainStaking::delegate( + Origin::signed(DAVE), + BOB, + 52_000_000 * UNIT, + 9, + 0 + )); + // Pot Account now is kicked from collator BOB's delegator list. + // But Pot Account is still on the delegator list of CHARLIE. + let pot_state2 = + ParachainStaking::delegator_state(&crate::Pallet::::account_id()) + .expect("just delegated => exists"); + assert_eq!(pot_state2.delegations.len(), 1); + assert_eq!(pot_state2.delegations.0[0].owner, CHARLIE); + assert_eq!(pot_state2.delegations.0[0].amount, 51_000_000 * UNIT); + + // StakedCollators didn't remove BOB. + assert_eq!(crate::StakedCollators::::iter().count(), 2); + assert_eq!(crate::StakedCollators::::get(&BOB), 51_000_000 * UNIT); + assert_eq!( + crate::StakedCollators::::get(&CHARLIE), + 51_000_000 * UNIT + ); + + let collator1_state = ParachainStaking::candidate_info(BOB).unwrap(); + let collator2_state = ParachainStaking::candidate_info(CHARLIE).unwrap(); + assert_eq!( + collator1_state.lowest_bottom_delegation_amount, + 52_000_000 * UNIT + ); + assert_eq!( + collator2_state.lowest_bottom_delegation_amount, + 50_000_000 * UNIT + ); + assert_eq!( + collator1_state.lowest_top_delegation_amount, + 52_000_000 * UNIT + ); + assert_eq!( + collator2_state.lowest_top_delegation_amount, + 51_000_000 * UNIT + ); + + let _pot_state2 = + ParachainStaking::delegator_state(&crate::Pallet::::account_id()) + .expect("just delegated => exists"); + assert_eq!(_pot_state2.delegations.len(), 1); + assert_eq!(_pot_state2.delegations.0[0].owner, CHARLIE); + assert_eq!(_pot_state2.delegations.0[0].amount, 51_000_000 * UNIT); + + // Deposit should only delegate to CHRALIE. + assert_ok!(Lottery::deposit(Origin::signed(ALICE), 53_000_000 * UNIT)); + assert_eq!(crate::StakedCollators::::get(&BOB), 51_000_000 * UNIT); + assert_eq!( + crate::StakedCollators::::get(&CHARLIE), + 104_000_000 * UNIT + ); + + let _pot_state3 = + ParachainStaking::delegator_state(&crate::Pallet::::account_id()) + .expect("just delegated => exists"); + assert_eq!(_pot_state3.delegations.len(), 1); + assert_eq!(_pot_state3.delegations.0[0].owner, CHARLIE); + assert_eq!(_pot_state3.delegations.0[0].amount, 104_000_000 * UNIT); + }); +} + +#[test] +fn delegator_unstaking_then_kicked_should_ignored() { + let reserve = 10_000 * UNIT; + let balance = 500_000_000 * UNIT; + ExtBuilder::default() + .with_balances(vec![ + (ALICE, HIGH_BALANCE), + (BOB, HIGH_BALANCE), + (CHARLIE, HIGH_BALANCE), + (DAVE, HIGH_BALANCE), + (DELEGATOR1, HIGH_BALANCE), + (DELEGATOR2, HIGH_BALANCE), + (DELEGATOR3, HIGH_BALANCE), + (DELEGATOR4, HIGH_BALANCE), + (DELEGATOR5, HIGH_BALANCE), + (DELEGATOR6, HIGH_BALANCE), + (DELEGATOR7, HIGH_BALANCE), + (DELEGATOR8, HIGH_BALANCE), + ]) + .with_candidates(vec![(BOB, balance), (CHARLIE, balance)]) + .with_delegations(vec![ + (DELEGATOR1, BOB, 80_000_000 * UNIT), + (DELEGATOR2, BOB, 70_000_000 * UNIT), + (DELEGATOR3, BOB, 60_000_000 * UNIT), + (DELEGATOR4, BOB, 50_000_000 * UNIT), + (DELEGATOR1, CHARLIE, 80_000_000 * UNIT), + (DELEGATOR2, CHARLIE, 70_000_000 * UNIT), + (DELEGATOR3, CHARLIE, 60_000_000 * UNIT), + (DELEGATOR4, CHARLIE, 50_000_000 * UNIT), + ]) + .with_funded_lottery_account(reserve) + .build() + .execute_with(|| { + assert_ok!(Lottery::deposit(Origin::signed(ALICE), 51_000_000 * UNIT)); + assert_eq!(crate::StakedCollators::::iter().count(), 1); + + assert_ok!(Lottery::deposit(Origin::signed(ALICE), 51_000_000 * UNIT)); + assert_eq!(crate::StakedCollators::::iter().count(), 2); + + // unstaking from CHARLIE + assert_ok!(Lottery::request_withdraw( + Origin::signed(ALICE), + 51_000_000 * UNIT + )); + assert_eq!(crate::StakedCollators::::iter().count(), 2); + assert_eq!(crate::UnstakingCollators::::get().len(), 1); + + // Pot Account delegate to two collators. + let pot_state1 = + ParachainStaking::delegator_state(&crate::Pallet::::account_id()) + .expect("just delegated => exists"); + assert_eq!(pot_state1.delegations.len(), 2); + assert_eq!(pot_state1.delegations.0[0].amount, 51_000_000 * UNIT); + assert_eq!(pot_state1.delegations.0[1].amount, 51_000_000 * UNIT); + + assert_ok!(ParachainStaking::delegate( + Origin::signed(DELEGATOR5), + BOB, + 53_000_000 * UNIT, + 5, + 0 + )); + assert_ok!(ParachainStaking::delegate( + Origin::signed(DELEGATOR6), + BOB, + 53_000_000 * UNIT, + 6, + 0 + )); + assert_ok!(ParachainStaking::delegate( + Origin::signed(DELEGATOR7), + BOB, + 53_000_000 * UNIT, + 7, + 0 + )); + assert_ok!(ParachainStaking::delegate( + Origin::signed(DELEGATOR8), + BOB, + 53_000_000 * UNIT, + 8, + 0 + )); + assert_ok!(ParachainStaking::delegate( + Origin::signed(DAVE), + BOB, + 53_000_000 * UNIT, + 9, + 0 + )); + let pot_state2 = + ParachainStaking::delegator_state(&crate::Pallet::::account_id()) + .expect("just delegated => exists"); + assert_eq!(pot_state2.delegations.len(), 1); + assert_eq!(pot_state2.delegations.0[0].owner, CHARLIE); + assert_eq!(pot_state2.delegations.0[0].amount, 51_000_000 * UNIT); + + assert_eq!(crate::StakedCollators::::iter().count(), 2); + + let collator1_state = ParachainStaking::candidate_info(BOB).unwrap(); + let collator2_state = ParachainStaking::candidate_info(CHARLIE).unwrap(); + assert_eq!( + collator1_state.lowest_top_delegation_amount, + 53_000_000 * UNIT + ); + assert_eq!( + collator1_state.lowest_bottom_delegation_amount, + 53_000_000 * UNIT + ); + assert_eq!( + collator2_state.lowest_top_delegation_amount, + 51_000_000 * UNIT + ); + assert_eq!( + collator2_state.lowest_bottom_delegation_amount, + 50_000_000 * UNIT + ); + + // Check PotAmount state, exist! + let _pot_state2 = + ParachainStaking::delegator_state(&crate::Pallet::::account_id()) + .expect("just delegated => exists"); + assert_eq!(_pot_state2.delegations.len(), 1); + assert_eq!(_pot_state2.delegations.0[0].owner, CHARLIE); + assert_eq!(_pot_state2.delegations.0[0].amount, 51_000_000 * UNIT); + + // Staked to BOB was already kicked Pot Account. + // And staked to CHARLIE is unstaking, now there're no collators to deposit. + // If we choose random one which is BOB, then delegator_bond_more has error. + assert_noop!( + Lottery::deposit(Origin::signed(ALICE), 53_000_000 * UNIT), + pallet_parachain_staking::Error::::DelegationDNE + ); + + // Check PotAmount state, exist! + let _pot_state3 = + ParachainStaking::delegator_state(&crate::Pallet::::account_id()) + .expect("just delegated => exists"); + assert_eq!(_pot_state3.delegations.len(), 1); + assert_eq!(_pot_state3.delegations.0[0].owner, CHARLIE); + assert_eq!(_pot_state3.delegations.0[0].amount, 51_000_000 * UNIT); + }); +} + +#[test] +fn delegator_unstaking_kicked_same_collator_should_ignored() { + let reserve = 10_000 * UNIT; + let balance = 500_000_000 * UNIT; + ExtBuilder::default() + .with_balances(vec![ + (ALICE, HIGH_BALANCE), + (BOB, HIGH_BALANCE), + (CHARLIE, HIGH_BALANCE), + (DAVE, HIGH_BALANCE), + (DELEGATOR1, HIGH_BALANCE), + (DELEGATOR2, HIGH_BALANCE), + (DELEGATOR3, HIGH_BALANCE), + (DELEGATOR4, HIGH_BALANCE), + (DELEGATOR5, HIGH_BALANCE), + (DELEGATOR6, HIGH_BALANCE), + (DELEGATOR7, HIGH_BALANCE), + (DELEGATOR8, HIGH_BALANCE), + ]) + .with_candidates(vec![(BOB, balance), (CHARLIE, balance)]) + .with_delegations(vec![ + (DELEGATOR1, BOB, 80_000_000 * UNIT), + (DELEGATOR2, BOB, 70_000_000 * UNIT), + (DELEGATOR3, BOB, 60_000_000 * UNIT), + (DELEGATOR4, BOB, 50_000_000 * UNIT), + (DELEGATOR1, CHARLIE, 80_000_000 * UNIT), + (DELEGATOR2, CHARLIE, 70_000_000 * UNIT), + (DELEGATOR3, CHARLIE, 60_000_000 * UNIT), + (DELEGATOR4, CHARLIE, 50_000_000 * UNIT), + ]) + .with_funded_lottery_account(reserve) + .build() + .execute_with(|| { + assert_ok!(Lottery::deposit(Origin::signed(ALICE), 52_000_000 * UNIT)); + assert_eq!(crate::StakedCollators::::iter().count(), 1); + + assert_ok!(Lottery::deposit(Origin::signed(ALICE), 51_000_000 * UNIT)); + assert_eq!(crate::StakedCollators::::iter().count(), 2); + + // unstaking from BOB + assert_ok!(Lottery::request_withdraw( + Origin::signed(ALICE), + 52_000_000 * UNIT + )); + assert_eq!(crate::StakedCollators::::iter().count(), 2); + assert_eq!(crate::UnstakingCollators::::get().len(), 1); + + // Pot Account delegate to two collators. + let pot_state1 = + ParachainStaking::delegator_state(&crate::Pallet::::account_id()) + .expect("just delegated => exists"); + assert_eq!(pot_state1.delegations.len(), 2); + + let firstDepositStakedToBob = pot_state1.delegations.0[0].amount == 52_000_000 * UNIT; + + // Can't ensure which collator accept which deposit. + // Either Bob get 52 and Charlie get 51, or Bob get 51 and Charlie get 52. + if firstDepositStakedToBob { + assert_eq!(pot_state1.delegations.0[1].amount, 51_000_000 * UNIT); + } else { + assert_eq!(pot_state1.delegations.0[1].amount, 52_000_000 * UNIT); + } + + assert_ok!(ParachainStaking::delegate( + Origin::signed(DELEGATOR5), + BOB, + 53_000_000 * UNIT, + 5, + 0 + )); + assert_ok!(ParachainStaking::delegate( + Origin::signed(DELEGATOR6), + BOB, + 53_000_000 * UNIT, + 6, + 0 + )); + assert_ok!(ParachainStaking::delegate( + Origin::signed(DELEGATOR7), + BOB, + 53_000_000 * UNIT, + 7, + 0 + )); + assert_ok!(ParachainStaking::delegate( + Origin::signed(DELEGATOR8), + BOB, + 53_000_000 * UNIT, + 8, + 0 + )); + assert_ok!(ParachainStaking::delegate( + Origin::signed(DAVE), + BOB, + 53_000_000 * UNIT, + 9, + 0 + )); + let pot_state2 = + ParachainStaking::delegator_state(&crate::Pallet::::account_id()) + .expect("just delegated => exists"); + assert_eq!(pot_state2.delegations.len(), 1); + // We can ensure that Pot Account now only staked to Charlie, but can't ensure the staked amount. + assert_eq!(pot_state2.delegations.0[0].owner, CHARLIE); + if firstDepositStakedToBob { + assert_eq!(pot_state2.delegations.0[0].amount, 51_000_000 * UNIT); + } else { + assert_eq!(pot_state2.delegations.0[0].amount, 52_000_000 * UNIT); + } + + assert_eq!(crate::StakedCollators::::iter().count(), 2); + + let collator1_state = ParachainStaking::candidate_info(BOB).unwrap(); + let collator2_state = ParachainStaking::candidate_info(CHARLIE).unwrap(); + if firstDepositStakedToBob { + assert_eq!( + collator1_state.lowest_top_delegation_amount, + 53_000_000 * UNIT + ); + assert_eq!( + collator1_state.lowest_bottom_delegation_amount, + 53_000_000 * UNIT + ); + + assert_eq!( + collator2_state.lowest_top_delegation_amount, + 51_000_000 * UNIT + ); + assert_eq!( + collator2_state.lowest_bottom_delegation_amount, + 50_000_000 * UNIT + ); + } else { + assert_eq!( + collator1_state.lowest_top_delegation_amount, + 53_000_000 * UNIT + ); + assert_eq!( + collator1_state.lowest_bottom_delegation_amount, + 53_000_000 * UNIT + ); + + assert_eq!( + collator2_state.lowest_top_delegation_amount, + 52_000_000 * UNIT + ); + assert_eq!( + collator2_state.lowest_bottom_delegation_amount, + 50_000_000 * UNIT + ); + } + + // Check PotAmount state, exist! + let _pot_state2 = + ParachainStaking::delegator_state(&crate::Pallet::::account_id()) + .expect("just delegated => exists"); + assert_eq!(_pot_state2.delegations.len(), 1); + assert_eq!(_pot_state2.delegations.0[0].owner, CHARLIE); + if firstDepositStakedToBob { + assert_eq!(_pot_state2.delegations.0[0].amount, 51_000_000 * UNIT); + } else { + assert_eq!(_pot_state2.delegations.0[0].amount, 52_000_000 * UNIT); + } + + if firstDepositStakedToBob { + assert_ok!(Lottery::deposit(Origin::signed(ALICE), 53_000_000 * UNIT)); + } else { + assert_noop!( + Lottery::deposit(Origin::signed(ALICE), 53_000_000 * UNIT), + pallet_parachain_staking::Error::::DelegationDNE + ); + } + + // Check PotAmount state, exist! + let _pot_state3 = + ParachainStaking::delegator_state(&crate::Pallet::::account_id()) + .expect("just delegated => exists"); + assert_eq!(_pot_state3.delegations.len(), 1); + assert_eq!(_pot_state3.delegations.0[0].owner, CHARLIE); + + if firstDepositStakedToBob { + assert_eq!(_pot_state3.delegations.0[0].amount, 104_000_000 * UNIT); + } else { + assert_eq!(_pot_state3.delegations.0[0].amount, 52_000_000 * UNIT); + } + }); +} + #[test] #[ignore = "Will fix it in next release"] fn many_deposit_withdrawals_work() { diff --git a/runtime/manta/Cargo.toml b/runtime/manta/Cargo.toml index 0bea1e8c5..f7c28ce88 100644 --- a/runtime/manta/Cargo.toml +++ b/runtime/manta/Cargo.toml @@ -10,7 +10,7 @@ version.workspace = true [dependencies] codec = { workspace = true } hex-literal = { workspace = true, optional = true } -log = { workspace = true } +log = { workspace = true, optional = true } scale-info = { workspace = true } serde = { workspace = true, optional = true } smallvec = { workspace = true } diff --git a/runtime/manta/src/lib.rs b/runtime/manta/src/lib.rs index 22d15a012..585417009 100644 --- a/runtime/manta/src/lib.rs +++ b/runtime/manta/src/lib.rs @@ -145,7 +145,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("manta"), impl_name: create_runtime_str!("manta"), authoring_version: 1, - spec_version: 4611, + spec_version: 4610, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 7,