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..78904f51c 100644 --- a/pallets/funding/src/instantiator/chain_interactions.rs +++ b/pallets/funding/src/instantiator/chain_interactions.rs @@ -2,10 +2,10 @@ use super::*; // general chain interactions impl< - T: Config + pallet_balances::Config>, - AllPalletsWithoutSystem: OnFinalize> + OnIdle> + OnInitialize>, - RuntimeEvent: From> + TryInto> + Parameter + Member + IsType<::RuntimeEvent>, - > Instantiator + T: Config + pallet_balances::Config>, + AllPalletsWithoutSystem: OnFinalize> + OnIdle> + OnInitialize>, + RuntimeEvent: From> + TryInto> + Parameter + Member + IsType<::RuntimeEvent>, +> Instantiator { pub fn new(ext: OptionalExternalities) -> Self { Self { ext, nonce: RefCell::new(0u64), _marker: PhantomData } @@ -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, @@ -73,6 +85,16 @@ impl< }) } + pub fn get_free_foreign_asset_balance_for( + &mut self, + asset_id: AssetIdOf, + user: AccountIdOf, + ) -> BalanceOf { + self.execute(|| { + ::FundingCurrency::balance(asset_id, &user) + }) + } + pub fn get_ct_asset_balances_for( &mut self, project_id: ProjectId, @@ -88,6 +110,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) @@ -232,10 +258,10 @@ impl< // assertions impl< - T: Config + pallet_balances::Config>, - AllPalletsWithoutSystem: OnFinalize> + OnIdle> + OnInitialize>, - RuntimeEvent: From> + TryInto> + Parameter + Member + IsType<::RuntimeEvent>, - > Instantiator + T: Config + pallet_balances::Config>, + AllPalletsWithoutSystem: OnFinalize> + OnIdle> + OnInitialize>, + RuntimeEvent: From> + TryInto> + Parameter + Member + IsType<::RuntimeEvent>, +> Instantiator { pub fn test_ct_created_for(&mut self, project_id: ProjectId) { self.execute(|| { @@ -352,10 +378,10 @@ impl< // project chain interactions impl< - T: Config + pallet_balances::Config>, - AllPalletsWithoutSystem: OnFinalize> + OnIdle> + OnInitialize>, - RuntimeEvent: From> + TryInto> + Parameter + Member + IsType<::RuntimeEvent>, - > Instantiator + T: Config + pallet_balances::Config>, + AllPalletsWithoutSystem: OnFinalize> + OnIdle> + OnInitialize>, + RuntimeEvent: From> + TryInto> + Parameter + Member + IsType<::RuntimeEvent>, +> Instantiator { pub fn get_issuer(&mut self, project_id: ProjectId) -> AccountIdOf { self.execute(|| ProjectsDetails::::get(project_id).unwrap().issuer_account) @@ -392,7 +418,7 @@ impl< project_metadata.clone(), generate_did_from_account(issuer.clone()), ) - .unwrap(); + .unwrap(); let last_project_metadata = ProjectsMetadata::::iter().last().unwrap(); log::trace!("Last project metadata: {:?}", last_project_metadata); }); @@ -775,17 +801,17 @@ impl< AcceptedFundingAsset::DOT.to_assethub_id(), vec![project_metadata.funding_destination_account.clone()], )[0] - .asset_amount; + .asset_amount; let total_stored_usdt = self.get_free_foreign_asset_balances_for( AcceptedFundingAsset::USDT.to_assethub_id(), vec![project_metadata.funding_destination_account.clone()], )[0] - .asset_amount; + .asset_amount; let total_stored_usdc = self.get_free_foreign_asset_balances_for( AcceptedFundingAsset::USDC.to_assethub_id(), vec![project_metadata.funding_destination_account.clone()], )[0] - .asset_amount; + .asset_amount; assert_eq!(total_expected_dot, total_stored_dot, "DOT amount is incorrect"); assert_eq!(total_expected_usdt, total_stored_usdt, "USDT amount is incorrect"); @@ -876,7 +902,7 @@ impl< } } - fn assert_migration( + pub(crate) fn assert_migration( &mut self, project_id: ProjectId, account: AccountIdOf, @@ -885,25 +911,32 @@ 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| { - let user = T::AccountId32Conversion::convert(account.clone()); - matches!(migration.origin, MigrationOrigin { user: m_user, id: m_id, participation_type: m_participation_type } if m_user == user && m_id == id && m_participation_type == participation_type) - }); + let user = T::AccountId32Conversion::convert(account.clone()); + matches!(migration.origin, MigrationOrigin { user: m_user, id: m_id, participation_type: m_participation_type } if m_user == user && m_id == id && m_participation_type == participation_type) + }); 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..3a15a0c38 100644 --- a/pallets/funding/src/tests/7_settlement.rs +++ b/pallets/funding/src/tests/7_settlement.rs @@ -1,334 +1,679 @@ 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); +#[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); + } + } +} - inst.settle_project(project_id).unwrap(); +#[cfg(test)] +mod settle_successful_evaluation_extrinsic { + use super::*; - 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 success { + use super::*; -#[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); + #[test] + fn evaluation_unchanged() { + let percentage = 89u64; - inst.settle_project(project_id).unwrap(); + let (mut inst, project_id) = + create_project_with_funding_percentage(percentage, Some(FundingOutcomeDecision::AcceptFunding)); - 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); -} + 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); -#[test] -fn cannot_settle_successful_project_twice() { - let percentage = 100u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); + assert_eq!( + inst.get_project_details(project_id).evaluation_round_info.evaluators_outcome, + EvaluatorsOutcomeOf::::Unchanged + ); - 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_ok!(inst.execute(|| PolimecFunding::settle_successful_evaluation( + RuntimeOrigin::signed(evaluator), + project_id, + evaluator, + first_evaluation.id + ))); - 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( + let post_balance = inst.get_free_plmc_balance_for(evaluator); + assert_eq!(post_balance, prev_balance + first_evaluation.current_plmc_bond); + } + + #[test] + fn evaluation_slashed() { + let percentage = 50u64; + 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 evaluator = first_evaluation.evaluator; + let prev_balance = inst.get_free_plmc_balances_for(vec![evaluator])[0].plmc_amount; + + assert_eq!( + inst.get_project_details(project_id).evaluation_round_info.evaluators_outcome, + EvaluatorsOutcomeOf::::Slashed + ); + + 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( + 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, + 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, + 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::*; + + #[test] + fn bid_is_correctly_settled() { + 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(); + let issuer = &inst.get_issuer(project_id); + let bidder = first_bid.bidder; + + assert_ok!(inst.execute(||PolimecFunding::settle_successful_bid( RuntimeOrigin::signed(bidder), project_id, bidder, first_bid.id - ), - Error::::ParticipationNotFound - ); + ))); + + let held_bidder = inst.get_reserved_plmc_balance_for(bidder.clone(), HoldReason::Participation(project_id).into()); + assert_eq!(held_bidder, 0u32.into()); + + let balance_issuer = inst.get_free_foreign_asset_balance_for(first_bid.funding_asset.to_assethub_id(), *issuer); + assert_eq!(balance_issuer, first_bid.funding_asset_amount_locked); + + let ct_amount = inst.get_ct_asset_balance_for(project_id, bidder); + assert_eq!(ct_amount, first_bid.final_ct_amount); + + inst.assert_migration(project_id, bidder, first_bid.final_ct_amount, first_bid.id, ParticipationType::Bid, true); + } + + #[test] + fn rejected_bids_dont_get_vest_schedule() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let issuer = ISSUER_1; + let project_metadata = default_project_metadata(issuer); + let evaluations = default_evaluations(); + let auction_token_allocation = + project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + + let mut bids = inst.generate_bids_from_total_usd( + Percent::from_percent(80) * project_metadata.minimum_price.saturating_mul_int(auction_token_allocation), + project_metadata.minimum_price, + vec![60, 40], + vec![BIDDER_1, BIDDER_2], + vec![1u8, 1u8], + ); - let contributor = first_contribution.contributor; - assert_ok!(crate::Pallet::::settle_successful_contribution( + let available_tokens = auction_token_allocation.saturating_sub(bids.iter().fold(0, |acc, bid| acc + bid.amount)); + + let rejected_bid = vec![BidParams::new(BIDDER_5, available_tokens, 1u8, AcceptedFundingAsset::USDT)]; + let accepted_bid = vec![BidParams::new(BIDDER_4, available_tokens, 2u8, AcceptedFundingAsset::USDT)]; + bids.extend(rejected_bid.clone()); + bids.extend(accepted_bid.clone()); + + let community_contributions = default_community_buys(); + + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, evaluations); + + let bidders_plmc = + inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket(&bids, project_metadata.clone(), None); + let bidders_existential_deposits = bidders_plmc.accounts().existential_deposits(); + inst.mint_plmc_to(bidders_plmc.clone()); + inst.mint_plmc_to(bidders_existential_deposits); + + let bidders_funding_assets = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( + &bids, + project_metadata.clone(), + None, + ); + inst.mint_foreign_asset_to(bidders_funding_assets); + + inst.bid_for_users(project_id, bids).unwrap(); + + inst.start_community_funding(project_id).unwrap(); + + let final_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); + let contributors_plmc = inst.calculate_contributed_plmc_spent(community_contributions.clone(), final_price); + let contributors_existential_deposits = contributors_plmc.accounts().existential_deposits(); + inst.mint_plmc_to(contributors_plmc.clone()); + inst.mint_plmc_to(contributors_existential_deposits); + + let contributors_funding_assets = + inst.calculate_contributed_funding_asset_spent(community_contributions.clone(), final_price); + inst.mint_foreign_asset_to(contributors_funding_assets); + + inst.contribute_for_users(project_id, community_contributions).unwrap(); + inst.start_remainder_or_end_funding(project_id).unwrap(); + inst.finish_funding(project_id).unwrap(); + + inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); + inst.settle_project(project_id).unwrap(); + + let plmc_locked_for_accepted_bid = inst.calculate_auction_plmc_charged_with_given_price(&accepted_bid, final_price); + let plmc_locked_for_rejected_bid = inst.calculate_auction_plmc_charged_with_given_price(&rejected_bid, final_price); + + let UserToPLMCBalance { account: accepted_user, plmc_amount: accepted_plmc_amount } = + plmc_locked_for_accepted_bid[0]; + let schedule = inst.execute(|| { + ::Vesting::total_scheduled_amount( + &accepted_user, + HoldReason::Participation(project_id).into(), + ) + }); + assert_close_enough!(schedule.unwrap(), accepted_plmc_amount, Perquintill::from_float(0.99)); + + let UserToPLMCBalance { account: rejected_user, .. } = plmc_locked_for_rejected_bid[0]; + assert!(inst + .execute(|| { + ::Vesting::total_scheduled_amount( + &rejected_user, + HoldReason::Participation(project_id).into(), + ) + }) + .is_none()); + } + } + + #[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::*; + + #[test] + fn contribution_is_correctly_settled_for_successful_project() { + 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(); + let issuer = &inst.get_issuer(project_id); + 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 - ); - }); + + let reason: RuntimeHoldReason = HoldReason::Participation(project_id).into(); + let held_contributor = ::NativeCurrency::balance_on_hold(&reason, &contributor); + assert_eq!(held_contributor, 0u32.into()); + + let balance_issuer = ::FundingCurrency::balance( + first_contribution.funding_asset.to_assethub_id(), + issuer, + ); + assert_eq!(balance_issuer, first_contribution.usd_contribution_amount); + + let ct_amount = ::ContributionTokenCurrency::balance(project_id, &contributor); + assert_eq!(ct_amount, first_contribution.ct_amount); + }); + } + } + + #[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 + ); + }); + } + } } -#[test] -fn cannot_settle_failed_project_twice() { - let percentage = 33u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); +#[cfg(test)] +mod settle_failed_evaluation_extrinsic { + use super::*; - 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(); + #[cfg(test)] + mod success { + use super::*; - 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( + #[test] + fn evaluation_unchanged() { + let percentage = 89u64; + + let (mut inst, project_id) = + create_project_with_funding_percentage(percentage, Some(FundingOutcomeDecision::RejectFunding)); + + 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); + + assert_eq!( + inst.get_project_details(project_id).evaluation_round_info.evaluators_outcome, + EvaluatorsOutcomeOf::::Unchanged + ); + + assert_ok!(inst.execute(|| PolimecFunding::settle_failed_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), - 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_failed_contribution( - RuntimeOrigin::signed(contributor), - project_id, - contributor, - first_contribution.id - )); - assert_noop!( - crate::Pallet::::settle_failed_contribution( - RuntimeOrigin::signed(contributor), + #[test] + fn evaluation_slashed() { + let percentage = 50u64; + let (mut inst, project_id) = + create_project_with_funding_percentage(percentage, Some(FundingOutcomeDecision::RejectFunding)); + + 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; + + assert_eq!( + inst.get_project_details(project_id).evaluation_round_info.evaluators_outcome, + EvaluatorsOutcomeOf::::Slashed + ); + + assert_ok!(inst.execute(|| PolimecFunding::settle_failed_evaluation( + RuntimeOrigin::signed(evaluator), project_id, - contributor, - first_contribution.id - ), - Error::::ParticipationNotFound - ); - }); -} + evaluator, + first_evaluation.id + ))); -/// Test that the correct amount of PLMC is slashed from the evaluator independent of the -/// project outcome. -#[test] -fn evaluator_slashed_if_between_33_and_75() { - let percentage = 50u64; - let project_1 = create_project_with_funding_percentage(percentage, Some(FundingOutcomeDecision::AcceptFunding)); - let project_2 = create_project_with_funding_percentage(percentage, Some(FundingOutcomeDecision::RejectFunding)); - let projects = vec![project_1, project_2]; - - for (mut inst, project_id) in projects { - let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); - let evaluator = first_evaluation.evaluator; - - inst.execute(|| { - let prev_balance = ::NativeCurrency::balance(&evaluator); - match ProjectsDetails::::get(project_id).unwrap().status { - ProjectStatus::FundingSuccessful => { - assert_ok!(crate::Pallet::::settle_successful_evaluation( - RuntimeOrigin::signed(evaluator), - project_id, - evaluator, - first_evaluation.id - )); - }, - ProjectStatus::FundingFailed => { - assert_ok!(crate::Pallet::::settle_failed_evaluation( - RuntimeOrigin::signed(evaluator), - project_id, - evaluator, - first_evaluation.id - )); - }, - _ => panic!("unexpected project status"), - } - let balance = ::NativeCurrency::balance(&evaluator); + let post_balance = inst.get_free_plmc_balances_for(vec![evaluator])[0].plmc_amount; assert_eq!( - balance, + post_balance, prev_balance + (Percent::from_percent(100) - ::EvaluatorSlash::get()) * first_evaluation.current_plmc_bond ); - }); + } } -} -// Test that the evaluators PLMC bond is not slashed if the project is between 76 and 89 -// percent funded independent of the project outcome. -#[test] -fn evaluator_plmc_unchanged_between_76_and_89() { - let percentage = 80u64; - let project_1 = create_project_with_funding_percentage(percentage, Some(FundingOutcomeDecision::AcceptFunding)); - let project_2 = create_project_with_funding_percentage(percentage, Some(FundingOutcomeDecision::RejectFunding)); - let projects = vec![project_1, project_2]; - - for (mut inst, project_id) in projects { - let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); - let evaluator = first_evaluation.evaluator; - - inst.execute(|| { - let prev_balance = ::NativeCurrency::balance(&evaluator); - match ProjectsDetails::::get(project_id).unwrap().status { - ProjectStatus::FundingSuccessful => { - assert_ok!(crate::Pallet::::settle_successful_evaluation( + #[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 - )); - }, - ProjectStatus::FundingFailed => { - assert_ok!(crate::Pallet::::settle_failed_evaluation( - RuntimeOrigin::signed(evaluator), - project_id, - evaluator, - first_evaluation.id - )); - }, - _ => panic!("unexpected project status"), - } - let balance = ::NativeCurrency::balance(&evaluator); - assert_eq!(balance, prev_balance + first_evaluation.current_plmc_bond); - }); + ), + Error::::ParticipationNotFound + ); + }); + } } } -#[test] -fn bid_is_correctly_settled_for_successful_project() { - 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(); - let issuer = &inst.get_issuer(project_id); - inst.execute(|| { - let bidder = first_bid.bidder; +#[cfg(test)] +mod settle_failed_bid_extrinsic { + use super::*; - assert_ok!(crate::Pallet::::settle_successful_bid( - RuntimeOrigin::signed(bidder), - project_id, - bidder, - first_bid.id - )); + #[cfg(test)] + mod success { + use super::*; - let reason: RuntimeHoldReason = HoldReason::Participation(project_id).into(); - let held_bidder = ::NativeCurrency::balance_on_hold(&reason, &bidder); - assert_eq!(held_bidder, 0u32.into()); + #[test] + fn bid_is_correctly_settled() { + let percentage = 25u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); + let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); + let issuer = &inst.get_issuer(project_id); + let bidder = first_bid.bidder; + + assert_ok!(inst.execute(||PolimecFunding::settle_failed_bid( + RuntimeOrigin::signed(bidder), + project_id, + bidder, + first_bid.id + ))); - let balance_issuer = - ::FundingCurrency::balance(first_bid.funding_asset.to_assethub_id(), issuer); - assert_eq!(balance_issuer, first_bid.funding_asset_amount_locked); + let held_bidder = inst.get_reserved_plmc_balance_for(bidder.clone(), HoldReason::Participation(project_id).into()); + assert_eq!(held_bidder, 0u32.into()); - let ct_amount = ::ContributionTokenCurrency::balance(project_id, &bidder); - assert_eq!(ct_amount, first_bid.final_ct_amount); - }); -} + let balance_issuer = inst.get_free_foreign_asset_balance_for(first_bid.funding_asset.to_assethub_id(), *issuer); + assert_eq!(balance_issuer, Zero::zero()); -#[test] -fn bid_is_correctly_settled_for_failed_project() { - 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 - )); + let usdt_balance_bidder = inst.get_free_foreign_asset_balance_for(first_bid.funding_asset.to_assethub_id(), bidder); + assert_eq!(usdt_balance_bidder, first_bid.funding_asset_amount_locked); - let reason: RuntimeHoldReason = HoldReason::Participation(project_id).into(); - let held_bidder = ::NativeCurrency::balance_on_hold(&reason, &bidder); - assert_eq!(held_bidder, 0u32.into()); + let plmc_balance_bidder = inst.get_free_plmc_balance_for(bidder); + assert_eq!(plmc_balance_bidder, first_bid.plmc_bond); - let funding_asset_bidder = - ::FundingCurrency::balance(first_bid.funding_asset.to_assethub_id(), &bidder); - assert_eq!(funding_asset_bidder, first_bid.funding_asset_amount_locked); + let ct_amount = inst.get_ct_asset_balance_for(project_id, bidder); + assert_eq!(ct_amount, Zero::zero()); - let ct_amount = ::ContributionTokenCurrency::balance(project_id, &bidder); - assert_eq!(ct_amount, Zero::zero()); - }); -} + inst.assert_migration(project_id, bidder, first_bid.final_ct_amount, first_bid.id, ParticipationType::Bid, false); + } -#[test] -fn contribution_is_correctly_settled_for_successful_project() { - 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(); - let issuer = &inst.get_issuer(project_id); - inst.execute(|| { - let contributor = first_contribution.contributor; - assert_ok!(crate::Pallet::::settle_successful_contribution( - RuntimeOrigin::signed(contributor), - project_id, - contributor, - first_contribution.id - )); - let reason: RuntimeHoldReason = HoldReason::Participation(project_id).into(); - let held_contributor = ::NativeCurrency::balance_on_hold(&reason, &contributor); - assert_eq!(held_contributor, 0u32.into()); + } - let balance_issuer = ::FundingCurrency::balance( - first_contribution.funding_asset.to_assethub_id(), - issuer, - ); - assert_eq!(balance_issuer, first_contribution.usd_contribution_amount); + #[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 + ); + }); + } + } +} - let ct_amount = ::ContributionTokenCurrency::balance(project_id, &contributor); - assert_eq!(ct_amount, first_contribution.ct_amount); - }); +#[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] fn contribution_is_correctly_settled_for_failed_project() { let percentage = 33u64; @@ -358,88 +703,4 @@ fn contribution_is_correctly_settled_for_failed_project() { assert_eq!(ct_amount, Zero::zero()); }); } -#[test] -fn unsuccessful_bids_dont_get_vest_schedule() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(issuer); - let evaluations = default_evaluations(); - let auction_token_allocation = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - - let mut bids = inst.generate_bids_from_total_usd( - Percent::from_percent(80) * project_metadata.minimum_price.saturating_mul_int(auction_token_allocation), - project_metadata.minimum_price, - vec![60, 40], - vec![BIDDER_1, BIDDER_2], - vec![1u8, 1u8], - ); - - let available_tokens = auction_token_allocation.saturating_sub(bids.iter().fold(0, |acc, bid| acc + bid.amount)); - - let rejected_bid = vec![BidParams::new(BIDDER_5, available_tokens, 1u8, AcceptedFundingAsset::USDT)]; - let accepted_bid = vec![BidParams::new(BIDDER_4, available_tokens, 2u8, AcceptedFundingAsset::USDT)]; - bids.extend(rejected_bid.clone()); - bids.extend(accepted_bid.clone()); - - let community_contributions = default_community_buys(); - - let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, evaluations); - - let bidders_plmc = - inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket(&bids, project_metadata.clone(), None); - let bidders_existential_deposits = bidders_plmc.accounts().existential_deposits(); - inst.mint_plmc_to(bidders_plmc.clone()); - inst.mint_plmc_to(bidders_existential_deposits); - - let bidders_funding_assets = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &bids, - project_metadata.clone(), - None, - ); - inst.mint_foreign_asset_to(bidders_funding_assets); - - inst.bid_for_users(project_id, bids).unwrap(); - - inst.start_community_funding(project_id).unwrap(); - - let final_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); - let contributors_plmc = inst.calculate_contributed_plmc_spent(community_contributions.clone(), final_price); - let contributors_existential_deposits = contributors_plmc.accounts().existential_deposits(); - inst.mint_plmc_to(contributors_plmc.clone()); - inst.mint_plmc_to(contributors_existential_deposits); - - let contributors_funding_assets = - inst.calculate_contributed_funding_asset_spent(community_contributions.clone(), final_price); - inst.mint_foreign_asset_to(contributors_funding_assets); - - inst.contribute_for_users(project_id, community_contributions).unwrap(); - inst.start_remainder_or_end_funding(project_id).unwrap(); - inst.finish_funding(project_id).unwrap(); - - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - inst.settle_project(project_id).unwrap(); - - let plmc_locked_for_accepted_bid = inst.calculate_auction_plmc_charged_with_given_price(&accepted_bid, final_price); - let plmc_locked_for_rejected_bid = inst.calculate_auction_plmc_charged_with_given_price(&rejected_bid, final_price); - - let UserToPLMCBalance { account: accepted_user, plmc_amount: accepted_plmc_amount } = - plmc_locked_for_accepted_bid[0]; - let schedule = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &accepted_user, - HoldReason::Participation(project_id).into(), - ) - }); - assert_close_enough!(schedule.unwrap(), accepted_plmc_amount, Perquintill::from_float(0.99)); - - let UserToPLMCBalance { account: rejected_user, .. } = plmc_locked_for_rejected_bid[0]; - assert!(inst - .execute(|| { - ::Vesting::total_scheduled_amount( - &rejected_user, - HoldReason::Participation(project_id).into(), - ) - }) - .is_none()); -} +