From 10ed7d7838dccde67dbb880ec3ca30679b6e3095 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Mon, 22 Jul 2024 15:19:52 +0200 Subject: [PATCH] Fix existing tests --- integration-tests/src/constants.rs | 12 +- integration-tests/src/tests/ct_migration.rs | 24 +- integration-tests/src/tests/e2e.rs | 32 +- integration-tests/src/tests/oracle.rs | 6 +- nodes/parachain/src/chain_spec/common.rs | 12 +- pallets/funding/src/benchmarking.rs | 160 +- pallets/funding/src/functions/3_auction.rs | 2 +- .../funding/src/functions/4_contribution.rs | 2 +- pallets/funding/src/functions/6_settlement.rs | 11 +- pallets/funding/src/functions/misc.rs | 4 +- .../funding/src/instantiator/calculations.rs | 183 +- .../src/instantiator/chain_interactions.rs | 503 ++--- pallets/funding/src/instantiator/mod.rs | 5 +- pallets/funding/src/instantiator/tests.rs | 48 +- pallets/funding/src/instantiator/types.rs | 39 +- pallets/funding/src/mock.rs | 37 +- pallets/funding/src/runtime_api.rs | 2 +- pallets/funding/src/tests/1_application.rs | 57 +- pallets/funding/src/tests/2_evaluation.rs | 58 +- pallets/funding/src/tests/3_auction.rs | 492 ++--- .../{4_community.rs => 4_contribution.rs} | 368 ++-- .../{6_funding_end.rs => 5_funding_end.rs} | 68 +- pallets/funding/src/tests/5_remainder.rs | 1888 ----------------- pallets/funding/src/tests/6_settlement.rs | 1176 ++++++++++ .../{8_ct_migration.rs => 7_ct_migration.rs} | 42 +- pallets/funding/src/tests/7_settlement.rs | 1539 -------------- pallets/funding/src/tests/mod.rs | 101 +- pallets/funding/src/tests/runtime_api.rs | 64 +- pallets/funding/src/types.rs | 2 +- runtimes/polimec/src/benchmarks/helpers.rs | 6 +- runtimes/shared-configuration/src/currency.rs | 6 +- runtimes/shared-configuration/src/funding.rs | 6 +- 32 files changed, 2173 insertions(+), 4782 deletions(-) rename pallets/funding/src/tests/{4_community.rs => 4_contribution.rs} (85%) rename pallets/funding/src/tests/{6_funding_end.rs => 5_funding_end.rs} (67%) delete mode 100644 pallets/funding/src/tests/5_remainder.rs create mode 100644 pallets/funding/src/tests/6_settlement.rs rename pallets/funding/src/tests/{8_ct_migration.rs => 7_ct_migration.rs} (89%) delete mode 100644 pallets/funding/src/tests/7_settlement.rs diff --git a/integration-tests/src/constants.rs b/integration-tests/src/constants.rs index 48e6e7d27..76d5c56a0 100644 --- a/integration-tests/src/constants.rs +++ b/integration-tests/src/constants.rs @@ -386,9 +386,9 @@ pub mod polimec { #[allow(unused)] pub fn set_prices() { PolimecNet::execute_with(|| { - let dot = (AcceptedFundingAsset::DOT.to_assethub_id(), FixedU128::from_rational(69, 1)); - let usdc = (AcceptedFundingAsset::USDC.to_assethub_id(), FixedU128::from_rational(1, 1)); - let usdt = (AcceptedFundingAsset::USDT.to_assethub_id(), FixedU128::from_rational(1, 1)); + let dot = (AcceptedFundingAsset::DOT.id(), FixedU128::from_rational(69, 1)); + let usdc = (AcceptedFundingAsset::USDC.id(), FixedU128::from_rational(1, 1)); + let usdt = (AcceptedFundingAsset::USDT.id(), FixedU128::from_rational(1, 1)); let plmc = (pallet_funding::PLMC_FOREIGN_ID, FixedU128::from_rational(840, 100)); let values: BoundedVec<(u32, FixedU128), ::MaxFeedValues> = @@ -424,9 +424,9 @@ pub mod polimec { } pub fn genesis() -> Storage { - let dot_asset_id = AcceptedFundingAsset::DOT.to_assethub_id(); - let usdt_asset_id = AcceptedFundingAsset::USDT.to_assethub_id(); - let usdc_asset_id = AcceptedFundingAsset::USDC.to_assethub_id(); + let dot_asset_id = AcceptedFundingAsset::DOT.id(); + let usdt_asset_id = AcceptedFundingAsset::USDT.id(); + let usdc_asset_id = AcceptedFundingAsset::USDC.id(); let mut funded_accounts = vec![ ( PolimecNet::sovereign_account_id_of((Parent, xcm::prelude::Parachain(penpal::PARA_ID)).into()), diff --git a/integration-tests/src/tests/ct_migration.rs b/integration-tests/src/tests/ct_migration.rs index 829fef786..c7f25c0ca 100644 --- a/integration-tests/src/tests/ct_migration.rs +++ b/integration-tests/src/tests/ct_migration.rs @@ -174,7 +174,10 @@ fn create_settled_project() -> (ProjectId, Vec) { default_community_contributions(), default_remainder_contributions(), ); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); + assert_eq!( + inst.go_to_next_state(project_id), + pallet_funding::ProjectStatus::SettlementStarted(FundingOutcome::Success) + ); let mut participants: Vec = pallet_funding::Evaluations::::iter_prefix_values((project_id,)) .map(|eval| eval.evaluator) @@ -187,7 +190,7 @@ fn create_settled_project() -> (ProjectId, Vec) { participants.sort(); participants.dedup(); - inst.settle_project(project_id).unwrap(); + inst.settle_project(project_id); (project_id, participants) }) } @@ -229,7 +232,10 @@ fn create_project_with_unsettled_participation(participation_type: Participation default_remainder_contributions(), ); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); + assert_eq!( + inst.go_to_next_state(project_id), + pallet_funding::ProjectStatus::SettlementStarted(FundingOutcome::Success) + ); let evaluations_to_settle = pallet_funding::Evaluations::::iter_prefix_values((project_id,)).collect_vec(); let bids_to_settle = pallet_funding::Bids::::iter_prefix_values((project_id,)).collect_vec(); @@ -247,7 +253,7 @@ fn create_project_with_unsettled_participation(participation_type: Participation let start = if participation_type == ParticipationType::Evaluation { 1 } else { 0 }; for evaluation in evaluations_to_settle[start..].iter() { - PolimecFunding::settle_successful_evaluation( + PolimecFunding::settle_evaluation( RuntimeOrigin::signed(alice()), project_id, evaluation.evaluator.clone(), @@ -258,18 +264,12 @@ fn create_project_with_unsettled_participation(participation_type: Participation let start = if participation_type == ParticipationType::Bid { 1 } else { 0 }; for bid in bids_to_settle[start..].iter() { - PolimecFunding::settle_successful_bid( - RuntimeOrigin::signed(alice()), - project_id, - bid.bidder.clone(), - bid.id, - ) - .unwrap() + PolimecFunding::settle_bid(RuntimeOrigin::signed(alice()), project_id, bid.bidder.clone(), bid.id).unwrap() } let start = if participation_type == ParticipationType::Contribution { 1 } else { 0 }; for contribution in contributions_to_settle[start..].iter() { - PolimecFunding::settle_successful_contribution( + PolimecFunding::settle_contribution( RuntimeOrigin::signed(alice()), project_id, contribution.contributor.clone(), diff --git a/integration-tests/src/tests/e2e.rs b/integration-tests/src/tests/e2e.rs index a08ce9010..8c30bde56 100644 --- a/integration-tests/src/tests/e2e.rs +++ b/integration-tests/src/tests/e2e.rs @@ -374,9 +374,8 @@ fn remainder_round_completed() { let total_stored = contributions.into_iter().fold(0, |acc, contribution| acc + contribution.funding_asset_amount); - let usdt_decimals = ::FundingCurrency::decimals( - AcceptedFundingAsset::USDT.to_assethub_id(), - ); + let usdt_decimals = + ::FundingCurrency::decimals(AcceptedFundingAsset::USDT.id()); let usdt_total_from_excel_f64 = 503_945.4_517_000_000f64; let usdt_total_from_excel_fixed = FixedU128::from_float(usdt_total_from_excel_f64); let usdt_total_from_excel = usdt_total_from_excel_fixed.saturating_mul_int(10u128.pow(usdt_decimals as u32)); @@ -392,7 +391,7 @@ fn funds_raised() { let mut inst = IntegrationInstantiator::new(None); PolimecNet::execute_with(|| { - let project_id = inst.create_finished_project( + let _project_id = inst.create_settled_project( excel_project(), ISSUER.into(), None, @@ -403,14 +402,13 @@ fn funds_raised() { ); inst.execute(|| { - let project_specific_account: AccountId = PolimecFunding::fund_account_id(project_id); + let funding_destination_account: AccountId = excel_project().funding_destination_account; let stored_usdt_funded = - PolimecForeignAssets::balance(AcceptedFundingAsset::USDT.to_assethub_id(), project_specific_account); + PolimecForeignAssets::balance(AcceptedFundingAsset::USDT.id(), funding_destination_account); let excel_usdt_funded_f64 = 1_004_256.0_140_000_000f64; let excet_usdt_funding_fixed = FixedU128::from_float(excel_usdt_funded_f64); - let usdt_decimals = ::FundingCurrency::decimals( - AcceptedFundingAsset::USDT.to_assethub_id(), - ); + let usdt_decimals = + ::FundingCurrency::decimals(AcceptedFundingAsset::USDT.id()); let excel_usdt_funded = excet_usdt_funding_fixed.saturating_mul_int(10u128.pow(usdt_decimals as u32)); assert_close_enough!(stored_usdt_funded, excel_usdt_funded, Perquintill::from_float(0.99)); }) @@ -424,7 +422,7 @@ fn ct_minted() { let mut inst = IntegrationInstantiator::new(None); PolimecNet::execute_with(|| { - let project_id = inst.create_finished_project( + let project_id = inst.create_settled_project( excel_project(), ISSUER.into(), None, @@ -433,9 +431,6 @@ fn ct_minted() { excel_contributions(), excel_remainders(), ); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - - inst.settle_project(project_id).unwrap(); for (contributor, expected_amount_fixed, project_id) in excel_ct_amounts() { let minted = inst @@ -453,7 +448,7 @@ fn ct_migrated() { let mut inst = IntegrationInstantiator::new(None); let project_id = PolimecNet::execute_with(|| { - let project_id = inst.create_finished_project( + let project_id = inst.create_settled_project( excel_project(), ISSUER.into(), None, @@ -462,22 +457,19 @@ fn ct_migrated() { excel_contributions(), excel_remainders(), ); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - - inst.settle_project(project_id).unwrap(); for (contributor, expected_amount_fixed, project_id) in excel_ct_amounts() { let minted = inst .execute(|| ::ContributionTokenCurrency::balance(project_id, &contributor)); let expected_amount = expected_amount_fixed.saturating_mul_int(CT_UNIT); - assert_close_enough!(minted, expected_amount, Perquintill::from_float(0.99)); + assert_close_enough!(minted, expected_amount, Perquintill::from_float(0.999)); } project_id }); let project_details = PolimecNet::execute_with(|| inst.get_project_details(project_id)); - assert!(matches!(project_details.evaluation_round_info.evaluators_outcome, EvaluatorsOutcome::Rewarded(_))); + assert!(matches!(project_details.evaluation_round_info.evaluators_outcome, Some(EvaluatorsOutcome::Rewarded(_)))); let ct_issued = PolimecNet::execute_with(|| ::ContributionTokenCurrency::total_issuance(project_id)); @@ -540,7 +532,7 @@ fn ct_migrated() { )); let key: [u8; 32] = account.clone().into(); println!("Migrated CTs for {}", names[&key]); - inst.advance_time(1u32).unwrap(); + inst.advance_time(1u32); }); } diff --git a/integration-tests/src/tests/oracle.rs b/integration-tests/src/tests/oracle.rs index 9202d6a47..337d761f7 100644 --- a/integration-tests/src/tests/oracle.rs +++ b/integration-tests/src/tests/oracle.rs @@ -41,7 +41,7 @@ fn members_can_feed_data() { PolimecNet::execute_with(|| { // pallet_funding genesis builder already inputs prices, so we need to advance one block to feed new values. - inst.advance_time(1u32).unwrap(); + inst.advance_time(1u32); let alice = PolimecNet::account_id_of(ALICE); assert_ok!(Oracle::feed_values(RuntimeOrigin::signed(alice.clone()), values([4.84, 1.0, 1.0, 0.4]))); @@ -81,7 +81,7 @@ fn data_is_correctly_combined() { let mut inst = IntegrationInstantiator::new(None); PolimecNet::execute_with(|| { // pallet_funding genesis builder already inputs prices, so we need to advance one block to feed new values. - inst.advance_time(1u32).unwrap(); + inst.advance_time(1u32); let alice = PolimecNet::account_id_of(ALICE); assert_ok!(Oracle::feed_values(RuntimeOrigin::signed(alice.clone()), values([1.0, 1.5, 1.1, 0.11111]))); @@ -113,7 +113,7 @@ fn pallet_funding_works() { PolimecNet::execute_with(|| { // pallet_funding genesis builder already inputs prices, so we need to advance one block to feed new values. - inst.advance_time(1u32).unwrap(); + inst.advance_time(1u32); let alice = PolimecNet::account_id_of(ALICE); assert_ok!(Oracle::feed_values(RuntimeOrigin::signed(alice.clone()), values([4.84, 1.0, 1.0, 0.4]))); diff --git a/nodes/parachain/src/chain_spec/common.rs b/nodes/parachain/src/chain_spec/common.rs index 9295da6a5..7405f9b39 100644 --- a/nodes/parachain/src/chain_spec/common.rs +++ b/nodes/parachain/src/chain_spec/common.rs @@ -101,9 +101,9 @@ pub fn genesis_config(genesis_config_params: GenesisConfigParams) -> serde_json: #[cfg(feature = "runtime-benchmarks")] let staking_candidates: Vec<(AccountId, Balance)> = vec![]; - let usdt_id = pallet_funding::types::AcceptedFundingAsset::USDT.to_assethub_id(); - let usdc_id = pallet_funding::types::AcceptedFundingAsset::USDC.to_assethub_id(); - let dot_id = pallet_funding::types::AcceptedFundingAsset::DOT.to_assethub_id(); + let usdt_id = pallet_funding::types::AcceptedFundingAsset::USDT.id(); + let usdc_id = pallet_funding::types::AcceptedFundingAsset::USDC.id(); + let dot_id = pallet_funding::types::AcceptedFundingAsset::DOT.id(); serde_json::json!({ "balances": { @@ -114,19 +114,19 @@ pub fn genesis_config(genesis_config_params: GenesisConfigParams) -> serde_json: }, "foreignAssets": { "assets": vec![( - pallet_funding::types::AcceptedFundingAsset::USDT.to_assethub_id(), + pallet_funding::types::AcceptedFundingAsset::USDT.id(), &AccountIdConversion::::into_account_truncating(&::PalletId::get()), true, 70000, ), ( - pallet_funding::types::AcceptedFundingAsset::USDC.to_assethub_id(), + pallet_funding::types::AcceptedFundingAsset::USDC.id(), &AccountIdConversion::::into_account_truncating(&::PalletId::get()), true, 70000, ), ( - pallet_funding::types::AcceptedFundingAsset::DOT.to_assethub_id(), + pallet_funding::types::AcceptedFundingAsset::DOT.id(), &AccountIdConversion::::into_account_truncating(&::PalletId::get()), true, 70000, diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs index 1991a7396..d602a868d 100644 --- a/pallets/funding/src/benchmarking.rs +++ b/pallets/funding/src/benchmarking.rs @@ -52,7 +52,7 @@ const CT_UNIT: u128 = 10u128.pow(CT_DECIMALS as u32); type BenchInstantiator = Instantiator::AllPalletsWithoutSystem, ::RuntimeEvent>; pub fn usdt_id() -> u32 { - AcceptedFundingAsset::USDT.to_assethub_id() + AcceptedFundingAsset::USDT.id() } pub fn default_project_metadata(issuer: AccountIdOf) -> ProjectMetadataOf @@ -302,16 +302,6 @@ pub fn fill_projects_to_update( } } -pub fn run_blocks_to_execute_next_transition( - project_id: ProjectId, - update_type: UpdateType, - inst: &mut BenchInstantiator, -) { - let update_block = inst.get_update_block(project_id, &update_type).unwrap(); - frame_system::Pallet::::set_block_number(update_block - 1u32.into()); - inst.advance_time(One::one()).unwrap(); -} - #[benchmarks( where T: Config + frame_system::Config::RuntimeEvent> + pallet_balances::Config>, @@ -336,7 +326,7 @@ mod benchmarks { let mut inst = BenchInstantiator::::new(None); ::SetPrices::set_prices(); // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); let ed = inst.get_ed(); @@ -387,7 +377,7 @@ mod benchmarks { let mut inst = BenchInstantiator::::new(None); ::SetPrices::set_prices(); // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); whitelist_account!(issuer); @@ -420,7 +410,7 @@ mod benchmarks { ::SetPrices::set_prices(); // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); let issuer_funding = account::>("issuer_funding", 0, 0); @@ -524,7 +514,7 @@ mod benchmarks { ::SetPrices::set_prices(); // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); whitelist_account!(issuer); @@ -591,11 +581,11 @@ mod benchmarks { inst.mint_plmc_to(plmc_for_evaluating); - inst.advance_time(One::one()).unwrap(); + inst.advance_time(One::one()); inst.evaluate_for_users(project_id, evaluations).expect("All evaluations are accepted"); run_blocks_to_execute_next_transition(project_id, UpdateType::EvaluationEnd, &mut inst); - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionInitializePeriod); @@ -636,7 +626,7 @@ mod benchmarks { ::SetPrices::set_prices(); // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); let test_evaluator = account::>("evaluator", 0, 0); @@ -659,7 +649,7 @@ mod benchmarks { inst.mint_plmc_to(plmc_for_existing_evaluations.clone()); inst.mint_plmc_to(plmc_for_extrinsic_evaluation.clone()); - inst.advance_time(One::one()).unwrap(); + inst.advance_time(One::one()); // do "x" evaluations for this user inst.evaluate_for_users(project_id, existing_evaluations).expect("All evaluations are accepted"); @@ -749,7 +739,7 @@ mod benchmarks { ::SetPrices::set_prices(); // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); let bidder = account::>("bidder", 0, 0); @@ -789,7 +779,7 @@ mod benchmarks { let existential_deposits: Vec> = vec![bidder.clone()].existential_deposits(); - let usdt_for_existing_bids: Vec> = inst + let usdt_for_existing_bids: Vec> = inst .calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( &existing_bids, project_metadata.clone(), @@ -797,11 +787,11 @@ mod benchmarks { ); let escrow_account = Pallet::::fund_account_id(project_id); let prev_total_escrow_usdt_locked = - inst.get_free_foreign_asset_balances_for(usdt_id(), vec![escrow_account.clone()]); + inst.get_free_funding_asset_balances_for(usdt_id(), vec![escrow_account.clone()]); inst.mint_plmc_to(plmc_for_existing_bids.clone()); inst.mint_plmc_to(existential_deposits.clone()); - inst.mint_foreign_asset_to(usdt_for_existing_bids.clone()); + inst.mint_funding_asset_to(usdt_for_existing_bids.clone()); // do "x" contributions for this user inst.bid_for_users(project_id, existing_bids.clone()).unwrap(); @@ -812,11 +802,8 @@ mod benchmarks { let mut maybe_filler_bid = None; let new_bidder = account::>("new_bidder", 0, 0); - let mut usdt_for_filler_bidder = vec![UserToForeignAssets::::new( - new_bidder.clone(), - Zero::zero(), - AcceptedFundingAsset::USDT.to_assethub_id(), - )]; + let mut usdt_for_filler_bidder = + vec![UserToFundingAsset::::new(new_bidder.clone(), Zero::zero(), AcceptedFundingAsset::USDT.id())]; if do_perform_bid_calls > 0 { let current_bucket = Buckets::::get(project_id).unwrap(); // first lets bring the bucket to almost its limit with another bidder: @@ -836,7 +823,7 @@ mod benchmarks { inst.mint_plmc_to(plmc_for_new_bidder); inst.mint_plmc_to(plmc_ed); - inst.mint_foreign_asset_to(usdt_for_new_bidder.clone()); + inst.mint_funding_asset_to(usdt_for_new_bidder.clone()); inst.bid_for_users(project_id, vec![bid_params]).unwrap(); @@ -864,20 +851,20 @@ mod benchmarks { Some(current_bucket), false, ); - let usdt_for_extrinsic_bids: Vec> = inst + let usdt_for_extrinsic_bids: Vec> = inst .calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( &vec![extrinsic_bid], project_metadata.clone(), Some(current_bucket), ); inst.mint_plmc_to(plmc_for_extrinsic_bids.clone()); - inst.mint_foreign_asset_to(usdt_for_extrinsic_bids.clone()); + inst.mint_funding_asset_to(usdt_for_extrinsic_bids.clone()); let total_free_plmc = existential_deposits[0].plmc_amount; let total_plmc_participation_bonded = inst.sum_balance_mappings(vec![plmc_for_extrinsic_bids.clone(), plmc_for_existing_bids.clone()]); let total_free_usdt = Zero::zero(); - let total_escrow_usdt_locked = inst.sum_foreign_mappings(vec![ + let total_escrow_usdt_locked = inst.sum_funding_asset_mappings(vec![ prev_total_escrow_usdt_locked.clone(), usdt_for_extrinsic_bids.clone(), usdt_for_existing_bids.clone(), @@ -981,10 +968,10 @@ mod benchmarks { let escrow_account = Pallet::::fund_account_id(project_id); let locked_usdt = - inst.get_free_foreign_asset_balances_for(usdt_id(), vec![escrow_account.clone()])[0].asset_amount; + inst.get_free_funding_asset_balances_for(usdt_id(), vec![escrow_account.clone()])[0].asset_amount; assert_eq!(locked_usdt, total_usdt_locked); - let free_usdt = inst.get_free_foreign_asset_balances_for(usdt_id(), vec![bidder])[0].asset_amount; + let free_usdt = inst.get_free_funding_asset_balances_for(usdt_id(), vec![bidder])[0].asset_amount; assert_eq!(free_usdt, total_free_usdt); // Events @@ -1131,13 +1118,13 @@ mod benchmarks { plmc_for_extrinsic_contribution.accounts().existential_deposits(); let escrow_account = Pallet::::fund_account_id(project_id); - let prev_total_usdt_locked = inst.get_free_foreign_asset_balances_for(usdt_id(), vec![escrow_account.clone()]); + let prev_total_usdt_locked = inst.get_free_funding_asset_balances_for(usdt_id(), vec![escrow_account.clone()]); inst.mint_plmc_to(plmc_for_existing_contributions.clone()); inst.mint_plmc_to(plmc_for_extrinsic_contribution.clone()); inst.mint_plmc_to(existential_deposits.clone()); - inst.mint_foreign_asset_to(usdt_for_existing_contributions.clone()); - inst.mint_foreign_asset_to(usdt_for_extrinsic_contribution.clone()); + inst.mint_funding_asset_to(usdt_for_existing_contributions.clone()); + inst.mint_funding_asset_to(usdt_for_extrinsic_contribution.clone()); // do "x" contributions for this user inst.contribute_for_users(project_id, existing_contributions).expect("All contributions are accepted"); @@ -1146,7 +1133,7 @@ mod benchmarks { plmc_for_existing_contributions.clone(), plmc_for_extrinsic_contribution.clone(), ]); - let mut total_usdt_locked = inst.sum_foreign_mappings(vec![ + let mut total_usdt_locked = inst.sum_funding_asset_mappings(vec![ prev_total_usdt_locked, usdt_for_existing_contributions.clone(), usdt_for_extrinsic_contribution.clone(), @@ -1244,10 +1231,10 @@ mod benchmarks { let escrow_account = Pallet::::fund_account_id(project_id); let locked_usdt = - inst.get_free_foreign_asset_balances_for(usdt_id(), vec![escrow_account.clone()])[0].asset_amount; + inst.get_free_funding_asset_balances_for(usdt_id(), vec![escrow_account.clone()])[0].asset_amount; assert_eq!(locked_usdt, total_usdt_locked); - let free_usdt = inst.get_free_foreign_asset_balances_for(usdt_id(), vec![contributor.clone()])[0].asset_amount; + let free_usdt = inst.get_free_funding_asset_balances_for(usdt_id(), vec![contributor.clone()])[0].asset_amount; assert_eq!(free_usdt, total_free_usdt); // Events @@ -1414,7 +1401,7 @@ mod benchmarks { vec![], ); - inst.advance_time(One::one()).unwrap(); + inst.advance_time(One::one()); let current_block = inst.current_block(); let insertion_block_number: BlockNumberFor = current_block + One::one(); @@ -1432,8 +1419,7 @@ mod benchmarks { // * validity checks * // Storage - let maybe_transition = - inst.get_update_block(project_id, &UpdateType::ProjectDecision(FundingOutcomeDecision::AcceptFunding)); + let maybe_transition = inst.go_to_next_state(project_id); assert!(maybe_transition.is_some()); } @@ -1444,7 +1430,7 @@ mod benchmarks { ::SetPrices::set_prices(); // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); let evaluations: Vec> = default_evaluations::(); @@ -1509,7 +1495,7 @@ mod benchmarks { ::SetPrices::set_prices(); // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); let evaluations = default_evaluations::(); @@ -1538,10 +1524,10 @@ mod benchmarks { let project_id = inst.create_finished_project(project_metadata, issuer, None, evaluations, bids, contributions, vec![]); - inst.advance_time(One::one()).unwrap(); + inst.advance_time(One::one()); assert_eq!( inst.get_project_details(project_id).status, - ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed) + ProjectStatus::SettlementStarted(FundingOutcome::Failure) ); let evaluation_to_settle = @@ -1593,7 +1579,7 @@ mod benchmarks { ::SetPrices::set_prices(); // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); let bids = default_bids::(); @@ -1614,7 +1600,7 @@ mod benchmarks { assert_eq!( inst.get_project_details(project_id).status, - ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful) + ProjectStatus::SettlementStarted(FundingOutcome::Success) ); let bid_to_settle = @@ -1644,7 +1630,7 @@ mod benchmarks { ::SetPrices::set_prices(); // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); let evaluations = default_evaluations::(); @@ -1680,16 +1666,16 @@ mod benchmarks { vec![], ); - inst.advance_time(One::one()).unwrap(); + inst.advance_time(One::one()); assert_eq!( inst.get_project_details(project_id).status, - ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed) + ProjectStatus::SettlementStarted(FundingOutcome::Failure) ); let bid_to_settle = inst.execute(|| Bids::::iter_prefix_values((project_id, bidder.clone())).next().unwrap()); - let asset = bid_to_settle.funding_asset.to_assethub_id(); - let free_assets_before = inst.get_free_foreign_asset_balances_for(asset, vec![bidder.clone()])[0].asset_amount; + let asset = bid_to_settle.funding_asset.id(); + let free_assets_before = inst.get_free_funding_asset_balances_for(asset, vec![bidder.clone()])[0].asset_amount; #[extrinsic_call] settle_failed_bid(RawOrigin::Signed(issuer.clone()), project_id, bidder.clone(), bid_to_settle.id); @@ -1698,7 +1684,7 @@ mod benchmarks { assert!(Bids::::get((project_id, bidder.clone(), bid_to_settle.id)).is_none()); // Balances - let free_assets = inst.get_free_foreign_asset_balances_for(asset, vec![bidder.clone()])[0].asset_amount; + let free_assets = inst.get_free_funding_asset_balances_for(asset, vec![bidder.clone()])[0].asset_amount; assert_eq!(free_assets, bid_to_settle.funding_asset_amount_locked + free_assets_before); // Events @@ -1714,7 +1700,7 @@ mod benchmarks { ::SetPrices::set_prices(); // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); let contributions = default_community_contributions::(); @@ -1735,7 +1721,7 @@ mod benchmarks { assert_eq!( inst.get_project_details(project_id).status, - ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful) + ProjectStatus::SettlementStarted(FundingOutcome::Success) ); let contribution_to_settle = @@ -1776,7 +1762,7 @@ mod benchmarks { ::SetPrices::set_prices(); // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); let evaluations = default_evaluations::(); @@ -1805,18 +1791,18 @@ mod benchmarks { let project_id = inst.create_finished_project(project_metadata, issuer, None, evaluations, bids, contributions, vec![]); - inst.advance_time(One::one()).unwrap(); + inst.advance_time(One::one()); assert_eq!( inst.get_project_details(project_id).status, - ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed) + ProjectStatus::SettlementStarted(FundingOutcome::Failure) ); let contribution_to_settle = inst.execute(|| Contributions::::iter_prefix_values((project_id, contributor.clone())).next().unwrap()); - let asset = contribution_to_settle.funding_asset.to_assethub_id(); + let asset = contribution_to_settle.funding_asset.id(); let free_assets_before = - inst.get_free_foreign_asset_balances_for(asset, vec![contributor.clone()])[0].asset_amount; + inst.get_free_funding_asset_balances_for(asset, vec![contributor.clone()])[0].asset_amount; #[extrinsic_call] settle_failed_contribution( RawOrigin::Signed(contributor.clone()), @@ -1830,7 +1816,7 @@ mod benchmarks { assert!(Contributions::::get((project_id, contributor.clone(), contribution_to_settle.id)).is_none()); // Balances - let free_assets = inst.get_free_foreign_asset_balances_for(asset, vec![contributor.clone()])[0].asset_amount; + let free_assets = inst.get_free_funding_asset_balances_for(asset, vec![contributor.clone()])[0].asset_amount; assert_eq!(free_assets, contribution_to_settle.funding_asset_amount + free_assets_before); // Events @@ -1856,7 +1842,7 @@ mod benchmarks { ::SetPrices::set_prices(); // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); whitelist_account!(issuer); @@ -1869,7 +1855,7 @@ mod benchmarks { inst.mint_plmc_to(plmc_for_evaluating); - inst.advance_time(One::one()).unwrap(); + inst.advance_time(One::one()); inst.evaluate_for_users(project_id, evaluations).expect("All evaluations are accepted"); let evaluation_end_block = @@ -1902,7 +1888,7 @@ mod benchmarks { ::SetPrices::set_prices(); // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); whitelist_account!(issuer); @@ -1932,7 +1918,7 @@ mod benchmarks { inst.mint_plmc_to(plmc_for_evaluating); - inst.advance_time(One::one()).unwrap(); + inst.advance_time(One::one()); inst.evaluate_for_users(project_id, evaluations).expect("All evaluations are accepted"); let evaluation_end_block = @@ -1964,7 +1950,7 @@ mod benchmarks { ::SetPrices::set_prices(); // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); whitelist_account!(issuer); @@ -2010,7 +1996,7 @@ mod benchmarks { let mut inst = BenchInstantiator::::new(None); ::SetPrices::set_prices(); // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); whitelist_account!(issuer); @@ -2084,11 +2070,11 @@ mod benchmarks { inst.mint_plmc_to(plmc_needed_for_bids); inst.mint_plmc_to(plmc_ed); - inst.mint_foreign_asset_to(funding_asset_needed_for_bids); + inst.mint_funding_asset_to(funding_asset_needed_for_bids); inst.bid_for_users(project_id, accepted_bids).unwrap(); - let transition_block = inst.get_update_block(project_id, &UpdateType::AuctionClosingStart).unwrap(); + let transition_block = inst.go_to_next_state(project_id).unwrap(); inst.jump_to_block(transition_block); let auction_closing_end_block = inst.get_project_details(project_id).phase_transition_points.auction_closing.end().unwrap(); @@ -2144,7 +2130,7 @@ mod benchmarks { let mut inst = BenchInstantiator::::new(None); ::SetPrices::set_prices(); // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); whitelist_account!(issuer); @@ -2219,7 +2205,7 @@ mod benchmarks { inst.mint_plmc_to(plmc_needed_for_bids); inst.mint_plmc_to(plmc_ed); - inst.mint_foreign_asset_to(funding_asset_needed_for_bids); + inst.mint_funding_asset_to(funding_asset_needed_for_bids); inst.bid_for_users(project_id, accepted_bids).unwrap(); @@ -2272,7 +2258,7 @@ mod benchmarks { ::SetPrices::set_prices(); // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); whitelist_account!(issuer); @@ -2642,7 +2628,7 @@ mod benchmarks { // * validity checks * let project_details = inst.get_project_details(project_id); - assert_eq!(project_details.status, ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful)); + assert_eq!(project_details.status, ProjectStatus::SettlementStarted(FundingOutcome::Success)); } #[benchmark] @@ -2692,7 +2678,7 @@ mod benchmarks { // * validity checks * let project_details = inst.get_project_details(project_id); - assert_eq!(project_details.status, ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed)); + assert_eq!(project_details.status, ProjectStatus::SettlementStarted(FundingOutcome::Failure)); } #[benchmark] @@ -2714,7 +2700,7 @@ mod benchmarks { vec![], ); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); + let settlement_block = inst.go_to_next_state(project_id).unwrap(); inst.jump_to_block(settlement_block); inst.settle_project(project_id).unwrap(); @@ -2764,7 +2750,7 @@ mod benchmarks { vec![], ); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); + let settlement_block = inst.go_to_next_state(project_id).unwrap(); inst.jump_to_block(settlement_block); inst.settle_project(project_id).unwrap(); @@ -2835,7 +2821,7 @@ mod benchmarks { participant_contributions, ); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); + let settlement_block = inst.go_to_next_state(project_id).unwrap(); inst.jump_to_block(settlement_block); inst.settle_project(project_id).unwrap(); @@ -2881,7 +2867,7 @@ mod benchmarks { vec![], ); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); + let settlement_block = inst.go_to_next_state(project_id).unwrap(); inst.jump_to_block(settlement_block); inst.settle_project(project_id).unwrap(); @@ -2940,7 +2926,7 @@ mod benchmarks { vec![], ); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); + let settlement_block = inst.go_to_next_state(project_id).unwrap(); inst.jump_to_block(settlement_block); inst.settle_project(project_id).unwrap(); @@ -3003,7 +2989,7 @@ mod benchmarks { vec![], ); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); + let settlement_block = inst.go_to_next_state(project_id).unwrap(); inst.jump_to_block(settlement_block); inst.settle_project(project_id).unwrap(); @@ -3084,7 +3070,7 @@ mod benchmarks { vec![], ); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); + let settlement_block = inst.go_to_next_state(project_id).unwrap(); inst.jump_to_block(settlement_block); inst.settle_project(project_id).unwrap(); @@ -3206,7 +3192,7 @@ mod benchmarks { participant_contributions, ); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); + let settlement_block = inst.go_to_next_state(project_id).unwrap(); inst.jump_to_block(settlement_block); inst.settle_project(project_id).unwrap(); @@ -3301,7 +3287,7 @@ mod benchmarks { participant_contributions, ); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); + let settlement_block = inst.go_to_next_state(project_id).unwrap(); inst.jump_to_block(settlement_block); inst.settle_project(project_id).unwrap(); @@ -3377,7 +3363,7 @@ mod benchmarks { vec![], ); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); + let settlement_block = inst.go_to_next_state(project_id).unwrap(); inst.jump_to_block(settlement_block); inst.settle_project(project_id).unwrap(); @@ -3427,7 +3413,7 @@ mod benchmarks { vec![], ); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); + let settlement_block = inst.go_to_next_state(project_id).unwrap(); inst.jump_to_block(settlement_block); inst.settle_project(project_id).unwrap(); diff --git a/pallets/funding/src/functions/3_auction.rs b/pallets/funding/src/functions/3_auction.rs index 5b3ee88d0..f4b1b9653 100644 --- a/pallets/funding/src/functions/3_auction.rs +++ b/pallets/funding/src/functions/3_auction.rs @@ -259,7 +259,7 @@ impl Pallet { }; Self::try_plmc_participation_lock(bidder, project_id, plmc_bond)?; - Self::try_funding_asset_hold(bidder, project_id, funding_asset_amount_locked, funding_asset.to_assethub_id())?; + Self::try_funding_asset_hold(bidder, project_id, funding_asset_amount_locked, funding_asset.id())?; Bids::::insert((project_id, bidder, bid_id), &new_bid); NextBidId::::set(bid_id.saturating_add(One::one())); diff --git a/pallets/funding/src/functions/4_contribution.rs b/pallets/funding/src/functions/4_contribution.rs index f0e5b0d2b..e922a4644 100644 --- a/pallets/funding/src/functions/4_contribution.rs +++ b/pallets/funding/src/functions/4_contribution.rs @@ -125,7 +125,7 @@ impl Pallet { // Try adding the new contribution to the system Self::try_plmc_participation_lock(contributor, project_id, plmc_bond)?; - Self::try_funding_asset_hold(contributor, project_id, funding_asset_amount, funding_asset.to_assethub_id())?; + Self::try_funding_asset_hold(contributor, project_id, funding_asset_amount, funding_asset.id())?; Contributions::::insert((project_id, contributor, contribution_id), &new_contribution); NextContributionId::::set(contribution_id.saturating_add(One::one())); diff --git a/pallets/funding/src/functions/6_settlement.rs b/pallets/funding/src/functions/6_settlement.rs index 2414c8519..62333b547 100644 --- a/pallets/funding/src/functions/6_settlement.rs +++ b/pallets/funding/src/functions/6_settlement.rs @@ -235,12 +235,13 @@ impl Pallet { pub fn do_settle_contribution(contribution: ContributionInfoOf, project_id: ProjectId) -> DispatchResult { let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let funding_end_block = project_details.funding_end_block.ok_or(Error::::ImpossibleState)?; let mut final_ct_amount = Zero::zero(); let ProjectStatus::SettlementStarted(outcome) = project_details.status else { return Err(Error::::SettlementNotStarted.into()); }; + let funding_end_block = project_details.funding_end_block.ok_or(Error::::ImpossibleState)?; + if outcome == FundingOutcome::Failure { // Release the held PLMC bond Self::release_participation_bond(project_id, &contribution.contributor, contribution.plmc_bond)?; @@ -350,13 +351,7 @@ impl Pallet { return Ok(()); } let project_pot = Self::fund_account_id(project_id); - T::FundingCurrency::transfer( - asset.to_assethub_id(), - &project_pot, - participant, - amount, - Preservation::Expendable, - )?; + T::FundingCurrency::transfer(asset.id(), &project_pot, participant, amount, Preservation::Expendable)?; Ok(()) } diff --git a/pallets/funding/src/functions/misc.rs b/pallets/funding/src/functions/misc.rs index 0ebe00116..a09e7e86b 100644 --- a/pallets/funding/src/functions/misc.rs +++ b/pallets/funding/src/functions/misc.rs @@ -46,7 +46,7 @@ impl Pallet { ticket_size: BalanceOf, asset_id: AcceptedFundingAsset, ) -> Result, DispatchError> { - let asset_id = asset_id.to_assethub_id(); + let asset_id = asset_id.id(); let asset_decimals = T::FundingCurrency::decimals(asset_id); let asset_usd_price = T::PriceProvider::get_decimals_aware_price(asset_id, USD_DECIMALS, asset_decimals) .ok_or(Error::::PriceNotFound)?; @@ -336,7 +336,7 @@ impl Pallet { ) -> Xcm<()> { // TODO: adjust this as benchmarks for polimec-receiver are written const MAX_WEIGHT: Weight = Weight::from_parts(10_000, 0); - const MAX_RESPONSE_WEIGHT: Weight = Weight::from_parts(700_000_000, 10_000); + const MAX_RESPONSE_WEIGHT: Weight = Weight::from_parts(700_000_000, 50_000); let migrations_item = Migrations::from(migrations.into()); // First byte is the pallet index, second byte is the call index diff --git a/pallets/funding/src/instantiator/calculations.rs b/pallets/funding/src/instantiator/calculations.rs index ea5bac920..d2ac340ce 100644 --- a/pallets/funding/src/instantiator/calculations.rs +++ b/pallets/funding/src/instantiator/calculations.rs @@ -1,4 +1,5 @@ use super::*; +use itertools::GroupBy; use polimec_common::USD_DECIMALS; impl< @@ -36,6 +37,7 @@ impl< output } + // A single bid can be split into multiple buckets. This function splits the bid into multiple ones at different prices. pub fn get_actual_price_charged_for_bucketed_bids( &self, bids: &Vec>, @@ -197,10 +199,10 @@ impl< &mut self, bids: &Vec>, ct_price: PriceOf, - ) -> Vec> { + ) -> Vec> { let mut output = Vec::new(); for bid in bids { - let funding_asset_id = bid.asset.to_assethub_id(); + let funding_asset_id = bid.asset.id(); let funding_asset_decimals = self.execute(|| T::FundingCurrency::decimals(funding_asset_id)); let funding_asset_usd_price = self.execute(|| { T::PriceProvider::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) @@ -208,7 +210,7 @@ impl< }); let usd_ticket_size = ct_price.saturating_mul_int(bid.amount); let funding_asset_spent = funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(usd_ticket_size); - output.push(UserToForeignAssets::new(bid.bidder.clone(), funding_asset_spent, bid.asset.to_assethub_id())); + output.push(UserToFundingAsset::new(bid.bidder.clone(), funding_asset_spent, bid.asset.id())); } output } @@ -219,11 +221,11 @@ impl< bids: &Vec>, project_metadata: ProjectMetadataOf, maybe_bucket: Option>, - ) -> Vec> { + ) -> Vec> { let mut output = Vec::new(); for (bid, price) in self.get_actual_price_charged_for_bucketed_bids(bids, project_metadata, maybe_bucket) { - let funding_asset_id = bid.asset.to_assethub_id(); + let funding_asset_id = bid.asset.id(); let funding_asset_decimals = self.execute(|| T::FundingCurrency::decimals(funding_asset_id)); let funding_asset_usd_price = self.execute(|| { T::PriceProvider::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) @@ -232,24 +234,19 @@ impl< }); let usd_ticket_size = price.saturating_mul_int(bid.amount); let funding_asset_spent = funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(usd_ticket_size); - output.push(UserToForeignAssets::::new( - bid.bidder.clone(), - funding_asset_spent, - bid.asset.to_assethub_id(), - )); + output.push(UserToFundingAsset::::new(bid.bidder.clone(), funding_asset_spent, bid.asset.id())); } output.merge_accounts(MergeOperation::Add) } - // WARNING: Only put bids that you are sure will be done before the random end of the closing auction pub fn calculate_auction_funding_asset_returned_from_all_bids_made( &mut self, // bids in the order they were made bids: &Vec>, project_metadata: ProjectMetadataOf, weighted_average_price: PriceOf, - ) -> Vec> { + ) -> Vec> { let mut output = Vec::new(); let charged_bids = self.get_actual_price_charged_for_bucketed_bids(bids, project_metadata.clone(), None); let grouped_by_price_bids = charged_bids.clone().into_iter().group_by(|&(_, price)| price); @@ -264,7 +261,7 @@ impl< for (price_charged, bids) in grouped_by_price_bids { for bid in bids { - let funding_asset_id = bid.asset.to_assethub_id(); + let funding_asset_id = bid.asset.id(); let funding_asset_decimals = self.execute(|| T::FundingCurrency::decimals(funding_asset_id)); let funding_asset_usd_price = self.execute(|| { T::PriceProvider::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) @@ -272,17 +269,12 @@ impl< .unwrap() }); let charged_usd_ticket_size = price_charged.saturating_mul_int(bid.amount); - let charged_usd_bond = - bid.multiplier.calculate_bonding_requirement::(charged_usd_ticket_size).unwrap(); + let charged_funding_asset = - funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(charged_usd_bond); + funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(charged_usd_ticket_size); if remaining_cts <= Zero::zero() { - output.push(UserToForeignAssets::new( - bid.bidder, - charged_funding_asset, - bid.asset.to_assethub_id(), - )); + output.push(UserToFundingAsset::new(bid.bidder, charged_funding_asset, bid.asset.id())); continue } @@ -293,18 +285,12 @@ impl< if weighted_average_price > price_charged { price_charged } else { weighted_average_price }; let actual_usd_ticket_size = final_price.saturating_mul_int(bought_cts); - let actual_usd_bond = - bid.multiplier.calculate_bonding_requirement::(actual_usd_ticket_size).unwrap(); let actual_funding_asset_spent = - funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(actual_usd_bond); + funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(actual_usd_ticket_size); let returned_foreign_asset = charged_funding_asset - actual_funding_asset_spent; - output.push(UserToForeignAssets::::new( - bid.bidder, - returned_foreign_asset, - bid.asset.to_assethub_id(), - )); + output.push(UserToFundingAsset::::new(bid.bidder, returned_foreign_asset, bid.asset.id())); } } @@ -316,7 +302,7 @@ impl< bids: &Vec>, project_metadata: ProjectMetadataOf, weighted_average_price: PriceOf, - ) -> Vec> { + ) -> Vec> { let funding_asset_charged = self.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( bids, project_metadata.clone(), @@ -428,10 +414,10 @@ impl< &mut self, contributions: Vec>, token_usd_price: PriceOf, - ) -> Vec> { + ) -> Vec> { let mut output = Vec::new(); for cont in contributions { - let funding_asset_id = cont.asset.to_assethub_id(); + let funding_asset_id = cont.asset.id(); let funding_asset_decimals = self.execute(|| T::FundingCurrency::decimals(funding_asset_id)); let funding_asset_usd_price = self.execute(|| { T::PriceProvider::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) @@ -440,7 +426,7 @@ impl< }); let usd_ticket_size = token_usd_price.saturating_mul_int(cont.amount); let funding_asset_spent = funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(usd_ticket_size); - output.push(UserToForeignAssets::new(cont.contributor, funding_asset_spent, cont.asset.to_assethub_id())); + output.push(UserToFundingAsset::new(cont.contributor, funding_asset_spent, cont.asset.id())); } output } @@ -508,14 +494,22 @@ impl< output } - pub fn sum_foreign_mappings(&self, mut mappings: Vec>>) -> BalanceOf { - let mut output = mappings - .swap_remove(0) - .into_iter() - .map(|user_to_asset| user_to_asset.asset_amount) - .fold(Zero::zero(), |a, b| a + b); - for map in mappings { - output += map.into_iter().map(|user_to_asset| user_to_asset.asset_amount).fold(Zero::zero(), |a, b| a + b); + pub fn sum_funding_asset_mappings( + &self, + mappings: Vec>>, + ) -> Vec<(AssetIdOf, BalanceOf)> { + let flattened_list = mappings.into_iter().flatten().collect_vec(); + + let ordered_list = flattened_list.into_iter().sorted_by(|a, b| a.asset_id.cmp(&b.asset_id)).collect_vec(); + + let asset_lists: GroupBy, _, fn(&UserToFundingAsset) -> AssetIdOf> = + ordered_list.into_iter().group_by(|item| item.asset_id); + + let mut output = Vec::new(); + + for (asset_id, asset_list) in &asset_lists { + let sum = asset_list.fold(Zero::zero(), |acc, item| acc + item.asset_amount); + output.push((asset_id, sum)); } output } @@ -643,54 +637,6 @@ impl< early_evaluators_rewards.saturating_add(normal_evaluators_rewards) } - // Assuming all purchases done before random end - pub fn dry_run_wap(&self, mut bucket: BucketOf, token_allocation: BalanceOf) -> PriceOf { - let mut accounted_tokens: BalanceOf = Zero::zero(); - let mut tickets = Vec::new(); - - if bucket.current_price == bucket.initial_price { - return bucket.initial_price - } - - // Do a reverse iteration over the bucket increments to see which tickets are accepted - while accounted_tokens < token_allocation { - let tokens_sold = if bucket.initial_price == bucket.current_price { - bucket.delta_amount * 10u32.into() - } else { - bucket.delta_amount - bucket.amount_left - }; - let price = bucket.current_price; - let remaining_tokens: BalanceOf = token_allocation - accounted_tokens; - let accepted_tokens = remaining_tokens.min(tokens_sold); - tickets.push((accepted_tokens, price)); - accounted_tokens += accepted_tokens; - - if price > bucket.initial_price { - bucket.amount_left = Zero::zero(); - bucket.current_price = bucket.current_price - bucket.delta_price; - } - } - - // Use the accepted tickets to calculate the WAP - let total_usd = tickets - .clone() - .into_iter() - .map(|(tokens, price)| price.saturating_mul_int(tokens.into())) - .reduce(|a, b| a + b) - .unwrap(); - let partial_waps = tickets - .into_iter() - .map(|(tokens, price)| { - let weight = PriceOf::::saturating_from_rational(price.saturating_mul_int(tokens.into()), total_usd); - let partial_wap = weight * price; - partial_wap - }) - .collect::>>(); - let wap = partial_waps.into_iter().reduce(|a, b| a + b).unwrap(); - - wap - } - pub fn find_bucket_for_wap(&self, project_metadata: ProjectMetadataOf, target_wap: PriceOf) -> BucketOf { let mut bucket = >::create_bucket_from_metadata(&project_metadata).unwrap(); let auction_allocation = @@ -705,9 +651,12 @@ impl< // Fill remaining buckets till we pass by the wap loop { - dbg!(&bucket.current_price); - let wap = self.dry_run_wap(bucket.clone(), auction_allocation); + dbg!(&bucket); + let wap = bucket.calculate_wap(auction_allocation); + if wap == target_wap { + return bucket + } if wap < target_wap { bucket.update(bucket.delta_amount); } else { @@ -719,28 +668,31 @@ impl< bucket.amount_left = bucket.delta_amount; bucket.current_price = bucket.current_price - bucket.delta_price; + dbg!(bucket.calculate_wap(auction_allocation)); + dbg!(target_wap); + // Do a binary search on the amount to reach the desired wap let mut lower_bound: BalanceOf = Zero::zero(); let mut upper_bound: BalanceOf = bucket.delta_amount; - let mid_point = |l: BalanceOf, u: BalanceOf| -> BalanceOf { (l.clone() + u.clone()) / 2u32.into() }; - bucket.amount_left = mid_point(lower_bound, upper_bound); - let mut new_wap = self.dry_run_wap(bucket.clone(), auction_allocation); - while new_wap != target_wap { - if new_wap > target_wap { - lower_bound = mid_point(lower_bound, upper_bound); - bucket.amount_left = mid_point(lower_bound, upper_bound); - } else { - upper_bound = mid_point(lower_bound, upper_bound); - bucket.amount_left = mid_point(lower_bound, upper_bound); - } - if lower_bound == upper_bound { - break + while lower_bound <= upper_bound { + let mid_point = (lower_bound + upper_bound) / 2u32.into(); + bucket.amount_left = mid_point; + let new_wap = bucket.calculate_wap(auction_allocation); + dbg!(&lower_bound); + dbg!(&upper_bound); + dbg!(&mid_point); + dbg!(&new_wap); + + if new_wap == target_wap { + return bucket + } else if new_wap < target_wap { + upper_bound = mid_point.saturating_sub(1u32.into()); + } else { + lower_bound = mid_point.saturating_add(1u32.into()); } - - new_wap = self.dry_run_wap(bucket.clone(), auction_allocation); } - dbg!(&bucket); + bucket } @@ -805,4 +757,21 @@ impl< AcceptedFundingAsset::USDT, ) } + + // Make sure the bids are in the order they were made + pub fn calculate_wap_from_all_bids_made( + &self, + project_metadata: &ProjectMetadataOf, + bids: &Vec>, + ) -> PriceOf { + let mut bucket = Pallet::::create_bucket_from_metadata(project_metadata).unwrap(); + + for bid in bids { + bucket.update(bid.amount); + } + + let auction_allocation = + project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + bucket.calculate_wap(auction_allocation) + } } diff --git a/pallets/funding/src/instantiator/chain_interactions.rs b/pallets/funding/src/instantiator/chain_interactions.rs index f028607ed..b7d819d19 100644 --- a/pallets/funding/src/instantiator/chain_interactions.rs +++ b/pallets/funding/src/instantiator/chain_interactions.rs @@ -69,23 +69,23 @@ impl< self.execute(|| ::NativeCurrency::balance_on_hold(&lock_type, &user)) } - pub fn get_free_foreign_asset_balances_for( + pub fn get_free_funding_asset_balances_for( &mut self, asset_id: AssetIdOf, user_keys: Vec>, - ) -> Vec> { + ) -> Vec> { self.execute(|| { - let mut balances: Vec> = Vec::new(); + let mut balances: Vec> = Vec::new(); for account in user_keys { let asset_amount = ::FundingCurrency::balance(asset_id, &account); - balances.push(UserToForeignAssets { account, asset_amount, asset_id }); + balances.push(UserToFundingAsset { account, asset_amount, asset_id }); } balances.sort_by(|a, b| a.account.cmp(&b.account)); balances }) } - pub fn get_free_foreign_asset_balance_for(&mut self, asset_id: AssetIdOf, user: AccountIdOf) -> BalanceOf { + pub fn get_free_funding_asset_balance_for(&mut self, asset_id: AssetIdOf, user: AccountIdOf) -> BalanceOf { self.execute(|| ::FundingCurrency::balance(asset_id, &user)) } @@ -121,9 +121,9 @@ impl< self.get_reserved_plmc_balances_for(user_keys, reserve_type) } - pub fn get_all_free_foreign_asset_balances(&mut self, asset_id: AssetIdOf) -> Vec> { + pub fn get_all_free_funding_asset_balances(&mut self, asset_id: AssetIdOf) -> Vec> { let user_keys = self.execute(|| frame_system::Account::::iter_keys().collect()); - self.get_free_foreign_asset_balances_for(asset_id, user_keys) + self.get_free_funding_asset_balances_for(asset_id, user_keys) } pub fn get_plmc_total_supply(&mut self) -> BalanceOf { @@ -138,7 +138,7 @@ impl< for UserToPLMCBalance { account, plmc_amount } in correct_funds { self.execute(|| { let reserved = ::NativeCurrency::balance_on_hold(&reserve_type, &account); - assert_eq!(reserved, plmc_amount, "account has unexpected reserved plmc balance"); + assert_eq!(reserved, plmc_amount, "account {account} has unexpected reserved plmc balance"); }); } } @@ -151,9 +151,9 @@ impl< }); } - pub fn mint_foreign_asset_to(&mut self, mapping: Vec>) { + pub fn mint_funding_asset_to(&mut self, mapping: Vec>) { self.execute(|| { - for UserToForeignAssets { account, asset_amount, asset_id } in mapping { + for UserToFundingAsset { account, asset_amount, asset_id } in mapping { ::FundingCurrency::mint_into(asset_id, &account, asset_amount) .expect("Minting should work"); } @@ -164,7 +164,7 @@ impl< self.execute(|| frame_system::Pallet::::block_number()) } - pub fn advance_time(&mut self, amount: BlockNumberFor) -> Result<(), DispatchError> { + pub fn advance_time(&mut self, amount: BlockNumberFor) { self.execute(|| { for _block in 0u32..amount.saturated_into() { let mut current_block = frame_system::Pallet::::block_number(); @@ -181,7 +181,6 @@ impl< as OnInitialize>>::on_initialize(current_block); >>::on_initialize(current_block); } - Ok(()) }) } @@ -189,9 +188,9 @@ impl< let current_block = self.current_block(); if block > current_block { self.execute(|| frame_system::Pallet::::set_block_number(block - One::one())); - self.advance_time(One::one()).unwrap(); + self.advance_time(One::one()); } else { - panic!("Cannot jump to a block in the present or past") + // panic!("Cannot jump to a block in the present or past") } } @@ -204,47 +203,11 @@ impl< } } - pub fn do_free_foreign_asset_assertions(&mut self, correct_funds: Vec>) { - for UserToForeignAssets { account, asset_amount, asset_id } in correct_funds { + pub fn do_free_funding_asset_assertions(&mut self, correct_funds: Vec>) { + for UserToFundingAsset { account, asset_amount: expected_amount, asset_id } in correct_funds { self.execute(|| { let real_amount = ::FundingCurrency::balance(asset_id, &account); - assert_eq!(asset_amount, real_amount, "Wrong foreign asset balance expected for user {:?}", account); - }); - } - } - - pub fn do_bid_transferred_foreign_asset_assertions( - &mut self, - correct_funds: Vec>, - project_id: ProjectId, - ) { - for UserToForeignAssets { account, asset_amount, .. } in correct_funds { - self.execute(|| { - // total amount of contributions for this user for this project stored in the mapping - let contribution_total: ::Balance = - Bids::::iter_prefix_values((project_id, account.clone())) - .map(|c| c.funding_asset_amount_locked) - .fold(Zero::zero(), |a, b| a + b); - assert_eq!( - contribution_total, asset_amount, - "Wrong funding balance expected for stored auction info on user {:?}", - account - ); - }); - } - } - - // Check if a Contribution storage item exists for the given funding asset transfer - pub fn do_contribution_transferred_foreign_asset_assertions( - &mut self, - correct_funds: Vec>, - project_id: ProjectId, - ) { - for UserToForeignAssets { account, asset_amount, .. } in correct_funds { - self.execute(|| { - Contributions::::iter_prefix_values((project_id, account.clone())) - .find(|c| c.funding_asset_amount == asset_amount) - .expect("Contribution not found in storage"); + assert_eq!(real_amount, expected_amount, "Wrong funding asset balance expected for user {:?}", account); }); } } @@ -363,6 +326,41 @@ impl< "Remaining CTs are incorrect" ); } + + pub fn assert_plmc_free_balance(&mut self, account_id: AccountIdOf, expected_balance: BalanceOf) { + let real_balance = self.get_free_plmc_balance_for(account_id.clone()); + assert_eq!(real_balance, expected_balance, "Unexpected PLMC balance for user {:?}", account_id); + } + + pub fn assert_plmc_held_balance( + &mut self, + account_id: AccountIdOf, + expected_balance: BalanceOf, + hold_reason: ::RuntimeHoldReason, + ) { + let real_balance = self.get_reserved_plmc_balance_for(account_id.clone(), hold_reason); + assert_eq!(real_balance, expected_balance, "Unexpected PLMC balance for user {:?}", account_id); + } + + pub fn assert_funding_asset_free_balance( + &mut self, + account_id: AccountIdOf, + asset_id: AssetIdOf, + expected_balance: BalanceOf, + ) { + let real_balance = self.get_free_funding_asset_balance_for(asset_id, account_id.clone()); + assert_eq!(real_balance, expected_balance, "Unexpected funding asset balance for user {:?}", account_id); + } + + pub fn assert_ct_balance( + &mut self, + project_id: ProjectId, + account_id: AccountIdOf, + expected_balance: BalanceOf, + ) { + let real_balance = self.get_ct_asset_balance_for(project_id, account_id.clone()); + assert_eq!(real_balance, expected_balance, "Unexpected CT balance for user {:?}", account_id); + } } // project chain interactions @@ -384,9 +382,40 @@ impl< self.execute(|| ProjectsDetails::::get(project_id).expect("Project details exists")) } - pub fn get_update_block(&mut self, _project_id: ProjectId, _update_type: &UpdateType) -> Option> { - Some(BlockNumberFor::::zero()) - // TODO: FIX + pub fn go_to_next_state(&mut self, project_id: ProjectId) -> ProjectStatus> { + let project_details = self.get_project_details(project_id); + let issuer = project_details.issuer_account; + let original_state = project_details.status; + if let Some(end_block) = project_details.round_duration.end() { + self.jump_to_block(end_block + One::one()); + } + let project_details = self.get_project_details(project_id); + + match project_details.status { + ProjectStatus::Application => { + self.execute(|| >::do_start_evaluation(issuer, project_id).unwrap()); + }, + ProjectStatus::EvaluationRound => { + self.execute(|| >::do_end_evaluation(project_id).unwrap()); + }, + ProjectStatus::AuctionInitializePeriod => { + self.execute(|| >::do_start_auction(issuer, project_id).unwrap()); + }, + ProjectStatus::AuctionRound => { + self.execute(|| >::do_end_auction(project_id).unwrap()); + }, + ProjectStatus::CommunityRound(..) => { + self.execute(|| >::do_end_funding(project_id).unwrap()); + }, + ProjectStatus::FundingSuccessful | ProjectStatus::FundingFailed => { + self.execute(|| >::do_start_settlement(project_id).unwrap()); + }, + _ => panic!("Unexpected project status"), + } + let new_details = self.get_project_details(project_id); + assert_ne!(original_state, new_details.status, "Project should have transitioned to a new state"); + + new_details.status } pub fn create_new_project( @@ -411,14 +440,6 @@ impl< created_project_id } - pub fn start_evaluation(&mut self, project_id: ProjectId, caller: AccountIdOf) -> Result<(), DispatchError> { - assert_eq!(self.get_project_details(project_id).status, ProjectStatus::Application); - self.execute(|| crate::Pallet::::do_start_evaluation(caller, project_id).unwrap()); - assert_eq!(self.get_project_details(project_id).status, ProjectStatus::EvaluationRound); - - Ok(()) - } - pub fn create_evaluating_project( &mut self, project_metadata: ProjectMetadataOf, @@ -426,7 +447,8 @@ impl< maybe_did: Option, ) -> ProjectId { let project_id = self.create_new_project(project_metadata, issuer.clone(), maybe_did); - self.start_evaluation(project_id, issuer).unwrap(); + assert_eq!(self.go_to_next_state(project_id), ProjectStatus::EvaluationRound); + project_id } @@ -450,24 +472,6 @@ impl< Ok(().into()) } - pub fn start_auction(&mut self, project_id: ProjectId, caller: AccountIdOf) -> Result<(), DispatchError> { - let project_details = self.get_project_details(project_id); - - if project_details.status == ProjectStatus::EvaluationRound { - let now = self.current_block(); - let evaluation_end_execution = self.get_update_block(project_id, &UpdateType::EvaluationEnd).unwrap(); - self.advance_time(evaluation_end_execution - now).unwrap(); - }; - - assert_eq!(self.get_project_details(project_id).status, ProjectStatus::AuctionInitializePeriod); - - self.execute(|| crate::Pallet::::do_start_auction(caller, project_id).unwrap()); - - assert_eq!(self.get_project_details(project_id).status, ProjectStatus::AuctionRound); - - Ok(()) - } - pub fn create_auctioning_project( &mut self, project_metadata: ProjectMetadataOf, @@ -500,7 +504,9 @@ impl< self.evaluation_assertions(project_id, expected_remaining_plmc, plmc_eval_deposits, expected_total_supply); - self.start_auction(project_id, issuer).unwrap(); + assert_eq!(self.go_to_next_state(project_id), ProjectStatus::AuctionInitializePeriod); + assert_eq!(self.go_to_next_state(project_id), ProjectStatus::AuctionRound); + project_id } @@ -525,25 +531,6 @@ impl< Ok(().into()) } - pub fn start_community_funding(&mut self, project_id: ProjectId) -> Result<(), DispatchError> { - if let Some(update_block) = self.get_update_block(project_id, &UpdateType::AuctionClosingStart) { - self.jump_to_block(update_block); - } - if let Some(update_block) = self.get_update_block(project_id, &UpdateType::AuctionClosingEnd) { - self.jump_to_block(update_block); - } - if let Some(update_block) = self.get_update_block(project_id, &UpdateType::CommunityFundingStart) { - self.jump_to_block(update_block); - } - - ensure!( - matches!(self.get_project_details(project_id).status, ProjectStatus::CommunityRound(..)), - DispatchError::from("Auction failed") - ); - - Ok(()) - } - pub fn create_community_contributing_project( &mut self, project_metadata: ProjectMetadataOf, @@ -555,14 +542,14 @@ impl< let project_id = self.create_auctioning_project(project_metadata.clone(), issuer, maybe_did, evaluations.clone()); if bids.is_empty() { - self.start_community_funding(project_id).unwrap(); + assert!(matches!(self.go_to_next_state(project_id), ProjectStatus::CommunityRound(_))); return project_id } let bidders = bids.accounts(); - let asset_id = bids[0].asset.to_assethub_id(); + let asset_id = bids[0].asset.id(); let prev_plmc_balances = self.get_free_plmc_balances_for(bidders.clone()); - let prev_funding_asset_balances = self.get_free_foreign_asset_balances_for(asset_id, bidders.clone()); + let prev_funding_asset_balances = self.get_free_funding_asset_balances_for(asset_id, bidders.clone()); let plmc_evaluation_deposits: Vec> = self.calculate_evaluation_plmc_spent(evaluations, false); let plmc_bid_deposits: Vec> = self @@ -602,7 +589,7 @@ impl< self.mint_plmc_to(necessary_plmc_mint.clone()); self.mint_plmc_to(plmc_existential_deposits.clone()); - self.mint_foreign_asset_to(funding_asset_deposits.clone()); + self.mint_funding_asset_to(funding_asset_deposits.clone()); self.bid_for_users(project_id, bids.clone()).unwrap(); @@ -610,15 +597,15 @@ impl< total_plmc_participation_locked.merge_accounts(MergeOperation::Add), HoldReason::Participation(project_id).into(), ); - self.do_bid_transferred_foreign_asset_assertions( - funding_asset_deposits.merge_accounts(MergeOperation::Add), - project_id, - ); + // self.do_bid_transferred_funding_asset_assertions( + // funding_asset_deposits.merge_accounts(MergeOperation::Add), + // project_id, + // ); self.do_free_plmc_assertions(expected_free_plmc_balances.merge_accounts(MergeOperation::Add)); - self.do_free_foreign_asset_assertions(prev_funding_asset_balances.merge_accounts(MergeOperation::Add)); + self.do_free_funding_asset_assertions(prev_funding_asset_balances.merge_accounts(MergeOperation::Add)); assert_eq!(self.get_plmc_total_supply(), post_supply); - self.start_community_funding(project_id).unwrap(); + assert!(matches!(self.go_to_next_state(project_id), ProjectStatus::CommunityRound(_))); project_id } @@ -654,46 +641,6 @@ impl< Ok(().into()) } - pub fn start_remainder_or_end_funding(&mut self, project_id: ProjectId) -> Result<(), DispatchError> { - let details = self.get_project_details(project_id); - assert!(matches!(details.status, ProjectStatus::CommunityRound(..))); - let remaining_tokens = details.remaining_contribution_tokens; - let update_type = - if remaining_tokens > Zero::zero() { UpdateType::RemainderFundingStart } else { UpdateType::FundingEnd }; - if let Some(transition_block) = self.get_update_block(project_id, &update_type) { - self.execute(|| frame_system::Pallet::::set_block_number(transition_block - One::one())); - self.advance_time(1u32.into()).unwrap(); - match self.get_project_details(project_id).status { - ProjectStatus::FundingSuccessful => Ok(()), - _ => panic!("Bad state"), - } - } else { - panic!("Bad state") - } - } - - pub fn finish_funding( - &mut self, - project_id: ProjectId, - _force_decision: Option, - ) -> Result<(), DispatchError> { - if let Some(update_block) = self.get_update_block(project_id, &UpdateType::RemainderFundingStart) { - self.execute(|| frame_system::Pallet::::set_block_number(update_block - One::one())); - self.advance_time(1u32.into()).unwrap(); - } - let update_block = - self.get_update_block(project_id, &UpdateType::FundingEnd).expect("Funding end block should exist"); - self.execute(|| frame_system::Pallet::::set_block_number(update_block - One::one())); - self.advance_time(1u32.into()).unwrap(); - let project_details = self.get_project_details(project_id); - assert!( - matches!(project_details.status, ProjectStatus::FundingSuccessful | ProjectStatus::FundingFailed), - "Project should be in Finished status" - ); - - Ok(()) - } - pub fn settle_project(&mut self, project_id: ProjectId) { self.execute(|| { Evaluations::::iter_prefix((project_id,)) @@ -749,18 +696,18 @@ impl< } } - let total_stored_dot = self.get_free_foreign_asset_balances_for( - AcceptedFundingAsset::DOT.to_assethub_id(), + let total_stored_dot = self.get_free_funding_asset_balances_for( + AcceptedFundingAsset::DOT.id(), vec![project_metadata.funding_destination_account.clone()], )[0] .asset_amount; - let total_stored_usdt = self.get_free_foreign_asset_balances_for( - AcceptedFundingAsset::USDT.to_assethub_id(), + let total_stored_usdt = self.get_free_funding_asset_balances_for( + AcceptedFundingAsset::USDT.id(), vec![project_metadata.funding_destination_account.clone()], )[0] .asset_amount; - let total_stored_usdc = self.get_free_foreign_asset_balances_for( - AcceptedFundingAsset::USDC.to_assethub_id(), + let total_stored_usdc = self.get_free_funding_asset_balances_for( + AcceptedFundingAsset::USDC.id(), vec![project_metadata.funding_destination_account.clone()], )[0] .asset_amount; @@ -776,31 +723,34 @@ impl< &mut self, project_id: ProjectId, evaluations: Vec>, + is_successful: bool, ) { let details = self.get_project_details(project_id); assert!(matches!(details.status, ProjectStatus::SettlementFinished(_))); + let evaluators_outcome = self.execute(|| { + ProjectsDetails::::get(project_id).unwrap().evaluation_round_info.evaluators_outcome.unwrap() + }); + for evaluation in evaluations { - let reward_info = self.execute(|| { - ProjectsDetails::::get(project_id).unwrap().evaluation_round_info.evaluators_outcome.unwrap() - }); let account = evaluation.evaluator.clone(); assert_eq!(self.execute(|| { Evaluations::::iter_prefix_values((&project_id, &account)).count() }), 0); - let (amount, should_exist) = { - let reward = match reward_info { - EvaluatorsOutcome::Rewarded(info) => Pallet::::calculate_evaluator_reward(&evaluation, &info), - _ => panic!("Evaluators should be rewarded"), - }; - (reward, true) + let amount = if let EvaluatorsOutcome::Rewarded(ref info) = evaluators_outcome { + assert!(is_successful); + Pallet::::calculate_evaluator_reward(&evaluation, &info) + } else { + assert!(!is_successful); + Zero::zero() }; + self.assert_migration( project_id, account, amount, evaluation.id, ParticipationType::Evaluation, - should_exist, + is_successful, ); } } @@ -897,61 +847,70 @@ impl< bids.clone(), ); - if contributions.is_empty() { - self.start_remainder_or_end_funding(project_id).unwrap(); - return project_id; - } - - let ct_price = self.get_project_details(project_id).weighted_average_price.unwrap(); - - let contributors = contributions.accounts(); + if !contributions.is_empty() { + let ct_price = self.get_project_details(project_id).weighted_average_price.unwrap(); - let asset_id = contributions[0].asset.to_assethub_id(); + let contributors = contributions.accounts(); - let prev_plmc_balances = self.get_free_plmc_balances_for(contributors.clone()); - let prev_funding_asset_balances = self.get_free_foreign_asset_balances_for(asset_id, contributors.clone()); + let asset_id = contributions[0].asset.id(); - let plmc_evaluation_deposits = self.calculate_evaluation_plmc_spent(evaluations.clone(), false); - let plmc_bid_deposits = self.calculate_auction_plmc_spent_post_wap(&bids, project_metadata.clone(), ct_price); - let plmc_contribution_deposits = self.calculate_contributed_plmc_spent(contributions.clone(), ct_price, false); - - let reducible_evaluator_balances = self.slash_evaluator_balances(plmc_evaluation_deposits.clone()); - let necessary_plmc_mint = self.generic_map_operation( - vec![plmc_contribution_deposits.clone(), reducible_evaluator_balances], - MergeOperation::Subtract, - ); - let total_plmc_participation_locked = - self.generic_map_operation(vec![plmc_bid_deposits, plmc_contribution_deposits], MergeOperation::Add); - let plmc_existential_deposits = contributors.existential_deposits(); + let prev_plmc_balances = self.get_free_plmc_balances_for(contributors.clone()); + let prev_funding_asset_balances = self.get_free_funding_asset_balances_for(asset_id, contributors.clone()); - let funding_asset_deposits = self.calculate_contributed_funding_asset_spent(contributions.clone(), ct_price); - let contributor_balances = - self.sum_balance_mappings(vec![necessary_plmc_mint.clone(), plmc_existential_deposits.clone()]); + let plmc_evaluation_deposits = self.calculate_evaluation_plmc_spent(evaluations.clone(), false); + let plmc_bid_deposits = self.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + &bids, + project_metadata.clone(), + None, + false, + ); + let plmc_contribution_deposits = + self.calculate_contributed_plmc_spent(contributions.clone(), ct_price, false); - let expected_free_plmc_balances = self - .generic_map_operation(vec![prev_plmc_balances, plmc_existential_deposits.clone()], MergeOperation::Add); + let reducible_evaluator_balances = self.slash_evaluator_balances(plmc_evaluation_deposits.clone()); + let necessary_plmc_mint = self.generic_map_operation( + vec![plmc_contribution_deposits.clone(), reducible_evaluator_balances], + MergeOperation::Subtract, + ); + let total_plmc_participation_locked = + self.generic_map_operation(vec![plmc_bid_deposits, plmc_contribution_deposits], MergeOperation::Add); + let plmc_existential_deposits = contributors.existential_deposits(); + + let funding_asset_deposits = + self.calculate_contributed_funding_asset_spent(contributions.clone(), ct_price); + let contributor_balances = + self.sum_balance_mappings(vec![necessary_plmc_mint.clone(), plmc_existential_deposits.clone()]); + + let expected_free_plmc_balances = self.generic_map_operation( + vec![prev_plmc_balances, plmc_existential_deposits.clone()], + MergeOperation::Add, + ); - let prev_supply = self.get_plmc_total_supply(); - let post_supply = prev_supply + contributor_balances; + let prev_supply = self.get_plmc_total_supply(); + let post_supply = prev_supply + contributor_balances; - self.mint_plmc_to(necessary_plmc_mint.clone()); - self.mint_plmc_to(plmc_existential_deposits.clone()); - self.mint_foreign_asset_to(funding_asset_deposits.clone()); + self.mint_plmc_to(necessary_plmc_mint.clone()); + self.mint_plmc_to(plmc_existential_deposits.clone()); + self.mint_funding_asset_to(funding_asset_deposits.clone()); - self.contribute_for_users(project_id, contributions).expect("Contributing should work"); + self.contribute_for_users(project_id, contributions).expect("Contributing should work"); - self.do_reserved_plmc_assertions( - total_plmc_participation_locked.merge_accounts(MergeOperation::Add), - HoldReason::Participation(project_id).into(), - ); + self.do_reserved_plmc_assertions( + total_plmc_participation_locked.merge_accounts(MergeOperation::Add), + HoldReason::Participation(project_id).into(), + ); - self.do_contribution_transferred_foreign_asset_assertions(funding_asset_deposits, project_id); + // self.do_contribution_transferred_funding_asset_assertions(funding_asset_deposits, project_id); - self.do_free_plmc_assertions(expected_free_plmc_balances.merge_accounts(MergeOperation::Add)); - self.do_free_foreign_asset_assertions(prev_funding_asset_balances.merge_accounts(MergeOperation::Add)); - assert_eq!(self.get_plmc_total_supply(), post_supply); + self.do_free_plmc_assertions(expected_free_plmc_balances.merge_accounts(MergeOperation::Add)); + self.do_free_funding_asset_assertions(prev_funding_asset_balances.merge_accounts(MergeOperation::Add)); + assert_eq!(self.get_plmc_total_supply(), post_supply); + } - self.start_remainder_or_end_funding(project_id).unwrap(); + let ProjectStatus::CommunityRound(remainder_block) = self.get_project_details(project_id).status else { + panic!("Project should be in CommunityRound status"); + }; + self.jump_to_block(remainder_block); project_id } @@ -975,69 +934,74 @@ impl< community_contributions.clone(), ); - match self.get_project_details(project_id).status { - ProjectStatus::FundingSuccessful => return project_id, - _ => {}, - }; - let ct_price = self.get_project_details(project_id).weighted_average_price.unwrap(); - let contributors = remainder_contributions.accounts(); - let asset_id = remainder_contributions[0].asset.to_assethub_id(); - let prev_plmc_balances = self.get_free_plmc_balances_for(contributors.clone()); - let prev_funding_asset_balances = self.get_free_foreign_asset_balances_for(asset_id, contributors.clone()); - - let plmc_evaluation_deposits = self.calculate_evaluation_plmc_spent(evaluations, false); - let plmc_bid_deposits = self.calculate_auction_plmc_spent_post_wap(&bids, project_metadata.clone(), ct_price); - let plmc_community_contribution_deposits = - self.calculate_contributed_plmc_spent(community_contributions.clone(), ct_price, false); - let plmc_remainder_contribution_deposits = - self.calculate_contributed_plmc_spent(remainder_contributions.clone(), ct_price, false); - - let reducible_evaluator_balances = self.slash_evaluator_balances(plmc_evaluation_deposits); - let remaining_reducible_evaluator_balances = self.generic_map_operation( - vec![reducible_evaluator_balances, plmc_bid_deposits.clone()], - MergeOperation::Subtract, - ); + if !remainder_contributions.is_empty() { + let ct_price = self.get_project_details(project_id).weighted_average_price.unwrap(); + let contributors = remainder_contributions.accounts(); + let asset_id = remainder_contributions[0].asset.id(); + let prev_plmc_balances = self.get_free_plmc_balances_for(contributors.clone()); + let prev_funding_asset_balances = self.get_free_funding_asset_balances_for(asset_id, contributors.clone()); - let necessary_plmc_mint = self.generic_map_operation( - vec![plmc_remainder_contribution_deposits.clone(), remaining_reducible_evaluator_balances], - MergeOperation::Subtract, - ); - let total_plmc_participation_locked = self.generic_map_operation( - vec![plmc_bid_deposits, plmc_community_contribution_deposits, plmc_remainder_contribution_deposits], - MergeOperation::Add, - ); - let plmc_existential_deposits = contributors.existential_deposits(); - let funding_asset_deposits = - self.calculate_contributed_funding_asset_spent(remainder_contributions.clone(), ct_price); + let plmc_evaluation_deposits = self.calculate_evaluation_plmc_spent(evaluations, false); + let plmc_bid_deposits = self.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + &bids, + project_metadata.clone(), + None, + false, + ); + let plmc_community_contribution_deposits = + self.calculate_contributed_plmc_spent(community_contributions.clone(), ct_price, false); + let plmc_remainder_contribution_deposits = + self.calculate_contributed_plmc_spent(remainder_contributions.clone(), ct_price, false); + + let reducible_evaluator_balances = self.slash_evaluator_balances(plmc_evaluation_deposits); + let remaining_reducible_evaluator_balances = self.generic_map_operation( + vec![reducible_evaluator_balances, plmc_bid_deposits.clone()], + MergeOperation::Subtract, + ); - let contributor_balances = - self.sum_balance_mappings(vec![necessary_plmc_mint.clone(), plmc_existential_deposits.clone()]); + let necessary_plmc_mint = self.generic_map_operation( + vec![plmc_remainder_contribution_deposits.clone(), remaining_reducible_evaluator_balances], + MergeOperation::Subtract, + ); + let total_plmc_participation_locked = self.generic_map_operation( + vec![plmc_bid_deposits, plmc_community_contribution_deposits, plmc_remainder_contribution_deposits], + MergeOperation::Add, + ); + let plmc_existential_deposits = contributors.existential_deposits(); + let funding_asset_deposits = + self.calculate_contributed_funding_asset_spent(remainder_contributions.clone(), ct_price); - let expected_free_plmc_balances = self - .generic_map_operation(vec![prev_plmc_balances, plmc_existential_deposits.clone()], MergeOperation::Add); + let contributor_balances = + self.sum_balance_mappings(vec![necessary_plmc_mint.clone(), plmc_existential_deposits.clone()]); - let prev_supply = self.get_plmc_total_supply(); - let post_supply = prev_supply + contributor_balances; + let expected_free_plmc_balances = self.generic_map_operation( + vec![prev_plmc_balances, plmc_existential_deposits.clone()], + MergeOperation::Add, + ); - self.mint_plmc_to(necessary_plmc_mint.clone()); - self.mint_plmc_to(plmc_existential_deposits.clone()); - self.mint_foreign_asset_to(funding_asset_deposits.clone()); + let prev_supply = self.get_plmc_total_supply(); + let post_supply = prev_supply + contributor_balances; - self.contribute_for_users(project_id, remainder_contributions.clone()) - .expect("Remainder Contributing should work"); + self.mint_plmc_to(necessary_plmc_mint.clone()); + self.mint_plmc_to(plmc_existential_deposits.clone()); + self.mint_funding_asset_to(funding_asset_deposits.clone()); - self.do_reserved_plmc_assertions( - total_plmc_participation_locked.merge_accounts(MergeOperation::Add), - HoldReason::Participation(project_id).into(), - ); - self.do_contribution_transferred_foreign_asset_assertions(funding_asset_deposits, project_id); - self.do_free_plmc_assertions(expected_free_plmc_balances.merge_accounts(MergeOperation::Add)); - self.do_free_foreign_asset_assertions(prev_funding_asset_balances.merge_accounts(MergeOperation::Add)); - assert_eq!(self.get_plmc_total_supply(), post_supply); + self.contribute_for_users(project_id, remainder_contributions.clone()) + .expect("Remainder Contributing should work"); - self.finish_funding(project_id, None).unwrap(); + self.do_reserved_plmc_assertions( + total_plmc_participation_locked.merge_accounts(MergeOperation::Add), + HoldReason::Participation(project_id).into(), + ); + // self.do_contribution_transferred_funding_asset_assertions(funding_asset_deposits, project_id); + self.do_free_plmc_assertions(expected_free_plmc_balances.merge_accounts(MergeOperation::Add)); + self.do_free_funding_asset_assertions(prev_funding_asset_balances.merge_accounts(MergeOperation::Add)); + assert_eq!(self.get_plmc_total_supply(), post_supply); + } - if self.get_project_details(project_id).status == ProjectStatus::FundingSuccessful { + let status = self.go_to_next_state(project_id); + + if status == ProjectStatus::FundingSuccessful { // Check that remaining CTs are updated let project_details = self.get_project_details(project_id); // if our bids were creating an oversubscription, then just take the total allocation size @@ -1059,6 +1023,10 @@ impl< remainder_bought_tokens, "Remaining CTs are incorrect" ); + } else if status == ProjectStatus::FundingFailed { + self.test_ct_not_created_for(project_id); + } else { + panic!("Project should be in FundingSuccessful or FundingFailed status"); } project_id @@ -1084,8 +1052,7 @@ impl< remainder_contributions.clone(), ); - let settlement_start = self.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - self.jump_to_block(settlement_start); + assert!(matches!(self.go_to_next_state(project_id), ProjectStatus::SettlementStarted(_))); self.settle_project(project_id); project_id diff --git a/pallets/funding/src/instantiator/mod.rs b/pallets/funding/src/instantiator/mod.rs index fd11c8938..a5291fa91 100644 --- a/pallets/funding/src/instantiator/mod.rs +++ b/pallets/funding/src/instantiator/mod.rs @@ -39,10 +39,7 @@ use sp_arithmetic::{ traits::{SaturatedConversion, Saturating, Zero}, FixedPointNumber, Percent, Perquintill, }; -use sp_runtime::{ - traits::{Convert, Member, One}, - DispatchError, -}; +use sp_runtime::traits::{Convert, Member, One}; use sp_std::{ cell::RefCell, collections::{btree_map::*, btree_set::*}, diff --git a/pallets/funding/src/instantiator/tests.rs b/pallets/funding/src/instantiator/tests.rs index 1bb3df471..3e732a77e 100644 --- a/pallets/funding/src/instantiator/tests.rs +++ b/pallets/funding/src/instantiator/tests.rs @@ -1,5 +1,5 @@ use crate::{ - instantiator::{UserToForeignAssets, UserToPLMCBalance}, + instantiator::{UserToFundingAsset, UserToPLMCBalance}, mock::{new_test_ext, TestRuntime, PLMC}, tests::{ defaults::{bounded_name, bounded_symbol, default_evaluations, default_project_metadata, ipfs_hash}, @@ -62,14 +62,14 @@ fn dry_run_wap() { .collect_vec(); let usdt_fundings = accounts .iter() - .map(|acc| UserToForeignAssets { + .map(|acc| UserToFundingAsset { account: acc.clone(), asset_amount: USD_UNIT * 1_000_000, - asset_id: AcceptedFundingAsset::USDT.to_assethub_id(), + asset_id: AcceptedFundingAsset::USDT.id(), }) .collect_vec(); inst.mint_plmc_to(plmc_fundings); - inst.mint_foreign_asset_to(usdt_fundings); + inst.mint_funding_asset_to(usdt_fundings); let project_id = inst.create_auctioning_project(project_metadata.clone(), 0, None, default_evaluations()); @@ -84,17 +84,13 @@ fn dry_run_wap() { inst.bid_for_users(project_id, bids).unwrap(); - inst.start_community_funding(project_id).unwrap(); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(_))); let project_details = inst.get_project_details(project_id); let wap = project_details.weighted_average_price.unwrap(); let bucket = inst.execute(|| Buckets::::get(project_id).unwrap()); - dbg!(bucket); - let dry_run_price = inst.dry_run_wap( - bucket, - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size, - ); - dbg!(project_details.funding_amount_reached_usd); + let dry_run_price = bucket + .calculate_wap(project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size); assert_eq!(dry_run_price, wap); } @@ -148,14 +144,14 @@ fn find_bucket_for_wap() { .collect_vec(); let usdt_fundings = accounts .iter() - .map(|acc| UserToForeignAssets { + .map(|acc| UserToFundingAsset { account: acc.clone(), asset_amount: USD_UNIT * 1_000_000, - asset_id: AcceptedFundingAsset::USDT.to_assethub_id(), + asset_id: AcceptedFundingAsset::USDT.id(), }) .collect_vec(); inst.mint_plmc_to(plmc_fundings); - inst.mint_foreign_asset_to(usdt_fundings); + inst.mint_funding_asset_to(usdt_fundings); let project_id = inst.create_auctioning_project(project_metadata.clone(), 0, None, default_evaluations()); @@ -170,26 +166,17 @@ fn find_bucket_for_wap() { inst.bid_for_users(project_id, bids).unwrap(); - inst.start_community_funding(project_id).unwrap(); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(_))); let project_details = inst.get_project_details(project_id); let wap = project_details.weighted_average_price.unwrap(); - dbg!(wap); - let bucket = inst.execute(|| Buckets::::get(project_id).unwrap()); - dbg!(bucket); - let dry_run_price = inst.dry_run_wap( - bucket, - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size, - ); - dbg!(project_details.funding_amount_reached_usd); - - assert_eq!(dry_run_price, wap); + let bucket_stored = inst.execute(|| Buckets::::get(project_id).unwrap()); let bucket_found = inst.find_bucket_for_wap(project_metadata.clone(), wap); - let wap_found = inst.dry_run_wap( - bucket_found, - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size, - ); + assert_eq!(bucket_found, bucket_stored); + + let wap_found = bucket_found + .calculate_wap(project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size); assert_eq!(wap_found, wap); } @@ -203,9 +190,7 @@ fn generate_bids_from_bucket() { let desired_price_aware_wap = PriceProviderOf::::calculate_decimals_aware_price(desired_real_wap, USD_DECIMALS, CT_DECIMALS) .unwrap(); - dbg!(desired_price_aware_wap); let necessary_bucket = inst.find_bucket_for_wap(project_metadata.clone(), desired_price_aware_wap); - dbg!(&necessary_bucket); let bids = inst.generate_bids_from_bucket( project_metadata.clone(), necessary_bucket, @@ -213,7 +198,6 @@ fn generate_bids_from_bucket() { |x| x + 1, AcceptedFundingAsset::USDT, ); - dbg!(&bids); let project_id = inst.create_community_contributing_project(project_metadata.clone(), 0, None, default_evaluations(), bids); let project_details = inst.get_project_details(project_id); diff --git a/pallets/funding/src/instantiator/types.rs b/pallets/funding/src/instantiator/types.rs index 6ffe55041..176efd83c 100644 --- a/pallets/funding/src/instantiator/types.rs +++ b/pallets/funding/src/instantiator/types.rs @@ -167,54 +167,57 @@ impl AccountMerge for Vec> { } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] -pub struct UserToForeignAssets { +pub struct UserToFundingAsset { pub account: AccountIdOf, pub asset_amount: BalanceOf, pub asset_id: AssetIdOf, } -impl UserToForeignAssets { +impl UserToFundingAsset { pub fn new(account: AccountIdOf, asset_amount: BalanceOf, asset_id: AssetIdOf) -> Self { Self { account, asset_amount, asset_id } } } -impl From<(AccountIdOf, BalanceOf, AssetIdOf)> for UserToForeignAssets { +impl From<(AccountIdOf, BalanceOf, AssetIdOf)> for UserToFundingAsset { fn from((account, asset_amount, asset_id): (AccountIdOf, BalanceOf, AssetIdOf)) -> Self { - UserToForeignAssets::::new(account, asset_amount, asset_id) + UserToFundingAsset::::new(account, asset_amount, asset_id) } } -impl From<(AccountIdOf, BalanceOf)> for UserToForeignAssets { +impl From<(AccountIdOf, BalanceOf)> for UserToFundingAsset { fn from((account, asset_amount): (AccountIdOf, BalanceOf)) -> Self { - UserToForeignAssets::::new(account, asset_amount, AcceptedFundingAsset::USDT.to_assethub_id()) + UserToFundingAsset::::new(account, asset_amount, AcceptedFundingAsset::USDT.id()) } } -impl Accounts for Vec> { +impl Accounts for Vec> { type Account = AccountIdOf; fn accounts(&self) -> Vec { let mut btree = BTreeSet::new(); - for UserToForeignAssets { account, .. } in self.iter() { + for UserToFundingAsset { account, .. } in self.iter() { btree.insert(account.clone()); } btree.into_iter().collect_vec() } } -impl AccountMerge for Vec> { - type Inner = UserToForeignAssets; +impl AccountMerge for Vec> { + type Inner = UserToFundingAsset; fn merge_accounts(&self, ops: MergeOperation) -> Self { let mut btree = BTreeMap::new(); - for UserToForeignAssets { account, asset_amount, asset_id } in self.iter() { + for UserToFundingAsset { account, asset_amount, asset_id } in self.iter() { btree - .entry(account.clone()) - .and_modify(|e: &mut (BalanceOf, u32)| { - e.0 = match ops { - MergeOperation::Add => e.0.saturating_add(*asset_amount), - MergeOperation::Subtract => e.0.saturating_sub(*asset_amount), + .entry((account.clone(), asset_id.clone())) + .and_modify(|e: &mut BalanceOf| { + *e = match ops { + MergeOperation::Add => e.saturating_add(*asset_amount), + MergeOperation::Subtract => e.saturating_sub(*asset_amount), } }) - .or_insert((*asset_amount, *asset_id)); + .or_insert(*asset_amount); } - btree.into_iter().map(|(account, info)| UserToForeignAssets::new(account, info.0, info.1)).collect() + btree + .into_iter() + .map(|((account, asset_id), asset_amount)| UserToFundingAsset::new(account, asset_amount, asset_id)) + .collect() } fn subtract_accounts(&self, other_list: Self) -> Self { diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs index 9991752e4..7e35cda90 100644 --- a/pallets/funding/src/mock.rs +++ b/pallets/funding/src/mock.rs @@ -371,9 +371,9 @@ impl ConvertBack for DummyConverter { } thread_local! { pub static PRICE_MAP: RefCell> = RefCell::new(BTreeMap::from_iter(vec![ - (AcceptedFundingAsset::DOT.to_assethub_id(), FixedU128::from_float(69f64)), // DOT - (AcceptedFundingAsset::USDC.to_assethub_id(), FixedU128::from_float(0.97f64)), // USDC - (AcceptedFundingAsset::USDT.to_assethub_id(), FixedU128::from_float(1.0f64)), // USDT + (AcceptedFundingAsset::DOT.id(), FixedU128::from_float(69f64)), // DOT + (AcceptedFundingAsset::USDC.id(), FixedU128::from_float(0.97f64)), // USDC + (AcceptedFundingAsset::USDT.id(), FixedU128::from_float(1.0f64)), // USDT (PLMC_FOREIGN_ID, FixedU128::from_float(8.4f64)), // PLMC ])); } @@ -478,38 +478,28 @@ pub fn new_test_ext() -> sp_io::TestExternalities { foreign_assets: ForeignAssetsConfig { assets: vec![ ( - AcceptedFundingAsset::USDT.to_assethub_id(), + AcceptedFundingAsset::USDT.id(), ::PalletId::get().into_account_truncating(), false, 10, ), ( - AcceptedFundingAsset::USDC.to_assethub_id(), + AcceptedFundingAsset::USDC.id(), ::PalletId::get().into_account_truncating(), false, 10, ), ( - AcceptedFundingAsset::DOT.to_assethub_id(), + AcceptedFundingAsset::DOT.id(), ::PalletId::get().into_account_truncating(), false, 10, ), ], metadata: vec![ - ( - AcceptedFundingAsset::USDT.to_assethub_id(), - "USDT".as_bytes().to_vec(), - "USDT".as_bytes().to_vec(), - 6, - ), - ( - AcceptedFundingAsset::USDC.to_assethub_id(), - "USDC".as_bytes().to_vec(), - "USDC".as_bytes().to_vec(), - 6, - ), - (AcceptedFundingAsset::DOT.to_assethub_id(), "DOT".as_bytes().to_vec(), "DOT".as_bytes().to_vec(), 10), + (AcceptedFundingAsset::USDT.id(), "USDT".as_bytes().to_vec(), "USDT".as_bytes().to_vec(), 6), + (AcceptedFundingAsset::USDC.id(), "USDC".as_bytes().to_vec(), "USDC".as_bytes().to_vec(), 6), + (AcceptedFundingAsset::DOT.id(), "DOT".as_bytes().to_vec(), "DOT".as_bytes().to_vec(), 10), ], accounts: vec![], }, @@ -529,18 +519,15 @@ pub fn new_test_ext() -> sp_io::TestExternalities { sp_api::mock_impl_runtime_apis! { impl Leaderboards for TestRuntime { fn top_evaluations(project_id: ProjectId, amount: u32) -> Vec> { - PolimecFunding::top_evaluations -(project_id, amount) + PolimecFunding::top_evaluations(project_id, amount) } fn top_bids(project_id: ProjectId, amount: u32) -> Vec> { - PolimecFunding::top_bids -(project_id, amount) + PolimecFunding::top_bids(project_id, amount) } fn top_contributions(project_id: ProjectId, amount: u32) -> Vec> { - PolimecFunding::top_contributions -(project_id, amount) + PolimecFunding::top_contributions(project_id, amount) } fn top_projects_by_usd_raised(amount: u32) -> Vec<(ProjectId, ProjectMetadataOf, ProjectDetailsOf)> { diff --git a/pallets/funding/src/runtime_api.rs b/pallets/funding/src/runtime_api.rs index a729ff1dc..61edaa376 100644 --- a/pallets/funding/src/runtime_api.rs +++ b/pallets/funding/src/runtime_api.rs @@ -133,7 +133,7 @@ impl Pallet { asset_amount: BalanceOf, ) -> BalanceOf { let project_details = ProjectsDetails::::get(project_id).expect("Project not found"); - let funding_asset_id = asset.to_assethub_id(); + let funding_asset_id = asset.id(); let funding_asset_decimals = T::FundingCurrency::decimals(funding_asset_id); let funding_asset_usd_price = T::PriceProvider::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) diff --git a/pallets/funding/src/tests/1_application.rs b/pallets/funding/src/tests/1_application.rs index 2f39a2f83..28cc08451 100644 --- a/pallets/funding/src/tests/1_application.rs +++ b/pallets/funding/src/tests/1_application.rs @@ -54,7 +54,7 @@ mod create_project_extrinsic { for _ in 0..512 { let project_metadata = default_project_metadata(issuer); inst.create_evaluating_project(project_metadata, issuer, None); - inst.advance_time(1u64).unwrap(); + inst.advance_time(1u64); issuer += 1; } } @@ -128,7 +128,7 @@ mod create_project_extrinsic { let failing_bids = vec![(BIDDER_1, 1000 * CT_UNIT).into(), (BIDDER_2, 1000 * CT_UNIT).into()]; inst.mint_plmc_to(default_plmc_balances()); - inst.mint_foreign_asset_to(default_usdt_balances()); + inst.mint_funding_asset_to(default_usdt_balances()); // Cannot create 2 projects consecutively inst.execute(|| { @@ -150,7 +150,8 @@ mod create_project_extrinsic { }); // A Project is "inactive" after the evaluation fails - inst.start_evaluation(0, ISSUER_1).unwrap(); + assert_eq!(inst.go_to_next_state(0), ProjectStatus::EvaluationRound); + inst.execute(|| { assert_noop!( Pallet::::create_project( @@ -161,11 +162,8 @@ mod create_project_extrinsic { Error::::HasActiveProject ); }); - inst.advance_time(::EvaluationDuration::get() + 1).unwrap(); - assert_eq!( - inst.get_project_details(0).status, - ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed) - ); + assert_eq!(inst.go_to_next_state(0), ProjectStatus::FundingFailed); + inst.execute(|| { assert_ok!(Pallet::::create_project( RuntimeOrigin::signed(ISSUER_1), @@ -175,11 +173,16 @@ mod create_project_extrinsic { }); // A Project is "inactive" after the funding fails - inst.start_evaluation(1, ISSUER_1).unwrap(); + assert_eq!(inst.go_to_next_state(1), ProjectStatus::EvaluationRound); + inst.evaluate_for_users(1, default_evaluations()).unwrap(); - inst.start_auction(1, ISSUER_1).unwrap(); + + assert_eq!(inst.go_to_next_state(1), ProjectStatus::AuctionInitializePeriod); + assert_eq!(inst.go_to_next_state(1), ProjectStatus::AuctionRound); + inst.bid_for_users(1, failing_bids).unwrap(); - inst.start_community_funding(1).unwrap(); + + assert!(matches!(inst.go_to_next_state(1), ProjectStatus::CommunityRound(_))); inst.execute(|| { assert_noop!( Pallet::::create_project( @@ -190,8 +193,7 @@ mod create_project_extrinsic { Error::::HasActiveProject ); }); - inst.finish_funding(1, None).unwrap(); - assert_eq!(inst.get_project_details(1).status, ProjectStatus::FundingFailed); + assert_eq!(inst.go_to_next_state(1), ProjectStatus::FundingFailed); inst.execute(|| { assert_ok!(Pallet::::create_project( RuntimeOrigin::signed(ISSUER_1), @@ -201,16 +203,24 @@ mod create_project_extrinsic { }); // A project is "inactive" after the funding succeeds - inst.start_evaluation(2, ISSUER_1).unwrap(); + assert_eq!(inst.go_to_next_state(2), ProjectStatus::EvaluationRound); inst.evaluate_for_users(2, default_evaluations()).unwrap(); - inst.start_auction(2, ISSUER_1).unwrap(); + + assert_eq!(inst.go_to_next_state(2), ProjectStatus::AuctionInitializePeriod); + assert_eq!(inst.go_to_next_state(2), ProjectStatus::AuctionRound); + inst.bid_for_users(2, default_bids()).unwrap(); - inst.start_community_funding(2).unwrap(); - inst.contribute_for_users(2, default_community_buys()).unwrap(); - inst.start_remainder_or_end_funding(2).unwrap(); - inst.contribute_for_users(2, default_remainder_buys()).unwrap(); - inst.finish_funding(2, None).unwrap(); - assert_eq!(inst.get_project_details(2).status, ProjectStatus::FundingSuccessful); + + let ProjectStatus::CommunityRound(remainder_start) = inst.go_to_next_state(2) else { + panic!("Expected CommunityRound"); + }; + + inst.contribute_for_users(2, default_community_contributions()).unwrap(); + inst.jump_to_block(remainder_start); + inst.contribute_for_users(2, default_remainder_contributions()).unwrap(); + + assert_eq!(inst.go_to_next_state(2), ProjectStatus::FundingSuccessful); + assert_ok!(inst.execute(|| crate::Pallet::::create_project( RuntimeOrigin::signed(ISSUER_1), jwt.clone(), @@ -1042,7 +1052,8 @@ mod edit_project_extrinsic { project_metadata.clone().policy_ipfs_cid.unwrap(), ); let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); - inst.start_evaluation(project_id, ISSUER_1).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::EvaluationRound); + inst.execute(|| { assert_noop!( crate::Pallet::::edit_project( @@ -1253,7 +1264,7 @@ mod remove_project_extrinsic { project_metadata.clone().policy_ipfs_cid.unwrap(), ); let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); - inst.start_evaluation(project_id, ISSUER_1).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::EvaluationRound); inst.execute(|| { assert_noop!( crate::Pallet::::remove_project( diff --git a/pallets/funding/src/tests/2_evaluation.rs b/pallets/funding/src/tests/2_evaluation.rs index 20c3b210e..38fbd1d6d 100644 --- a/pallets/funding/src/tests/2_evaluation.rs +++ b/pallets/funding/src/tests/2_evaluation.rs @@ -54,7 +54,8 @@ mod round_flow { let old_price = ::PriceProvider::get_price(PLMC_FOREIGN_ID).unwrap(); PRICE_MAP.with_borrow_mut(|map| map.insert(PLMC_FOREIGN_ID, old_price / 2.into())); - inst.start_auction(project_id, ISSUER_1).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionInitializePeriod); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionRound); // Increasing the price before the end doesn't make a project under the threshold succeed. let evaluations = vec![(EVALUATOR_1, target_evaluation_usd / 2).into()]; @@ -67,11 +68,8 @@ mod round_flow { let old_price = ::PriceProvider::get_price(PLMC_FOREIGN_ID).unwrap(); PRICE_MAP.with_borrow_mut(|map| map.insert(PLMC_FOREIGN_ID, old_price * 2.into())); - let update_block = inst.get_update_block(project_id, &UpdateType::EvaluationEnd).unwrap(); - let now = inst.current_block(); - inst.advance_time(update_block - now + 1).unwrap(); - let project_status = inst.get_project_details(project_id).status; - assert_eq!(project_status, ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed)); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); } #[test] @@ -180,7 +178,8 @@ mod round_flow { ))); // The evaluation should succeed when we bond the threshold PLMC amount in total. - inst.start_auction(project_id, issuer).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionInitializePeriod); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionRound); }; for decimals in 6..=18 { @@ -208,7 +207,6 @@ mod round_flow { #[test] fn round_fails_after_not_enough_bonds() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let now = inst.current_block(); let issuer = ISSUER_1; let project_metadata = default_project_metadata(issuer); let evaluations = default_failing_evaluations(); @@ -226,25 +224,15 @@ mod round_flow { let project_id = inst.create_evaluating_project(project_metadata, issuer, None); - let evaluation_end = inst - .get_project_details(project_id) - .round_duration - .end() - .expect("Evaluation round end block should be set"); - inst.evaluate_for_users(project_id, default_failing_evaluations()).expect("Bonding should work"); inst.do_free_plmc_assertions(plmc_existential_deposits); inst.do_reserved_plmc_assertions(plmc_eval_deposits, HoldReason::Evaluation(project_id).into()); - inst.advance_time(evaluation_end - now + 1).unwrap(); - - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); - - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); - inst.settle_project(project_id).unwrap(); + inst.settle_project(project_id); inst.do_free_plmc_assertions(expected_evaluator_balances); } } @@ -300,7 +288,7 @@ mod start_evaluation_extrinsic { is_frozen: true, weighted_average_price: None, status: ProjectStatus::EvaluationRound, - round_duration: BlockNumberPair::new(None, None), + round_duration: BlockNumberPair::new(Some(1), Some(::EvaluationDuration::get())), random_end_block: None, fundraising_target_usd: project_metadata .minimum_price @@ -310,7 +298,7 @@ mod start_evaluation_extrinsic { evaluation_round_info: EvaluationRoundInfoOf:: { total_bonded_usd: 0u128, total_bonded_plmc: 0u128, - evaluators_outcome: EvaluatorsOutcome::Unchanged, + evaluators_outcome: None, }, usd_bid_on_oversubscription: None, funding_end_block: None, @@ -397,7 +385,7 @@ mod start_evaluation_extrinsic { inst.execute(|| { assert_noop!( PolimecFunding::start_evaluation(RuntimeOrigin::signed(issuer), jwt, project_id), - Error::::IncorrectRound + Error::::ProjectAlreadyFrozen ); }); } @@ -645,12 +633,11 @@ mod evaluate_extrinsic { inst.mint_plmc_to(new_plmc_required.clone()); inst.evaluate_for_users(project_id, new_evaluations).unwrap(); - inst.start_auction(project_id, ISSUER_1).unwrap(); - inst.start_community_funding(project_id).unwrap(); - inst.start_remainder_or_end_funding(project_id).unwrap(); - inst.finish_funding(project_id, None).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionInitializePeriod); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionRound); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(_))); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); let free_balance = inst.get_free_plmc_balance_for(EVALUATOR_4); let evaluation_held_balance = @@ -664,17 +651,11 @@ mod evaluate_extrinsic { let treasury_account = ::BlockchainOperationTreasury::get(); let pre_slash_treasury_balance = inst.get_free_plmc_balance_for(treasury_account); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); inst.execute(|| { - PolimecFunding::settle_failed_evaluation( - RuntimeOrigin::signed(EVALUATOR_4), - project_id, - EVALUATOR_4, - 0, - ) - .unwrap(); + PolimecFunding::settle_evaluation(RuntimeOrigin::signed(EVALUATOR_4), project_id, EVALUATOR_4, 0) + .unwrap(); }); let post_slash_treasury_balance = inst.get_free_plmc_balance_for(treasury_account); @@ -889,7 +870,6 @@ mod evaluate_extrinsic { project_id, 500 * USD_UNIT, generate_did_from_account(ISSUER_1), - InvestorType::Institutional, project_metadata.clone().policy_ipfs_cid.unwrap(), )), Error::::ParticipationToOwnProject diff --git a/pallets/funding/src/tests/3_auction.rs b/pallets/funding/src/tests/3_auction.rs index a524a6ca1..4e39caec0 100644 --- a/pallets/funding/src/tests/3_auction.rs +++ b/pallets/funding/src/tests/3_auction.rs @@ -1,5 +1,5 @@ use super::*; - +use frame_support::traits::fungible::InspectFreeze; #[cfg(test)] mod round_flow { use super::*; @@ -9,7 +9,7 @@ mod round_flow { use super::*; use frame_support::traits::fungibles::metadata::Inspect; use sp_core::bounded_vec; - use std::{collections::HashSet, ops::Not}; + use std::collections::HashSet; #[test] fn auction_round_completed() { @@ -94,14 +94,14 @@ mod round_flow { .collect_vec(); let usdt_fundings = accounts .iter() - .map(|acc| UserToForeignAssets { + .map(|acc| UserToFundingAsset { account: acc.clone(), asset_amount: USD_UNIT * 1_000_000, - asset_id: AcceptedFundingAsset::USDT.to_assethub_id(), + asset_id: AcceptedFundingAsset::USDT.id(), }) .collect_vec(); inst.mint_plmc_to(plmc_fundings); - inst.mint_foreign_asset_to(usdt_fundings); + inst.mint_funding_asset_to(usdt_fundings); let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); @@ -117,8 +117,9 @@ mod round_flow { inst.bid_for_users(project_id, bids).unwrap(); - inst.start_community_funding(project_id).unwrap(); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); + let project_details = inst.get_project_details(project_id); let token_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); let normalized_wap = PriceProviderOf::::convert_back_to_normal_price(token_price, USD_DECIMALS, CT_DECIMALS) @@ -132,40 +133,6 @@ mod round_flow { ); } - #[test] - fn bids_at_higher_price_than_weighted_average_use_average() { - 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 mut bids: Vec> = inst.generate_bids_from_total_usd( - project_metadata.minimum_price.saturating_mul_int( - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size, - ), - project_metadata.minimum_price, - default_weights(), - default_bidders(), - default_bidder_multipliers(), - ); - - let second_bucket_bid = (BIDDER_6, 500 * CT_UNIT).into(); - bids.push(second_bucket_bid); - - let project_id = - inst.create_community_contributing_project(project_metadata.clone(), issuer, None, evaluations, bids); - let bidder_5_bid = - inst.execute(|| Bids::::iter_prefix_values((project_id, BIDDER_6)).next().unwrap()); - let wabgp = inst.get_project_details(project_id).weighted_average_price.unwrap(); - let price_normalized = ::PriceProvider::convert_back_to_normal_price( - bidder_5_bid.original_ct_usd_price, - USD_DECIMALS, - project_metadata.token_information.decimals, - ) - .unwrap(); - assert_eq!(price_normalized.to_float(), 11.0); - assert_eq!(bidder_5_bid.final_ct_usd_price, wabgp); - } - #[test] fn auction_gets_percentage_of_ct_total_allocation() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); @@ -187,7 +154,7 @@ mod round_flow { let mut bid_infos = Bids::::iter_prefix_values((project_id,)); let bid_info = inst.execute(|| bid_infos.next().unwrap()); assert!(inst.execute(|| bid_infos.next().is_none())); - assert_eq!(bid_info.final_ct_amount, auction_allocation); + assert_eq!(bid_info.original_ct_amount, auction_allocation); let project_metadata = default_project_metadata(ISSUER_2); let bids = vec![(BIDDER_1, auction_allocation).into(), (BIDDER_1, 1000 * CT_UNIT).into()]; @@ -198,35 +165,52 @@ mod round_flow { evaluations.clone(), bids, ); - let mut bid_infos = Bids::::iter_prefix_values((project_id,)); - let bid_info_1 = inst.execute(|| bid_infos.next().unwrap()); - let bid_info_2 = inst.execute(|| bid_infos.next().unwrap()); + let project_details = inst.get_project_details(project_id); + + let bid_info_1 = inst.execute(|| Bids::::get((project_id, BIDDER_1, 1)).unwrap()); + let bid_info_2 = inst.execute(|| Bids::::get((project_id, BIDDER_1, 2)).unwrap()); assert!(inst.execute(|| bid_infos.next().is_none())); assert_eq!( - bid_info_1.final_ct_amount + bid_info_2.final_ct_amount, - auction_allocation, + bid_info_1.status, + BidStatus::PartiallyAccepted(auction_allocation - 1000 * CT_UNIT), "Should not be able to buy more than auction allocation" ); + assert_eq!(bid_info_2.status, BidStatus::Accepted, "Should outbid the previous bid"); + assert_eq!(project_details.remaining_contribution_tokens, total_allocation - auction_allocation); } // Partial acceptance at price <= wap (refund due to less CT bought) // Full Acceptance at price > wap (refund due to final price lower than original price paid) // Rejection due to no more tokens left (full refund) #[test] - fn bids_get_rejected_and_refunded_part_one() { + fn bids_get_rejected_and_refunded() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let issuer = ISSUER_1; - let project_metadata = default_project_metadata(issuer); + let mut project_metadata = default_project_metadata(issuer); + project_metadata.total_allocation_size = 100_000 * CT_UNIT; + project_metadata.mainnet_token_max_supply = project_metadata.total_allocation_size; + project_metadata.auction_round_allocation_percentage = Percent::from_percent(50); + project_metadata.minimum_price = ConstPriceProvider::calculate_decimals_aware_price( + FixedU128::from_float(10.0f64), + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + project_metadata.participation_currencies = + bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT]; + let evaluations = default_evaluations(); - let bid_1 = BidParams::new(BIDDER_1, 5000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let bid_2 = BidParams::new(BIDDER_2, 40_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let bid_3 = BidParams::new(BIDDER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let bid_4 = BidParams::new(BIDDER_3, 6000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let bid_5 = BidParams::new(BIDDER_4, 2000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + // We use multiplier > 1 so after settlement, only the refunds defined above are done. The rest will be done + // through the linear release pallet + let bid_1 = BidParams::new(BIDDER_1, 5000 * CT_UNIT, 5u8, AcceptedFundingAsset::USDT); + let bid_2 = BidParams::new(BIDDER_2, 40_000 * CT_UNIT, 5u8, AcceptedFundingAsset::USDC); + let bid_3 = BidParams::new(BIDDER_1, 10_000 * CT_UNIT, 5u8, AcceptedFundingAsset::DOT); + let bid_4 = BidParams::new(BIDDER_3, 6000 * CT_UNIT, 5u8, AcceptedFundingAsset::USDT); + let bid_5 = BidParams::new(BIDDER_4, 2000 * CT_UNIT, 5u8, AcceptedFundingAsset::DOT); // post bucketing, the bids look like this: // (BIDDER_1, 5k) - (BIDDER_2, 40k) - (BIDDER_1, 5k) - (BIDDER_1, 5k) - (BIDDER_3 - 5k) - (BIDDER_3 - 1k) - (BIDDER_4 - 2k) - // | -------------------- 1USD ----------------------|---- 1.1 USD ---|---- 1.2 USD ----|----------- 1.3 USD -------------| + // | -------------------- 10USD ----------------------|---- 11 USD ---|---- 12 USD ----|----------- 13 USD -------------| // post wap ~ 1.0557252: // (Accepted, 5k) - (Partially, 32k) - (Rejected, 5k) - (Accepted, 5k) - (Accepted - 5k) - (Accepted - 1k) - (Accepted - 2k) @@ -234,23 +218,23 @@ mod round_flow { let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); - let plmc_fundings = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + let plmc_amounts = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( &bids, project_metadata.clone(), None, false, ); - let usdt_fundings = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( + let funding_asset_amounts = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( &bids, project_metadata.clone(), None, ); - let plmc_existential_amounts = plmc_fundings.accounts().existential_deposits(); + let plmc_existential_amounts = plmc_amounts.accounts().existential_deposits(); - inst.mint_plmc_to(plmc_fundings.clone()); + inst.mint_plmc_to(plmc_amounts.clone()); inst.mint_plmc_to(plmc_existential_amounts.clone()); - inst.mint_foreign_asset_to(usdt_fundings.clone()); + inst.mint_funding_asset_to(funding_asset_amounts.clone()); inst.bid_for_users(project_id, bids.clone()).unwrap(); @@ -258,10 +242,9 @@ mod round_flow { UserToPLMCBalance::new(BIDDER_1, inst.get_ed()), UserToPLMCBalance::new(BIDDER_2, inst.get_ed()), ]); - inst.do_reserved_plmc_assertions(plmc_fundings.clone(), HoldReason::Participation(project_id).into()); - inst.do_bid_transferred_foreign_asset_assertions(usdt_fundings.clone(), project_id); + inst.do_reserved_plmc_assertions(plmc_amounts.clone(), HoldReason::Participation(project_id).into()); - inst.start_community_funding(project_id).unwrap(); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(_))); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); let returned_auction_plmc = @@ -275,182 +258,73 @@ mod round_flow { ); let expected_free_funding_assets = inst.generic_map_operation(vec![returned_funding_assets.clone()], MergeOperation::Add); - let expected_reserved_plmc = inst - .generic_map_operation(vec![plmc_fundings.clone(), returned_auction_plmc], MergeOperation::Subtract); - let expected_held_funding_assets = inst - .generic_map_operation(vec![usdt_fundings.clone(), returned_funding_assets], MergeOperation::Subtract); - - inst.do_free_plmc_assertions(expected_free_plmc); - - inst.do_reserved_plmc_assertions(expected_reserved_plmc, HoldReason::Participation(project_id).into()); - - inst.do_free_foreign_asset_assertions(expected_free_funding_assets); - inst.do_bid_transferred_foreign_asset_assertions(expected_held_funding_assets, project_id); - } - - #[test] - // Partial acceptance at price > wap (refund due to less CT bought, and final price lower than original price paid) - // Rejection due to bid being made after random end (full refund) - fn bids_get_rejected_and_refunded_part_two() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = - inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); - - let total_auction_ct_amount = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - - let full_ct_bid_rejected = - BidParams::new(BIDDER_1, total_auction_ct_amount, 1u8, AcceptedFundingAsset::USDT); - let full_ct_bid_partially_accepted = - BidParams::new(BIDDER_2, total_auction_ct_amount, 1u8, AcceptedFundingAsset::USDT); - let oversubscription_bid = BidParams::new(BIDDER_3, 100_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let after_random_end_bid = BidParams::new(BIDDER_4, 100_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - - let all_bids = vec![ - full_ct_bid_rejected.clone(), - full_ct_bid_partially_accepted.clone(), - oversubscription_bid.clone(), - after_random_end_bid.clone(), - ]; - let all_included_bids = - vec![full_ct_bid_rejected.clone(), full_ct_bid_partially_accepted.clone(), oversubscription_bid]; - - let necessary_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &all_bids, - project_metadata.clone(), - None, - false, - ); - let plmc_existential_amounts = necessary_plmc.accounts().existential_deposits(); - let necessary_usdt = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &all_bids, - project_metadata.clone(), - None, + let expected_reserved_plmc = + inst.generic_map_operation(vec![plmc_amounts.clone(), returned_auction_plmc], MergeOperation::Subtract); + let expected_final_funding_spent = inst.generic_map_operation( + vec![funding_asset_amounts.clone(), returned_funding_assets], + MergeOperation::Subtract, ); + let expected_issuer_funding = inst.sum_funding_asset_mappings(vec![expected_final_funding_spent]); - inst.mint_plmc_to(necessary_plmc.clone()); - inst.mint_plmc_to(plmc_existential_amounts.clone()); - inst.mint_foreign_asset_to(necessary_usdt.clone()); - inst.bid_for_users(project_id, all_included_bids.clone()).unwrap(); - inst.advance_time( - ::AuctionOpeningDuration::get() + - ::AuctionClosingDuration::get() - - 1, - ) + // Assertions about rejected bid + let rejected_bid = inst.execute(|| Bids::::get((project_id, BIDDER_1, 2)).unwrap()); + assert_eq!(rejected_bid.status, BidStatus::Rejected); + let bidder_plmc_pre_balance = inst.get_free_plmc_balance_for(rejected_bid.bidder); + let bidder_funding_asset_pre_balance = + inst.get_free_funding_asset_balance_for(rejected_bid.funding_asset.id(), rejected_bid.bidder); + inst.execute(|| { + PolimecFunding::settle_bid( + RuntimeOrigin::signed(rejected_bid.bidder), + project_id, + rejected_bid.bidder, + 2, + ) + }) .unwrap(); - - inst.bid_for_users(project_id, vec![after_random_end_bid]).unwrap(); - inst.do_free_plmc_assertions(vec![ - UserToPLMCBalance::new(BIDDER_1, inst.get_ed()), - UserToPLMCBalance::new(BIDDER_2, inst.get_ed()), - UserToPLMCBalance::new(BIDDER_3, inst.get_ed()), - UserToPLMCBalance::new(BIDDER_4, inst.get_ed()), - ]); - inst.do_reserved_plmc_assertions(necessary_plmc.clone(), HoldReason::Participation(project_id).into()); - inst.do_bid_transferred_foreign_asset_assertions(necessary_usdt.clone(), project_id); - inst.start_community_funding(project_id).unwrap(); - - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - let plmc_returned = inst.calculate_auction_plmc_returned_from_all_bids_made( - &all_included_bids, - project_metadata.clone(), - wap, - ); - let usdt_returned = inst.calculate_auction_funding_asset_returned_from_all_bids_made( - &all_included_bids, - project_metadata.clone(), - wap, + let bidder_plmc_post_balance = inst.get_free_plmc_balance_for(rejected_bid.bidder); + let bidder_funding_asset_post_balance = + inst.get_free_funding_asset_balance_for(rejected_bid.funding_asset.id(), rejected_bid.bidder); + assert!(inst.execute(|| Bids::::get((project_id, BIDDER_1, 2))).is_none()); + assert_eq!(bidder_plmc_post_balance, bidder_plmc_pre_balance + rejected_bid.plmc_bond); + assert_eq!( + bidder_funding_asset_post_balance, + bidder_funding_asset_pre_balance + rejected_bid.funding_asset_amount_locked ); - let rejected_bid_necessary_plmc = &necessary_plmc[3]; - let rejected_bid_necessary_usdt = &necessary_usdt[3]; - - let expected_free = inst.generic_map_operation( - vec![plmc_returned.clone(), plmc_existential_amounts, vec![rejected_bid_necessary_plmc.clone()]], - MergeOperation::Add, - ); - inst.do_free_plmc_assertions(expected_free); - let expected_reserved = inst.generic_map_operation( - vec![necessary_plmc.clone(), plmc_returned.clone(), vec![rejected_bid_necessary_plmc.clone()]], - MergeOperation::Subtract, - ); - inst.do_reserved_plmc_assertions(expected_reserved, HoldReason::Participation(project_id).into()); - let expected_reserved = inst.generic_map_operation( - vec![necessary_usdt.clone(), usdt_returned.clone(), vec![rejected_bid_necessary_usdt.clone()]], - MergeOperation::Subtract, + // Any refunds on bids that were accepted/partially accepted will be done at the settlement once funding finishes + assert_eq!( + inst.execute(|| Bids::::get((project_id, BIDDER_2, 1)).unwrap()).status, + BidStatus::PartiallyAccepted(32_000 * CT_UNIT) ); - inst.do_bid_transferred_foreign_asset_assertions(expected_reserved, project_id); - } - - #[test] - fn no_bids_made() { - 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 project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); - let details = inst.get_project_details(project_id); - let opening_end = details.round_duration.end().unwrap(); - let now = inst.current_block(); - inst.advance_time(opening_end - now + 2).unwrap(); + inst.settle_project(project_id); - let details = inst.get_project_details(project_id); - let closing_end = details.round_duration.end().unwrap(); - let now = inst.current_block(); - inst.advance_time(closing_end - now + 2).unwrap(); + inst.do_free_plmc_assertions(expected_free_plmc); + inst.do_reserved_plmc_assertions(expected_reserved_plmc, HoldReason::Participation(project_id).into()); + inst.do_free_funding_asset_assertions(expected_free_funding_assets); - let details = inst.get_project_details(project_id); - assert!(matches!(details.status, ProjectStatus::CommunityRound(..))); - assert_eq!(details.weighted_average_price, Some(project_metadata.minimum_price)); + for (asset, expected_amount) in expected_issuer_funding { + let real_amount = inst.get_free_funding_asset_balance_for(asset, ISSUER_1); + assert_eq!(real_amount, expected_amount); + } } #[test] - fn all_bids_rejected() { + fn no_bids_made() { 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 bids = default_bids(); let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); - let necessary_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &bids, - project_metadata.clone(), - None, - true, - ); - let necessary_usdt = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &bids, - project_metadata.clone(), - None, - ); - - inst.mint_plmc_to(necessary_plmc.clone()); - inst.mint_foreign_asset_to(necessary_usdt.clone()); - inst.advance_time( - ::AuctionOpeningDuration::get() + - ::AuctionClosingDuration::get() - - 1, - ) - .unwrap(); - - // We bid at the last block, which we assume will be after the random end - inst.bid_for_users(project_id, bids.clone()).unwrap(); - - inst.start_community_funding(project_id).unwrap(); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); - let stored_bids = inst.execute(|| Bids::::iter_prefix_values((project_id,)).collect_vec()); - // let non_rejected_bids = stored_bids - // .into_iter() - // .filter(|bid| { - // (bid.final_ct_amount == 0 && bid.status == BidStatus::Rejected(RejectionReason::RejectionReason)) - // .not() - // }) - // .count(); - // assert_eq!(non_rejected_bids, 0); - // assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound); + assert_eq!( + inst.get_project_details(project_id).weighted_average_price, + Some(project_metadata.minimum_price) + ); } #[test] @@ -485,14 +359,14 @@ mod round_flow { .iter() .map(|acc| { let accepted_asset = fundings.next().unwrap(); - let asset_id = accepted_asset.to_assethub_id(); + let asset_id = accepted_asset.id(); let asset_decimals = inst.execute(|| ::FundingCurrency::decimals(asset_id)); let asset_unit = 10u128.checked_pow(asset_decimals.into()).unwrap(); - UserToForeignAssets { account: acc.clone(), asset_amount: asset_unit * 1_000_000, asset_id } + UserToFundingAsset { account: acc.clone(), asset_amount: asset_unit * 1_000_000, asset_id } }) .collect_vec(); inst.mint_plmc_to(plmc_fundings); - inst.mint_foreign_asset_to(usdt_fundings); + inst.mint_funding_asset_to(usdt_fundings); let project_id = inst.create_auctioning_project(project_metadata, ISSUER_1, None, default_evaluations()); @@ -507,7 +381,7 @@ mod round_flow { inst.bid_for_users(project_id, bids).unwrap(); - inst.start_community_funding(project_id).unwrap(); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); let token_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); let normalized_wap = @@ -546,25 +420,25 @@ mod round_flow { }); let usdt_price = inst.execute(|| { ::PriceProvider::get_decimals_aware_price( - AcceptedFundingAsset::USDT.to_assethub_id(), + AcceptedFundingAsset::USDT.id(), USD_DECIMALS, - ForeignAssets::decimals(AcceptedFundingAsset::USDT.to_assethub_id()), + ForeignAssets::decimals(AcceptedFundingAsset::USDT.id()), ) .unwrap() }); let usdc_price = inst.execute(|| { ::PriceProvider::get_decimals_aware_price( - AcceptedFundingAsset::USDC.to_assethub_id(), + AcceptedFundingAsset::USDC.id(), USD_DECIMALS, - ForeignAssets::decimals(AcceptedFundingAsset::USDC.to_assethub_id()), + ForeignAssets::decimals(AcceptedFundingAsset::USDC.id()), ) .unwrap() }); let dot_price = inst.execute(|| { ::PriceProvider::get_decimals_aware_price( - AcceptedFundingAsset::DOT.to_assethub_id(), + AcceptedFundingAsset::DOT.id(), USD_DECIMALS, - ForeignAssets::decimals(AcceptedFundingAsset::DOT.to_assethub_id()), + ForeignAssets::decimals(AcceptedFundingAsset::DOT.id()), ) .unwrap() }); @@ -631,10 +505,10 @@ mod round_flow { // A minimum bid goes through. This is a fixed USD value, but the extrinsic amount depends on CT decimals. inst.mint_plmc_to(vec![UserToPLMCBalance::new(BIDDER_1, min_professional_bid_plmc + ed)]); - inst.mint_foreign_asset_to(vec![UserToForeignAssets::new( + inst.mint_funding_asset_to(vec![UserToFundingAsset::new( BIDDER_1, min_professional_bid_funding_asset, - funding_asset.to_assethub_id(), + funding_asset.id(), )]); assert_ok!(inst.execute(|| PolimecFunding::bid( @@ -735,7 +609,6 @@ mod round_flow { ); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - dbg!(wap); assert!(wap > project_metadata.minimum_price); } } @@ -779,7 +652,7 @@ mod start_auction_extrinsic { use super::*; #[test] - fn pallet_can_start_auction_automatically() { + fn anyone_can_start_auction_after_initialize_period() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); let evaluations = default_evaluations(); @@ -788,48 +661,28 @@ mod start_auction_extrinsic { inst.mint_plmc_to(required_plmc); inst.evaluate_for_users(project_id, evaluations).unwrap(); - let update_block = inst.get_update_block(project_id, &UpdateType::EvaluationEnd).unwrap(); - inst.execute(|| System::set_block_number(update_block - 1)); - inst.advance_time(1).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionInitializePeriod); + let end_block = inst.get_project_details(project_id).round_duration.end().unwrap(); + inst.jump_to_block(end_block); - let update_block = inst.get_update_block(project_id, &UpdateType::AuctionOpeningStart).unwrap(); - inst.execute(|| System::set_block_number(update_block - 1)); - inst.advance_time(1).unwrap(); - } + inst.execute(|| PolimecFunding::start_auction(RuntimeOrigin::signed(420u32), project_id)).unwrap(); - #[test] - fn issuer_can_start_auction_manually() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); - let evaluations = default_evaluations(); - let required_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); - inst.mint_plmc_to(required_plmc); - inst.evaluate_for_users(project_id, evaluations).unwrap(); - inst.advance_time(::EvaluationDuration::get() + 1).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionInitializePeriod); - inst.advance_time(1).unwrap(); - inst.execute(|| Pallet::::do_start_auction(ISSUER_1, project_id)).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Auction); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionRound); } #[test] - fn stranger_cannot_start_auction_manually() { + fn issuer_can_start_auction_before_initialize_period_end() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); let evaluations = default_evaluations(); let required_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); inst.mint_plmc_to(required_plmc); inst.evaluate_for_users(project_id, evaluations).unwrap(); - inst.advance_time(::EvaluationDuration::get() + 1).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionInitializePeriod); - inst.advance_time(1).unwrap(); - for account in 6000..6010 { - inst.execute(|| { - let response = Pallet::::do_start_auction(account, project_id); - assert_noop!(response, Error::::NotIssuer); - }); - } + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionInitializePeriod); + + inst.execute(|| Pallet::::start_auction(RuntimeOrigin::signed(ISSUER_1), project_id)).unwrap(); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionRound); } } @@ -838,53 +691,51 @@ mod start_auction_extrinsic { use super::*; #[test] - fn cannot_start_auction_manually_before_evaluation_finishes() { + fn anyone_cannot_start_auction_before_initialize_period() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); + let evaluations = default_evaluations(); + let required_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); + + inst.mint_plmc_to(required_plmc); + inst.evaluate_for_users(project_id, evaluations).unwrap(); + + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionInitializePeriod); + inst.execute(|| { assert_noop!( - PolimecFunding::do_start_auction(ISSUER_1, project_id), - Error::::TransitionPointNotSet + PolimecFunding::start_auction(RuntimeOrigin::signed(420u32), project_id), + Error::::TooEarlyForRound ); }); + + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionInitializePeriod); } #[test] - fn cannot_start_auction_manually_if_evaluation_fails() { + fn issuer_cannot_start_auction_manually_before_evaluation_finishes() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); - inst.advance_time(::EvaluationDuration::get() + 1).unwrap(); inst.execute(|| { assert_noop!( - PolimecFunding::do_start_auction(ISSUER_1, project_id), - Error::::TransitionPointNotSet + PolimecFunding::start_auction(RuntimeOrigin::signed(ISSUER_1), project_id), + Error::::IncorrectRound ); }); - assert_eq!( - inst.get_project_details(project_id).status, - ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed) - ); } #[test] - fn auction_doesnt_start_automatically_if_evaluation_fails() { - // Test our success assumption is ok - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); - let evaluations = default_evaluations(); - let required_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); - inst.mint_plmc_to(required_plmc); - inst.evaluate_for_users(project_id, evaluations).unwrap(); - inst.start_auction(project_id, ISSUER_1).unwrap(); - - // Main test with failed evaluation + fn cannot_start_auction_manually_if_evaluation_fails() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); - let evaluation_end_execution = inst.get_update_block(project_id, &UpdateType::EvaluationEnd).unwrap(); - inst.execute(|| System::set_block_number(evaluation_end_execution - 1)); - inst.advance_time(1).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); + inst.execute(|| { + assert_noop!( + PolimecFunding::start_auction(RuntimeOrigin::signed(ISSUER_1), project_id), + Error::::IncorrectRound + ); + }); } } } @@ -896,7 +747,7 @@ mod bid_extrinsic { #[cfg(test)] mod success { use super::*; - use frame_support::{dispatch::DispatchResultWithPostInfo, traits::fungible::InspectFreeze}; + use frame_support::pallet_prelude::DispatchResultWithPostInfo; #[test] fn evaluation_bond_counts_towards_bid() { @@ -934,7 +785,7 @@ mod bid_extrinsic { evaluator_bidder, necessary_plmc_for_bid - usable_evaluation_plmc, )]); - inst.mint_foreign_asset_to(necessary_usdt_for_bid); + inst.mint_funding_asset_to(necessary_usdt_for_bid); inst.bid_for_users(project_id, vec![evaluator_bid]).unwrap(); @@ -998,9 +849,9 @@ mod bid_extrinsic { &vec![usdt_bid.clone(), usdc_bid.clone(), dot_bid.clone()], project_metadata_all.minimum_price, ); - inst.mint_foreign_asset_to(usdt_fundings.clone()); - inst.mint_foreign_asset_to(usdt_fundings.clone()); - inst.mint_foreign_asset_to(usdt_fundings.clone()); + inst.mint_funding_asset_to(usdt_fundings.clone()); + inst.mint_funding_asset_to(usdt_fundings.clone()); + inst.mint_funding_asset_to(usdt_fundings.clone()); let project_id_all = inst.create_auctioning_project(project_metadata_all, ISSUER_1, None, evaluations.clone()); @@ -1074,7 +925,7 @@ mod bid_extrinsic { inst.calculate_auction_funding_asset_charged_with_given_price(&vec![bid.clone()], min_price); inst.mint_plmc_to(necessary_plmc.clone()); - inst.mint_foreign_asset_to(necessary_usdt.clone()); + inst.mint_funding_asset_to(necessary_usdt.clone()); } inst.execute(|| { Pallet::::bid( @@ -1181,7 +1032,7 @@ mod bid_extrinsic { None, ); inst.mint_plmc_to(necessary_plmc.clone()); - inst.mint_foreign_asset_to(necessary_usdt.clone()); + inst.mint_funding_asset_to(necessary_usdt.clone()); inst.bid_for_users(project_id, bid_40_percent.clone()).unwrap(); let stored_bids = inst.execute(|| Bids::::iter_prefix_values((project_id,)).collect_vec()); @@ -1262,7 +1113,7 @@ mod bid_extrinsic { project_metadata.clone(), None, ); - inst.mint_foreign_asset_to(usdt_required); + inst.mint_funding_asset_to(usdt_required); inst.execute(|| { assert_noop!( @@ -1287,11 +1138,8 @@ mod bid_extrinsic { )); }); - inst.start_community_funding(project_id).unwrap(); - inst.start_remainder_or_end_funding(project_id).unwrap(); - inst.finish_funding(project_id, None).unwrap(); - - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); let bid_held_balance = @@ -1302,11 +1150,10 @@ mod bid_extrinsic { assert_eq!(bid_held_balance, frozen_amount); assert_eq!(frozen_balance, frozen_amount); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); inst.execute(|| { - PolimecFunding::settle_failed_bid(RuntimeOrigin::signed(BIDDER_4), project_id, BIDDER_4, 0).unwrap(); + PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_4), project_id, BIDDER_4, 0).unwrap(); }); let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); @@ -1349,7 +1196,7 @@ mod bid_extrinsic { project_metadata.clone(), None, ); - inst.mint_foreign_asset_to(usdt_required); + inst.mint_funding_asset_to(usdt_required); inst.execute(|| { assert_noop!( @@ -1374,7 +1221,7 @@ mod bid_extrinsic { )); }); - inst.start_community_funding(project_id).unwrap(); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); let contributions = inst.generate_contributions_from_total_ct_percent( @@ -1390,16 +1237,12 @@ mod bid_extrinsic { inst.mint_plmc_to(plmc_existential_deposits.clone()); let usdt_required = inst.calculate_contributed_funding_asset_spent(contributions.clone(), wap); - inst.mint_foreign_asset_to(usdt_required.clone()); + inst.mint_funding_asset_to(usdt_required.clone()); inst.contribute_for_users(project_id, contributions).unwrap(); - inst.start_remainder_or_end_funding(project_id).unwrap(); - inst.finish_funding(project_id, None).unwrap(); - - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); let bid_held_balance = @@ -1411,8 +1254,7 @@ mod bid_extrinsic { assert_eq!(frozen_balance, frozen_amount); inst.execute(|| { - PolimecFunding::settle_successful_bid(RuntimeOrigin::signed(BIDDER_4), project_id, BIDDER_4, 0) - .unwrap(); + PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_4), project_id, BIDDER_4, 0).unwrap(); }); let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); @@ -1468,7 +1310,7 @@ mod bid_extrinsic { project_metadata.minimum_price, ); - inst.mint_foreign_asset_to(necessary_usdt_for_bid); + inst.mint_funding_asset_to(necessary_usdt_for_bid); assert_err!( inst.bid_for_users(project_id, vec![evaluator_bid]), @@ -1515,7 +1357,7 @@ mod bid_extrinsic { evaluator_bidder, necessary_plmc_for_bid - usable_evaluation_plmc, )]); - inst.mint_foreign_asset_to(necessary_usdt_for_bid); + inst.mint_funding_asset_to(necessary_usdt_for_bid); inst.execute(|| { assert_noop!( @@ -1588,7 +1430,7 @@ mod bid_extrinsic { ); inst.mint_plmc_to(plmc_for_bidding.clone()); - inst.mint_foreign_asset_to(usdt_for_bidding.clone()); + inst.mint_funding_asset_to(usdt_for_bidding.clone()); inst.bid_for_users(project_id, bids.clone()).unwrap(); let current_bucket = inst.execute(|| Buckets::::get(project_id)).unwrap(); @@ -1611,7 +1453,7 @@ mod bid_extrinsic { ); inst.mint_plmc_to(plmc_for_failing_bid.clone()); - inst.mint_foreign_asset_to(usdt_for_bidding.clone()); + inst.mint_funding_asset_to(usdt_for_bidding.clone()); inst.execute(|| { assert_noop!( @@ -1693,7 +1535,7 @@ mod bid_extrinsic { ); inst.mint_plmc_to(plmc_for_bidding.clone()); - inst.mint_foreign_asset_to(usdt_for_bidding.clone()); + inst.mint_funding_asset_to(usdt_for_bidding.clone()); inst.bid_for_users(project_id, bids.clone()).unwrap(); let current_bucket = inst.execute(|| Buckets::::get(project_id)).unwrap(); @@ -1714,7 +1556,7 @@ mod bid_extrinsic { Some(current_bucket), ); inst.mint_plmc_to(plmc_for_failing_bid.clone()); - inst.mint_foreign_asset_to(usdt_for_bidding.clone()); + inst.mint_funding_asset_to(usdt_for_bidding.clone()); inst.execute(|| { assert_noop!( @@ -1789,7 +1631,7 @@ mod bid_extrinsic { inst.mint_plmc_to(vec![(BIDDER_1, 50_000 * CT_UNIT).into(), (BIDDER_2, 50_000 * CT_UNIT).into()]); - inst.mint_foreign_asset_to(vec![ + inst.mint_funding_asset_to(vec![ (BIDDER_1, 50_000 * USD_UNIT).into(), (BIDDER_2, 50_000 * USD_UNIT).into(), ]); @@ -1855,7 +1697,7 @@ mod bid_extrinsic { (BIDDER_2, 200_000 * PLMC).into(), (BIDDER_3, 200_000 * PLMC).into(), ]); - inst.mint_foreign_asset_to(vec![ + inst.mint_funding_asset_to(vec![ (BIDDER_1, 200_000 * USDT_UNIT).into(), (BIDDER_2, 200_000 * USDT_UNIT).into(), (BIDDER_3, 200_000 * USDT_UNIT).into(), @@ -1939,7 +1781,7 @@ mod bid_extrinsic { (BIDDER_4, 500_000 * CT_UNIT).into(), ]); - inst.mint_foreign_asset_to(vec![ + inst.mint_funding_asset_to(vec![ (BIDDER_1, 500_000 * USD_UNIT).into(), (BIDDER_2, 500_000 * USD_UNIT).into(), (BIDDER_3, 500_000 * USD_UNIT).into(), diff --git a/pallets/funding/src/tests/4_community.rs b/pallets/funding/src/tests/4_contribution.rs similarity index 85% rename from pallets/funding/src/tests/4_community.rs rename to pallets/funding/src/tests/4_contribution.rs index b3ded28f6..67c003c71 100644 --- a/pallets/funding/src/tests/4_community.rs +++ b/pallets/funding/src/tests/4_contribution.rs @@ -12,15 +12,16 @@ mod round_flow { use std::collections::HashSet; #[test] - fn community_round_completed() { + fn contribution_round_completed() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let _ = inst.create_remainder_contributing_project( + let _ = inst.create_finished_project( default_project_metadata(ISSUER_1), ISSUER_1, None, default_evaluations(), default_bids(), - default_community_buys(), + default_community_contributions(), + default_remainder_contributions(), ); } @@ -33,7 +34,7 @@ mod round_flow { let project4 = default_project_metadata(ISSUER_4); let evaluations = default_evaluations(); let bids = default_bids(); - let community_buys = default_community_buys(); + let community_buys = default_community_contributions(); inst.create_remainder_contributing_project( project1, @@ -63,7 +64,7 @@ mod round_flow { } #[test] - fn community_round_ends_on_all_ct_sold_exact() { + fn contribution_round_ends_on_all_ct_sold_exact() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let bids = vec![ BidParams::new_with_defaults(BIDDER_1, 40_000 * CT_UNIT), @@ -89,12 +90,12 @@ mod round_flow { inst.mint_plmc_to(plmc_fundings.clone()); inst.mint_plmc_to(plmc_existential_deposits.clone()); - inst.mint_foreign_asset_to(foreign_asset_fundings.clone()); + inst.mint_funding_asset_to(foreign_asset_fundings.clone()); // Buy remaining CTs inst.contribute_for_users(project_id, contributions) .expect("The Buyer should be able to buy the exact amount of remaining CTs"); - inst.advance_time(2u64).unwrap(); + // Check remaining CTs is 0 assert_eq!( inst.get_project_details(project_id).remaining_contribution_tokens, @@ -103,19 +104,19 @@ mod round_flow { ); // Check project is in FundingEnded state - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); inst.do_free_plmc_assertions(plmc_existential_deposits); - inst.do_free_foreign_asset_assertions(vec![UserToForeignAssets::::new( + inst.do_free_funding_asset_assertions(vec![UserToFundingAsset::::new( BOB, 0_u128, - AcceptedFundingAsset::USDT.to_assethub_id(), + AcceptedFundingAsset::USDT.id(), )]); inst.do_reserved_plmc_assertions( vec![plmc_fundings[0].clone()], HoldReason::Participation(project_id).into(), ); - inst.do_contribution_transferred_foreign_asset_assertions(foreign_asset_fundings, project_id); } #[test] @@ -132,8 +133,10 @@ mod round_flow { ); let project_details = inst.get_project_details(project_id); let bid_ct_sold: BalanceOf = inst.execute(|| { - Bids::::iter_prefix_values((project_id,)) - .fold(Zero::zero(), |acc, bid| acc + bid.final_ct_amount) + Bids::::iter_prefix_values((project_id,)).fold(Zero::zero(), |acc, bid| { + assert_eq!(bid.status, BidStatus::Accepted); + acc + bid.original_ct_amount + }) }); assert_eq!( project_details.remaining_contribution_tokens, @@ -155,7 +158,7 @@ mod round_flow { contributions.clone(), project_details.weighted_average_price.unwrap(), ); - inst.mint_foreign_asset_to(foreign_asset_contribution_funding.clone()); + inst.mint_funding_asset_to(foreign_asset_contribution_funding.clone()); inst.contribute_for_users(project_id, contributions).unwrap(); @@ -185,25 +188,25 @@ mod round_flow { }); let usdt_price = inst.execute(|| { ::PriceProvider::get_decimals_aware_price( - AcceptedFundingAsset::USDT.to_assethub_id(), + AcceptedFundingAsset::USDT.id(), USD_DECIMALS, - ForeignAssets::decimals(AcceptedFundingAsset::USDT.to_assethub_id()), + ForeignAssets::decimals(AcceptedFundingAsset::USDT.id()), ) .unwrap() }); let usdc_price = inst.execute(|| { ::PriceProvider::get_decimals_aware_price( - AcceptedFundingAsset::USDC.to_assethub_id(), + AcceptedFundingAsset::USDC.id(), USD_DECIMALS, - ForeignAssets::decimals(AcceptedFundingAsset::USDC.to_assethub_id()), + ForeignAssets::decimals(AcceptedFundingAsset::USDC.id()), ) .unwrap() }); let dot_price = inst.execute(|| { ::PriceProvider::get_decimals_aware_price( - AcceptedFundingAsset::DOT.to_assethub_id(), + AcceptedFundingAsset::DOT.id(), USD_DECIMALS, - ForeignAssets::decimals(AcceptedFundingAsset::DOT.to_assethub_id()), + ForeignAssets::decimals(AcceptedFundingAsset::DOT.id()), ) .unwrap() }); @@ -274,10 +277,10 @@ mod round_flow { // Buying all the remaining tokens. This is a fixed USD value, but the extrinsic amount depends on CT decimals. inst.mint_plmc_to(vec![UserToPLMCBalance::new(BUYER_1, total_funding_plmc + ed)]); - inst.mint_foreign_asset_to(vec![UserToForeignAssets::new( + inst.mint_funding_asset_to(vec![UserToFundingAsset::new( BUYER_1, total_funding_funding_asset, - funding_asset.to_assethub_id(), + funding_asset.id(), )]); assert_ok!(inst.execute(|| PolimecFunding::contribute( @@ -298,7 +301,7 @@ mod round_flow { assert_eq!(inst.get_project_details(project_id).remaining_contribution_tokens, 0); // We can successfully finish the project - inst.finish_funding(project_id, None).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); }; for decimals in 6..=18 { @@ -328,7 +331,6 @@ mod contribute_extrinsic { mod success { use super::*; use frame_support::{dispatch::DispatchResultWithPostInfo, traits::fungible::InspectFreeze}; - use polimec_common_test_utils::get_mock_jwt; #[test] fn evaluation_bond_counts_towards_contribution() { @@ -392,7 +394,7 @@ mod contribute_extrinsic { // Can partially use the usable evaluation bond (half in this case) let contribution_usdt = inst.calculate_contributed_funding_asset_spent(vec![(BOB, usable_ct / 2).into()], ct_price); - inst.mint_foreign_asset_to(contribution_usdt.clone()); + inst.mint_funding_asset_to(contribution_usdt.clone()); inst.execute(|| { assert_ok!(Pallet::::contribute( RuntimeOrigin::signed(BOB), @@ -412,7 +414,7 @@ mod contribute_extrinsic { // Can use the full evaluation bond let contribution_usdt = inst.calculate_contributed_funding_asset_spent(vec![(CARL, usable_ct).into()], ct_price); - inst.mint_foreign_asset_to(contribution_usdt.clone()); + inst.mint_funding_asset_to(contribution_usdt.clone()); inst.execute(|| { assert_ok!(Pallet::::contribute( RuntimeOrigin::signed(CARL), @@ -435,63 +437,78 @@ mod contribute_extrinsic { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let bob = 42069; let project_metadata = default_project_metadata(ISSUER_1); + // An evaluator that did a bid but it was not accepted at the end of the auction, can use that PLMC for contributing let mut evaluations = default_evaluations(); - let bob_evaluation = (bob, 1337 * USD_UNIT).into(); + let bob_evaluation = (bob, 10_000 * USD_UNIT).into(); evaluations.push(bob_evaluation); - let bids = default_bids(); - let bob_bid: BidParams = (bob, 1337 * CT_UNIT).into(); - let all_bids = bids.iter().chain(vec![bob_bid.clone()].iter()).cloned().collect_vec(); + let plmc_price = ::PriceProvider::get_decimals_aware_price( + PLMC_FOREIGN_ID, + USD_DECIMALS, + PLMC_DECIMALS, + ) + .unwrap(); let project_id = inst.create_auctioning_project(default_project_metadata(ISSUER_2), ISSUER_2, None, evaluations); + let bucket = inst.execute(|| Buckets::::get(project_id).unwrap()); + let first_bucket = bucket.amount_left; - let evaluation_plmc_bond = + // Failed bids can only happen on oversubscription. We want Bob's bid as the last one of the first bucket + let bob_plmc_bond = inst.execute(|| Balances::balance_on_hold(&HoldReason::Evaluation(project_id).into(), &bob)); - let slashable_plmc_bond = ::EvaluatorSlash::get() * evaluation_plmc_bond; - let usable_plmc_bond = evaluation_plmc_bond - slashable_plmc_bond; + let usable_bond = bob_plmc_bond - ::EvaluatorSlash::get() * bob_plmc_bond; + let usable_usd = plmc_price.saturating_mul_int(usable_bond); + let usable_bob_ct = bucket.current_price.reciprocal().unwrap().saturating_mul_int(usable_usd); + + let bids = vec![ + (BIDDER_1, first_bucket - usable_bob_ct).into(), + (bob, usable_bob_ct).into(), + (BIDDER_2, usable_bob_ct).into(), + ]; - let bids_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &all_bids, + let mut bids_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + &bids, project_metadata.clone(), None, true, ); + // We don't want bob to get any PLMC + bids_plmc.remove(2); + inst.mint_plmc_to(bids_plmc.clone()); + assert_eq!(inst.execute(|| Balances::free_balance(&bob)), inst.get_ed()); - let bids_foreign = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &all_bids, + let bids_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(bids_foreign.clone()); - + inst.mint_funding_asset_to(bids_funding_assets.clone()); inst.bid_for_users(project_id, bids).unwrap(); - let auction_end = ::AuctionOpeningDuration::get() + - ::AuctionClosingDuration::get(); - inst.advance_time(auction_end - 1).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Auction); - inst.bid_for_users(project_id, vec![bob_bid]).unwrap(); + assert_eq!(inst.execute(|| Balances::free_balance(&bob)), inst.get_ed()); - inst.start_community_funding(project_id).unwrap(); - assert!(matches!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound(..))); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); - let plmc_price = ::PriceProvider::get_decimals_aware_price( - PLMC_FOREIGN_ID, - USD_DECIMALS, - PLMC_DECIMALS, - ) - .unwrap(); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); + // Free up the plmc and usdt from the failed bid: + inst.execute(|| { + PolimecFunding::settle_bid(RuntimeOrigin::signed(bob), project_id, bob, 1).unwrap(); + }); + let bob_plmc = inst.execute(|| Balances::free_balance(&bob)); + assert_close_enough!(bob_plmc, inst.get_ed() + usable_bond, Perquintill::from_float(0.9999)); - let usable_usd = plmc_price.saturating_mul_int(usable_plmc_bond); - let usable_ct = wap.reciprocal().unwrap().saturating_mul_int(usable_usd); + // Calculate how much CTs can bob buy with his evaluation PLMC bond + let usable_bob_plmc = bob_plmc - inst.get_ed(); + let usable_bob_usd = plmc_price.saturating_mul_int(usable_bob_plmc); + let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); + let usable_bob_ct = wap.reciprocal().unwrap().saturating_mul_int(usable_bob_usd); - let bob_contribution = (bob, 1337 * CT_UNIT).into(); + let bob_contribution = (bob, usable_bob_ct).into(); let contribution_usdt = inst.calculate_contributed_funding_asset_spent(vec![bob_contribution], wap); - inst.mint_foreign_asset_to(contribution_usdt.clone()); + inst.mint_funding_asset_to(contribution_usdt.clone()); + inst.execute(|| { assert_ok!(Pallet::::contribute( RuntimeOrigin::signed(bob), @@ -502,11 +519,18 @@ mod contribute_extrinsic { project_metadata.clone().policy_ipfs_cid.unwrap() ), project_id, - usable_ct, + usable_bob_ct, 1u8.try_into().unwrap(), AcceptedFundingAsset::USDT, )); }); + + // Check he had no free PLMC + assert_close_enough!( + inst.execute(|| Balances::free_balance(&bob)), + inst.get_ed(), + Perquintill::from_float(0.999) + ); } #[test] @@ -546,7 +570,7 @@ mod contribute_extrinsic { vec![usdt_contribution.clone(), usdc_contribution.clone(), dot_contribution.clone()], wap, ); - inst.mint_foreign_asset_to(asset_hub_fundings.clone()); + inst.mint_funding_asset_to(asset_hub_fundings.clone()); assert_ok!(inst.contribute_for_users( project_id, @@ -586,7 +610,7 @@ mod contribute_extrinsic { inst.mint_plmc_to(necessary_plmc.clone()); inst.mint_plmc_to(plmc_existential_amounts.clone()); - inst.mint_foreign_asset_to(necessary_usdt.clone()); + inst.mint_funding_asset_to(necessary_usdt.clone()); } inst.execute(|| { Pallet::::contribute( @@ -601,7 +625,7 @@ mod contribute_extrinsic { } #[test] - fn non_retail_multiplier_limits() { + fn multiplier_limits() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let mut project_metadata = default_project_metadata(ISSUER_1); project_metadata.mainnet_token_max_supply = 80_000_000 * CT_UNIT; @@ -629,6 +653,23 @@ mod contribute_extrinsic { let project_id = inst.create_community_contributing_project(project_metadata.clone(), ISSUER_1, None, evaluations, bids); + // Retail contributions: 0x multiplier should fail + assert_err!( + test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Retail, 0), + Error::::ForbiddenMultiplier + ); + // Retail contributions: 1 - 5x multiplier should work + for multiplier in 1..=5u8 { + assert_ok!(test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Retail, multiplier)); + } + // Retail contributions: >= 6 multiplier should fail + for multiplier in 6..=30u8 { + assert_err!( + test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Retail, multiplier), + Error::::ForbiddenMultiplier + ); + } + // Professional contributions: 0x multiplier should fail assert_err!( test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Professional, 0), @@ -676,61 +717,6 @@ mod contribute_extrinsic { } } - #[test] - fn retail_multiplier_limits() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let mut issuer: AccountId = 6969420; - - let mut create_project = |inst: &mut MockInstantiator| { - issuer += 1; - inst.create_community_contributing_project( - default_project_metadata(issuer), - issuer, - None, - default_evaluations(), - default_bids(), - ) - }; - - let max_allowed_multipliers_map = vec![(2, 1), (4, 2), (9, 4), (24, 7), (25, 10)]; - - let mut previous_projects_created = 0; - for (projects_participated_amount, max_allowed_multiplier) in max_allowed_multipliers_map { - (previous_projects_created..projects_participated_amount - 1).for_each(|_| { - let project_id = create_project(&mut inst); - assert_ok!(test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Retail, 1)); - }); - - let project_id = create_project(&mut inst); - previous_projects_created = projects_participated_amount; - - // 0x multiplier should fail - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Retail, 0), - Error::::ForbiddenMultiplier - ); - - // Multipliers that should work - for multiplier in 1..=max_allowed_multiplier { - assert_ok!(test_contribution_setup( - &mut inst, - project_id, - BUYER_1, - InvestorType::Retail, - multiplier - )); - } - - // Multipliers that should NOT work - for multiplier in max_allowed_multiplier + 1..=50 { - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Retail, multiplier), - Error::::ForbiddenMultiplier - ); - } - } - } - #[test] fn did_with_losing_bid_can_contribute() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); @@ -742,18 +728,11 @@ mod contribute_extrinsic { BidParams::new(BIDDER_1, 400_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), BidParams::new(BIDDER_2, 100_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), ]; - let failing_bids_after_random_end = - vec![(BIDDER_3, 25_000 * CT_UNIT).into(), (BIDDER_4, 25_000 * CT_UNIT).into()]; + // This bids should fill the first bucket. - let failing_bids_sold_out = - vec![(BIDDER_5, 250_000 * CT_UNIT).into(), (BIDDER_6, 250_000 * CT_UNIT).into()]; + let failing_bids_sold_out = vec![(BIDDER_6, 250_000 * CT_UNIT).into()]; - let all_bids = failing_bids_sold_out - .iter() - .chain(successful_bids.iter()) - .chain(failing_bids_after_random_end.iter()) - .cloned() - .collect_vec(); + let all_bids = failing_bids_sold_out.iter().chain(successful_bids.iter()).cloned().collect_vec(); let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); @@ -771,18 +750,11 @@ mod contribute_extrinsic { project_metadata.clone(), None, ); - inst.mint_foreign_asset_to(foreign_funding.clone()); + inst.mint_funding_asset_to(foreign_funding.clone()); inst.bid_for_users(project_id, failing_bids_sold_out).unwrap(); inst.bid_for_users(project_id, successful_bids).unwrap(); - inst.advance_time( - ::AuctionOpeningDuration::get() + - ::AuctionClosingDuration::get() - - 1, - ) - .unwrap(); - inst.bid_for_users(project_id, failing_bids_after_random_end).unwrap(); - inst.start_community_funding(project_id).unwrap(); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); // Some low amount of plmc and usdt to cover a purchase of 10CTs. let plmc_mints = vec![ @@ -806,7 +778,7 @@ mod contribute_extrinsic { (BUYER_5, 42069 * CT_UNIT).into(), (BUYER_6, 42069 * CT_UNIT).into(), ]; - inst.mint_foreign_asset_to(usdt_mints); + inst.mint_funding_asset_to(usdt_mints); let mut bid_should_succeed = |account, investor_type, did_acc| { inst.execute(|| { @@ -826,38 +798,14 @@ mod contribute_extrinsic { }); }; - // Bidder 3 has a losing bid due to bidding after the random end. His did should be able to contribute regardless of what investor type - // or account he uses to sign the transaction - bid_should_succeed(BIDDER_3, InvestorType::Institutional, BIDDER_3); - bid_should_succeed(BUYER_3, InvestorType::Institutional, BIDDER_3); - bid_should_succeed(BIDDER_3, InvestorType::Professional, BIDDER_3); - bid_should_succeed(BUYER_3, InvestorType::Professional, BIDDER_3); - bid_should_succeed(BIDDER_3, InvestorType::Retail, BIDDER_3); - bid_should_succeed(BUYER_3, InvestorType::Retail, BIDDER_3); - - // Bidder 4 has a losing bid due to bidding after the random end, and he was also an evaluator. Same conditions as before should apply. - bid_should_succeed(BIDDER_4, InvestorType::Institutional, BIDDER_4); - bid_should_succeed(BUYER_4, InvestorType::Institutional, BIDDER_4); - bid_should_succeed(BIDDER_4, InvestorType::Professional, BIDDER_4); - bid_should_succeed(BUYER_4, InvestorType::Professional, BIDDER_4); - bid_should_succeed(BIDDER_4, InvestorType::Retail, BIDDER_4); - bid_should_succeed(BUYER_4, InvestorType::Retail, BIDDER_4); - - // Bidder 5 has a losing bid due to CTs being sold out at his price point. Same conditions as before should apply. + // Bidder has a losing bid due to CTs being sold out at his price point. + // Their did should be able to contribute regardless of what investor type or account he uses to sign the transaction bid_should_succeed(BIDDER_5, InvestorType::Institutional, BIDDER_5); bid_should_succeed(BUYER_5, InvestorType::Institutional, BIDDER_5); bid_should_succeed(BIDDER_5, InvestorType::Professional, BIDDER_5); bid_should_succeed(BUYER_5, InvestorType::Professional, BIDDER_5); bid_should_succeed(BIDDER_5, InvestorType::Retail, BIDDER_5); bid_should_succeed(BUYER_5, InvestorType::Retail, BIDDER_5); - - // Bidder 6 has a losing bid due to CTs being sold out at his price point, and he was also an evaluator. Same conditions as before should apply. - bid_should_succeed(BIDDER_6, InvestorType::Institutional, BIDDER_6); - bid_should_succeed(BUYER_6, InvestorType::Institutional, BIDDER_6); - bid_should_succeed(BIDDER_6, InvestorType::Professional, BIDDER_6); - bid_should_succeed(BUYER_6, InvestorType::Professional, BIDDER_6); - bid_should_succeed(BIDDER_6, InvestorType::Retail, BIDDER_6); - bid_should_succeed(BUYER_6, InvestorType::Retail, BIDDER_6); } #[test] @@ -887,7 +835,7 @@ mod contribute_extrinsic { }); let usdt_required = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); - inst.mint_foreign_asset_to(usdt_required); + inst.mint_funding_asset_to(usdt_required); inst.execute(|| { assert_noop!( @@ -912,10 +860,7 @@ mod contribute_extrinsic { )); }); - inst.start_remainder_or_end_funding(project_id).unwrap(); - inst.finish_funding(project_id, None).unwrap(); - - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); let free_balance = inst.get_free_plmc_balance_for(BUYER_4); let bid_held_balance = @@ -926,12 +871,10 @@ mod contribute_extrinsic { assert_eq!(bid_held_balance, frozen_amount); assert_eq!(frozen_balance, frozen_amount); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); inst.execute(|| { - PolimecFunding::settle_failed_contribution(RuntimeOrigin::signed(BUYER_4), project_id, BUYER_4, 0) - .unwrap(); + PolimecFunding::settle_contribution(RuntimeOrigin::signed(BUYER_4), project_id, BUYER_4, 0).unwrap(); }); let free_balance = inst.get_free_plmc_balance_for(BUYER_4); @@ -971,7 +914,7 @@ mod contribute_extrinsic { }); let usdt_required = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); - inst.mint_foreign_asset_to(usdt_required); + inst.mint_funding_asset_to(usdt_required); inst.execute(|| { assert_noop!( @@ -996,11 +939,8 @@ mod contribute_extrinsic { )); }); - inst.start_remainder_or_end_funding(project_id).unwrap(); - inst.finish_funding(project_id, None).unwrap(); - - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); let free_balance = inst.get_free_plmc_balance_for(BUYER_4); let bid_held_balance = @@ -1012,8 +952,7 @@ mod contribute_extrinsic { assert_eq!(frozen_balance, frozen_amount); inst.execute(|| { - PolimecFunding::settle_successful_contribution(RuntimeOrigin::signed(BUYER_4), project_id, BUYER_4, 0) - .unwrap(); + PolimecFunding::settle_contribution(RuntimeOrigin::signed(BUYER_4), project_id, BUYER_4, 0).unwrap(); }); let free_balance = inst.get_free_plmc_balance_for(BUYER_4); @@ -1045,6 +984,36 @@ mod contribute_extrinsic { assert_eq!(bid_held_balance, Zero::zero()); assert_eq!(frozen_balance, frozen_amount); } + + #[test] + fn participant_was_evaluator_and_bidder() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let issuer = ISSUER_1; + let participant = 42069u32; + let project_metadata = default_project_metadata(issuer); + let mut evaluations = default_evaluations(); + evaluations.push((participant, 100 * USD_UNIT).into()); + let mut bids = default_bids(); + bids.push(BidParams::new(participant, 1000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT)); + let community_contributions = default_community_contributions(); + let mut remainder_contributions = default_remainder_contributions(); + remainder_contributions.push(ContributionParams::new( + participant, + 10 * CT_UNIT, + 1u8, + AcceptedFundingAsset::USDT, + )); + + let _project_id = inst.create_finished_project( + project_metadata.clone(), + issuer, + None, + evaluations, + bids, + community_contributions, + remainder_contributions, + ); + } } #[cfg(test)] @@ -1087,7 +1056,7 @@ mod contribute_extrinsic { inst.mint_plmc_to(plmc_funding.clone()); inst.mint_plmc_to(plmc_existential_deposits.clone()); - inst.mint_foreign_asset_to(foreign_funding.clone()); + inst.mint_funding_asset_to(foreign_funding.clone()); // Reach up to the limit of contributions for a user-project assert!(inst.contribute_for_users(project_id, contributions).is_ok()); @@ -1101,10 +1070,7 @@ mod contribute_extrinsic { let contributor_post_buy_plmc_balance = inst.execute(|| ::NativeCurrency::balance(&CONTRIBUTOR)); let contributor_post_buy_foreign_asset_balance = inst.execute(|| { - ::FundingCurrency::balance( - AcceptedFundingAsset::USDT.to_assethub_id(), - CONTRIBUTOR, - ) + ::FundingCurrency::balance(AcceptedFundingAsset::USDT.id(), CONTRIBUTOR) }); assert_eq!(contributor_post_buy_plmc_balance, inst.get_ed()); @@ -1123,7 +1089,10 @@ mod contribute_extrinsic { }); assert_eq!(plmc_bond_stored, inst.sum_balance_mappings(vec![plmc_funding.clone()])); - assert_eq!(foreign_asset_contributions_stored, inst.sum_foreign_mappings(vec![foreign_funding.clone()])); + assert_eq!( + foreign_asset_contributions_stored, + inst.sum_funding_asset_mappings(vec![foreign_funding.clone()])[0].1 + ); } #[test] @@ -1245,7 +1214,7 @@ mod contribute_extrinsic { (BUYER_3, 50_000 * CT_UNIT).into(), ]); - inst.mint_foreign_asset_to(vec![ + inst.mint_funding_asset_to(vec![ (BUYER_1, 50_000 * USD_UNIT).into(), (BUYER_2, 50_000 * USD_UNIT).into(), (BUYER_3, 50_000 * USD_UNIT).into(), @@ -1342,7 +1311,7 @@ mod contribute_extrinsic { (BUYER_6, 500_000 * CT_UNIT).into(), ]); - inst.mint_foreign_asset_to(vec![ + inst.mint_funding_asset_to(vec![ (BUYER_1, 500_000 * USD_UNIT).into(), (BUYER_2, 500_000 * USD_UNIT).into(), (BUYER_3, 500_000 * USD_UNIT).into(), @@ -1525,7 +1494,7 @@ mod contribute_extrinsic { inst.execute(|| Balances::burn_from(&BUYER_1, 1, Precision::BestEffort, Fortitude::Force)).unwrap(); let foreign_funding = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); - inst.mint_foreign_asset_to(foreign_funding.clone()); + inst.mint_funding_asset_to(foreign_funding.clone()); inst.execute(|| { assert_noop!( Pallet::::contribute( @@ -1547,12 +1516,12 @@ mod contribute_extrinsic { inst.mint_plmc_to(plmc_existential_deposits.clone()); let foreign_funding = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); - inst.execute(|| ForeignAssets::set_balance(AcceptedFundingAsset::USDT.to_assethub_id(), &BUYER_1, 0)); - inst.mint_foreign_asset_to(foreign_funding.clone()); + inst.execute(|| ForeignAssets::set_balance(AcceptedFundingAsset::USDT.id(), &BUYER_1, 0)); + inst.mint_funding_asset_to(foreign_funding.clone()); inst.execute(|| { ForeignAssets::burn_from( - AcceptedFundingAsset::USDT.to_assethub_id(), + AcceptedFundingAsset::USDT.id(), &BUYER_1, 100, Precision::BestEffort, @@ -1587,26 +1556,17 @@ mod contribute_extrinsic { None, default_evaluations(), ); - let remaining_project = inst.create_remainder_contributing_project( - default_project_metadata(ISSUER_4), - ISSUER_4, - None, - default_evaluations(), - default_bids(), - default_community_buys(), - ); let finished_project = inst.create_finished_project( default_project_metadata(ISSUER_5), ISSUER_5, None, default_evaluations(), default_bids(), - default_community_buys(), - default_remainder_buys(), + default_community_contributions(), + default_remainder_contributions(), ); - let projects = - vec![created_project, evaluating_project, auctioning_project, remaining_project, finished_project]; + let projects = vec![created_project, evaluating_project, auctioning_project, finished_project]; for project in projects { let project_policy = inst.get_project_metadata(project).policy_ipfs_cid.unwrap(); inst.execute(|| { @@ -1737,7 +1697,7 @@ mod contribute_extrinsic { evaluator_contributor, necessary_plmc_for_contribution - usable_evaluation_plmc, )]); - inst.mint_foreign_asset_to(necessary_usdt_for_contribution); + inst.mint_funding_asset_to(necessary_usdt_for_contribution); inst.execute(|| { assert_noop!( diff --git a/pallets/funding/src/tests/6_funding_end.rs b/pallets/funding/src/tests/5_funding_end.rs similarity index 67% rename from pallets/funding/src/tests/6_funding_end.rs rename to pallets/funding/src/tests/5_funding_end.rs index 1754dd2e6..749690a3f 100644 --- a/pallets/funding/src/tests/6_funding_end.rs +++ b/pallets/funding/src/tests/5_funding_end.rs @@ -7,40 +7,45 @@ mod round_flow { use super::*; #[test] - fn evaluator_slash_is_decided() { - let (mut inst, project_id) = create_project_with_funding_percentage(20, None, true); - assert_eq!( - inst.get_project_details(project_id).status, - ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed) - ); - assert_eq!( - inst.get_project_details(project_id).evaluation_round_info.evaluators_outcome, - EvaluatorsOutcome::Slashed - ); - } - - #[test] - fn evaluator_unchanged_is_decided() { - let (mut inst, project_id) = - create_project_with_funding_percentage(80, Some(FundingOutcomeDecision::AcceptFunding), true); - assert_eq!( - inst.get_project_details(project_id).status, - ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful) - ); - assert_eq!( - inst.get_project_details(project_id).evaluation_round_info.evaluators_outcome, - EvaluatorsOutcome::Unchanged - ); + fn evaluator_outcome_bounds() { + let try_for_percentage = |percentage: u8, should_slash: bool| { + let (mut inst, project_id) = create_project_with_funding_percentage(percentage.into(), true); + if should_slash { + assert_eq!( + inst.get_project_details(project_id).status, + ProjectStatus::SettlementStarted(FundingOutcome::Failure) + ); + assert_eq!( + inst.get_project_details(project_id).evaluation_round_info.evaluators_outcome, + Some(EvaluatorsOutcome::Slashed) + ); + } else { + assert_eq!( + inst.get_project_details(project_id).status, + ProjectStatus::SettlementStarted(FundingOutcome::Success) + ); + assert!(matches!( + inst.get_project_details(project_id).evaluation_round_info.evaluators_outcome, + Some(EvaluatorsOutcome::Rewarded(..)) + )); + } + }; + for i in 1..=32u8 { + try_for_percentage(i, true); + } + for i in 33..130u8 { + try_for_percentage(i, false); + } } #[test] - fn evaluator_reward_is_decided() { - let (mut inst, project_id) = create_project_with_funding_percentage(95, None, true); + fn evaluator_reward_is_correct() { + let (mut inst, project_id) = create_project_with_funding_percentage(95, true); let project_details = inst.get_project_details(project_id); let project_metadata = inst.get_project_metadata(project_id); assert_eq!( inst.get_project_details(project_id).status, - ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful) + ProjectStatus::SettlementStarted(FundingOutcome::Success) ); // We want to test rewards over the 3 brackets, which means > 5MM USD funded @@ -58,8 +63,7 @@ mod round_flow { let total_ct_fee = total_fee * (project_metadata.total_allocation_size - project_details.remaining_contribution_tokens); - let total_evaluator_reward = - Perquintill::from_percent(95u64) * Perquintill::from_percent(30) * total_ct_fee; + let total_evaluator_reward = Perquintill::from_percent(30) * total_ct_fee; let early_evaluator_reward = Perquintill::from_percent(20u64) * total_evaluator_reward; @@ -75,7 +79,7 @@ mod round_flow { }; assert_eq!( inst.get_project_details(project_id).evaluation_round_info.evaluators_outcome, - EvaluatorsOutcome::Rewarded(expected_reward_info) + Some(EvaluatorsOutcome::Rewarded(expected_reward_info)) ); } @@ -101,8 +105,8 @@ mod round_flow { None, default_evaluations(), bids, - default_community_buys(), - default_remainder_buys(), + default_community_contributions(), + default_remainder_contributions(), ); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); diff --git a/pallets/funding/src/tests/5_remainder.rs b/pallets/funding/src/tests/5_remainder.rs deleted file mode 100644 index 13f13f6e8..000000000 --- a/pallets/funding/src/tests/5_remainder.rs +++ /dev/null @@ -1,1888 +0,0 @@ -use super::*; -// use crate::instantiator::async_features::create_multiple_projects_at; -use frame_support::{dispatch::DispatchResultWithPostInfo, traits::fungibles::metadata::Inspect}; -use sp_runtime::bounded_vec; -use std::collections::HashSet; - -#[cfg(test)] -mod round_flow { - use super::*; - - #[cfg(test)] - mod success { - use super::*; - - #[test] - fn remainder_round_works() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let _ = inst.create_finished_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - default_community_buys(), - default_remainder_buys(), - ); - } - - #[test] - fn remainder_round_ends_on_all_ct_sold_exact() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_remainder_contributing_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - default_community_buys(), - ); - const BOB: AccountId = 808; - - let remaining_ct = inst.get_project_details(project_id).remaining_contribution_tokens; - let ct_price = inst.get_project_details(project_id).weighted_average_price.expect("CT Price should exist"); - - let contributions = vec![ContributionParams::new(BOB, remaining_ct, 1u8, AcceptedFundingAsset::USDT)]; - let plmc_fundings = inst.calculate_contributed_plmc_spent(contributions.clone(), ct_price, false); - let plmc_existential_deposits = contributions.accounts().existential_deposits(); - let foreign_asset_fundings = - inst.calculate_contributed_funding_asset_spent(contributions.clone(), ct_price); - - inst.mint_plmc_to(plmc_fundings.clone()); - inst.mint_plmc_to(plmc_existential_deposits.clone()); - inst.mint_foreign_asset_to(foreign_asset_fundings.clone()); - - // Buy remaining CTs - inst.contribute_for_users(project_id, contributions) - .expect("The Buyer should be able to buy the exact amount of remaining CTs"); - inst.advance_time(2u64).unwrap(); - - // Check remaining CTs is 0 - assert_eq!( - inst.get_project_details(project_id).remaining_contribution_tokens, - 0, - "There are still remaining CTs" - ); - - // Check project is in FundingEnded state - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); - - inst.do_free_plmc_assertions(plmc_existential_deposits); - inst.do_free_foreign_asset_assertions(vec![UserToForeignAssets::::new( - BOB, - 0_u128, - AcceptedFundingAsset::USDT.to_assethub_id(), - )]); - inst.do_reserved_plmc_assertions( - vec![plmc_fundings[0].clone()], - HoldReason::Participation(project_id).into(), - ); - inst.do_contribution_transferred_foreign_asset_assertions(foreign_asset_fundings, project_id); - } - - #[test] - fn round_has_total_ct_allocation_minus_auction_sold() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let evaluations = default_evaluations(); - let bids = default_bids(); - - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - evaluations.clone(), - bids.clone(), - vec![], - ); - let project_details = inst.get_project_details(project_id); - let bid_ct_sold: BalanceOf = inst.execute(|| { - Bids::::iter_prefix_values((project_id,)) - .fold(Zero::zero(), |acc, bid| acc + bid.final_ct_amount) - }); - assert_eq!( - project_details.remaining_contribution_tokens, - project_metadata.total_allocation_size - bid_ct_sold - ); - - let contributions = vec![(BUYER_1, project_details.remaining_contribution_tokens).into()]; - - let plmc_contribution_funding = inst.calculate_contributed_plmc_spent( - contributions.clone(), - project_details.weighted_average_price.unwrap(), - false, - ); - let plmc_existential_deposits = plmc_contribution_funding.accounts().existential_deposits(); - inst.mint_plmc_to(plmc_contribution_funding.clone()); - inst.mint_plmc_to(plmc_existential_deposits.clone()); - - let foreign_asset_contribution_funding = inst.calculate_contributed_funding_asset_spent( - contributions.clone(), - project_details.weighted_average_price.unwrap(), - ); - inst.mint_foreign_asset_to(foreign_asset_contribution_funding.clone()); - - inst.contribute_for_users(project_id, contributions).unwrap(); - - assert_eq!(inst.get_project_details(project_id).remaining_contribution_tokens, 0); - } - - #[test] - fn different_decimals_ct_works_as_expected() { - // Setup some base values to compare different decimals - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let ed = inst.get_ed(); - let default_project_metadata = default_project_metadata(ISSUER_1); - let original_decimal_aware_price = default_project_metadata.minimum_price; - let original_price = ::PriceProvider::convert_back_to_normal_price( - original_decimal_aware_price, - USD_DECIMALS, - default_project_metadata.token_information.decimals, - ) - .unwrap(); - let usable_plmc_price = inst.execute(|| { - ::PriceProvider::get_decimals_aware_price( - PLMC_FOREIGN_ID, - USD_DECIMALS, - PLMC_DECIMALS, - ) - .unwrap() - }); - let usdt_price = inst.execute(|| { - ::PriceProvider::get_decimals_aware_price( - AcceptedFundingAsset::USDT.to_assethub_id(), - USD_DECIMALS, - ForeignAssets::decimals(AcceptedFundingAsset::USDT.to_assethub_id()), - ) - .unwrap() - }); - let usdc_price = inst.execute(|| { - ::PriceProvider::get_decimals_aware_price( - AcceptedFundingAsset::USDC.to_assethub_id(), - USD_DECIMALS, - ForeignAssets::decimals(AcceptedFundingAsset::USDC.to_assethub_id()), - ) - .unwrap() - }); - let dot_price = inst.execute(|| { - ::PriceProvider::get_decimals_aware_price( - AcceptedFundingAsset::DOT.to_assethub_id(), - USD_DECIMALS, - ForeignAssets::decimals(AcceptedFundingAsset::DOT.to_assethub_id()), - ) - .unwrap() - }); - - let mut funding_assets_cycle = - vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT] - .into_iter() - .cycle(); - - let mut total_fundings_ct = Vec::new(); - let mut total_fundings_usd = Vec::new(); - let mut total_fundings_plmc = Vec::new(); - - let mut decimal_test = |decimals: u8| { - let funding_asset = funding_assets_cycle.next().unwrap(); - let funding_asset_usd_price = match funding_asset { - AcceptedFundingAsset::USDT => usdt_price, - AcceptedFundingAsset::USDC => usdc_price, - AcceptedFundingAsset::DOT => dot_price, - }; - - let mut project_metadata = default_project_metadata.clone(); - project_metadata.token_information.decimals = decimals; - project_metadata.minimum_price = - ::PriceProvider::calculate_decimals_aware_price( - original_price, - USD_DECIMALS, - decimals, - ) - .unwrap(); - - project_metadata.total_allocation_size = 1_000_000 * 10u128.pow(decimals as u32); - project_metadata.mainnet_token_max_supply = project_metadata.total_allocation_size; - project_metadata.participation_currencies = bounded_vec!(funding_asset); - - let issuer: AccountIdOf = (10_000 + inst.get_new_nonce()).try_into().unwrap(); - let evaluations = inst.generate_successful_evaluations( - project_metadata.clone(), - default_evaluators(), - default_weights(), - ); - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - issuer, - None, - evaluations, - vec![], - vec![], - ); - - let total_funding_ct = project_metadata.total_allocation_size; - let total_funding_usd = project_metadata.minimum_price.saturating_mul_int(total_funding_ct); - let total_funding_plmc = usable_plmc_price.reciprocal().unwrap().saturating_mul_int(total_funding_usd); - let total_funding_funding_asset = - funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(total_funding_usd); - - total_fundings_ct.push(total_funding_ct); - total_fundings_usd.push(total_funding_usd); - total_fundings_plmc.push(total_funding_plmc); - - // Every project should want to raise 10MM USD - assert_eq!(total_funding_usd, 10_000_000 * USD_UNIT); - - // Every project should produce the same PLMC bond when having the full funding at multiplier 1. - assert_close_enough!(total_funding_plmc, 1_190_476 * PLMC, Perquintill::from_float(0.999)); - - // Every project should have a different amount of CTs to raise, depending on their decimals - assert_eq!(total_funding_ct, 1_000_000 * 10u128.pow(decimals as u32)); - - // Buying all the remaining tokens. This is a fixed USD value, but the extrinsic amount depends on CT decimals. - inst.mint_plmc_to(vec![UserToPLMCBalance::new(BUYER_1, total_funding_plmc + ed)]); - inst.mint_foreign_asset_to(vec![UserToForeignAssets::new( - BUYER_1, - total_funding_funding_asset, - funding_asset.to_assethub_id(), - )]); - - assert_ok!(inst.execute(|| PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_1), - get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Retail, - generate_did_from_account(BUYER_1), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - total_funding_ct, - 1u8.try_into().unwrap(), - funding_asset, - ))); - - // the remaining tokens should be zero - assert_eq!(inst.get_project_details(project_id).remaining_contribution_tokens, 0); - - // We can successfully finish the project - inst.finish_funding(project_id, None).unwrap(); - }; - - for decimals in 6..=18 { - decimal_test(decimals); - } - - // Since we use the same original price and allocation size and adjust for decimals, - // the USD and PLMC amounts should be the same - assert!(total_fundings_usd.iter().all(|x| *x == total_fundings_usd[0])); - assert!(total_fundings_plmc.iter().all(|x| *x == total_fundings_plmc[0])); - - // CT amounts however should be different from each other - let mut hash_set_1 = HashSet::new(); - for amount in total_fundings_ct { - assert!(!hash_set_1.contains(&amount)); - hash_set_1.insert(amount); - } - } - } -} - -#[cfg(test)] -mod contribute_extrinsic { - use super::*; - - #[cfg(test)] - mod success { - use super::*; - use frame_support::traits::fungible::InspectFreeze; - use pallet_balances::AccountData; - - #[test] - fn evaluation_bond_counts_towards_contribution() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - - const BOB: AccountId = 42069; - const CARL: AccountId = 420691; - let mut evaluations = default_evaluations(); - let bob_evaluation: UserToUSDBalance = (BOB, 1337 * USD_UNIT).into(); - let carl_evaluation: UserToUSDBalance = (CARL, 1337 * USD_UNIT).into(); - evaluations.push(bob_evaluation.clone()); - evaluations.push(carl_evaluation.clone()); - - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - evaluations, - default_bids(), - vec![], - ); - let ct_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); - let plmc_price = ::PriceProvider::get_decimals_aware_price( - PLMC_FOREIGN_ID, - USD_DECIMALS, - PLMC_DECIMALS, - ) - .unwrap(); - - let evaluation_plmc_bond = - inst.execute(|| Balances::balance_on_hold(&HoldReason::Evaluation(project_id).into(), &BOB)); - let slashable_plmc = ::EvaluatorSlash::get() * evaluation_plmc_bond; - let usable_plmc = evaluation_plmc_bond - slashable_plmc; - - let usable_usd = plmc_price.checked_mul_int(usable_plmc).unwrap(); - let slashable_usd = plmc_price.checked_mul_int(slashable_plmc).unwrap(); - - let usable_ct = ct_price.reciprocal().unwrap().saturating_mul_int(usable_usd); - let slashable_ct = ct_price.reciprocal().unwrap().saturating_mul_int(slashable_usd); - - // Can't contribute with only the evaluation bond - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BOB), - get_mock_jwt_with_cid( - BOB, - InvestorType::Retail, - generate_did_from_account(BOB), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - usable_ct + slashable_ct, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - ), - Error::::ParticipantNotEnoughFunds - ); - }); - - // Can partially use the usable evaluation bond (half in this case) - let contribution_usdt = - inst.calculate_contributed_funding_asset_spent(vec![(BOB, usable_ct / 2).into()], ct_price); - inst.mint_foreign_asset_to(contribution_usdt.clone()); - inst.execute(|| { - assert_ok!(Pallet::::contribute( - RuntimeOrigin::signed(BOB), - get_mock_jwt_with_cid( - BOB, - InvestorType::Retail, - generate_did_from_account(BOB), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - usable_ct / 2, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - )); - }); - - // Can use the full evaluation bond - let contribution_usdt = - inst.calculate_contributed_funding_asset_spent(vec![(CARL, usable_ct).into()], ct_price); - inst.mint_foreign_asset_to(contribution_usdt.clone()); - inst.execute(|| { - assert_ok!(Pallet::::contribute( - RuntimeOrigin::signed(CARL), - get_mock_jwt_with_cid( - CARL, - InvestorType::Retail, - generate_did_from_account(CARL), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - usable_ct, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - )); - }); - } - - #[test] - fn evaluation_bond_used_on_failed_bid_can_be_reused_on_contribution() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let bob = 42069; - let project_metadata = default_project_metadata(ISSUER_1); - // An evaluator that did a bid but it was not accepted at the end of the auction, can use that PLMC for contributing - let mut evaluations = default_evaluations(); - let bob_evaluation = (bob, 1337 * USD_UNIT).into(); - evaluations.push(bob_evaluation); - - let bids = default_bids(); - let bob_bid: BidParams = (bob, 1337 * CT_UNIT).into(); - let all_bids = bids.iter().chain(vec![bob_bid.clone()].iter()).cloned().collect_vec(); - - let project_id = - inst.create_auctioning_project(default_project_metadata(ISSUER_2), ISSUER_2, None, evaluations); - - let evaluation_plmc_bond = - inst.execute(|| Balances::balance_on_hold(&HoldReason::Evaluation(project_id).into(), &bob)); - let slashable_plmc_bond = ::EvaluatorSlash::get() * evaluation_plmc_bond; - let usable_plmc_bond = evaluation_plmc_bond - slashable_plmc_bond; - - let bids_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &all_bids, - project_metadata.clone(), - None, - true, - ); - inst.mint_plmc_to(bids_plmc.clone()); - - let bids_foreign = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &all_bids, - project_metadata.clone(), - None, - ); - inst.mint_foreign_asset_to(bids_foreign.clone()); - - inst.bid_for_users(project_id, bids).unwrap(); - - let auction_end = ::AuctionOpeningDuration::get() + - ::AuctionClosingDuration::get(); - inst.advance_time(auction_end - 1).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Auction); - inst.bid_for_users(project_id, vec![bob_bid]).unwrap(); - - inst.start_community_funding(project_id).unwrap(); - assert!(matches!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound(..))); - inst.start_remainder_or_end_funding(project_id).unwrap(); - - let plmc_price = ::PriceProvider::get_decimals_aware_price( - PLMC_FOREIGN_ID, - USD_DECIMALS, - PLMC_DECIMALS, - ) - .unwrap(); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - let usable_usd = plmc_price.saturating_mul_int(usable_plmc_bond); - let usable_ct = wap.reciprocal().unwrap().saturating_mul_int(usable_usd); - - let bob_contribution = (bob, 1337 * CT_UNIT).into(); - let contribution_usdt = inst.calculate_contributed_funding_asset_spent(vec![bob_contribution], wap); - inst.mint_foreign_asset_to(contribution_usdt.clone()); - inst.execute(|| { - assert_ok!(Pallet::::contribute( - RuntimeOrigin::signed(bob), - get_mock_jwt_with_cid( - bob, - InvestorType::Retail, - generate_did_from_account(bob), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - usable_ct, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - )); - }); - } - - #[test] - fn contribute_with_multiple_currencies() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - - let mut project_metadata_all = default_project_metadata(ISSUER_1); - project_metadata_all.participation_currencies = - vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT] - .try_into() - .unwrap(); - - let mut project_metadata_usdt = default_project_metadata(ISSUER_2); - project_metadata_usdt.participation_currencies = vec![AcceptedFundingAsset::USDT].try_into().unwrap(); - - let mut project_metadata_usdc = default_project_metadata(ISSUER_3); - project_metadata_usdc.participation_currencies = vec![AcceptedFundingAsset::USDC].try_into().unwrap(); - - let mut project_metadata_dot = default_project_metadata(ISSUER_4); - project_metadata_dot.participation_currencies = vec![AcceptedFundingAsset::DOT].try_into().unwrap(); - - let evaluations = default_evaluations(); - - let usdt_bids = default_bids() - .into_iter() - .map(|mut b| { - b.asset = AcceptedFundingAsset::USDT; - b - }) - .collect::>(); - - let usdc_bids = default_bids() - .into_iter() - .map(|mut b| { - b.asset = AcceptedFundingAsset::USDC; - b - }) - .collect::>(); - - let dot_bids = default_bids() - .into_iter() - .map(|mut b| { - b.asset = AcceptedFundingAsset::DOT; - b - }) - .collect::>(); - - let usdt_contribution = ContributionParams::new(BUYER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let usdc_contribution = ContributionParams::new(BUYER_2, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDC); - let dot_contribution = ContributionParams::new(BUYER_3, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::DOT); - - let project_id_all = inst.create_remainder_contributing_project( - project_metadata_all.clone(), - ISSUER_1, - None, - evaluations.clone(), - default_bids(), - vec![], - ); - let wap = inst.get_project_details(project_id_all).weighted_average_price.unwrap(); - - let plmc_fundings = inst.calculate_contributed_plmc_spent( - vec![usdt_contribution.clone(), usdc_contribution.clone(), dot_contribution.clone()], - wap, - false, - ); - let plmc_existential_deposits = plmc_fundings.accounts().existential_deposits(); - let plmc_all_mints = - inst.generic_map_operation(vec![plmc_fundings, plmc_existential_deposits], MergeOperation::Add); - inst.mint_plmc_to(plmc_all_mints.clone()); - inst.mint_plmc_to(plmc_all_mints.clone()); - inst.mint_plmc_to(plmc_all_mints.clone()); - - let usdt_fundings = inst.calculate_contributed_funding_asset_spent( - vec![usdt_contribution.clone(), usdc_contribution.clone(), dot_contribution.clone()], - wap, - ); - inst.mint_foreign_asset_to(usdt_fundings.clone()); - inst.mint_foreign_asset_to(usdt_fundings.clone()); - inst.mint_foreign_asset_to(usdt_fundings.clone()); - - assert_ok!(inst.contribute_for_users( - project_id_all, - vec![usdt_contribution.clone(), usdc_contribution.clone(), dot_contribution.clone()] - )); - - let project_id_usdt = inst.create_remainder_contributing_project( - project_metadata_usdt.clone(), - ISSUER_2, - None, - evaluations.clone(), - usdt_bids, - vec![], - ); - - assert_ok!(inst.contribute_for_users(project_id_usdt, vec![usdt_contribution.clone()])); - assert_err!( - inst.contribute_for_users(project_id_usdt, vec![usdc_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - assert_err!( - inst.contribute_for_users(project_id_usdt, vec![dot_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - - let project_id_usdc = inst.create_remainder_contributing_project( - project_metadata_usdc.clone(), - ISSUER_3, - None, - evaluations.clone(), - usdc_bids, - vec![], - ); - - assert_err!( - inst.contribute_for_users(project_id_usdc, vec![usdt_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - assert_ok!(inst.contribute_for_users(project_id_usdc, vec![usdc_contribution.clone()])); - assert_err!( - inst.contribute_for_users(project_id_usdc, vec![dot_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - - let project_id_dot = inst.create_remainder_contributing_project( - project_metadata_dot.clone(), - ISSUER_4, - None, - evaluations.clone(), - dot_bids, - vec![], - ); - - assert_err!( - inst.contribute_for_users(project_id_dot, vec![usdt_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - assert_err!( - inst.contribute_for_users(project_id_dot, vec![usdc_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - assert_ok!(inst.contribute_for_users(project_id_dot, vec![dot_contribution.clone()])); - } - - fn test_contribution_setup( - inst: &mut MockInstantiator, - project_id: ProjectId, - contributor: AccountIdOf, - investor_type: InvestorType, - u8_multiplier: u8, - ) -> DispatchResultWithPostInfo { - let project_policy = inst.get_project_metadata(project_id).policy_ipfs_cid.unwrap(); - let jwt = get_mock_jwt_with_cid( - contributor.clone(), - investor_type, - generate_did_from_account(contributor), - project_policy, - ); - let amount = 1000 * CT_UNIT; - let multiplier = Multiplier::force_new(u8_multiplier); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - if u8_multiplier > 0 { - let contribution = ContributionParams:: { - contributor: contributor.clone(), - amount, - multiplier, - asset: AcceptedFundingAsset::USDT, - }; - - let necessary_plmc = inst.calculate_contributed_plmc_spent(vec![contribution.clone()], wap, false); - let plmc_existential_amounts = necessary_plmc.accounts().existential_deposits(); - let necessary_usdt = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); - - inst.mint_plmc_to(necessary_plmc.clone()); - inst.mint_plmc_to(plmc_existential_amounts.clone()); - inst.mint_foreign_asset_to(necessary_usdt.clone()); - } - inst.execute(|| { - Pallet::::contribute( - RuntimeOrigin::signed(contributor), - jwt, - project_id, - amount, - multiplier, - AcceptedFundingAsset::USDT, - ) - }) - } - - #[test] - fn non_retail_multiplier_limits() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let mut project_metadata = default_project_metadata(ISSUER_1); - project_metadata.mainnet_token_max_supply = 80_000_000 * CT_UNIT; - project_metadata.total_allocation_size = 10_000_000 * CT_UNIT; - project_metadata.bidding_ticket_sizes = BiddingTicketSizes { - professional: TicketSize::new(5000 * USD_UNIT, None), - institutional: TicketSize::new(5000 * USD_UNIT, None), - phantom: Default::default(), - }; - project_metadata.contributing_ticket_sizes = ContributingTicketSizes { - retail: TicketSize::new(USD_UNIT, None), - professional: TicketSize::new(USD_UNIT, None), - institutional: TicketSize::new(USD_UNIT, None), - phantom: Default::default(), - }; - let evaluations = - inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); - let bids = inst.generate_bids_from_total_ct_percent( - project_metadata.clone(), - 50, - default_weights(), - default_bidders(), - default_multipliers(), - ); - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - evaluations, - bids, - vec![], - ); - - // Professional contributions: 0x multiplier should fail - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Professional, 0), - Error::::ForbiddenMultiplier - ); - // Professional contributions: 1 - 10x multiplier should work - for multiplier in 1..=10u8 { - assert_ok!(test_contribution_setup( - &mut inst, - project_id, - BUYER_1, - InvestorType::Professional, - multiplier - )); - } - // Professional contributions: >=11x multiplier should fail - for multiplier in 11..=50u8 { - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Professional, multiplier), - Error::::ForbiddenMultiplier - ); - } - - // Institutional contributions: 0x multiplier should fail - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_2, InvestorType::Institutional, 0), - Error::::ForbiddenMultiplier - ); - // Institutional contributions: 1 - 25x multiplier should work - for multiplier in 1..=25u8 { - assert_ok!(test_contribution_setup( - &mut inst, - project_id, - BUYER_2, - InvestorType::Institutional, - multiplier - )); - } - // Institutional contributions: >=26x multiplier should fail - for multiplier in 26..=50u8 { - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_2, InvestorType::Institutional, multiplier), - Error::::ForbiddenMultiplier - ); - } - } - - #[test] - fn retail_multiplier_limits() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let mut issuer: AccountId = 6969420; - - let mut create_project = |inst: &mut MockInstantiator| { - issuer += 1; - inst.create_remainder_contributing_project( - default_project_metadata(issuer), - issuer, - None, - default_evaluations(), - default_bids(), - vec![], - ) - }; - - let max_allowed_multipliers_map = vec![(2, 1), (4, 2), (9, 4), (24, 7), (25, 10)]; - - let mut previous_projects_created = 0; - for (projects_participated_amount, max_allowed_multiplier) in max_allowed_multipliers_map { - (previous_projects_created..projects_participated_amount - 1).for_each(|_| { - let project_id = create_project(&mut inst); - assert_ok!(test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Retail, 1)); - }); - - let project_id = create_project(&mut inst); - previous_projects_created = projects_participated_amount; - - // 0x multiplier should fail - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Retail, 0), - Error::::ForbiddenMultiplier - ); - - // Multipliers that should work - for multiplier in 1..=max_allowed_multiplier { - assert_ok!(test_contribution_setup( - &mut inst, - project_id, - BUYER_1, - InvestorType::Retail, - multiplier - )); - } - - // Multipliers that should NOT work - for multiplier in max_allowed_multiplier + 1..=50 { - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Retail, multiplier), - Error::::ForbiddenMultiplier - ); - } - } - } - - #[test] - fn did_with_winning_bid_can_contribute() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let mut evaluations = default_evaluations(); - evaluations.push((BIDDER_4, 1337 * USD_UNIT).into()); - - let successful_bids = vec![ - BidParams::new(BIDDER_1, 400_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - BidParams::new(BIDDER_2, 100_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ]; - let failing_bids_after_random_end = - vec![(BIDDER_3, 25_000 * CT_UNIT).into(), (BIDDER_4, 25_000 * CT_UNIT).into()]; - // This bids should fill the first bucket. - let failing_bids_sold_out = - vec![(BIDDER_5, 250_000 * CT_UNIT).into(), (BIDDER_6, 250_000 * CT_UNIT).into()]; - - let all_bids = failing_bids_sold_out - .iter() - .chain(successful_bids.iter()) - .chain(failing_bids_after_random_end.iter()) - .cloned() - .collect_vec(); - - let project_id = - inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); - - let plmc_fundings = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &all_bids.clone(), - project_metadata.clone(), - None, - true, - ); - inst.mint_plmc_to(plmc_fundings.clone()); - - let foreign_funding = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &all_bids.clone(), - project_metadata.clone(), - None, - ); - inst.mint_foreign_asset_to(foreign_funding.clone()); - - inst.bid_for_users(project_id, failing_bids_sold_out).unwrap(); - inst.bid_for_users(project_id, successful_bids).unwrap(); - inst.advance_time( - ::AuctionOpeningDuration::get() + - ::AuctionClosingDuration::get(), - ) - .unwrap(); - inst.bid_for_users(project_id, failing_bids_after_random_end).unwrap(); - inst.advance_time(2).unwrap(); - assert!(matches!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound(..))); - inst.start_remainder_or_end_funding(project_id).unwrap(); - - // Some low amount of plmc and usdt to cover a purchase of 10CTs. - let plmc_mints = vec![ - (BIDDER_3, 42069 * PLMC).into(), - (BIDDER_4, 42069 * PLMC).into(), - (BIDDER_5, 42069 * PLMC).into(), - (BIDDER_6, 42069 * PLMC).into(), - (BUYER_3, 42069 * PLMC).into(), - (BUYER_4, 42069 * PLMC).into(), - (BUYER_5, 42069 * PLMC).into(), - (BUYER_6, 42069 * PLMC).into(), - ]; - inst.mint_plmc_to(plmc_mints); - let usdt_mints = vec![ - (BIDDER_3, 42069 * CT_UNIT).into(), - (BIDDER_4, 42069 * CT_UNIT).into(), - (BIDDER_5, 42069 * CT_UNIT).into(), - (BIDDER_6, 42069 * CT_UNIT).into(), - (BUYER_3, 42069 * CT_UNIT).into(), - (BUYER_4, 42069 * CT_UNIT).into(), - (BUYER_5, 42069 * CT_UNIT).into(), - (BUYER_6, 42069 * CT_UNIT).into(), - ]; - inst.mint_foreign_asset_to(usdt_mints); - - let mut bid_should_succeed = |account, investor_type, did_acc| { - inst.execute(|| { - assert_ok!(Pallet::::contribute( - RuntimeOrigin::signed(account), - get_mock_jwt_with_cid( - account, - investor_type, - generate_did_from_account(did_acc), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - 10 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - )); - }); - }; - - // Bidder 3 has a losing bid due to bidding after the random end. His did should be able to contribute regardless of what investor type - // or account he uses to sign the transaction - bid_should_succeed(BIDDER_3, InvestorType::Institutional, BIDDER_3); - bid_should_succeed(BUYER_3, InvestorType::Institutional, BIDDER_3); - bid_should_succeed(BIDDER_3, InvestorType::Professional, BIDDER_3); - bid_should_succeed(BUYER_3, InvestorType::Professional, BIDDER_3); - bid_should_succeed(BIDDER_3, InvestorType::Retail, BIDDER_3); - bid_should_succeed(BUYER_3, InvestorType::Retail, BIDDER_3); - - // Bidder 4 has a losing bid due to bidding after the random end, and he was also an evaluator. Same conditions as before should apply. - bid_should_succeed(BIDDER_4, InvestorType::Institutional, BIDDER_4); - bid_should_succeed(BUYER_4, InvestorType::Institutional, BIDDER_4); - bid_should_succeed(BIDDER_4, InvestorType::Professional, BIDDER_4); - bid_should_succeed(BUYER_4, InvestorType::Professional, BIDDER_4); - bid_should_succeed(BIDDER_4, InvestorType::Retail, BIDDER_4); - bid_should_succeed(BUYER_4, InvestorType::Retail, BIDDER_4); - - // Bidder 5 has a losing bid due to CTs being sold out at his price point. Same conditions as before should apply. - bid_should_succeed(BIDDER_5, InvestorType::Institutional, BIDDER_5); - bid_should_succeed(BUYER_5, InvestorType::Institutional, BIDDER_5); - bid_should_succeed(BIDDER_5, InvestorType::Professional, BIDDER_5); - bid_should_succeed(BUYER_5, InvestorType::Professional, BIDDER_5); - bid_should_succeed(BIDDER_5, InvestorType::Retail, BIDDER_5); - bid_should_succeed(BUYER_5, InvestorType::Retail, BIDDER_5); - - // Bidder 6 has a losing bid due to CTs being sold out at his price point, and he was also an evaluator. Same conditions as before should apply. - bid_should_succeed(BIDDER_6, InvestorType::Institutional, BIDDER_6); - bid_should_succeed(BUYER_6, InvestorType::Institutional, BIDDER_6); - bid_should_succeed(BIDDER_6, InvestorType::Professional, BIDDER_6); - bid_should_succeed(BUYER_6, InvestorType::Professional, BIDDER_6); - bid_should_succeed(BIDDER_6, InvestorType::Retail, BIDDER_6); - bid_should_succeed(BUYER_6, InvestorType::Retail, BIDDER_6); - } - - #[test] - fn can_contribute_with_frozen_tokens_funding_failed() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(issuer); - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - issuer, - None, - default_evaluations(), - vec![], - vec![], - ); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - let contribution = ContributionParams::new(BUYER_4, 500 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let plmc_required = inst.calculate_contributed_plmc_spent(vec![contribution.clone()], wap, false); - let frozen_amount = plmc_required[0].plmc_amount; - let plmc_existential_deposits = plmc_required.accounts().existential_deposits(); - - inst.mint_plmc_to(plmc_existential_deposits); - inst.mint_plmc_to(plmc_required.clone()); - - inst.execute(|| { - mock::Balances::set_freeze(&(), &BUYER_4, plmc_required[0].plmc_amount).unwrap(); - }); - - let usdt_required = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); - inst.mint_foreign_asset_to(usdt_required); - - inst.execute(|| { - assert_noop!( - Balances::transfer_allow_death(RuntimeOrigin::signed(BUYER_4), ISSUER_1, frozen_amount,), - TokenError::Frozen - ); - }); - - inst.execute(|| { - assert_ok!(PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_4), - get_mock_jwt_with_cid( - BUYER_4, - InvestorType::Institutional, - generate_did_from_account(BUYER_4), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - contribution.amount, - contribution.multiplier, - contribution.asset - )); - }); - - inst.finish_funding(project_id, None).unwrap(); - - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); - - let free_balance = inst.get_free_plmc_balance_for(BUYER_4); - let bid_held_balance = - inst.get_reserved_plmc_balance_for(BUYER_4, HoldReason::Participation(project_id).into()); - let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BUYER_4)); - let account_data = inst.execute(|| System::account(&BUYER_4)).data; - - assert_eq!(free_balance, inst.get_ed()); - assert_eq!(bid_held_balance, frozen_amount); - assert_eq!(frozen_balance, frozen_amount); - let expected_account_data = AccountData { - free: inst.get_ed(), - reserved: frozen_amount, - frozen: frozen_amount, - flags: Default::default(), - }; - assert_eq!(account_data, expected_account_data); - - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); - - inst.execute(|| { - PolimecFunding::settle_failed_contribution(RuntimeOrigin::signed(BUYER_4), project_id, BUYER_4, 0) - .unwrap(); - }); - - let free_balance = inst.get_free_plmc_balance_for(BUYER_4); - let bid_held_balance = - inst.get_reserved_plmc_balance_for(BUYER_4, HoldReason::Evaluation(project_id).into()); - let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BUYER_4)); - let account_data = inst.execute(|| System::account(&BUYER_4)).data; - - assert_eq!(free_balance, inst.get_ed() + frozen_amount); - assert_eq!(bid_held_balance, Zero::zero()); - assert_eq!(frozen_balance, frozen_amount); - let expected_account_data = AccountData { - free: inst.get_ed() + frozen_amount, - reserved: Zero::zero(), - frozen: frozen_amount, - flags: Default::default(), - }; - assert_eq!(account_data, expected_account_data); - assert_eq!(account_data.frozen, account_data.free - inst.get_ed()); - } - - #[test] - fn can_contribute_with_frozen_tokens_funding_success() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(issuer); - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - issuer, - None, - default_evaluations(), - default_bids(), - vec![], - ); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - let contribution = ContributionParams::new(BUYER_4, 500 * CT_UNIT, 5u8, AcceptedFundingAsset::USDT); - let plmc_required = inst.calculate_contributed_plmc_spent(vec![contribution.clone()], wap, false); - let frozen_amount = plmc_required[0].plmc_amount; - let plmc_existential_deposits = plmc_required.accounts().existential_deposits(); - - inst.mint_plmc_to(plmc_existential_deposits); - inst.mint_plmc_to(plmc_required.clone()); - - inst.execute(|| { - mock::Balances::set_freeze(&(), &BUYER_4, plmc_required[0].plmc_amount).unwrap(); - }); - - let usdt_required = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); - inst.mint_foreign_asset_to(usdt_required); - - inst.execute(|| { - assert_noop!( - Balances::transfer_allow_death(RuntimeOrigin::signed(BUYER_4), ISSUER_1, frozen_amount,), - TokenError::Frozen - ); - }); - - inst.execute(|| { - assert_ok!(PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_4), - get_mock_jwt_with_cid( - BUYER_4, - InvestorType::Institutional, - generate_did_from_account(BUYER_4), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - contribution.amount, - contribution.multiplier, - contribution.asset - )); - }); - - inst.finish_funding(project_id, None).unwrap(); - - let decision_block = inst - .get_update_block(project_id, &UpdateType::ProjectDecision(FundingOutcomeDecision::AcceptFunding)) - .unwrap(); - inst.jump_to_block(decision_block); - - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); - - let free_balance = inst.get_free_plmc_balance_for(BUYER_4); - let bid_held_balance = - inst.get_reserved_plmc_balance_for(BUYER_4, HoldReason::Participation(project_id).into()); - let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BUYER_4)); - let account_data = inst.execute(|| System::account(&BUYER_4)).data; - - assert_eq!(free_balance, inst.get_ed()); - assert_eq!(bid_held_balance, frozen_amount); - assert_eq!(frozen_balance, frozen_amount); - let expected_account_data = AccountData { - free: inst.get_ed(), - reserved: frozen_amount, - frozen: frozen_amount, - flags: Default::default(), - }; - assert_eq!(account_data, expected_account_data); - - inst.execute(|| { - PolimecFunding::settle_successful_contribution(RuntimeOrigin::signed(BUYER_4), project_id, BUYER_4, 0) - .unwrap(); - }); - - let free_balance = inst.get_free_plmc_balance_for(BUYER_4); - let bid_held_balance = - inst.get_reserved_plmc_balance_for(BUYER_4, HoldReason::Participation(project_id).into()); - let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BUYER_4)); - let account_data = inst.execute(|| System::account(&BUYER_4)).data; - - assert_eq!(free_balance, inst.get_ed()); - assert_eq!(bid_held_balance, frozen_amount); - assert_eq!(frozen_balance, frozen_amount); - let expected_account_data = AccountData { - free: inst.get_ed(), - reserved: frozen_amount, - frozen: frozen_amount, - flags: Default::default(), - }; - assert_eq!(account_data, expected_account_data); - - let vest_duration = - MultiplierOf::::new(5u8).unwrap().calculate_vesting_duration::(); - let now = inst.current_block(); - inst.jump_to_block(now + vest_duration + 1u64); - inst.execute(|| { - assert_ok!(mock::LinearRelease::vest( - RuntimeOrigin::signed(BUYER_4), - HoldReason::Participation(project_id).into() - )); - }); - - let free_balance = inst.get_free_plmc_balance_for(BUYER_4); - let bid_held_balance = - inst.get_reserved_plmc_balance_for(BUYER_4, HoldReason::Participation(project_id).into()); - let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BUYER_4)); - let account_data = inst.execute(|| System::account(&BUYER_4)).data; - - assert_eq!(free_balance, inst.get_ed() + frozen_amount); - assert_eq!(bid_held_balance, Zero::zero()); - assert_eq!(frozen_balance, frozen_amount); - let expected_account_data = AccountData { - free: inst.get_ed() + frozen_amount, - reserved: Zero::zero(), - frozen: frozen_amount, - flags: Default::default(), - }; - assert_eq!(account_data, expected_account_data); - } - - #[test] - fn participant_was_evaluator_and_bidder() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let participant = 42069u32; - let project_metadata = default_project_metadata(issuer); - let mut evaluations = default_evaluations(); - evaluations.push((participant, 100 * USD_UNIT).into()); - let mut bids = default_bids(); - bids.push(BidParams::new(participant, 1000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT)); - let community_contributions = default_community_buys(); - let mut remainder_contributions = default_remainder_buys(); - remainder_contributions.push(ContributionParams::new( - participant, - 10 * CT_UNIT, - 1u8, - AcceptedFundingAsset::USDT, - )); - - let _project_id = inst.create_finished_project( - project_metadata.clone(), - issuer, - None, - evaluations, - bids, - community_contributions, - remainder_contributions, - ); - } - } - - #[cfg(test)] - mod failure { - use super::*; - use frame_support::traits::{ - fungible::Mutate, - fungibles::Mutate as OtherMutate, - tokens::{Fortitude, Precision}, - }; - - #[test] - fn contribution_errors_if_user_limit_is_reached() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_remainder_contributing_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - vec![], - ); - const CONTRIBUTOR: AccountIdOf = 420; - - let project_details = inst.get_project_details(project_id); - let token_price = project_details.weighted_average_price.unwrap(); - - // Create a contribution vector that will reach the limit of contributions for a user-project - let token_amount: BalanceOf = CT_UNIT; - let range = 0..::MaxContributionsPerUser::get(); - let contributions: Vec> = range - .map(|_| ContributionParams::new(CONTRIBUTOR, token_amount, 1u8, AcceptedFundingAsset::USDT)) - .collect(); - - let plmc_funding = inst.calculate_contributed_plmc_spent(contributions.clone(), token_price, false); - let plmc_existential_deposits = plmc_funding.accounts().existential_deposits(); - - let foreign_funding = inst.calculate_contributed_funding_asset_spent(contributions.clone(), token_price); - - inst.mint_plmc_to(plmc_funding.clone()); - inst.mint_plmc_to(plmc_existential_deposits.clone()); - - inst.mint_foreign_asset_to(foreign_funding.clone()); - - // Reach up to the limit of contributions for a user-project - assert!(inst.contribute_for_users(project_id, contributions).is_ok()); - - // Try to contribute again, but it should fail because the limit of contributions for a user-project was reached. - let over_limit_contribution = - ContributionParams::new(CONTRIBUTOR, token_amount, 1u8, AcceptedFundingAsset::USDT); - assert!(inst.contribute_for_users(project_id, vec![over_limit_contribution]).is_err()); - - // Check that the right amount of PLMC is bonded, and funding currency is transferred - let contributor_post_buy_plmc_balance = - inst.execute(|| ::NativeCurrency::balance(&CONTRIBUTOR)); - let contributor_post_buy_foreign_asset_balance = inst.execute(|| { - ::FundingCurrency::balance( - AcceptedFundingAsset::USDT.to_assethub_id(), - CONTRIBUTOR, - ) - }); - - assert_eq!(contributor_post_buy_plmc_balance, inst.get_ed()); - assert_eq!(contributor_post_buy_foreign_asset_balance, 0); - - let plmc_bond_stored = inst.execute(|| { - ::NativeCurrency::balance_on_hold( - &HoldReason::Participation(project_id.into()).into(), - &CONTRIBUTOR, - ) - }); - let foreign_asset_contributions_stored = inst.execute(|| { - Contributions::::iter_prefix_values((project_id, CONTRIBUTOR)) - .map(|c| c.funding_asset_amount) - .sum::>() - }); - - assert_eq!(plmc_bond_stored, inst.sum_balance_mappings(vec![plmc_funding.clone()])); - assert_eq!(foreign_asset_contributions_stored, inst.sum_foreign_mappings(vec![foreign_funding.clone()])); - } - - #[test] - fn issuer_cannot_contribute_his_project() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - default_community_buys(), - ); - assert_err!( - inst.execute(|| crate::Pallet::::do_contribute( - &(&ISSUER_1 + 1), - project_id, - 500 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - generate_did_from_account(ISSUER_1), - InvestorType::Institutional, - project_metadata.clone().policy_ipfs_cid.unwrap(), - )), - Error::::ParticipationToOwnProject - ); - } - - #[test] - fn per_credential_type_ticket_size_minimums() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = ProjectMetadata { - token_information: default_token_information(), - mainnet_token_max_supply: 8_000_000 * CT_UNIT, - total_allocation_size: 1_000_000 * CT_UNIT, - auction_round_allocation_percentage: Percent::from_percent(50u8), - minimum_price: PriceProviderOf::::calculate_decimals_aware_price( - PriceOf::::from_float(10.0), - USD_DECIMALS, - CT_DECIMALS, - ) - .unwrap(), - bidding_ticket_sizes: BiddingTicketSizes { - professional: TicketSize::new(8000 * USD_UNIT, None), - institutional: TicketSize::new(20_000 * USD_UNIT, None), - phantom: Default::default(), - }, - contributing_ticket_sizes: ContributingTicketSizes { - retail: TicketSize::new(10 * USD_UNIT, None), - professional: TicketSize::new(100_000 * USD_UNIT, None), - institutional: TicketSize::new(200_000 * USD_UNIT, None), - phantom: Default::default(), - }, - participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), - funding_destination_account: ISSUER_1, - policy_ipfs_cid: Some(ipfs_hash()), - }; - - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - vec![], - ); - - inst.mint_plmc_to(vec![ - (BUYER_4, 50_000 * PLMC).into(), - (BUYER_5, 50_000 * PLMC).into(), - (BUYER_6, 50_000 * PLMC).into(), - ]); - - inst.mint_foreign_asset_to(vec![ - (BUYER_4, 50_000 * USDT_UNIT).into(), - (BUYER_5, 50_000 * USDT_UNIT).into(), - (BUYER_6, 50_000 * USDT_UNIT).into(), - ]); - - // contribution below 1 CT (10 USD) should fail for retail - let jwt = get_mock_jwt_with_cid( - BUYER_4, - InvestorType::Retail, - generate_did_from_account(BUYER_4), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BUYER_4), - jwt, - project_id, - CT_UNIT / 2, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - ), - Error::::TooLow - ); - }); - // contribution below 10_000 CT (100k USD) should fail for professionals - let jwt = get_mock_jwt_with_cid( - BUYER_5, - InvestorType::Professional, - generate_did_from_account(BUYER_5), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BUYER_5), - jwt, - project_id, - 9_999, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - ), - Error::::TooLow - ); - }); - - // contribution below 20_000 CT (200k USD) should fail for institutionals - let jwt = get_mock_jwt_with_cid( - BUYER_6, - InvestorType::Institutional, - generate_did_from_account(BUYER_6), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BUYER_6), - jwt, - project_id, - 19_999, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - ), - Error::::TooLow - ); - }); - } - - #[test] - fn per_credential_type_ticket_size_maximums() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = ProjectMetadata { - token_information: default_token_information(), - mainnet_token_max_supply: 8_000_000 * CT_UNIT, - total_allocation_size: 1_000_000 * CT_UNIT, - auction_round_allocation_percentage: Percent::from_percent(50u8), - minimum_price: PriceProviderOf::::calculate_decimals_aware_price( - PriceOf::::from_float(10.0), - USD_DECIMALS, - CT_DECIMALS, - ) - .unwrap(), - bidding_ticket_sizes: BiddingTicketSizes { - professional: TicketSize::new(5000 * USD_UNIT, None), - institutional: TicketSize::new(5000 * USD_UNIT, None), - phantom: Default::default(), - }, - contributing_ticket_sizes: ContributingTicketSizes { - retail: TicketSize::new(USD_UNIT, Some(300_000 * USD_UNIT)), - professional: TicketSize::new(USD_UNIT, Some(20_000 * USD_UNIT)), - institutional: TicketSize::new(USD_UNIT, Some(50_000 * USD_UNIT)), - phantom: Default::default(), - }, - participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), - funding_destination_account: ISSUER_1, - policy_ipfs_cid: Some(ipfs_hash()), - }; - - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - vec![], - ); - - inst.mint_plmc_to(vec![ - (BUYER_4, 500_000 * PLMC).into(), - (BUYER_5, 500_000 * PLMC).into(), - (BUYER_6, 500_000 * PLMC).into(), - (BUYER_7, 500_000 * PLMC).into(), - (BUYER_8, 500_000 * PLMC).into(), - (BUYER_9, 500_000 * PLMC).into(), - ]); - - inst.mint_foreign_asset_to(vec![ - (BUYER_4, 500_000 * USDT_UNIT).into(), - (BUYER_5, 500_000 * USDT_UNIT).into(), - (BUYER_6, 500_000 * USDT_UNIT).into(), - (BUYER_7, 500_000 * USDT_UNIT).into(), - (BUYER_8, 500_000 * USDT_UNIT).into(), - (BUYER_9, 500_000 * USDT_UNIT).into(), - ]); - - // total contributions with same DID above 30k CT (300k USD) should fail for retail - inst.execute(|| { - assert_ok!(Pallet::::do_contribute( - &BUYER_4, - project_id, - 28_000 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - generate_did_from_account(BUYER_4), - InvestorType::Retail, - project_metadata.clone().policy_ipfs_cid.unwrap(), - )); - }); - inst.execute(|| { - assert_noop!( - Pallet::::do_contribute( - &BUYER_5, - project_id, - 2001 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - // note we use the same did as bidder 1, on a different account - generate_did_from_account(BUYER_4), - InvestorType::Retail, - project_metadata.clone().policy_ipfs_cid.unwrap(), - ), - Error::::TooHigh - ); - }); - // bidding 2k total works - inst.execute(|| { - assert_ok!(Pallet::::do_contribute( - &BUYER_5, - project_id, - 2000 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - // note we use the same did as bidder 1, on a different account - generate_did_from_account(BUYER_4), - InvestorType::Retail, - project_metadata.clone().policy_ipfs_cid.unwrap(), - )); - }); - - // total contributions with same DID above 2k CT (20k USD) should fail for professionals - inst.execute(|| { - assert_ok!(Pallet::::do_contribute( - &BUYER_6, - project_id, - 1800 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - generate_did_from_account(BUYER_6), - InvestorType::Professional, - project_metadata.clone().policy_ipfs_cid.unwrap(), - )); - }); - inst.execute(|| { - assert_noop!( - Pallet::::do_contribute( - &BUYER_7, - project_id, - 201 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - // note we use the same did as bidder 1, on a different account - generate_did_from_account(BUYER_6), - InvestorType::Professional, - project_metadata.clone().policy_ipfs_cid.unwrap(), - ), - Error::::TooHigh - ); - }); - // bidding 2k total works - inst.execute(|| { - assert_ok!(Pallet::::do_contribute( - &BUYER_7, - project_id, - 200 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - // note we use the same did as bidder 1, on a different account - generate_did_from_account(BUYER_6), - InvestorType::Professional, - project_metadata.clone().policy_ipfs_cid.unwrap(), - )); - }); - - // total contributions with same DID above 5k CT (50 USD) should fail for institutionals - inst.execute(|| { - assert_ok!(Pallet::::do_contribute( - &BUYER_8, - project_id, - 4690 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - generate_did_from_account(BUYER_8), - InvestorType::Institutional, - project_metadata.clone().policy_ipfs_cid.unwrap(), - )); - }); - inst.execute(|| { - assert_noop!( - Pallet::::do_contribute( - &BUYER_9, - project_id, - 311 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - // note we use the same did as bidder 3, on a different account - generate_did_from_account(BUYER_8), - InvestorType::Institutional, - project_metadata.clone().policy_ipfs_cid.unwrap(), - ), - Error::::TooHigh - ); - }); - // bidding 5k total works - inst.execute(|| { - assert_ok!(Pallet::::do_contribute( - &BUYER_9, - project_id, - 310 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - // note we use the same did as bidder 3, on a different account - generate_did_from_account(BUYER_8), - InvestorType::Institutional, - project_metadata.clone().policy_ipfs_cid.unwrap(), - )); - }); - } - - #[test] - fn insufficient_funds() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - vec![], - ); - - let jwt = get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Retail, - generate_did_from_account(BUYER_1), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - let contribution = ContributionParams::new(BUYER_1, 1_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - // 1 unit less native asset than needed - let plmc_funding = inst.calculate_contributed_plmc_spent(vec![contribution.clone()], wap, false); - let plmc_existential_deposits = plmc_funding.accounts().existential_deposits(); - inst.mint_plmc_to(plmc_funding.clone()); - inst.mint_plmc_to(plmc_existential_deposits.clone()); - inst.execute(|| Balances::burn_from(&BUYER_1, 1, Precision::BestEffort, Fortitude::Force)).unwrap(); - - let foreign_funding = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); - inst.mint_foreign_asset_to(foreign_funding.clone()); - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BUYER_1), - jwt.clone(), - project_id, - 1_000 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - ), - Error::::ParticipantNotEnoughFunds - ); - }); - - // 1 unit less funding asset than needed - let plmc_funding = inst.calculate_contributed_plmc_spent(vec![contribution.clone()], wap, false); - let plmc_existential_deposits = plmc_funding.accounts().existential_deposits(); - inst.mint_plmc_to(plmc_funding.clone()); - inst.mint_plmc_to(plmc_existential_deposits.clone()); - let foreign_funding = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); - - inst.execute(|| ForeignAssets::set_balance(AcceptedFundingAsset::USDT.to_assethub_id(), &BUYER_1, 0)); - inst.mint_foreign_asset_to(foreign_funding.clone()); - - inst.execute(|| { - ForeignAssets::burn_from( - AcceptedFundingAsset::USDT.to_assethub_id(), - &BUYER_1, - 100, - Precision::BestEffort, - Fortitude::Force, - ) - }) - .unwrap(); - - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BUYER_1), - jwt, - project_id, - 1_000 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - ), - Error::::ParticipantNotEnoughFunds - ); - }); - } - - #[test] - fn called_outside_remainder_round() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - ISSUER_4, - None, - default_evaluations(), - default_bids(), - ); - - inst.execute(|| { - assert_noop!( - PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_1), - get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Retail, - generate_did_from_account(BUYER_1), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - 1000 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT - ), - Error::::IncorrectRound - ); - }); - } - - #[test] - fn contribute_with_unaccepted_currencies() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - - let mut project_metadata_usdt = default_project_metadata(ISSUER_2); - project_metadata_usdt.participation_currencies = vec![AcceptedFundingAsset::USDT].try_into().unwrap(); - - let mut project_metadata_usdc = default_project_metadata(ISSUER_3); - project_metadata_usdc.participation_currencies = vec![AcceptedFundingAsset::USDC].try_into().unwrap(); - - let evaluations = default_evaluations(); - - let usdt_bids = default_bids() - .into_iter() - .map(|mut b| { - b.asset = AcceptedFundingAsset::USDT; - b - }) - .collect::>(); - - let usdc_bids = default_bids() - .into_iter() - .map(|mut b| { - b.asset = AcceptedFundingAsset::USDC; - b - }) - .collect::>(); - - let usdt_contribution = ContributionParams::new(BUYER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let usdc_contribution = ContributionParams::new(BUYER_2, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDC); - let dot_contribution = ContributionParams::new(BUYER_3, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::DOT); - - let project_id_usdc = inst.create_remainder_contributing_project( - project_metadata_usdc, - ISSUER_3, - None, - evaluations.clone(), - usdc_bids, - vec![], - ); - assert_err!( - inst.contribute_for_users(project_id_usdc, vec![usdt_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - - let project_id_usdt = inst.create_remainder_contributing_project( - project_metadata_usdt, - ISSUER_2, - None, - evaluations.clone(), - usdt_bids, - vec![], - ); - assert_err!( - inst.contribute_for_users(project_id_usdt, vec![usdc_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - assert_err!( - inst.contribute_for_users(project_id_usdt, vec![dot_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - } - - #[test] - fn cannot_use_evaluation_bond_on_another_project_contribution() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata_1 = default_project_metadata(ISSUER_1); - let project_metadata_2 = default_project_metadata(ISSUER_2); - - let mut evaluations_1 = default_evaluations(); - let evaluations_2 = default_evaluations(); - - let evaluator_contributor = 69; - let evaluation_amount = 420 * USD_UNIT; - let evaluator_contribution = - ContributionParams::new(evaluator_contributor, 600 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - evaluations_1.push((evaluator_contributor, evaluation_amount).into()); - - let _project_id_1 = inst.create_remainder_contributing_project( - project_metadata_1.clone(), - ISSUER_1, - None, - evaluations_1, - default_bids(), - vec![], - ); - let project_id_2 = inst.create_remainder_contributing_project( - project_metadata_2.clone(), - ISSUER_2, - None, - evaluations_2, - default_bids(), - vec![], - ); - - let wap = inst.get_project_details(project_id_2).weighted_average_price.unwrap(); - - // Necessary Mints - let already_bonded_plmc = inst - .calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount).into()], false)[0] - .plmc_amount; - let usable_evaluation_plmc = - already_bonded_plmc - ::EvaluatorSlash::get() * already_bonded_plmc; - let necessary_plmc_for_contribution = - inst.calculate_contributed_plmc_spent(vec![evaluator_contribution.clone()], wap, false)[0].plmc_amount; - let necessary_usdt_for_contribution = - inst.calculate_contributed_funding_asset_spent(vec![evaluator_contribution.clone()], wap); - inst.mint_plmc_to(vec![UserToPLMCBalance::new( - evaluator_contributor, - necessary_plmc_for_contribution - usable_evaluation_plmc, - )]); - inst.mint_foreign_asset_to(necessary_usdt_for_contribution); - - inst.execute(|| { - assert_noop!( - PolimecFunding::contribute( - RuntimeOrigin::signed(evaluator_contributor), - get_mock_jwt_with_cid( - evaluator_contributor, - InvestorType::Retail, - generate_did_from_account(evaluator_contributor), - project_metadata_2.clone().policy_ipfs_cid.unwrap(), - ), - project_id_2, - evaluator_contribution.amount, - evaluator_contribution.multiplier, - evaluator_contribution.asset - ), - Error::::ParticipantNotEnoughFunds - ); - }); - } - - #[test] - fn wrong_policy_on_jwt() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - vec![], - ); - - inst.execute(|| { - assert_noop!( - PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_1), - get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Retail, - generate_did_from_account(BUYER_1), - "wrong_cid".as_bytes().to_vec().try_into().unwrap() - ), - project_id, - 5000 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT - ), - Error::::PolicyMismatch - ); - }); - } - } -} diff --git a/pallets/funding/src/tests/6_settlement.rs b/pallets/funding/src/tests/6_settlement.rs new file mode 100644 index 000000000..347b2477c --- /dev/null +++ b/pallets/funding/src/tests/6_settlement.rs @@ -0,0 +1,1176 @@ +use super::*; +use sp_runtime::bounded_vec; + +#[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, true); + 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); + + inst.assert_total_funding_paid_out(project_id, bids.clone(), contributions.clone()); + inst.assert_evaluations_migrations_created(project_id, evaluations, true); + 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 = 32u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, true); + 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); + + inst.assert_evaluations_migrations_created(project_id, evaluations, false); + inst.assert_bids_migrations_created(project_id, bids, false); + inst.assert_contributions_migrations_created(project_id, contributions, false); + } + } +} + +#[cfg(test)] +mod settle_evaluation_extrinsic { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + + #[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, + None, + 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![], + ); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); + + // The rewards are calculated as follows: + // Data: + // - Funding USD reached: 10_000_000 USD + // - Total CTs sold: 1_000_000 CT + // - USD target reached percent: 100% + + // Step 1) Calculate the total USD fee: + // USD fee 1 = 0.1 * 1_000_000 = 100_000 USD + // USD fee 2 = 0.08 * 4_000_000 = 320_000 USD + // USD fee 3 = 0.06 * 5_000_000 = 300_000 USD + // Total USD fee = 100_000 + 320_000 + 300_000 = 720_000 USD + + // Step 2) Calculate CT fee as follows: + // Percent fee = Total USD fee / Funding USD reached = 720_000 / 10_000_000 = 0.072 + // CT fee = Percent fee * Total CTs sold = 0.072 * 1_000_000 = 72_000 CT + + // Step 3) Calculate Early and Normal evaluator reward pots: + // Total evaluators reward pot = CT fee * 0.3 * USD target reached percent = 72_000 * 0.3 * 1 = 21_600 CT + // Early evaluators reward pot = Total evaluators reward pot * 0.2 = 21_600 * 0.2 = 4_320 CT + // Normal evaluators reward pot = Total evaluators reward pot * 0.8 = 21_600 * 0.8 = 17_280 CT + + // Step 4) Calculate the early and normal weights of each evaluation: + // Evaluation 1 = 500_000 USD + // Evaluation 2 = 250_000 USD + // Evaluation 3 = 320_000 USD + + // Early amount 1 = 500_000 USD + // Early amount 2 = 250_000 USD + // Early amount 3 = 250_000 USD + + // Total Normal amount = Evaluation 1 + Evaluation 2 + Evaluation 3 = 500_000 + 250_000 + 320_000 = 1_070_000 USD + // Total Early amount = 10% of USD target = 1_000_000 USD + + // Early weight 1 = Early amount 1 / Total Early amount = 500_000 / 1_000_000 = 0.5 + // Early weight 2 = Early amount 2 / Total Early amount = 250_000 / 1_000_000 = 0.25 + // Early weight 3 = Early amount 3 / Total Early amount = 250_000 / 1_000_000 = 0.25 + + // Normal weight 1 = Evaluation 1 / Total Normal amount = 500_000 / 1_070_000 = 0.467289719626168 + // Normal weight 2 = Evaluation 2 / Total Normal amount = 250_000 / 1_070_000 = 0.233644859813084 + // Normal weight 3 = Evaluation 3 / Total Normal amount = 320_000 / 1_070_000 = 0.299065420560748 + + // Step 5) Calculate the rewards for each evaluation: + // Evaluation 1 Early reward = Early weight 1 * Early evaluators reward pot = 0.5 * 4_320 = 2_160 CT + // Evaluation 2 Early reward = Early weight 2 * Early evaluators reward pot = 0.25 * 4_320 = 1_080 CT + // Evaluation 3 Early reward = Early weight 3 * Early evaluators reward pot = 0.25 * 4_320 = 1_080 CT + + // Evaluation 1 Normal reward = Normal weight 1 * Normal evaluators reward pot = 0.467289719626168 * 17_280 = 8'074.766355140186916 CT + // Evaluation 2 Normal reward = Normal weight 2 * Normal evaluators reward pot = 0.233644859813084 * 17_280 = 4'037.383177570093458 CT + // Evaluation 3 Normal reward = Normal weight 3 * Normal evaluators reward pot = 0.299065420560748 * 17_280 = 5'167.850467289719626 CT + + // Evaluation 1 Total reward = Evaluation 1 Early reward + Evaluation 1 Normal reward = 2_160 + 8_066 = 10'234.766355140186916 CT + // Evaluation 2 Total reward = Evaluation 2 Early reward + Evaluation 2 Normal reward = 1_080 + 4_033 = 5'117.383177570093458 CT + // Evaluation 3 Total reward = Evaluation 3 Early reward + Evaluation 3 Normal reward = 1_080 + 5_201 = 6'247.850467289719626 CT + + 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())); + + let evals = vec![(EVALUATOR_1, EVAL_1_REWARD), (EVALUATOR_2, EVAL_2_REWARD), (EVALUATOR_3, EVAL_3_REWARD)]; + + for (evaluator, expected_reward) in evals { + let evaluation_locked_plmc = + inst.get_reserved_plmc_balance_for(evaluator, HoldReason::Evaluation(project_id).into()); + let free_plmc = inst.get_free_plmc_balance_for(evaluator); + assert_ok!(inst.execute(|| PolimecFunding::settle_evaluation( + RuntimeOrigin::signed(evaluator), + project_id, + evaluator, + evaluator - 21 // The First evaluation index is 0, the first evaluator account is 21 + ))); + let ct_rewarded = inst.get_ct_asset_balance_for(project_id, evaluator); + assert_close_enough!(ct_rewarded, expected_reward, Perquintill::from_float(0.9999)); + assert_eq!(inst.get_reserved_plmc_balance_for(evaluator, HoldReason::Evaluation(project_id).into()), 0); + assert_eq!(inst.get_free_plmc_balance_for(evaluator), free_plmc + evaluation_locked_plmc); + inst.assert_migration(project_id, evaluator, expected_reward, 0, ParticipationType::Evaluation, true); + } + } + + #[test] + fn evaluation_slashed() { + let percentage = 20u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, true); + + 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, + Some(EvaluatorsOutcomeOf::::Slashed) + ); + + dbg!(inst.get_project_details(project_id).status); + assert_ok!(inst.execute(|| PolimecFunding::settle_evaluation( + RuntimeOrigin::signed(evaluator), + project_id, + evaluator, + first_evaluation.id + ))); + + 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_round_failed() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + let evaluation = UserToUSDBalance::new(EVALUATOR_1, 1_000 * USD_UNIT); + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); + + let evaluation_plmc = inst.calculate_evaluation_plmc_spent(vec![evaluation.clone()], true); + inst.mint_plmc_to(evaluation_plmc.clone()); + inst.evaluate_for_users(project_id, vec![evaluation]).unwrap(); + + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); + + 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_evaluation( + RuntimeOrigin::signed(EVALUATOR_1), + project_id, + EVALUATOR_1, + 0 + ))); + + assert_eq!(inst.get_ct_asset_balance_for(project_id, EVALUATOR_1), 0); + 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); + } + } + + #[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, true); + + let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); + inst.execute(|| { + let evaluator = first_evaluation.evaluator; + assert_ok!(crate::Pallet::::settle_evaluation( + RuntimeOrigin::signed(evaluator), + project_id, + evaluator, + first_evaluation.id + )); + assert_noop!( + crate::Pallet::::settle_evaluation( + RuntimeOrigin::signed(evaluator), + project_id, + evaluator, + first_evaluation.id + ), + Error::::ParticipationNotFound + ); + }); + } + + #[test] + fn cannot_be_called_before_settlement_started() { + let percentage = 100u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, false); + + let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); + let evaluator = first_evaluation.evaluator; + + inst.execute(|| { + assert_noop!( + PolimecFunding::settle_evaluation( + RuntimeOrigin::signed(evaluator), + project_id, + evaluator, + first_evaluation.id + ), + Error::::SettlementNotStarted + ); + }); + } + } +} + +#[cfg(test)] +mod settle_bid_extrinsic { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + + #[test] + fn accepted_bid_with_refund_on_project_success() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let ed = inst.get_ed(); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.participation_currencies = + bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; + let auction_allocation = + project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let partial_amount_bid_params = + BidParams::new(BIDDER_1, auction_allocation, 1u8, AcceptedFundingAsset::USDT); + let lower_price_bid_params = BidParams::new(BIDDER_2, 2000 * CT_UNIT, 5u8, AcceptedFundingAsset::DOT); + let bids = vec![partial_amount_bid_params.clone(), lower_price_bid_params.clone()]; + + let project_id = inst.create_finished_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + bids, + default_community_contributions(), + vec![], + ); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); + let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); + + // Partial amount bid assertions + let partial_amount_bid_stored = + inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); + let mut final_partial_amount_bid_params = partial_amount_bid_params.clone(); + final_partial_amount_bid_params.amount = auction_allocation - 2000 * CT_UNIT; + let expected_final_plmc_bonded = inst.calculate_auction_plmc_charged_with_given_price( + &vec![final_partial_amount_bid_params.clone()], + project_metadata.minimum_price, + false, + )[0] + .plmc_amount; + let expected_final_usdt_paid = inst.calculate_auction_funding_asset_charged_with_given_price( + &vec![final_partial_amount_bid_params], + project_metadata.minimum_price, + )[0] + .asset_amount; + + let expected_plmc_refund = partial_amount_bid_stored.plmc_bond - expected_final_plmc_bonded; + let expected_usdt_refund = partial_amount_bid_stored.funding_asset_amount_locked - expected_final_usdt_paid; + + let pre_issuer_usdt_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::USDT.id(), + project_metadata.funding_destination_account, + ); + + inst.execute(|| { + assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 0)); + }); + + let post_issuer_usdt_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::USDT.id(), + project_metadata.funding_destination_account, + ); + + inst.assert_funding_asset_free_balance(BIDDER_1, AcceptedFundingAsset::USDT.id(), expected_usdt_refund); + assert_eq!(post_issuer_usdt_balance, pre_issuer_usdt_balance + expected_final_usdt_paid); + + inst.assert_plmc_free_balance(BIDDER_1, expected_plmc_refund + ed); + inst.assert_ct_balance(project_id, BIDDER_1, auction_allocation - 2000 * CT_UNIT); + + inst.assert_migration( + project_id, + BIDDER_1, + auction_allocation - 2000 * CT_UNIT, + 0, + ParticipationType::Bid, + true, + ); + + // Multiplier one should be fully unbonded the next block + inst.advance_time(1_u64); + + let hold_reason: RuntimeHoldReason = HoldReason::Participation(project_id).into(); + inst.execute(|| LinearRelease::vest(RuntimeOrigin::signed(BIDDER_1), hold_reason).expect("Vesting failed")); + + inst.assert_plmc_free_balance(BIDDER_1, expected_plmc_refund + expected_final_plmc_bonded + ed); + + // Price > wap bid assertions + let lower_price_bid_stored = inst.execute(|| Bids::::get((project_id, BIDDER_2, 1)).unwrap()); + let expected_final_plmc_bonded = + inst.calculate_auction_plmc_charged_with_given_price(&vec![lower_price_bid_params.clone()], wap, false) + [0] + .plmc_amount; + let expected_final_dot_paid = inst + .calculate_auction_funding_asset_charged_with_given_price(&vec![lower_price_bid_params.clone()], wap)[0] + .asset_amount; + let expected_plmc_refund = lower_price_bid_stored.plmc_bond - expected_final_plmc_bonded; + let expected_dot_refund = lower_price_bid_stored.funding_asset_amount_locked - expected_final_dot_paid; + + let pre_issuer_dot_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::DOT.id(), + project_metadata.funding_destination_account, + ); + + inst.execute(|| { + assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BUYER_1), project_id, BIDDER_2, 1)); + }); + + let post_issuer_dot_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::DOT.id(), + project_metadata.funding_destination_account, + ); + + inst.assert_funding_asset_free_balance(BIDDER_2, AcceptedFundingAsset::DOT.id(), expected_dot_refund); + assert_eq!(post_issuer_dot_balance, pre_issuer_dot_balance + expected_final_dot_paid); + + inst.assert_plmc_free_balance(BIDDER_2, expected_plmc_refund + ed); + inst.assert_ct_balance(project_id, BIDDER_2, 2000 * CT_UNIT); + + inst.assert_migration(project_id, BIDDER_2, 2000 * CT_UNIT, 1, ParticipationType::Bid, true); + + // Multiplier 5 should be unbonded no earlier than after 8.67 weeks (i.e. 436'867 blocks) + let vesting_time = lower_price_bid_params.multiplier.calculate_vesting_duration::(); + + // Sanity check, 5 blocks should not be enough + inst.advance_time(5u64); + inst.execute(|| LinearRelease::vest(RuntimeOrigin::signed(BIDDER_2), hold_reason).expect("Vesting failed")); + assert_ne!( + inst.get_free_plmc_balance_for(BIDDER_2), + expected_plmc_refund + expected_final_plmc_bonded + ed + ); + + // After the vesting time, the full amount should be vested + let current_block = inst.current_block(); + inst.jump_to_block(current_block + vesting_time - 5u64); + inst.execute(|| LinearRelease::vest(RuntimeOrigin::signed(BIDDER_2), hold_reason).expect("Vesting failed")); + inst.assert_plmc_free_balance(BIDDER_2, expected_plmc_refund + expected_final_plmc_bonded + ed); + } + + #[test] + fn accepted_bid_without_refund_on_project_success() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let ed = inst.get_ed(); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.participation_currencies = + bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; + let auction_allocation = + project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let no_refund_bid_params = + BidParams::new(BIDDER_1, auction_allocation / 2, 16u8, AcceptedFundingAsset::USDT); + + let project_id = inst.create_finished_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + vec![no_refund_bid_params.clone()], + default_community_contributions(), + vec![], + ); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); + + let no_refund_bid_stored = inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); + + let pre_issuer_usdc_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::USDT.id(), + project_metadata.funding_destination_account, + ); + + inst.execute(|| { + assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 0)); + }); + + let post_issuer_usdc_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::USDT.id(), + project_metadata.funding_destination_account, + ); + + inst.assert_funding_asset_free_balance(BIDDER_1, AcceptedFundingAsset::USDT.id(), Zero::zero()); + assert_eq!( + post_issuer_usdc_balance, + pre_issuer_usdc_balance + no_refund_bid_stored.funding_asset_amount_locked + ); + + inst.assert_plmc_free_balance(BIDDER_1, ed); + inst.assert_ct_balance(project_id, BIDDER_1, auction_allocation / 2); + + inst.assert_migration(project_id, BIDDER_1, auction_allocation / 2, 0, ParticipationType::Bid, true); + + let hold_reason: RuntimeHoldReason = HoldReason::Participation(project_id).into(); + + let vesting_time = no_refund_bid_params.multiplier.calculate_vesting_duration::(); + + // Sanity check, 5 blocks should not be enough + inst.advance_time(5u64); + inst.execute(|| LinearRelease::vest(RuntimeOrigin::signed(BIDDER_1), hold_reason).expect("Vesting failed")); + assert_ne!(inst.get_free_plmc_balance_for(BIDDER_1), no_refund_bid_stored.plmc_bond + ed); + + // After the vesting time, the full amount should be vested + let current_block = inst.current_block(); + inst.jump_to_block(current_block + vesting_time - 5u64 + 1u64); + inst.execute(|| LinearRelease::vest(RuntimeOrigin::signed(BIDDER_1), hold_reason).expect("Vesting failed")); + inst.assert_plmc_free_balance(BIDDER_1, no_refund_bid_stored.plmc_bond + ed); + } + + #[test] + fn accepted_bid_with_refund_on_project_failure() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let ed = inst.get_ed(); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.participation_currencies = + bounded_vec![AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT]; + project_metadata.auction_round_allocation_percentage = Percent::from_percent(10); + let auction_allocation = + project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let partial_amount_bid_params = + BidParams::new(BIDDER_1, auction_allocation, 1u8, AcceptedFundingAsset::USDC); + let lower_price_bid_params = BidParams::new(BIDDER_2, 2000 * CT_UNIT, 5u8, AcceptedFundingAsset::DOT); + let bids = vec![partial_amount_bid_params.clone(), lower_price_bid_params.clone()]; + + let project_id = inst.create_finished_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + bids, + vec![], + vec![], + ); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); + let hold_reason: RuntimeHoldReason = HoldReason::Participation(project_id).into(); + + // Partial amount bid assertions + let partial_amount_bid_stored = + inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); + + let pre_issuer_usdc_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::USDC.id(), + project_metadata.funding_destination_account, + ); + + inst.execute(|| { + assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 0)); + }); + + let post_issuer_usdc_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::USDC.id(), + project_metadata.funding_destination_account, + ); + + inst.assert_funding_asset_free_balance( + BIDDER_1, + AcceptedFundingAsset::USDC.id(), + partial_amount_bid_stored.funding_asset_amount_locked, + ); + assert_eq!(post_issuer_usdc_balance, pre_issuer_usdc_balance); + + inst.assert_plmc_free_balance(BIDDER_1, partial_amount_bid_stored.plmc_bond + ed); + inst.assert_ct_balance(project_id, BIDDER_1, Zero::zero()); + + inst.assert_migration(project_id, BIDDER_1, Zero::zero(), 0, ParticipationType::Bid, false); + + inst.execute(|| { + assert_noop!( + LinearRelease::vest(RuntimeOrigin::signed(BIDDER_1), hold_reason), + pallet_linear_release::Error::::NotVesting + ); + }); + + // Price > wap bid assertions + let lower_price_bid_stored = inst.execute(|| Bids::::get((project_id, BIDDER_2, 1)).unwrap()); + + let pre_issuer_dot_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::DOT.id(), + project_metadata.funding_destination_account, + ); + + inst.execute(|| { + assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BUYER_1), project_id, BIDDER_2, 1)); + }); + + let post_issuer_dot_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::DOT.id(), + project_metadata.funding_destination_account, + ); + + inst.assert_funding_asset_free_balance( + BIDDER_2, + AcceptedFundingAsset::DOT.id(), + lower_price_bid_stored.funding_asset_amount_locked, + ); + assert_eq!(post_issuer_dot_balance, pre_issuer_dot_balance); + + inst.assert_plmc_free_balance(BIDDER_2, lower_price_bid_stored.plmc_bond + ed); + inst.assert_ct_balance(project_id, BIDDER_2, Zero::zero()); + + inst.assert_migration(project_id, BIDDER_2, Zero::zero(), 1, ParticipationType::Bid, false); + + inst.execute(|| { + assert_noop!( + LinearRelease::vest(RuntimeOrigin::signed(BIDDER_2), hold_reason), + pallet_linear_release::Error::::NotVesting + ); + }); + } + + #[test] + fn accepted_bid_without_refund_on_project_failure() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let ed = inst.get_ed(); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.participation_currencies = + bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; + let no_refund_bid_params = BidParams::new(BIDDER_1, 500 * CT_UNIT, 16u8, AcceptedFundingAsset::USDT); + + let project_id = inst.create_finished_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + vec![no_refund_bid_params.clone()], + vec![], + vec![], + ); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); + + // Partial amount bid assertions + let no_refund_bid_stored = inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); + + let pre_issuer_usdc_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::USDT.id(), + project_metadata.funding_destination_account, + ); + + inst.execute(|| { + assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 0)); + }); + + let post_issuer_usdc_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::USDT.id(), + project_metadata.funding_destination_account, + ); + + inst.assert_funding_asset_free_balance( + BIDDER_1, + AcceptedFundingAsset::USDT.id(), + no_refund_bid_stored.funding_asset_amount_locked, + ); + assert_eq!(post_issuer_usdc_balance, pre_issuer_usdc_balance); + + inst.assert_plmc_free_balance(BIDDER_1, ed + no_refund_bid_stored.plmc_bond); + inst.assert_ct_balance(project_id, BIDDER_1, Zero::zero()); + + let hold_reason: RuntimeHoldReason = HoldReason::Participation(project_id).into(); + inst.execute(|| { + assert_noop!( + LinearRelease::vest(RuntimeOrigin::signed(BIDDER_2), hold_reason), + pallet_linear_release::Error::::NotVesting + ); + }); + } + + #[test] + fn rejected_bid_on_community_round() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let ed = inst.get_ed(); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.participation_currencies = + bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; + let auction_allocation = + project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let rejected_bid_params = BidParams::new(BIDDER_1, auction_allocation, 4u8, AcceptedFundingAsset::USDT); + let accepted_bid_params = BidParams::new(BIDDER_2, auction_allocation, 1u8, AcceptedFundingAsset::DOT); + + let bids = vec![rejected_bid_params.clone(), accepted_bid_params.clone()]; + let project_id = inst.create_community_contributing_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + bids, + ); + + let rejected_bid_stored = inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); + assert_eq!(rejected_bid_stored.status, BidStatus::Rejected); + + let pre_issuer_usdt_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::USDT.id(), + project_metadata.funding_destination_account, + ); + + inst.execute(|| { + assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 0)); + }); + + let post_issuer_usdt_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::USDT.id(), + project_metadata.funding_destination_account, + ); + + inst.assert_funding_asset_free_balance( + BIDDER_1, + AcceptedFundingAsset::USDT.id(), + rejected_bid_stored.funding_asset_amount_locked, + ); + assert_eq!(post_issuer_usdt_balance, pre_issuer_usdt_balance); + + inst.assert_plmc_free_balance(BIDDER_1, rejected_bid_stored.plmc_bond + ed); + inst.assert_ct_balance(project_id, BIDDER_1, Zero::zero()); + + let hold_reason = HoldReason::Participation(project_id).into(); + inst.execute(|| { + assert_noop!( + LinearRelease::vest(RuntimeOrigin::signed(BIDDER_2), hold_reason), + pallet_linear_release::Error::::NotVesting + ); + }); + } + + #[test] + fn rejected_bid_on_project_success() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let ed = inst.get_ed(); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.participation_currencies = + bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; + let auction_allocation = + project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let rejected_bid_params = BidParams::new(BIDDER_1, auction_allocation, 4u8, AcceptedFundingAsset::USDT); + let accepted_bid_params = BidParams::new(BIDDER_2, auction_allocation, 1u8, AcceptedFundingAsset::DOT); + + let bids = vec![rejected_bid_params.clone(), accepted_bid_params.clone()]; + let project_id = inst.create_finished_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + bids, + default_community_contributions(), + vec![], + ); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); + + let rejected_bid_stored = inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); + assert_eq!(rejected_bid_stored.status, BidStatus::Rejected); + + let pre_issuer_usdt_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::USDT.id(), + project_metadata.funding_destination_account, + ); + + inst.execute(|| { + assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 0)); + }); + + let post_issuer_usdt_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::USDT.id(), + project_metadata.funding_destination_account, + ); + + inst.assert_funding_asset_free_balance( + BIDDER_1, + AcceptedFundingAsset::USDT.id(), + rejected_bid_stored.funding_asset_amount_locked, + ); + assert_eq!(post_issuer_usdt_balance, pre_issuer_usdt_balance); + + inst.assert_plmc_free_balance(BIDDER_1, rejected_bid_stored.plmc_bond + ed); + inst.assert_ct_balance(project_id, BIDDER_1, Zero::zero()); + + let hold_reason = HoldReason::Participation(project_id).into(); + inst.execute(|| { + assert_noop!( + LinearRelease::vest(RuntimeOrigin::signed(BIDDER_2), hold_reason), + pallet_linear_release::Error::::NotVesting + ); + }); + } + + #[test] + fn rejected_bid_on_project_failure() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let ed = inst.get_ed(); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.participation_currencies = + bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; + project_metadata.auction_round_allocation_percentage = Percent::from_percent(10); + let auction_allocation = + project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let rejected_bid_params = BidParams::new(BIDDER_1, auction_allocation, 4u8, AcceptedFundingAsset::USDT); + let accepted_bid_params = BidParams::new(BIDDER_2, auction_allocation, 1u8, AcceptedFundingAsset::DOT); + + let bids = vec![rejected_bid_params.clone(), accepted_bid_params.clone()]; + let project_id = inst.create_finished_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + bids, + vec![], + vec![], + ); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); + + let rejected_bid_stored = inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); + assert_eq!(rejected_bid_stored.status, BidStatus::Rejected); + + let pre_issuer_usdt_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::USDT.id(), + project_metadata.funding_destination_account, + ); + + inst.execute(|| { + assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 0)); + }); + + let post_issuer_usdt_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::USDT.id(), + project_metadata.funding_destination_account, + ); + + inst.assert_funding_asset_free_balance( + BIDDER_1, + AcceptedFundingAsset::USDT.id(), + rejected_bid_stored.funding_asset_amount_locked, + ); + assert_eq!(post_issuer_usdt_balance, pre_issuer_usdt_balance); + + inst.assert_plmc_free_balance(BIDDER_1, rejected_bid_stored.plmc_bond + ed); + inst.assert_ct_balance(project_id, BIDDER_1, Zero::zero()); + + let hold_reason = HoldReason::Participation(project_id).into(); + inst.execute(|| { + assert_noop!( + LinearRelease::vest(RuntimeOrigin::signed(BIDDER_2), hold_reason), + pallet_linear_release::Error::::NotVesting + ); + }); + } + } + + #[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, true); + + let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); + inst.execute(|| { + let bidder = first_bid.bidder; + assert_ok!(crate::Pallet::::settle_bid( + RuntimeOrigin::signed(bidder), + project_id, + bidder, + first_bid.id + )); + assert_noop!( + crate::Pallet::::settle_bid( + RuntimeOrigin::signed(bidder), + project_id, + bidder, + first_bid.id + ), + Error::::ParticipationNotFound + ); + }); + } + + #[test] + fn cannot_be_called_before_settlement_started() { + let percentage = 100u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, false); + + let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); + let bidder = first_bid.bidder; + inst.execute(|| { + assert_noop!( + crate::Pallet::::settle_bid( + RuntimeOrigin::signed(bidder), + project_id, + bidder, + first_bid.id + ), + Error::::SettlementNotStarted + ); + }); + } + } +} + +#[cfg(test)] +mod settle_contribution_extrinsic { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + + #[test] + fn contribution_on_successful_project() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let ed = inst.get_ed(); + let project_metadata = default_project_metadata(ISSUER_1); + + let contribution = + ContributionParams::::new(BUYER_1, 1000 * CT_UNIT, 2, AcceptedFundingAsset::USDT); + + let project_id = inst.create_finished_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + default_bids(), + vec![contribution.clone()], + vec![], + ); + + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); + + // First contribution assertions + let stored_contribution = + inst.execute(|| Contributions::::get((project_id, BUYER_1, 0)).unwrap()); + let hold_reason: RuntimeHoldReason = HoldReason::Participation(project_id).into(); + + inst.assert_plmc_free_balance(BUYER_1, ed); + inst.assert_plmc_held_balance(BUYER_1, stored_contribution.plmc_bond, hold_reason); + inst.assert_ct_balance(project_id, BUYER_1, Zero::zero()); + inst.assert_funding_asset_free_balance(BUYER_1, AcceptedFundingAsset::USDT.id(), Zero::zero()); + inst.assert_funding_asset_free_balance( + project_metadata.funding_destination_account, + AcceptedFundingAsset::USDT.id(), + Zero::zero(), + ); + + inst.execute(|| { + assert_ok!(PolimecFunding::settle_contribution(RuntimeOrigin::signed(BUYER_1), project_id, BUYER_1, 0)); + }); + + inst.assert_plmc_free_balance(BUYER_1, ed); + inst.assert_plmc_held_balance(BUYER_1, stored_contribution.plmc_bond, hold_reason); + inst.assert_ct_balance(project_id, BUYER_1, stored_contribution.ct_amount); + inst.assert_funding_asset_free_balance(BUYER_1, AcceptedFundingAsset::USDT.id(), Zero::zero()); + inst.assert_funding_asset_free_balance( + project_metadata.funding_destination_account, + AcceptedFundingAsset::USDT.id(), + stored_contribution.funding_asset_amount, + ); + inst.assert_migration( + project_id, + BUYER_1, + stored_contribution.ct_amount, + 0, + ParticipationType::Contribution, + true, + ); + + let vesting_time = contribution.multiplier.calculate_vesting_duration::(); + let current_block = inst.current_block(); + inst.jump_to_block(current_block + vesting_time + 1); + inst.execute(|| LinearRelease::vest(RuntimeOrigin::signed(BUYER_1), hold_reason).expect("Vesting failed")); + inst.assert_plmc_free_balance(BUYER_1, ed + stored_contribution.plmc_bond); + inst.assert_plmc_held_balance(BUYER_1, Zero::zero(), hold_reason); + } + + #[test] + fn contribution_on_failed_project() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + let issuer = ISSUER_1; + let evaluations = + inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); + let bids = inst.generate_bids_from_total_ct_percent( + project_metadata.clone(), + 10, + default_weights(), + default_bidders(), + default_multipliers(), + ); + let mut community_contributions = inst.generate_contributions_from_total_ct_percent( + project_metadata.clone(), + 10, + default_weights(), + default_community_contributors(), + default_community_contributor_multipliers(), + ); + + let contribution_mul_1 = + ContributionParams::::new(BUYER_6, 1000 * CT_UNIT, 1, AcceptedFundingAsset::USDT); + let contribution_mul_2 = + ContributionParams::::new(BUYER_7, 1000 * CT_UNIT, 2, AcceptedFundingAsset::USDT); + + community_contributions.push(contribution_mul_1); + + let project_id = inst.create_remainder_contributing_project( + project_metadata.clone(), + issuer, + None, + evaluations, + bids, + community_contributions, + ); + let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); + + let plmc_required = inst.calculate_contributed_plmc_spent(vec![contribution_mul_2.clone()], wap, false); + let plmc_ed = plmc_required.accounts().existential_deposits(); + inst.mint_plmc_to(plmc_required.clone()); + inst.mint_plmc_to(plmc_ed); + + let usdt_required = inst.calculate_contributed_funding_asset_spent(vec![contribution_mul_2.clone()], wap); + inst.mint_funding_asset_to(usdt_required.clone()); + + inst.execute(|| { + assert_ok!(PolimecFunding::contribute( + RuntimeOrigin::signed(BUYER_7), + get_mock_jwt_with_cid( + BUYER_7, + InvestorType::Professional, + generate_did_from_account(BUYER_7), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ), + project_id, + contribution_mul_2.amount, + contribution_mul_2.multiplier, + contribution_mul_2.asset + )); + }); + + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); + + // First contribution assertions + let stored_contribution = + inst.execute(|| Contributions::::get((project_id, BUYER_6, 5)).unwrap()); + let plmc_free_amount = inst.get_free_plmc_balance_for(BUYER_6); + let plmc_held_amount = + inst.get_reserved_plmc_balance_for(BUYER_6, HoldReason::Participation(project_id).into()); + let ct_amount = inst.get_ct_asset_balance_for(project_id, BUYER_6); + let issuer_usdt_balance = + inst.get_free_funding_asset_balance_for(stored_contribution.funding_asset.id(), issuer); + let unvested_amount = inst.execute(|| { + ::Vesting::total_scheduled_amount( + &BUYER_6, + HoldReason::Participation(project_id).into(), + ) + }); + + assert_eq!(plmc_free_amount, inst.get_ed()); + assert_eq!(plmc_held_amount, stored_contribution.plmc_bond); + assert_eq!(ct_amount, 0u128); + assert_eq!(issuer_usdt_balance, 0u128); + assert!(unvested_amount.is_none()); + + inst.execute(|| { + assert_ok!(PolimecFunding::settle_contribution(RuntimeOrigin::signed(BUYER_6), project_id, BUYER_6, 5)); + }); + + assert!(inst.execute(|| Contributions::::get((project_id, BUYER_6, 6)).is_none())); + let plmc_free_amount = inst.get_free_plmc_balance_for(BUYER_6); + let plmc_held_amount = + inst.get_reserved_plmc_balance_for(BUYER_6, HoldReason::Participation(project_id).into()); + let ct_amount = inst.get_ct_asset_balance_for(project_id, BUYER_6); + let issuer_usdt_balance = + inst.get_free_funding_asset_balance_for(stored_contribution.funding_asset.id(), issuer); + let unvested_amount = inst.execute(|| { + ::Vesting::total_scheduled_amount( + &BUYER_6, + HoldReason::Participation(project_id).into(), + ) + }); + + assert_eq!(plmc_free_amount, inst.get_ed() + stored_contribution.plmc_bond); + assert_eq!(plmc_held_amount, 0u128); + assert_eq!(ct_amount, Zero::zero()); + assert_eq!(issuer_usdt_balance, Zero::zero()); + assert!(unvested_amount.is_none()); + inst.assert_migration( + project_id, + BUYER_6, + stored_contribution.ct_amount, + 5, + ParticipationType::Contribution, + false, + ); + + // Second contribution assertions + let stored_contribution = + inst.execute(|| Contributions::::get((project_id, BUYER_7, 6)).unwrap()); + let plmc_free_amount = inst.get_free_plmc_balance_for(BUYER_7); + let plmc_held_amount = + inst.get_reserved_plmc_balance_for(BUYER_7, HoldReason::Participation(project_id).into()); + let ct_amount = inst.get_ct_asset_balance_for(project_id, BUYER_7); + let issuer_usdt_balance_2 = + inst.get_free_funding_asset_balance_for(stored_contribution.funding_asset.id(), issuer); + let unvested_amount = inst.execute(|| { + ::Vesting::total_scheduled_amount( + &BUYER_7, + HoldReason::Participation(project_id).into(), + ) + }); + assert_eq!(plmc_free_amount, inst.get_ed()); + assert_eq!(plmc_held_amount, stored_contribution.plmc_bond); + assert_eq!(ct_amount, 0u128); + assert_eq!(issuer_usdt_balance_2, issuer_usdt_balance); + assert!(unvested_amount.is_none()); + + inst.execute(|| { + assert_ok!(PolimecFunding::settle_contribution(RuntimeOrigin::signed(BUYER_7), project_id, BUYER_7, 6)); + }); + + assert!(inst.execute(|| Contributions::::get((project_id, BUYER_7, 7)).is_none())); + let plmc_free_amount = inst.get_free_plmc_balance_for(BUYER_7); + let plmc_held_amount = + inst.get_reserved_plmc_balance_for(BUYER_7, HoldReason::Participation(project_id).into()); + let ct_amount = inst.get_ct_asset_balance_for(project_id, BUYER_7); + let issuer_usdt_balance_2 = + inst.get_free_funding_asset_balance_for(stored_contribution.funding_asset.id(), issuer); + let unvested_amount = inst.execute(|| { + ::Vesting::total_scheduled_amount( + &BUYER_7, + HoldReason::Participation(project_id).into(), + ) + }); + + assert_eq!(plmc_free_amount, inst.get_ed() + stored_contribution.plmc_bond); + assert_eq!(plmc_held_amount, 0u128); + assert_eq!(ct_amount, Zero::zero()); + assert_eq!(issuer_usdt_balance_2, Zero::zero()); + assert!(unvested_amount.is_none()); + + inst.assert_migration( + project_id, + BUYER_7, + stored_contribution.ct_amount, + 6, + ParticipationType::Contribution, + false, + ); + } + } + + #[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, true); + + let first_contribution = inst.get_contributions(project_id).into_iter().next().unwrap(); + inst.execute(|| { + let contributor = first_contribution.contributor; + assert_ok!(crate::Pallet::::settle_contribution( + RuntimeOrigin::signed(contributor), + project_id, + contributor, + first_contribution.id + )); + assert_noop!( + crate::Pallet::::settle_contribution( + RuntimeOrigin::signed(contributor), + project_id, + contributor, + first_contribution.id + ), + Error::::ParticipationNotFound + ); + }); + } + + #[test] + fn cannot_be_called_before_settlement_started() { + let percentage = 100u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, false); + let first_contribution = inst.get_contributions(project_id).into_iter().next().unwrap(); + let contributor = first_contribution.contributor; + inst.execute(|| { + assert_noop!( + crate::Pallet::::settle_contribution( + RuntimeOrigin::signed(contributor), + project_id, + contributor, + first_contribution.id + ), + Error::::SettlementNotStarted + ); + }); + } + } +} diff --git a/pallets/funding/src/tests/8_ct_migration.rs b/pallets/funding/src/tests/7_ct_migration.rs similarity index 89% rename from pallets/funding/src/tests/8_ct_migration.rs rename to pallets/funding/src/tests/7_ct_migration.rs index bf9349de8..35cc68a58 100644 --- a/pallets/funding/src/tests/8_ct_migration.rs +++ b/pallets/funding/src/tests/7_ct_migration.rs @@ -15,11 +15,11 @@ mod pallet_migration { None, default_evaluations(), default_bids(), - default_community_buys(), - default_remainder_buys(), + default_community_contributions(), + default_remainder_contributions(), ); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - inst.settle_project(project_id).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); + inst.settle_project(project_id); inst.execute(|| { assert_err!( @@ -68,11 +68,11 @@ mod pallet_migration { None, default_evaluations(), default_bids(), - default_community_buys(), - default_remainder_buys(), + default_community_contributions(), + default_remainder_contributions(), ); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - inst.settle_project(project_id).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); + inst.settle_project(project_id); inst.execute(|| { assert_ok!(crate::Pallet::::do_start_pallet_migration( &ISSUER_1, @@ -198,11 +198,11 @@ mod offchain_migration { None, default_evaluations(), default_bids(), - default_community_buys(), - default_remainder_buys(), + default_community_contributions(), + default_remainder_contributions(), ); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - inst.settle_project(project_id).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); + inst.settle_project(project_id); let project_id = inst.create_finished_project( default_project_metadata(ISSUER_1), @@ -210,11 +210,11 @@ mod offchain_migration { None, default_evaluations(), default_bids(), - default_community_buys(), - default_remainder_buys(), + default_community_contributions(), + default_remainder_contributions(), ); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - inst.settle_project(project_id).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); + inst.settle_project(project_id); inst.execute(|| { assert_err!( @@ -237,11 +237,11 @@ mod offchain_migration { None, default_evaluations(), default_bids(), - default_community_buys(), - default_remainder_buys(), + default_community_contributions(), + default_remainder_contributions(), ); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - inst.settle_project(project_id).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); + inst.settle_project(project_id); inst.execute(|| { assert_ok!(crate::Pallet::::do_start_offchain_migration(project_id, ISSUER_1,)); }); @@ -284,6 +284,4 @@ mod offchain_migration { assert_ok!(crate::Pallet::::do_mark_project_ct_migration_as_finished(project_id)); }); } - - // Can't start if project is not settled } diff --git a/pallets/funding/src/tests/7_settlement.rs b/pallets/funding/src/tests/7_settlement.rs deleted file mode 100644 index f35835ea5..000000000 --- a/pallets/funding/src/tests/7_settlement.rs +++ /dev/null @@ -1,1539 +0,0 @@ -use super::*; - -#[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, true); - 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, true); - 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); - } - } -} - -#[cfg(test)] -mod settle_successful_evaluation_extrinsic { - use super::*; - - #[cfg(test)] - mod success { - use super::*; - - #[test] - fn evaluation_unchanged() { - let percentage = 89u64; - - let (mut inst, project_id) = - create_project_with_funding_percentage(percentage, Some(FundingOutcomeDecision::AcceptFunding), true); - - 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_successful_evaluation( - RuntimeOrigin::signed(evaluator), - project_id, - evaluator, - first_evaluation.id - ))); - - 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), true); - - 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 - ))); - - 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, - None, - 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); - - // The rewards are calculated as follows: - // Data: - // - Funding USD reached: 10_000_000 USD - // - Total CTs sold: 1_000_000 CT - // - USD target reached percent: 100% - - // Step 1) Calculate the total USD fee: - // USD fee 1 = 0.1 * 1_000_000 = 100_000 USD - // USD fee 2 = 0.08 * 4_000_000 = 320_000 USD - // USD fee 3 = 0.06 * 5_000_000 = 300_000 USD - // Total USD fee = 100_000 + 320_000 + 300_000 = 720_000 USD - - // Step 2) Calculate CT fee as follows: - // Percent fee = Total USD fee / Funding USD reached = 720_000 / 10_000_000 = 0.072 - // CT fee = Percent fee * Total CTs sold = 0.072 * 1_000_000 = 72_000 CT - - // Step 3) Calculate Early and Normal evaluator reward pots: - // Total evaluators reward pot = CT fee * 0.3 * USD target reached percent = 72_000 * 0.3 * 1 = 21_600 CT - // Early evaluators reward pot = Total evaluators reward pot * 0.2 = 21_600 * 0.2 = 4_320 CT - // Normal evaluators reward pot = Total evaluators reward pot * 0.8 = 21_600 * 0.8 = 17_280 CT - - // Step 4) Calculate the early and normal weights of each evaluation: - // Evaluation 1 = 500_000 USD - // Evaluation 2 = 250_000 USD - // Evaluation 3 = 320_000 USD - - // Early amount 1 = 500_000 USD - // Early amount 2 = 250_000 USD - // Early amount 3 = 250_000 USD - - // Total Normal amount = Evaluation 1 + Evaluation 2 + Evaluation 3 = 500_000 + 250_000 + 320_000 = 1_070_000 USD - // Total Early amount = 10% of USD target = 1_000_000 USD - - // Early weight 1 = Early amount 1 / Total Early amount = 500_000 / 1_000_000 = 0.5 - // Early weight 2 = Early amount 2 / Total Early amount = 250_000 / 1_000_000 = 0.25 - // Early weight 3 = Early amount 3 / Total Early amount = 250_000 / 1_000_000 = 0.25 - - // Normal weight 1 = Evaluation 1 / Total Normal amount = 500_000 / 1_070_000 = 0.467289719626168 - // Normal weight 2 = Evaluation 2 / Total Normal amount = 250_000 / 1_070_000 = 0.233644859813084 - // Normal weight 3 = Evaluation 3 / Total Normal amount = 320_000 / 1_070_000 = 0.299065420560748 - - // Step 5) Calculate the rewards for each evaluation: - // Evaluation 1 Early reward = Early weight 1 * Early evaluators reward pot = 0.5 * 4_320 = 2_160 CT - // Evaluation 2 Early reward = Early weight 2 * Early evaluators reward pot = 0.25 * 4_320 = 1_080 CT - // Evaluation 3 Early reward = Early weight 3 * Early evaluators reward pot = 0.25 * 4_320 = 1_080 CT - - // Evaluation 1 Normal reward = Normal weight 1 * Normal evaluators reward pot = 0.467289719626168 * 17_280 = 8'074.766355140186916 CT - // Evaluation 2 Normal reward = Normal weight 2 * Normal evaluators reward pot = 0.233644859813084 * 17_280 = 4'037.383177570093458 CT - // Evaluation 3 Normal reward = Normal weight 3 * Normal evaluators reward pot = 0.299065420560748 * 17_280 = 5'167.850467289719626 CT - - // Evaluation 1 Total reward = Evaluation 1 Early reward + Evaluation 1 Normal reward = 2_160 + 8_066 = 10'234.766355140186916 CT - // Evaluation 2 Total reward = Evaluation 2 Early reward + Evaluation 2 Normal reward = 1_080 + 4_033 = 5'117.383177570093458 CT - // Evaluation 3 Total reward = Evaluation 3 Early reward + Evaluation 3 Normal reward = 1_080 + 5_201 = 6'247.850467289719626 CT - - 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())); - - let evals = vec![(EVALUATOR_1, EVAL_1_REWARD), (EVALUATOR_2, EVAL_2_REWARD), (EVALUATOR_3, EVAL_3_REWARD)]; - - for (evaluator, expected_reward) in evals { - let evaluation_locked_plmc = - inst.get_reserved_plmc_balance_for(evaluator, HoldReason::Evaluation(project_id).into()); - let free_plmc = inst.get_free_plmc_balance_for(evaluator); - assert_ok!(inst.execute(|| PolimecFunding::settle_successful_evaluation( - RuntimeOrigin::signed(evaluator), - project_id, - evaluator, - evaluator - 21 // The First evaluation index is 0, the first evaluator account is 21 - ))); - let ct_rewarded = inst.get_ct_asset_balance_for(project_id, evaluator); - assert_close_enough!(ct_rewarded, expected_reward, Perquintill::from_float(0.9999)); - assert_eq!(inst.get_reserved_plmc_balance_for(evaluator, HoldReason::Evaluation(project_id).into()), 0); - assert_eq!(inst.get_free_plmc_balance_for(evaluator), free_plmc + evaluation_locked_plmc); - inst.assert_migration(project_id, evaluator, expected_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, true); - - 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 - ); - }); - } - - #[test] - fn cannot_be_called_on_wrong_outcome() { - let percentage = 10u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, true); - - let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); - let evaluator = first_evaluation.evaluator; - - inst.execute(|| { - assert_noop!( - PolimecFunding::settle_successful_evaluation( - RuntimeOrigin::signed(evaluator), - project_id, - evaluator, - first_evaluation.id - ), - Error::::FundingSuccessSettlementNotStarted - ); - }); - } - - #[test] - fn cannot_be_called_before_settlement_started() { - let percentage = 100u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, false); - - let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); - let evaluator = first_evaluation.evaluator; - - inst.execute(|| { - assert_noop!( - PolimecFunding::settle_successful_evaluation( - RuntimeOrigin::signed(evaluator), - project_id, - evaluator, - first_evaluation.id - ), - Error::::FundingSuccessSettlementNotStarted - ); - }); - } - } -} - -#[cfg(test)] -mod settle_successful_bid_extrinsic { - use super::*; - - #[cfg(test)] - mod success { - use super::*; - - #[test] - fn bid_is_correctly_settled() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let issuer = ISSUER_1; - let evaluations = - inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); - let bid_1 = BidParams::new(BIDDER_1, 1000 * CT_UNIT, 1, AcceptedFundingAsset::USDT); - let bid_2 = BidParams::new(BIDDER_2, 1000 * CT_UNIT, 2, AcceptedFundingAsset::USDT); - - let community_contributions = inst.generate_contributions_from_total_ct_percent( - project_metadata.clone(), - 90, - default_weights(), - default_community_contributors(), - default_community_contributor_multipliers(), - ); - - let project_id = inst.create_finished_project( - project_metadata.clone(), - issuer, - None, - evaluations, - vec![bid_1, bid_2], - community_contributions, - vec![], - ); - - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); - - // First bid assertions - let stored_bid = inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); - let plmc_free_amount = inst.get_free_plmc_balance_for(BIDDER_1); - let plmc_held_amount = - inst.get_reserved_plmc_balance_for(BIDDER_1, HoldReason::Participation(project_id).into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BIDDER_1); - let issuer_usdt_balance = - inst.get_free_foreign_asset_balance_for(stored_bid.funding_asset.to_assethub_id(), issuer); - let unvested_amount = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &BIDDER_1, - HoldReason::Participation(project_id).into(), - ) - }); - - assert_eq!(plmc_free_amount, inst.get_ed()); - assert_eq!(plmc_held_amount, stored_bid.plmc_bond); - assert_eq!(ct_amount, 0u128); - assert_eq!(issuer_usdt_balance, 0u128); - assert!(unvested_amount.is_none()); - - inst.execute(|| { - assert_ok!(PolimecFunding::settle_successful_bid( - RuntimeOrigin::signed(BIDDER_1), - project_id, - BIDDER_1, - 0 - )); - }); - - assert!(inst.execute(|| Contributions::::get((project_id, BIDDER_1, 0)).is_none())); - let plmc_free_amount = inst.get_free_plmc_balance_for(BIDDER_1); - let plmc_held_amount = - inst.get_reserved_plmc_balance_for(BIDDER_1, HoldReason::Participation(project_id).into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BIDDER_1); - let issuer_usdt_balance = - inst.get_free_foreign_asset_balance_for(stored_bid.funding_asset.to_assethub_id(), issuer); - let unvested_amount = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &BIDDER_1, - HoldReason::Participation(project_id).into(), - ) - }); - - assert_eq!(plmc_free_amount, inst.get_ed() + stored_bid.plmc_bond); - assert_eq!(plmc_held_amount, 0u128); - assert_eq!(ct_amount, stored_bid.final_ct_amount); - assert_eq!( - issuer_usdt_balance, - stored_bid.final_ct_usd_price.saturating_mul_int(stored_bid.final_ct_amount) - ); - assert!(unvested_amount.is_none()); - inst.assert_migration(project_id, BIDDER_1, stored_bid.final_ct_amount, 0, ParticipationType::Bid, true); - - // Second bid assertions - let stored_bid = inst.execute(|| Bids::::get((project_id, BIDDER_2, 1)).unwrap()); - let plmc_free_amount = inst.get_free_plmc_balance_for(BIDDER_2); - let plmc_held_amount = - inst.get_reserved_plmc_balance_for(BIDDER_2, HoldReason::Participation(project_id).into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BIDDER_2); - let issuer_usdt_balance_2 = - inst.get_free_foreign_asset_balance_for(stored_bid.funding_asset.to_assethub_id(), issuer); - let unvested_amount = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &BIDDER_2, - HoldReason::Participation(project_id).into(), - ) - }); - assert_eq!(plmc_free_amount, inst.get_ed()); - assert_eq!(plmc_held_amount, stored_bid.plmc_bond); - assert_eq!(ct_amount, 0u128); - assert_eq!(issuer_usdt_balance_2, issuer_usdt_balance); - assert!(unvested_amount.is_none()); - - inst.execute(|| { - assert_ok!(PolimecFunding::settle_successful_bid( - RuntimeOrigin::signed(BIDDER_2), - project_id, - BIDDER_2, - 1 - )); - }); - - assert!(inst.execute(|| Contributions::::get((project_id, BIDDER_2, 1)).is_none())); - let plmc_free_amount = inst.get_free_plmc_balance_for(BIDDER_2); - let plmc_held_amount = - inst.get_reserved_plmc_balance_for(BIDDER_2, HoldReason::Participation(project_id).into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BIDDER_2); - let issuer_usdt_balance_2 = - inst.get_free_foreign_asset_balance_for(stored_bid.funding_asset.to_assethub_id(), issuer); - let unvested_amount = inst - .execute(|| { - ::Vesting::total_scheduled_amount( - &BIDDER_2, - HoldReason::Participation(project_id).into(), - ) - }) - .unwrap(); - assert_eq!(plmc_free_amount, inst.get_ed()); - assert_eq!(plmc_held_amount, stored_bid.plmc_bond); - assert_eq!(ct_amount, stored_bid.final_ct_amount); - assert_eq!( - issuer_usdt_balance_2, - issuer_usdt_balance + stored_bid.final_ct_usd_price.saturating_mul_int(stored_bid.final_ct_amount) - ); - assert_eq!(unvested_amount, stored_bid.plmc_bond); - - let vesting_time = stored_bid.multiplier.calculate_vesting_duration::(); - let now = inst.current_block(); - inst.jump_to_block(vesting_time + now + 1u64); - inst.execute(|| { - assert_ok!(::Vesting::vest( - RuntimeOrigin::signed(BIDDER_2), - HoldReason::Participation(project_id).into() - )); - }); - let unvested_amount = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &BIDDER_2, - HoldReason::Participation(project_id).into(), - ) - }); - assert!(unvested_amount.is_none()); - let plmc_free_amount = inst.get_free_plmc_balance_for(BIDDER_2); - let plmc_held_amount = - inst.get_reserved_plmc_balance_for(BIDDER_2, HoldReason::Participation(project_id).into()); - assert_eq!(plmc_free_amount, inst.get_ed() + stored_bid.plmc_bond); - assert_eq!(plmc_held_amount, 0u128); - inst.assert_migration(project_id, BIDDER_2, stored_bid.final_ct_amount, 1, ParticipationType::Bid, true); - } - - #[test] - fn rejected_bids_dont_get_vest_schedule() { - // * Test Setup * - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - - // Project variables - 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 community_contributions = default_community_buys(); - - // Add rejected and accepted bids to test our vesting schedule assertions - 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 project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); - - // Mint the necessary bidding balances - let bidders_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &bids, - project_metadata.clone(), - None, - true, - ); - 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(); - - // Mint the necessary community contribution balances - 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, false); - 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(); - - // Finish and Settle project - inst.start_remainder_or_end_funding(project_id).unwrap(); - inst.finish_funding(project_id, None).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, false); - let plmc_locked_for_rejected_bid = - inst.calculate_auction_plmc_charged_with_given_price(&rejected_bid, final_price, false); - - let UserToPLMCBalance { account: accepted_user, plmc_amount: accepted_plmc_amount } = - plmc_locked_for_accepted_bid[0]; - let UserToPLMCBalance { account: rejected_user, .. } = plmc_locked_for_rejected_bid[0]; - - // * Assertions * - 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)); - 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, true); - - 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 - ); - }); - } - - #[test] - fn cannot_be_called_on_wrong_outcome() { - let percentage = 10u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, true); - - let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); - let bidder = first_bid.bidder; - inst.execute(|| { - assert_noop!( - crate::Pallet::::settle_successful_bid( - RuntimeOrigin::signed(bidder), - project_id, - bidder, - first_bid.id - ), - Error::::FundingSuccessSettlementNotStarted - ); - }); - } - - #[test] - fn cannot_be_called_before_settlement_started() { - let percentage = 100u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, false); - - let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); - let bidder = first_bid.bidder; - inst.execute(|| { - assert_noop!( - crate::Pallet::::settle_successful_bid( - RuntimeOrigin::signed(bidder), - project_id, - bidder, - first_bid.id - ), - Error::::FundingSuccessSettlementNotStarted - ); - }); - } - } -} - -#[cfg(test)] -mod settle_successful_contribution_extrinsic { - use super::*; - - #[cfg(test)] - mod success { - use super::*; - - #[test] - fn contribution_is_correctly_settled() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let issuer = ISSUER_1; - let evaluations = - inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); - let bids = inst.generate_bids_from_total_ct_percent( - project_metadata.clone(), - 50, - default_weights(), - default_bidders(), - default_multipliers(), - ); - let mut community_contributions = inst.generate_contributions_from_total_ct_percent( - project_metadata.clone(), - 40, - default_weights(), - default_community_contributors(), - default_community_contributor_multipliers(), - ); - - let contribution_mul_1 = - ContributionParams::::new(BUYER_6, 1000 * CT_UNIT, 1, AcceptedFundingAsset::USDT); - let contribution_mul_2 = - ContributionParams::::new(BUYER_7, 1000 * CT_UNIT, 2, AcceptedFundingAsset::USDT); - - community_contributions.push(contribution_mul_1); - - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - issuer, - None, - evaluations, - bids, - community_contributions, - ); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - let plmc_required = inst.calculate_contributed_plmc_spent(vec![contribution_mul_2.clone()], wap, false); - let plmc_ed = plmc_required.accounts().existential_deposits(); - inst.mint_plmc_to(plmc_required.clone()); - inst.mint_plmc_to(plmc_ed); - - let usdt_required = inst.calculate_contributed_funding_asset_spent(vec![contribution_mul_2.clone()], wap); - inst.mint_foreign_asset_to(usdt_required.clone()); - - inst.execute(|| { - assert_ok!(PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_7), - get_mock_jwt_with_cid( - BUYER_7, - InvestorType::Professional, - generate_did_from_account(BUYER_7), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ), - project_id, - contribution_mul_2.amount, - contribution_mul_2.multiplier, - contribution_mul_2.asset - )); - }); - - inst.finish_funding(project_id, None).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); - - // First contribution assertions - let stored_contribution = - inst.execute(|| Contributions::::get((project_id, BUYER_6, 5)).unwrap()); - let plmc_free_amount = inst.get_free_plmc_balance_for(BUYER_6); - let plmc_held_amount = - inst.get_reserved_plmc_balance_for(BUYER_6, HoldReason::Participation(project_id).into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BUYER_6); - let issuer_usdt_balance = - inst.get_free_foreign_asset_balance_for(stored_contribution.funding_asset.to_assethub_id(), issuer); - let unvested_amount = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &BUYER_6, - HoldReason::Participation(project_id).into(), - ) - }); - - assert_eq!(plmc_free_amount, inst.get_ed()); - assert_eq!(plmc_held_amount, stored_contribution.plmc_bond); - assert_eq!(ct_amount, 0u128); - assert_eq!(issuer_usdt_balance, 0u128); - assert!(unvested_amount.is_none()); - - inst.execute(|| { - assert_ok!(PolimecFunding::settle_successful_contribution( - RuntimeOrigin::signed(BUYER_6), - project_id, - BUYER_6, - 5 - )); - }); - - assert!(inst.execute(|| Contributions::::get((project_id, BUYER_6, 6)).is_none())); - let plmc_free_amount = inst.get_free_plmc_balance_for(BUYER_6); - let plmc_held_amount = - inst.get_reserved_plmc_balance_for(BUYER_6, HoldReason::Participation(project_id).into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BUYER_6); - let issuer_usdt_balance = - inst.get_free_foreign_asset_balance_for(stored_contribution.funding_asset.to_assethub_id(), issuer); - let unvested_amount = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &BUYER_6, - HoldReason::Participation(project_id).into(), - ) - }); - - assert_eq!(plmc_free_amount, inst.get_ed() + stored_contribution.plmc_bond); - assert_eq!(plmc_held_amount, 0u128); - assert_eq!(ct_amount, stored_contribution.ct_amount); - assert_eq!(issuer_usdt_balance, stored_contribution.usd_contribution_amount); - assert!(unvested_amount.is_none()); - inst.assert_migration( - project_id, - BUYER_6, - stored_contribution.ct_amount, - 5, - ParticipationType::Contribution, - true, - ); - - // Second contribution assertions - let stored_contribution = - inst.execute(|| Contributions::::get((project_id, BUYER_7, 6)).unwrap()); - let plmc_free_amount = inst.get_free_plmc_balance_for(BUYER_7); - let plmc_held_amount = - inst.get_reserved_plmc_balance_for(BUYER_7, HoldReason::Participation(project_id).into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BUYER_7); - let issuer_usdt_balance_2 = - inst.get_free_foreign_asset_balance_for(stored_contribution.funding_asset.to_assethub_id(), issuer); - let unvested_amount = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &BUYER_7, - HoldReason::Participation(project_id).into(), - ) - }); - assert_eq!(plmc_free_amount, inst.get_ed()); - assert_eq!(plmc_held_amount, stored_contribution.plmc_bond); - assert_eq!(ct_amount, 0u128); - assert_eq!(issuer_usdt_balance_2, issuer_usdt_balance); - assert!(unvested_amount.is_none()); - - inst.execute(|| { - assert_ok!(PolimecFunding::settle_successful_contribution( - RuntimeOrigin::signed(BUYER_7), - project_id, - BUYER_7, - 6 - )); - }); - - assert!(inst.execute(|| Contributions::::get((project_id, BUYER_7, 7)).is_none())); - let plmc_free_amount = inst.get_free_plmc_balance_for(BUYER_7); - let plmc_held_amount = - inst.get_reserved_plmc_balance_for(BUYER_7, HoldReason::Participation(project_id).into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BUYER_7); - let issuer_usdt_balance_2 = - inst.get_free_foreign_asset_balance_for(stored_contribution.funding_asset.to_assethub_id(), issuer); - let unvested_amount = inst - .execute(|| { - ::Vesting::total_scheduled_amount( - &BUYER_7, - HoldReason::Participation(project_id).into(), - ) - }) - .unwrap(); - assert_eq!(plmc_free_amount, inst.get_ed()); - assert_eq!(plmc_held_amount, stored_contribution.plmc_bond); - assert_eq!(ct_amount, stored_contribution.ct_amount); - assert_eq!(issuer_usdt_balance_2, issuer_usdt_balance + stored_contribution.usd_contribution_amount); - assert_eq!(unvested_amount, stored_contribution.plmc_bond); - - let vesting_time = stored_contribution.multiplier.calculate_vesting_duration::(); - let now = inst.current_block(); - inst.jump_to_block(vesting_time + now + 1u64); - inst.execute(|| { - assert_ok!(::Vesting::vest( - RuntimeOrigin::signed(BUYER_7), - HoldReason::Participation(project_id).into() - )); - }); - let unvested_amount = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &BUYER_7, - HoldReason::Participation(project_id).into(), - ) - }); - assert!(unvested_amount.is_none()); - let plmc_free_amount = inst.get_free_plmc_balance_for(BUYER_7); - let plmc_held_amount = - inst.get_reserved_plmc_balance_for(BUYER_7, HoldReason::Participation(project_id).into()); - assert_eq!(plmc_free_amount, inst.get_ed() + stored_contribution.plmc_bond); - assert_eq!(plmc_held_amount, 0u128); - inst.assert_migration( - project_id, - BUYER_7, - stored_contribution.ct_amount, - 6, - ParticipationType::Contribution, - 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, true); - - 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_be_called_on_wrong_outcome() { - let percentage = 10u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, true); - - let first_contribution = inst.get_contributions(project_id).into_iter().next().unwrap(); - let contributor = first_contribution.contributor; - inst.execute(|| { - assert_noop!( - crate::Pallet::::settle_successful_contribution( - RuntimeOrigin::signed(contributor), - project_id, - contributor, - first_contribution.id - ), - Error::::FundingSuccessSettlementNotStarted - ); - }); - } - - #[test] - fn cannot_be_called_before_settlement_started() { - let percentage = 100u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, false); - let first_contribution = inst.get_contributions(project_id).into_iter().next().unwrap(); - let contributor = first_contribution.contributor; - inst.execute(|| { - assert_noop!( - crate::Pallet::::settle_successful_contribution( - RuntimeOrigin::signed(contributor), - project_id, - contributor, - first_contribution.id - ), - Error::::FundingSuccessSettlementNotStarted - ); - }); - } - } -} - -#[cfg(test)] -mod settle_failed_evaluation_extrinsic { - use super::*; - - #[cfg(test)] - mod success { - use super::*; - - #[test] - fn evaluation_unchanged() { - let percentage = 89u64; - - let (mut inst, project_id) = - create_project_with_funding_percentage(percentage, Some(FundingOutcomeDecision::RejectFunding), true); - - 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 - ))); - - 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::RejectFunding), true); - - 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, - evaluator, - first_evaluation.id - ))); - - 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 - ); - } - } - - #[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, true); - - 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 - ); - }); - } - - #[test] - fn cannot_be_called_on_wrong_outcome() { - let percentage = 100u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, true); - - let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); - let evaluator = first_evaluation.evaluator; - - inst.execute(|| { - assert_noop!( - PolimecFunding::settle_failed_evaluation( - RuntimeOrigin::signed(evaluator), - project_id, - evaluator, - first_evaluation.id - ), - Error::::FundingFailedSettlementNotStarted - ); - }); - } - - #[test] - fn cannot_be_called_before_settlement_started() { - let percentage = 10u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, false); - - let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); - let evaluator = first_evaluation.evaluator; - - inst.execute(|| { - assert_noop!( - PolimecFunding::settle_failed_evaluation( - RuntimeOrigin::signed(evaluator), - project_id, - evaluator, - first_evaluation.id - ), - Error::::FundingFailedSettlementNotStarted - ); - }); - } - } -} - -#[cfg(test)] -mod settle_failed_bid_extrinsic { - use super::*; - - #[cfg(test)] - mod success { - use super::*; - - #[test] - fn bid_is_correctly_settled() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let issuer = ISSUER_1; - let evaluations = - inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); - let bid_1 = BidParams::new(BIDDER_1, 1000 * CT_UNIT, 1, AcceptedFundingAsset::USDT); - let bid_2 = BidParams::new(BIDDER_2, 1000 * CT_UNIT, 2, AcceptedFundingAsset::USDT); - - let community_contributions = inst.generate_contributions_from_total_ct_percent( - project_metadata.clone(), - 20, - default_weights(), - default_community_contributors(), - default_community_contributor_multipliers(), - ); - - let project_id = inst.create_finished_project( - project_metadata.clone(), - issuer, - None, - evaluations, - vec![bid_1, bid_2], - community_contributions, - vec![], - ); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); - - // First bid assertions - let stored_bid = inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); - let plmc_free_amount = inst.get_free_plmc_balance_for(BIDDER_1); - let plmc_held_amount = - inst.get_reserved_plmc_balance_for(BIDDER_1, HoldReason::Participation(project_id).into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BIDDER_1); - let issuer_usdt_balance = - inst.get_free_foreign_asset_balance_for(stored_bid.funding_asset.to_assethub_id(), issuer); - let unvested_amount = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &BIDDER_1, - HoldReason::Participation(project_id).into(), - ) - }); - - assert_eq!(plmc_free_amount, inst.get_ed()); - assert_eq!(plmc_held_amount, stored_bid.plmc_bond); - assert_eq!(ct_amount, 0u128); - assert_eq!(issuer_usdt_balance, 0u128); - assert!(unvested_amount.is_none()); - - inst.execute(|| { - assert_ok!(PolimecFunding::settle_failed_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 0)); - }); - - assert!(inst.execute(|| Contributions::::get((project_id, BIDDER_1, 0)).is_none())); - let plmc_free_amount = inst.get_free_plmc_balance_for(BIDDER_1); - let plmc_held_amount = - inst.get_reserved_plmc_balance_for(BIDDER_1, HoldReason::Participation(project_id).into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BIDDER_1); - let issuer_usdt_balance = - inst.get_free_foreign_asset_balance_for(stored_bid.funding_asset.to_assethub_id(), issuer); - let unvested_amount = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &BIDDER_1, - HoldReason::Participation(project_id).into(), - ) - }); - - assert_eq!(plmc_free_amount, inst.get_ed() + stored_bid.plmc_bond); - assert_eq!(plmc_held_amount, 0u128); - assert_eq!(ct_amount, Zero::zero()); - assert_eq!(issuer_usdt_balance, Zero::zero()); - assert!(unvested_amount.is_none()); - inst.assert_migration(project_id, BIDDER_1, stored_bid.final_ct_amount, 0, ParticipationType::Bid, false); - - // Second bid assertions - let stored_bid = inst.execute(|| Bids::::get((project_id, BIDDER_2, 1)).unwrap()); - let plmc_free_amount = inst.get_free_plmc_balance_for(BIDDER_2); - let plmc_held_amount = - inst.get_reserved_plmc_balance_for(BIDDER_2, HoldReason::Participation(project_id).into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BIDDER_2); - let issuer_usdt_balance_2 = - inst.get_free_foreign_asset_balance_for(stored_bid.funding_asset.to_assethub_id(), issuer); - let unvested_amount = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &BIDDER_2, - HoldReason::Participation(project_id).into(), - ) - }); - assert_eq!(plmc_free_amount, inst.get_ed()); - assert_eq!(plmc_held_amount, stored_bid.plmc_bond); - assert_eq!(ct_amount, 0u128); - assert_eq!(issuer_usdt_balance_2, issuer_usdt_balance); - assert!(unvested_amount.is_none()); - - inst.execute(|| { - assert_ok!(PolimecFunding::settle_failed_bid(RuntimeOrigin::signed(BIDDER_2), project_id, BIDDER_2, 1)); - }); - - assert!(inst.execute(|| Contributions::::get((project_id, BIDDER_2, 1)).is_none())); - let plmc_free_amount = inst.get_free_plmc_balance_for(BIDDER_2); - let plmc_held_amount = - inst.get_reserved_plmc_balance_for(BIDDER_2, HoldReason::Participation(project_id).into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BIDDER_2); - let issuer_usdt_balance_2 = - inst.get_free_foreign_asset_balance_for(stored_bid.funding_asset.to_assethub_id(), issuer); - let unvested_amount = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &BIDDER_2, - HoldReason::Participation(project_id).into(), - ) - }); - assert_eq!(plmc_free_amount, inst.get_ed() + stored_bid.plmc_bond); - assert_eq!(plmc_held_amount, Zero::zero()); - assert_eq!(ct_amount, Zero::zero()); - assert_eq!(issuer_usdt_balance_2, Zero::zero()); - assert!(unvested_amount.is_none()); - - inst.assert_migration(project_id, BIDDER_2, stored_bid.final_ct_amount, 1, ParticipationType::Bid, false); - } - } - - #[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, true); - - 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 - ); - }); - } - - #[test] - fn cannot_be_called_on_wrong_outcome() { - let percentage = 100u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, true); - let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); - let bidder = first_bid.bidder; - inst.execute(|| { - assert_noop!( - crate::Pallet::::settle_failed_bid( - RuntimeOrigin::signed(bidder), - project_id, - bidder, - first_bid.id - ), - Error::::FundingFailedSettlementNotStarted - ); - }); - } - - #[test] - fn cannot_be_called_before_settlement_started() { - let percentage = 10u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, false); - - let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); - let bidder = first_bid.bidder; - inst.execute(|| { - assert_noop!( - crate::Pallet::::settle_failed_bid( - RuntimeOrigin::signed(bidder), - project_id, - bidder, - first_bid.id - ), - Error::::FundingFailedSettlementNotStarted - ); - }); - } - } -} - -#[cfg(test)] -mod settle_failed_contribution_extrinsic { - use super::*; - - #[cfg(test)] - mod success { - use super::*; - - #[test] - fn contribution_is_correctly_settled() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let issuer = ISSUER_1; - let evaluations = - inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); - let bids = inst.generate_bids_from_total_ct_percent( - project_metadata.clone(), - 10, - default_weights(), - default_bidders(), - default_multipliers(), - ); - let mut community_contributions = inst.generate_contributions_from_total_ct_percent( - project_metadata.clone(), - 10, - default_weights(), - default_community_contributors(), - default_community_contributor_multipliers(), - ); - - let contribution_mul_1 = - ContributionParams::::new(BUYER_6, 1000 * CT_UNIT, 1, AcceptedFundingAsset::USDT); - let contribution_mul_2 = - ContributionParams::::new(BUYER_7, 1000 * CT_UNIT, 2, AcceptedFundingAsset::USDT); - - community_contributions.push(contribution_mul_1); - - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - issuer, - None, - evaluations, - bids, - community_contributions, - ); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - let plmc_required = inst.calculate_contributed_plmc_spent(vec![contribution_mul_2.clone()], wap, false); - let plmc_ed = plmc_required.accounts().existential_deposits(); - inst.mint_plmc_to(plmc_required.clone()); - inst.mint_plmc_to(plmc_ed); - - let usdt_required = inst.calculate_contributed_funding_asset_spent(vec![contribution_mul_2.clone()], wap); - inst.mint_foreign_asset_to(usdt_required.clone()); - - inst.execute(|| { - assert_ok!(PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_7), - get_mock_jwt_with_cid( - BUYER_7, - InvestorType::Professional, - generate_did_from_account(BUYER_7), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ), - project_id, - contribution_mul_2.amount, - contribution_mul_2.multiplier, - contribution_mul_2.asset - )); - }); - - inst.finish_funding(project_id, None).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); - - // First contribution assertions - let stored_contribution = - inst.execute(|| Contributions::::get((project_id, BUYER_6, 5)).unwrap()); - let plmc_free_amount = inst.get_free_plmc_balance_for(BUYER_6); - let plmc_held_amount = - inst.get_reserved_plmc_balance_for(BUYER_6, HoldReason::Participation(project_id).into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BUYER_6); - let issuer_usdt_balance = - inst.get_free_foreign_asset_balance_for(stored_contribution.funding_asset.to_assethub_id(), issuer); - let unvested_amount = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &BUYER_6, - HoldReason::Participation(project_id).into(), - ) - }); - - assert_eq!(plmc_free_amount, inst.get_ed()); - assert_eq!(plmc_held_amount, stored_contribution.plmc_bond); - assert_eq!(ct_amount, 0u128); - assert_eq!(issuer_usdt_balance, 0u128); - assert!(unvested_amount.is_none()); - - inst.execute(|| { - assert_ok!(PolimecFunding::settle_failed_contribution( - RuntimeOrigin::signed(BUYER_6), - project_id, - BUYER_6, - 5 - )); - }); - - assert!(inst.execute(|| Contributions::::get((project_id, BUYER_6, 6)).is_none())); - let plmc_free_amount = inst.get_free_plmc_balance_for(BUYER_6); - let plmc_held_amount = - inst.get_reserved_plmc_balance_for(BUYER_6, HoldReason::Participation(project_id).into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BUYER_6); - let issuer_usdt_balance = - inst.get_free_foreign_asset_balance_for(stored_contribution.funding_asset.to_assethub_id(), issuer); - let unvested_amount = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &BUYER_6, - HoldReason::Participation(project_id).into(), - ) - }); - - assert_eq!(plmc_free_amount, inst.get_ed() + stored_contribution.plmc_bond); - assert_eq!(plmc_held_amount, 0u128); - assert_eq!(ct_amount, Zero::zero()); - assert_eq!(issuer_usdt_balance, Zero::zero()); - assert!(unvested_amount.is_none()); - inst.assert_migration( - project_id, - BUYER_6, - stored_contribution.ct_amount, - 5, - ParticipationType::Contribution, - false, - ); - - // Second contribution assertions - let stored_contribution = - inst.execute(|| Contributions::::get((project_id, BUYER_7, 6)).unwrap()); - let plmc_free_amount = inst.get_free_plmc_balance_for(BUYER_7); - let plmc_held_amount = - inst.get_reserved_plmc_balance_for(BUYER_7, HoldReason::Participation(project_id).into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BUYER_7); - let issuer_usdt_balance_2 = - inst.get_free_foreign_asset_balance_for(stored_contribution.funding_asset.to_assethub_id(), issuer); - let unvested_amount = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &BUYER_7, - HoldReason::Participation(project_id).into(), - ) - }); - assert_eq!(plmc_free_amount, inst.get_ed()); - assert_eq!(plmc_held_amount, stored_contribution.plmc_bond); - assert_eq!(ct_amount, 0u128); - assert_eq!(issuer_usdt_balance_2, issuer_usdt_balance); - assert!(unvested_amount.is_none()); - - inst.execute(|| { - assert_ok!(PolimecFunding::settle_failed_contribution( - RuntimeOrigin::signed(BUYER_7), - project_id, - BUYER_7, - 6 - )); - }); - - assert!(inst.execute(|| Contributions::::get((project_id, BUYER_7, 7)).is_none())); - let plmc_free_amount = inst.get_free_plmc_balance_for(BUYER_7); - let plmc_held_amount = - inst.get_reserved_plmc_balance_for(BUYER_7, HoldReason::Participation(project_id).into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BUYER_7); - let issuer_usdt_balance_2 = - inst.get_free_foreign_asset_balance_for(stored_contribution.funding_asset.to_assethub_id(), issuer); - let unvested_amount = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &BUYER_7, - HoldReason::Participation(project_id).into(), - ) - }); - - assert_eq!(plmc_free_amount, inst.get_ed() + stored_contribution.plmc_bond); - assert_eq!(plmc_held_amount, 0u128); - assert_eq!(ct_amount, Zero::zero()); - assert_eq!(issuer_usdt_balance_2, Zero::zero()); - assert!(unvested_amount.is_none()); - - inst.assert_migration( - project_id, - BUYER_7, - stored_contribution.ct_amount, - 6, - ParticipationType::Contribution, - false, - ); - } - } - - #[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, true); - - 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 cannot_be_called_on_wrong_outcome() { - let percentage = 100u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, true); - - let first_contribution = inst.get_contributions(project_id).into_iter().next().unwrap(); - let contributor = first_contribution.contributor; - inst.execute(|| { - assert_noop!( - crate::Pallet::::settle_failed_contribution( - RuntimeOrigin::signed(contributor), - project_id, - contributor, - first_contribution.id - ), - Error::::FundingFailedSettlementNotStarted - ); - }); - } - - #[test] - fn cannot_be_called_before_settlement_started() { - let percentage = 10u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, false); - - let first_contribution = inst.get_contributions(project_id).into_iter().next().unwrap(); - let contributor = first_contribution.contributor; - inst.execute(|| { - assert_noop!( - crate::Pallet::::settle_failed_contribution( - RuntimeOrigin::signed(contributor), - project_id, - contributor, - first_contribution.id - ), - Error::::FundingFailedSettlementNotStarted - ); - }); - } - } -} diff --git a/pallets/funding/src/tests/mod.rs b/pallets/funding/src/tests/mod.rs index 43803e434..837d9dca5 100644 --- a/pallets/funding/src/tests/mod.rs +++ b/pallets/funding/src/tests/mod.rs @@ -21,6 +21,24 @@ use sp_arithmetic::{traits::Zero, Percent, Perquintill}; use sp_runtime::TokenError; use sp_std::cell::RefCell; use std::iter::zip; + +#[path = "1_application.rs"] +mod application; +#[path = "3_auction.rs"] +mod auction; +#[path = "4_contribution.rs"] +mod community; +#[path = "7_ct_migration.rs"] +mod ct_migration; +#[path = "2_evaluation.rs"] +mod evaluation; +#[path = "5_funding_end.rs"] +mod funding_end; +mod misc; +mod runtime_api; +#[path = "6_settlement.rs"] +mod settlement; + pub type MockInstantiator = Instantiator::AllPalletsWithoutSystem, RuntimeEvent>; pub const CT_DECIMALS: u8 = 15; @@ -35,7 +53,6 @@ const ISSUER_4: AccountId = 14; const ISSUER_5: AccountId = 15; const ISSUER_6: AccountId = 16; const ISSUER_7: AccountId = 17; -const ISSUER_8: AccountId = 18; const EVALUATOR_1: AccountId = 21; const EVALUATOR_2: AccountId = 22; const EVALUATOR_3: AccountId = 23; @@ -54,27 +71,6 @@ const BUYER_4: AccountId = 44; const BUYER_5: AccountId = 45; const BUYER_6: AccountId = 46; const BUYER_7: AccountId = 47; -const BUYER_8: AccountId = 48; -const BUYER_9: AccountId = 49; - -#[path = "1_application.rs"] -mod application; -#[path = "3_auction.rs"] -mod auction; -#[path = "4_community.rs"] -mod community; -#[path = "8_ct_migration.rs"] -mod ct_migration; -#[path = "2_evaluation.rs"] -mod evaluation; -#[path = "6_funding_end.rs"] -mod funding_end; -mod misc; -#[path = "5_remainder.rs"] -mod remainder; -mod runtime_api; -#[path = "7_settlement.rs"] -mod settlement; pub mod defaults { use super::*; @@ -167,7 +163,7 @@ pub mod defaults { ] } - pub fn default_usdt_balances() -> Vec> { + pub fn default_usdt_balances() -> Vec> { vec![ (ISSUER_1, 10_000_000 * USDT_UNIT).into(), (EVALUATOR_1, 10_000_000 * USDT_UNIT).into(), @@ -222,7 +218,7 @@ pub mod defaults { ] } - pub fn default_community_buys() -> Vec> { + pub fn default_community_contributions() -> Vec> { vec![ ContributionParams::new(BUYER_1, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), ContributionParams::new(BUYER_2, 130_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), @@ -232,7 +228,7 @@ pub mod defaults { ] } - pub fn default_remainder_buys() -> Vec> { + pub fn default_remainder_contributions() -> Vec> { vec![ ContributionParams::new(EVALUATOR_2, 20_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), ContributionParams::new(BUYER_2, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), @@ -371,7 +367,6 @@ pub mod defaults { pub fn create_project_with_funding_percentage( percentage: u64, - maybe_decision: Option, start_settlement: bool, ) -> (MockInstantiator, ProjectId) { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); @@ -397,42 +392,16 @@ pub fn create_project_with_funding_percentage( let project_id = inst.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids, contributions, vec![]); - match inst.get_project_details(project_id).status { - ProjectStatus::FundingSuccessful => { - assert!(percentage >= 33); - }, - ProjectStatus::FundingFailed => { - assert!(percentage <= 33); - }, - _ => panic!("unexpected project status"), - }; - if start_settlement { - let settlement_execution = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_execution); - - let funding_sucessful = match percentage { - 0..=33 => false, - 34..=89 if matches!(maybe_decision, Some(FundingOutcomeDecision::RejectFunding)) => false, - 34..=89 if matches!(maybe_decision, Some(FundingOutcomeDecision::AcceptFunding)) => true, - 90..=100 => true, - _ => panic!("unexpected percentage"), - }; - if funding_sucessful { - assert_eq!( - inst.get_project_details(project_id).status, - ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful) - ); - inst.test_ct_created_for(project_id); - } else { - assert_eq!( - inst.get_project_details(project_id).status, - ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed) - ); - inst.test_ct_not_created_for(project_id); - } + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(_))); } + // Sanity check + let project_details = inst.get_project_details(project_id); + let percent_reached = + Perquintill::from_rational(project_details.funding_amount_reached_usd, project_details.fundraising_target_usd); + assert_eq!(percent_reached, Perquintill::from_percent(percentage)); + (inst, project_id) } @@ -447,7 +416,6 @@ pub fn create_finished_project_with_usd_raised( project_metadata.minimum_price.reciprocal().unwrap().saturating_mul_int(usd_target); project_metadata.auction_round_allocation_percentage = Percent::from_percent(50u8); - dbg!(project_metadata.minimum_price); let required_price = if usd_raised <= usd_target { project_metadata.minimum_price } else { @@ -462,7 +430,6 @@ pub fn create_finished_project_with_usd_raised( // selling all the CTs, we need the price to be double FixedU128::from_rational(2, 1) * required_price }; - dbg!(required_price); let evaluations = default_evaluations(); @@ -472,7 +439,6 @@ pub fn create_finished_project_with_usd_raised( let project_details = inst.get_project_details(project_id); let wap = project_details.weighted_average_price.unwrap(); - dbg!(wap); let usd_raised_so_far = project_details.funding_amount_reached_usd; let usd_remaining = usd_raised - usd_raised_so_far; @@ -487,16 +453,13 @@ pub fn create_finished_project_with_usd_raised( let plmc_required = inst.calculate_contributed_plmc_spent(community_contributions.clone(), required_price, true); let usdt_required = inst.calculate_contributed_funding_asset_spent(community_contributions.clone(), required_price); inst.mint_plmc_to(plmc_required); - inst.mint_foreign_asset_to(usdt_required); + inst.mint_funding_asset_to(usdt_required); inst.contribute_for_users(project_id, community_contributions).unwrap(); - inst.start_remainder_or_end_funding(project_id).unwrap(); - if matches!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound(_)) { - inst.finish_funding(project_id, Some(FundingOutcomeDecision::AcceptFunding)).unwrap(); - } + + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); let project_details = inst.get_project_details(project_id); - dbg!(project_details.remaining_contribution_tokens); - assert_eq!(project_details.status, ProjectStatus::FundingSuccessful); + // We are happy if the amount raised is 99.999 of what we wanted assert_close_enough!(project_details.funding_amount_reached_usd, usd_raised, Perquintill::from_float(0.999)); assert_eq!(project_details.fundraising_target_usd, usd_target); diff --git a/pallets/funding/src/tests/runtime_api.rs b/pallets/funding/src/tests/runtime_api.rs index 66001f2e9..0e414ef01 100644 --- a/pallets/funding/src/tests/runtime_api.rs +++ b/pallets/funding/src/tests/runtime_api.rs @@ -209,22 +209,22 @@ fn top_projects_by_usd_target_percent_reached() { #[test] fn contribution_tokens() { let bob = 420; - let mut contributions_with_bob_1 = default_community_buys(); + let mut contributions_with_bob_1 = default_community_contributions(); let bob_amount_1 = 10_000 * CT_UNIT; contributions_with_bob_1.last_mut().unwrap().contributor = bob; contributions_with_bob_1.last_mut().unwrap().amount = bob_amount_1; - let mut contributions_with_bob_2 = default_community_buys(); + let mut contributions_with_bob_2 = default_community_contributions(); let bob_amount_2 = 25_000 * CT_UNIT; contributions_with_bob_2.last_mut().unwrap().contributor = bob; contributions_with_bob_2.last_mut().unwrap().amount = bob_amount_2; - let mut contributions_with_bob_3 = default_community_buys(); + let mut contributions_with_bob_3 = default_community_contributions(); let bob_amount_3 = 5_020 * CT_UNIT; contributions_with_bob_3.last_mut().unwrap().contributor = bob; contributions_with_bob_3.last_mut().unwrap().amount = bob_amount_3; - let mut contributions_with_bob_4 = default_community_buys(); + let mut contributions_with_bob_4 = default_community_contributions(); let bob_amount_4 = 420 * CT_UNIT; contributions_with_bob_4.last_mut().unwrap().contributor = bob; contributions_with_bob_4.last_mut().unwrap().amount = bob_amount_4; @@ -237,7 +237,7 @@ fn contribution_tokens() { default_evaluations(), default_bids(), contributions_with_bob_1, - default_remainder_buys(), + default_remainder_contributions(), ); let _project_id_2 = inst.create_settled_project( default_project_metadata(ISSUER_2), @@ -245,8 +245,8 @@ fn contribution_tokens() { None, default_evaluations(), default_bids(), - default_community_buys(), - default_remainder_buys(), + default_community_contributions(), + default_remainder_contributions(), ); let _project_id_3 = inst.create_settled_project( default_project_metadata(ISSUER_3), @@ -254,8 +254,8 @@ fn contribution_tokens() { None, default_evaluations(), default_bids(), - default_community_buys(), - default_remainder_buys(), + default_community_contributions(), + default_remainder_contributions(), ); let project_id_4 = inst.create_settled_project( default_project_metadata(ISSUER_4), @@ -264,7 +264,7 @@ fn contribution_tokens() { default_evaluations(), default_bids(), contributions_with_bob_2, - default_remainder_buys(), + default_remainder_contributions(), ); let _project_id_5 = inst.create_settled_project( default_project_metadata(ISSUER_5), @@ -272,8 +272,8 @@ fn contribution_tokens() { None, default_evaluations(), default_bids(), - default_community_buys(), - default_remainder_buys(), + default_community_contributions(), + default_remainder_contributions(), ); let project_id_6 = inst.create_settled_project( default_project_metadata(ISSUER_6), @@ -282,7 +282,7 @@ fn contribution_tokens() { default_evaluations(), default_bids(), contributions_with_bob_3, - default_remainder_buys(), + default_remainder_contributions(), ); let project_id_7 = inst.create_settled_project( default_project_metadata(ISSUER_7), @@ -291,7 +291,7 @@ fn contribution_tokens() { default_evaluations(), default_bids(), contributions_with_bob_4, - default_remainder_buys(), + default_remainder_contributions(), ); let expected_items = vec![ @@ -315,7 +315,7 @@ fn funding_asset_to_ct_amount() { // We want to use a funding asset that is not equal to 1 USD // Sanity check assert_eq!( - PriceProviderOf::::get_price(AcceptedFundingAsset::DOT.to_assethub_id()).unwrap(), + PriceProviderOf::::get_price(AcceptedFundingAsset::DOT.id()).unwrap(), PriceOf::::from_float(69.0f64) ); @@ -412,7 +412,7 @@ fn funding_asset_to_ct_amount() { None, ); inst.mint_plmc_to(necessary_plmc); - inst.mint_foreign_asset_to(necessary_usdt); + inst.mint_funding_asset_to(necessary_usdt); inst.bid_for_users(project_id_3, bids).unwrap(); // Sanity check @@ -535,7 +535,7 @@ fn all_project_participations_by_did() { vec![bids_usdt, community_contributions_usdt, remainder_contributions_usdt], MergeOperation::Add, ); - inst.mint_foreign_asset_to(all_usdt); + inst.mint_funding_asset_to(all_usdt); inst.evaluate_for_users(project_id, evaluations[..1].to_vec()).unwrap(); for evaluation in evaluations[1..].to_vec() { @@ -546,7 +546,8 @@ fn all_project_participations_by_did() { }); } - inst.start_auction(project_id, ISSUER_1).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionInitializePeriod); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionRound); inst.bid_for_users(project_id, bids[..1].to_vec()).unwrap(); for bid in bids[1..].to_vec() { @@ -564,11 +565,12 @@ fn all_project_participations_by_did() { }); } - inst.start_community_funding(project_id).unwrap(); - + let ProjectStatus::CommunityRound(remainder_start) = inst.go_to_next_state(project_id) else { + panic!("Expected CommunityRound") + }; inst.contribute_for_users(project_id, community_contributions).unwrap(); - inst.start_remainder_or_end_funding(project_id).unwrap(); + inst.jump_to_block(remainder_start); for contribution in remainder_contributions { let jwt = @@ -586,7 +588,7 @@ fn all_project_participations_by_did() { }); } - inst.finish_funding(project_id, None).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); inst.execute(|| { let block_hash = System::block_hash(System::block_number()); @@ -653,8 +655,8 @@ fn projects_by_did() { Some(did_user.clone()), default_evaluations(), default_bids(), - default_community_buys(), - default_remainder_buys(), + default_community_contributions(), + default_remainder_contributions(), ); let _project_id_2 = inst.create_settled_project( @@ -663,8 +665,8 @@ fn projects_by_did() { None, default_evaluations(), default_bids(), - default_community_buys(), - default_remainder_buys(), + default_community_contributions(), + default_remainder_contributions(), ); let project_id_3 = inst.create_settled_project( @@ -673,8 +675,8 @@ fn projects_by_did() { Some(did_user.clone()), default_evaluations(), default_bids(), - default_community_buys(), - default_remainder_buys(), + default_community_contributions(), + default_remainder_contributions(), ); let _project_id_4 = inst.create_settled_project( @@ -683,11 +685,13 @@ fn projects_by_did() { None, default_evaluations(), default_bids(), - default_community_buys(), - default_remainder_buys(), + default_community_contributions(), + default_remainder_contributions(), ); inst.execute(|| { + let projects = ProjectsDetails::::iter().collect_vec(); + dbg!(projects); let block_hash = System::block_hash(System::block_number()); let project_ids = TestRuntime::projects_by_did(&TestRuntime, block_hash, did_user).unwrap(); assert_eq!(project_ids, vec![project_id_1, project_id_3]); diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index 1b37b1011..b8f965f8d 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -683,7 +683,7 @@ pub mod inner_types { DOT, } impl AcceptedFundingAsset { - pub const fn to_assethub_id(&self) -> u32 { + pub const fn id(&self) -> u32 { match self { AcceptedFundingAsset::USDT => 1984, AcceptedFundingAsset::DOT => 10, diff --git a/runtimes/polimec/src/benchmarks/helpers.rs b/runtimes/polimec/src/benchmarks/helpers.rs index bd0aede0d..709c8d69d 100644 --- a/runtimes/polimec/src/benchmarks/helpers.rs +++ b/runtimes/polimec/src/benchmarks/helpers.rs @@ -6,9 +6,9 @@ pub use sp_runtime::FixedU128; pub struct SetOraclePrices; impl SetPrices for SetOraclePrices { fn set_prices() { - let dot = (AcceptedFundingAsset::DOT.to_assethub_id(), FixedU128::from_rational(69, 1)); - let usdc = (AcceptedFundingAsset::USDC.to_assethub_id(), FixedU128::from_rational(1, 1)); - let usdt = (AcceptedFundingAsset::USDT.to_assethub_id(), FixedU128::from_rational(1, 1)); + let dot = (AcceptedFundingAsset::DOT.id(), FixedU128::from_rational(69, 1)); + let usdc = (AcceptedFundingAsset::USDC.id(), FixedU128::from_rational(1, 1)); + let usdt = (AcceptedFundingAsset::USDT.id(), FixedU128::from_rational(1, 1)); let plmc = (pallet_funding::PLMC_FOREIGN_ID, FixedU128::from_rational(840, 100)); let values: BoundedVec<(u32, FixedU128), ::MaxFeedValues> = diff --git a/runtimes/shared-configuration/src/currency.rs b/runtimes/shared-configuration/src/currency.rs index 9900bc242..2e1b81ad9 100644 --- a/runtimes/shared-configuration/src/currency.rs +++ b/runtimes/shared-configuration/src/currency.rs @@ -86,9 +86,9 @@ pub struct AssetPriceConverter; impl Convert<(AssetName, FixedU128), (AssetId, Price)> for AssetPriceConverter { fn convert((asset, price): (AssetName, FixedU128)) -> (AssetId, Price) { match asset { - AssetName::DOT => (AcceptedFundingAsset::DOT.to_assethub_id(), price), - AssetName::USDC => (AcceptedFundingAsset::USDC.to_assethub_id(), price), - AssetName::USDT => (AcceptedFundingAsset::USDT.to_assethub_id(), price), + AssetName::DOT => (AcceptedFundingAsset::DOT.id(), price), + AssetName::USDC => (AcceptedFundingAsset::USDC.id(), price), + AssetName::USDT => (AcceptedFundingAsset::USDT.id(), price), AssetName::PLMC => (pallet_funding::PLMC_FOREIGN_ID, price), } } diff --git a/runtimes/shared-configuration/src/funding.rs b/runtimes/shared-configuration/src/funding.rs index b7de80cc2..fa7c079ff 100644 --- a/runtimes/shared-configuration/src/funding.rs +++ b/runtimes/shared-configuration/src/funding.rs @@ -102,9 +102,9 @@ parameter_types! { pub const SuccessToSettlementTime: BlockNumber = SUCCESS_TO_SETTLEMENT_TIME; pub const FundingPalletId: PalletId = PalletId(*b"plmc/fun"); pub PriceMap: BTreeMap = BTreeMap::from_iter(vec![ - (AcceptedFundingAsset::DOT.to_assethub_id(), FixedU128::from_rational(69, 1)), // DOT - (AcceptedFundingAsset::USDC.to_assethub_id(), FixedU128::from_rational(100, 100)), // USDC - (AcceptedFundingAsset::USDT.to_assethub_id(), FixedU128::from_rational(100, 100)), // USDT + (AcceptedFundingAsset::DOT.id(), FixedU128::from_rational(69, 1)), // DOT + (AcceptedFundingAsset::USDC.id(), FixedU128::from_rational(100, 100)), // USDC + (AcceptedFundingAsset::USDT.id(), FixedU128::from_rational(100, 100)), // USDT (pallet_funding::PLMC_FOREIGN_ID, FixedU128::from_rational(840, 100)), // PLMC ]); pub FeeBrackets: Vec<(Percent, Balance)> = vec![