diff --git a/pallets/funding/src/functions/misc.rs b/pallets/funding/src/functions/misc.rs index 2948a6a51..9bde8e308 100644 --- a/pallets/funding/src/functions/misc.rs +++ b/pallets/funding/src/functions/misc.rs @@ -540,8 +540,6 @@ impl Pallet { Ok((liquidity_pools_reward_pot, long_term_holder_reward_pot)) } - - pub fn migrations_per_xcm_message_allowed() -> u32 { const MAX_WEIGHT: Weight = Weight::from_parts(20_000_000_000, 1_000_000); diff --git a/pallets/funding/src/instantiator/chain_interactions.rs b/pallets/funding/src/instantiator/chain_interactions.rs index bc0fbd707..21cf4b9a3 100644 --- a/pallets/funding/src/instantiator/chain_interactions.rs +++ b/pallets/funding/src/instantiator/chain_interactions.rs @@ -41,6 +41,10 @@ impl< }) } + pub fn get_free_plmc_balance_for(&mut self, user: AccountIdOf) -> BalanceOf { + self.execute(|| ::NativeCurrency::balance(&user)) + } + pub fn get_reserved_plmc_balances_for( &mut self, user_keys: Vec>, @@ -57,6 +61,14 @@ impl< }) } + pub fn get_reserved_plmc_balance_for( + &mut self, + user: AccountIdOf, + lock_type: ::RuntimeHoldReason, + ) -> BalanceOf { + self.execute(|| ::NativeCurrency::balance_on_hold(&lock_type, &user)) + } + pub fn get_free_foreign_asset_balances_for( &mut self, asset_id: AssetIdOf, @@ -88,6 +100,10 @@ impl< }) } + pub fn get_ct_asset_balance_for(&mut self, project_id: ProjectId, user: AccountIdOf) -> BalanceOf { + self.execute(|| ::ContributionTokenCurrency::balance(project_id, &user)) + } + pub fn get_all_free_plmc_balances(&mut self) -> Vec> { let user_keys = self.execute(|| frame_system::Account::::iter_keys().collect()); self.get_free_plmc_balances_for(user_keys) @@ -876,7 +892,7 @@ impl< } } - fn assert_migration( + pub(crate) fn assert_migration( &mut self, project_id: ProjectId, account: AccountIdOf, @@ -885,7 +901,7 @@ impl< participation_type: ParticipationType, should_exist: bool, ) { - let correct = match (should_exist, self.execute(|| UserMigrations::::get(project_id, account.clone()))) { + match (should_exist, self.execute(|| UserMigrations::::get(project_id, account.clone()))) { // User has migrations, so we need to check if any matches our criteria (_, Some((_, migrations))) => { let maybe_migration = migrations.into_iter().find(|migration| { @@ -894,16 +910,23 @@ impl< }); match maybe_migration { // Migration exists so we check if the amount is correct and if it should exist - Some(migration) => migration.info.contribution_token_amount == amount.into() && should_exist, + Some(migration) => { + assert!(should_exist); + assert_close_enough!( + migration.info.contribution_token_amount, + amount.into(), + Perquintill::from_percent(99u64) + ); + }, + // Migration doesn't exist so we check if it should not exist - None => !should_exist, + None => assert!(should_exist), } }, // User does not have any migrations, so the migration should not exist - (false, None) => true, - (true, None) => false, + (false, None) => (), + (true, None) => panic!("No migration should have been found"), }; - assert!(correct); } pub fn create_remainder_contributing_project( diff --git a/pallets/funding/src/tests/6_funding_end.rs b/pallets/funding/src/tests/6_funding_end.rs index 3c3eaed34..dca85e13d 100644 --- a/pallets/funding/src/tests/6_funding_end.rs +++ b/pallets/funding/src/tests/6_funding_end.rs @@ -44,8 +44,6 @@ mod round_flow { let fee_2 = FEE_2 * 4_000_000 * USD_UNIT; let fee_3 = FEE_3 * 4_500_000 * USD_UNIT; - let x = PolimecFunding::calculate_fees(USD_REACHED); - dbg!(x); let total_fee = Perquintill::from_rational(fee_1 + fee_2 + fee_3, USD_REACHED); let total_ct_fee = diff --git a/pallets/funding/src/tests/7_settlement.rs b/pallets/funding/src/tests/7_settlement.rs index a348ac855..aeca7c25c 100644 --- a/pallets/funding/src/tests/7_settlement.rs +++ b/pallets/funding/src/tests/7_settlement.rs @@ -1,160 +1,437 @@ use super::*; -#[test] -fn can_settle_accepted_project() { - let percentage = 100u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); - let evaluations = inst.get_evaluations(project_id); - let bids = inst.get_bids(project_id); - let contributions = inst.get_contributions(project_id); - - inst.settle_project(project_id).unwrap(); - - inst.assert_total_funding_paid_out(project_id, bids.clone(), contributions.clone()); - inst.assert_evaluations_migrations_created(project_id, evaluations, percentage); - inst.assert_bids_migrations_created(project_id, bids, true); - inst.assert_contributions_migrations_created(project_id, contributions, true); +#[cfg(test)] +mod round_flow { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + + #[test] + fn can_fully_settle_accepted_project() { + let percentage = 100u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); + let evaluations = inst.get_evaluations(project_id); + let bids = inst.get_bids(project_id); + let contributions = inst.get_contributions(project_id); + + inst.settle_project(project_id).unwrap(); + + inst.assert_total_funding_paid_out(project_id, bids.clone(), contributions.clone()); + inst.assert_evaluations_migrations_created(project_id, evaluations, percentage); + inst.assert_bids_migrations_created(project_id, bids, true); + inst.assert_contributions_migrations_created(project_id, contributions, true); + } + + #[test] + fn can_fully_settle_failed_project() { + let percentage = 33u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); + let evaluations = inst.get_evaluations(project_id); + let bids = inst.get_bids(project_id); + let contributions = inst.get_contributions(project_id); + + inst.settle_project(project_id).unwrap(); + + inst.assert_evaluations_migrations_created(project_id, evaluations, percentage); + inst.assert_bids_migrations_created(project_id, bids, false); + inst.assert_contributions_migrations_created(project_id, contributions, false); + } + } } -#[test] -fn can_settle_failed_project() { - let percentage = 33u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); - let evaluations = inst.get_evaluations(project_id); - let bids = inst.get_bids(project_id); - let contributions = inst.get_contributions(project_id); +#[cfg(test)] +mod settle_successful_evaluation_extrinsic { + use super::*; - inst.settle_project(project_id).unwrap(); + #[cfg(test)] + mod success { + use super::*; - inst.assert_evaluations_migrations_created(project_id, evaluations, percentage); - inst.assert_bids_migrations_created(project_id, bids, false); - inst.assert_contributions_migrations_created(project_id, contributions, false); -} + #[test] + fn evaluation_unchanged() { + let percentage = 89u64; -#[test] -fn cannot_settle_successful_project_twice() { - let percentage = 100u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); + let (mut inst, project_id) = + create_project_with_funding_percentage(percentage, Some(FundingOutcomeDecision::AcceptFunding)); - let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); - let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); - let first_contribution = inst.get_contributions(project_id).into_iter().next().unwrap(); + let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); + let evaluator = first_evaluation.evaluator; + let prev_balance = inst.get_free_plmc_balance_for(evaluator); - inst.execute(|| { - let evaluator = first_evaluation.evaluator; - assert_ok!(crate::Pallet::::settle_successful_evaluation( - RuntimeOrigin::signed(evaluator), - project_id, - evaluator, - first_evaluation.id - )); - assert_noop!( - crate::Pallet::::settle_successful_evaluation( + assert_eq!( + inst.get_project_details(project_id).evaluation_round_info.evaluators_outcome, + EvaluatorsOutcomeOf::::Unchanged + ); + + assert_ok!(inst.execute(|| PolimecFunding::settle_successful_evaluation( RuntimeOrigin::signed(evaluator), project_id, evaluator, first_evaluation.id - ), - Error::::ParticipationNotFound - ); + ))); - let bidder = first_bid.bidder; - assert_ok!(crate::Pallet::::settle_successful_bid( - RuntimeOrigin::signed(bidder), - project_id, - bidder, - first_bid.id - )); - assert_noop!( - crate::Pallet::::settle_successful_bid( - RuntimeOrigin::signed(bidder), - project_id, - bidder, - first_bid.id - ), - Error::::ParticipationNotFound - ); + let post_balance = inst.get_free_plmc_balance_for(evaluator); + assert_eq!(post_balance, prev_balance + first_evaluation.current_plmc_bond); + } - let contributor = first_contribution.contributor; - assert_ok!(crate::Pallet::::settle_successful_contribution( - RuntimeOrigin::signed(contributor), - project_id, - contributor, - first_contribution.id - )); - assert_noop!( - crate::Pallet::::settle_successful_contribution( - RuntimeOrigin::signed(contributor), - project_id, - contributor, - first_contribution.id - ), - Error::::ParticipationNotFound - ); - }); -} + #[test] + fn evaluation_slashed() { + let percentage = 50u64; + let (mut inst, project_id) = + create_project_with_funding_percentage(percentage, Some(FundingOutcomeDecision::AcceptFunding)); -#[test] -fn cannot_settle_failed_project_twice() { - let percentage = 33u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); + let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); + let evaluator = first_evaluation.evaluator; + let prev_balance = inst.get_free_plmc_balances_for(vec![evaluator])[0].plmc_amount; - let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); - let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); - let first_contribution = inst.get_contributions(project_id).into_iter().next().unwrap(); + assert_eq!( + inst.get_project_details(project_id).evaluation_round_info.evaluators_outcome, + EvaluatorsOutcomeOf::::Slashed + ); - inst.execute(|| { - let evaluator = first_evaluation.evaluator; - assert_ok!(crate::Pallet::::settle_failed_evaluation( - RuntimeOrigin::signed(evaluator), - project_id, - evaluator, - first_evaluation.id - )); - assert_noop!( - crate::Pallet::::settle_failed_evaluation( + assert_ok!(inst.execute(|| PolimecFunding::settle_successful_evaluation( RuntimeOrigin::signed(evaluator), project_id, evaluator, first_evaluation.id - ), - Error::::ParticipationNotFound - ); + ))); - let bidder = first_bid.bidder; - assert_ok!(crate::Pallet::::settle_failed_bid( - RuntimeOrigin::signed(bidder), - project_id, - bidder, - first_bid.id - )); - assert_noop!( - crate::Pallet::::settle_failed_bid( - RuntimeOrigin::signed(bidder), + let post_balance = inst.get_free_plmc_balances_for(vec![evaluator])[0].plmc_amount; + assert_eq!( + post_balance, + prev_balance + + (Percent::from_percent(100) - ::EvaluatorSlash::get()) * + first_evaluation.current_plmc_bond + ); + } + + #[test] + fn evaluation_rewarded() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + let project_id = inst.create_finished_project( + project_metadata.clone(), + ISSUER_1, + vec![ + UserToUSDBalance::new(EVALUATOR_1, 500_000 * USD_UNIT), + UserToUSDBalance::new(EVALUATOR_2, 250_000 * USD_UNIT), + UserToUSDBalance::new(EVALUATOR_3, 320_000 * USD_UNIT), + ], + inst.generate_bids_from_total_ct_percent( + project_metadata.clone(), + 50, + default_weights(), + default_bidders(), + default_multipliers(), + ), + inst.generate_contributions_from_total_ct_percent( + project_metadata.clone(), + 50, + default_weights(), + default_community_contributors(), + default_community_contributor_multipliers(), + ), + vec![], + ); + let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); + inst.jump_to_block(settlement_block); + + let project_details = inst.get_project_details(project_id); + let project_metadata = inst.get_project_metadata(project_id); + const EVAL_1_REWARD: u128 = 10_234_766355140186916; + const EVAL_2_REWARD: u128 = 5_117_383177570093458; + const EVAL_3_REWARD: u128 = 6_247_850467289719626; + + let prev_ct_balances = inst.get_ct_asset_balances_for(project_id, vec![ISSUER_1, ISSUER_2, ISSUER_3]); + assert!(prev_ct_balances.iter().all(|x| *x == Zero::zero())); + + // EVAL 1 + let evaluation_locked_plmc = + inst.get_reserved_plmc_balance_for(EVALUATOR_1, HoldReason::Evaluation(project_id).into()); + let free_plmc = inst.get_free_plmc_balance_for(EVALUATOR_1); + assert_ok!(inst.execute(|| PolimecFunding::settle_successful_evaluation( + RuntimeOrigin::signed(EVALUATOR_1), project_id, - bidder, - first_bid.id - ), - Error::::ParticipationNotFound - ); - - let contributor = first_contribution.contributor; - assert_ok!(crate::Pallet::::settle_failed_contribution( - RuntimeOrigin::signed(contributor), - project_id, - contributor, - first_contribution.id - )); - assert_noop!( - crate::Pallet::::settle_failed_contribution( - RuntimeOrigin::signed(contributor), + EVALUATOR_1, + 0 + ))); + let eval_1_ct_rewarded = inst.get_ct_asset_balance_for(project_id, EVALUATOR_1); + assert_close_enough!(eval_1_ct_rewarded, EVAL_1_REWARD, Perquintill::from_float(0.9999)); + assert_eq!(inst.get_reserved_plmc_balance_for(EVALUATOR_1, HoldReason::Evaluation(project_id).into()), 0); + assert_eq!(inst.get_free_plmc_balance_for(EVALUATOR_1), free_plmc + evaluation_locked_plmc); + inst.assert_migration(project_id, EVALUATOR_1, EVAL_1_REWARD, 0, ParticipationType::Evaluation, true); + + // EVAL 2 + let evaluation_locked_plmc = + inst.get_reserved_plmc_balance_for(EVALUATOR_2, HoldReason::Evaluation(project_id).into()); + let free_plmc = inst.get_free_plmc_balance_for(EVALUATOR_2); + assert_ok!(inst.execute(|| PolimecFunding::settle_successful_evaluation( + RuntimeOrigin::signed(EVALUATOR_2), project_id, - contributor, - first_contribution.id - ), - Error::::ParticipationNotFound - ); - }); + EVALUATOR_2, + 1 + ))); + let eval_2_ct_rewarded = inst.get_ct_asset_balance_for(project_id, EVALUATOR_2); + assert_close_enough!(eval_2_ct_rewarded, EVAL_2_REWARD, Perquintill::from_float(0.9999)); + assert_eq!(inst.get_reserved_plmc_balance_for(EVALUATOR_2, HoldReason::Evaluation(project_id).into()), 0); + assert_eq!(inst.get_free_plmc_balance_for(EVALUATOR_2), free_plmc + evaluation_locked_plmc); + inst.assert_migration(project_id, EVALUATOR_2, EVAL_2_REWARD, 0, ParticipationType::Evaluation, true); + + // EVAL 3 + let evaluation_locked_plmc = + inst.get_reserved_plmc_balance_for(EVALUATOR_3, HoldReason::Evaluation(project_id).into()); + let free_plmc = inst.get_free_plmc_balance_for(EVALUATOR_3); + assert_ok!(inst.execute(|| PolimecFunding::settle_successful_evaluation( + RuntimeOrigin::signed(EVALUATOR_3), + project_id, + EVALUATOR_3, + 2 + ))); + let eval_3_ct_rewarded = inst.get_ct_asset_balance_for(project_id, EVALUATOR_3); + assert_close_enough!(eval_3_ct_rewarded, EVAL_3_REWARD, Perquintill::from_float(0.9999)); + assert_eq!(inst.get_reserved_plmc_balance_for(EVALUATOR_3, HoldReason::Evaluation(project_id).into()), 0); + assert_eq!(inst.get_free_plmc_balance_for(EVALUATOR_3), free_plmc + evaluation_locked_plmc); + inst.assert_migration(project_id, EVALUATOR_3, EVAL_3_REWARD, 0, ParticipationType::Evaluation, true); + } + } + + #[cfg(test)] + mod failure { + use super::*; + + #[test] + fn cannot_settle_twice() { + let percentage = 100u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); + + let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); + inst.execute(|| { + let evaluator = first_evaluation.evaluator; + assert_ok!(crate::Pallet::::settle_successful_evaluation( + RuntimeOrigin::signed(evaluator), + project_id, + evaluator, + first_evaluation.id + )); + assert_noop!( + crate::Pallet::::settle_successful_evaluation( + RuntimeOrigin::signed(evaluator), + project_id, + evaluator, + first_evaluation.id + ), + Error::::ParticipationNotFound + ); + }); + } + } +} + +#[cfg(test)] +mod settle_successful_bid_extrinsic { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + } + + #[cfg(test)] + mod failure { + use super::*; + + #[test] + fn cannot_settle_twice() { + let percentage = 100u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); + + let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); + inst.execute(|| { + let bidder = first_bid.bidder; + assert_ok!(crate::Pallet::::settle_successful_bid( + RuntimeOrigin::signed(bidder), + project_id, + bidder, + first_bid.id + )); + assert_noop!( + crate::Pallet::::settle_successful_bid( + RuntimeOrigin::signed(bidder), + project_id, + bidder, + first_bid.id + ), + Error::::ParticipationNotFound + ); + }); + } + } +} + +#[cfg(test)] +mod settle_successful_contribution_extrinsic { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + } + + #[cfg(test)] + mod failure { + use super::*; + + #[test] + fn cannot_settle_twice() { + let percentage = 100u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); + + let first_contribution = inst.get_contributions(project_id).into_iter().next().unwrap(); + inst.execute(|| { + let contributor = first_contribution.contributor; + assert_ok!(crate::Pallet::::settle_successful_contribution( + RuntimeOrigin::signed(contributor), + project_id, + contributor, + first_contribution.id + )); + assert_noop!( + crate::Pallet::::settle_successful_contribution( + RuntimeOrigin::signed(contributor), + project_id, + contributor, + first_contribution.id + ), + Error::::ParticipationNotFound + ); + }); + } + } +} + +#[cfg(test)] +mod settle_failed_evaluation_extrinsic { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + } + + #[cfg(test)] + mod failure { + use super::*; + + #[test] + fn cannot_settle_twice() { + let percentage = 33u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); + + let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); + inst.execute(|| { + let evaluator = first_evaluation.evaluator; + assert_ok!(crate::Pallet::::settle_failed_evaluation( + RuntimeOrigin::signed(evaluator), + project_id, + evaluator, + first_evaluation.id + )); + assert_noop!( + crate::Pallet::::settle_failed_evaluation( + RuntimeOrigin::signed(evaluator), + project_id, + evaluator, + first_evaluation.id + ), + Error::::ParticipationNotFound + ); + }); + } + } +} + +#[cfg(test)] +mod settle_failed_bid_extrinsic { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + } + + #[cfg(test)] + mod failure { + use super::*; + + #[test] + fn cannot_settle_twice() { + let percentage = 33u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); + + let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); + inst.execute(|| { + let bidder = first_bid.bidder; + assert_ok!(crate::Pallet::::settle_failed_bid( + RuntimeOrigin::signed(bidder), + project_id, + bidder, + first_bid.id + )); + assert_noop!( + crate::Pallet::::settle_failed_bid( + RuntimeOrigin::signed(bidder), + project_id, + bidder, + first_bid.id + ), + Error::::ParticipationNotFound + ); + }); + } + } +} + +#[cfg(test)] +mod settle_failed_contribution_extrinsic { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + } + + #[cfg(test)] + mod failure { + use super::*; + + #[test] + fn cannot_settle_twice() { + let percentage = 33u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); + + let first_contribution = inst.get_contributions(project_id).into_iter().next().unwrap(); + inst.execute(|| { + let contributor = first_contribution.contributor; + assert_ok!(crate::Pallet::::settle_failed_contribution( + RuntimeOrigin::signed(contributor), + project_id, + contributor, + first_contribution.id + )); + assert_noop!( + crate::Pallet::::settle_failed_contribution( + RuntimeOrigin::signed(contributor), + project_id, + contributor, + first_contribution.id + ), + Error::::ParticipationNotFound + ); + }); + } + } } /// Test that the correct amount of PLMC is slashed from the evaluator independent of the