From 706a0c45f7e87e5277f0881dc4b62371e50a021d 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 | 4 +- integration-tests/src/tests/e2e.rs | 18 +- integration-tests/src/tests/oracle.rs | 6 +- nodes/parachain/src/chain_spec/common.rs | 12 +- pallets/funding/src/benchmarking.rs | 156 +- pallets/funding/src/functions/3_auction.rs | 2 +- .../funding/src/functions/4_contribution.rs | 2 +- pallets/funding/src/functions/6_settlement.rs | 13 +- pallets/funding/src/functions/misc.rs | 2 +- .../funding/src/instantiator/calculations.rs | 173 +- .../src/instantiator/chain_interactions.rs | 499 +- pallets/funding/src/instantiator/mod.rs | 1 - pallets/funding/src/instantiator/tests.rs | 42 +- pallets/funding/src/instantiator/types.rs | 19 +- pallets/funding/src/mock.rs | 37 +- pallets/funding/src/runtime_api.rs | 26 +- pallets/funding/src/tests/1_application.rs | 2549 +++++----- pallets/funding/src/tests/2_evaluation.rs | 2000 ++++---- pallets/funding/src/tests/3_auction.rs | 4096 ++++++++--------- pallets/funding/src/tests/4_community.rs | 1794 -------- pallets/funding/src/tests/4_contribution.rs | 1754 +++++++ pallets/funding/src/tests/5_funding_end.rs | 119 + pallets/funding/src/tests/5_remainder.rs | 1888 -------- pallets/funding/src/tests/6_funding_end.rs | 113 - pallets/funding/src/tests/6_settlement.rs | 1176 +++++ pallets/funding/src/tests/7_ct_migration.rs | 287 ++ pallets/funding/src/tests/7_settlement.rs | 1539 ------- pallets/funding/src/tests/8_ct_migration.rs | 289 -- pallets/funding/src/tests/misc.rs | 736 +-- pallets/funding/src/tests/mod.rs | 973 ++-- pallets/funding/src/tests/runtime_api.rs | 1395 +++--- 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 +- 36 files changed, 9587 insertions(+), 12165 deletions(-) delete mode 100644 pallets/funding/src/tests/4_community.rs create mode 100644 pallets/funding/src/tests/4_contribution.rs create mode 100644 pallets/funding/src/tests/5_funding_end.rs delete mode 100644 pallets/funding/src/tests/5_remainder.rs delete mode 100644 pallets/funding/src/tests/6_funding_end.rs create mode 100644 pallets/funding/src/tests/6_settlement.rs create mode 100644 pallets/funding/src/tests/7_ct_migration.rs delete mode 100644 pallets/funding/src/tests/7_settlement.rs delete mode 100644 pallets/funding/src/tests/8_ct_migration.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..ca2ad135f 100644 --- a/integration-tests/src/tests/ct_migration.rs +++ b/integration-tests/src/tests/ct_migration.rs @@ -174,7 +174,7 @@ fn create_settled_project() -> (ProjectId, Vec) { default_community_contributions(), default_remainder_contributions(), ); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); + inst.advance_time(::SuccessToSettlementTime::get()); let mut participants: Vec = pallet_funding::Evaluations::::iter_prefix_values((project_id,)) .map(|eval| eval.evaluator) @@ -229,7 +229,7 @@ fn create_project_with_unsettled_participation(participation_type: Participation default_remainder_contributions(), ); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); + inst.advance_time(::SuccessToSettlementTime::get()); 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(); diff --git a/integration-tests/src/tests/e2e.rs b/integration-tests/src/tests/e2e.rs index a08ce9010..cb4a95b46 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)); @@ -405,12 +404,11 @@ fn funds_raised() { inst.execute(|| { let project_specific_account: AccountId = PolimecFunding::fund_account_id(project_id); let stored_usdt_funded = - PolimecForeignAssets::balance(AcceptedFundingAsset::USDT.to_assethub_id(), project_specific_account); + PolimecForeignAssets::balance(AcceptedFundingAsset::USDT.id(), project_specific_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)); }) @@ -433,7 +431,7 @@ fn ct_minted() { excel_contributions(), excel_remainders(), ); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); + inst.advance_time(::SuccessToSettlementTime::get()); inst.settle_project(project_id).unwrap(); @@ -462,7 +460,7 @@ fn ct_migrated() { excel_contributions(), excel_remainders(), ); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); + inst.advance_time(::SuccessToSettlementTime::get()); inst.settle_project(project_id).unwrap(); @@ -540,7 +538,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..462dd2da7 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); @@ -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![UserToForeignAssets::::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(); @@ -871,13 +858,13 @@ mod benchmarks { 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_foreign_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_foreign_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 0f1291dc5..afc4f20e7 100644 --- a/pallets/funding/src/functions/6_settlement.rs +++ b/pallets/funding/src/functions/6_settlement.rs @@ -202,7 +202,7 @@ impl Pallet { account: bid.bidder, id: bid.id, final_ct_amount, - final_ct_price + final_ct_price, }); Ok(()) @@ -239,12 +239,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)?; @@ -354,13 +355,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 63b43c1c9..eab1b3e48 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)?; diff --git a/pallets/funding/src/instantiator/calculations.rs b/pallets/funding/src/instantiator/calculations.rs index ea5bac920..f2962a5bf 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>, @@ -200,7 +202,7 @@ impl< ) -> 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(UserToForeignAssets::new(bid.bidder.clone(), funding_asset_spent, bid.asset.id())); } output } @@ -223,7 +225,7 @@ impl< 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,17 +234,12 @@ 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(UserToForeignAssets::::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 @@ -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(UserToForeignAssets::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(UserToForeignAssets::::new(bid.bidder, returned_foreign_asset, bid.asset.id())); } } @@ -431,7 +417,7 @@ impl< ) -> 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(UserToForeignAssets::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_foreign_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(&UserToForeignAssets) -> 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 211f52e1c..0844d6dc5 100644 --- a/pallets/funding/src/instantiator/chain_interactions.rs +++ b/pallets/funding/src/instantiator/chain_interactions.rs @@ -69,7 +69,7 @@ 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>, @@ -85,7 +85,7 @@ impl< }) } - 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,7 +151,7 @@ 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 { ::FundingCurrency::mint_into(asset_id, &account, asset_amount) @@ -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 UserToForeignAssets { 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, ); } } @@ -819,7 +769,9 @@ impl< BidStatus::Accepted => bid.original_ct_amount, BidStatus::PartiallyAccepted(amount) => amount, BidStatus::Rejected => 0u64.into(), - BidStatus::YetUnknown => {panic!("Bid should have a different status than YetUnknown")} + BidStatus::YetUnknown => { + panic!("Bid should have a different status than YetUnknown") + }, }; self.assert_migration(project_id, account, amount, bid.id, ParticipationType::Bid, is_successful); } @@ -902,61 +854,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(); + if !contributions.is_empty() { + let ct_price = self.get_project_details(project_id).weighted_average_price.unwrap(); - let contributors = contributions.accounts(); + let contributors = contributions.accounts(); - let asset_id = contributions[0].asset.to_assethub_id(); + let asset_id = contributions[0].asset.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.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 } @@ -980,69 +941,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 @@ -1064,6 +1030,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 @@ -1089,8 +1059,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..4bddb819c 100644 --- a/pallets/funding/src/instantiator/mod.rs +++ b/pallets/funding/src/instantiator/mod.rs @@ -41,7 +41,6 @@ use sp_arithmetic::{ }; use sp_runtime::{ traits::{Convert, Member, One}, - DispatchError, }; use sp_std::{ cell::RefCell, diff --git a/pallets/funding/src/instantiator/tests.rs b/pallets/funding/src/instantiator/tests.rs index 1bb3df471..5291a53ab 100644 --- a/pallets/funding/src/instantiator/tests.rs +++ b/pallets/funding/src/instantiator/tests.rs @@ -65,11 +65,11 @@ fn dry_run_wap() { .map(|acc| UserToForeignAssets { 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); } @@ -151,11 +147,11 @@ fn find_bucket_for_wap() { .map(|acc| UserToForeignAssets { 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..781531b9b 100644 --- a/pallets/funding/src/instantiator/types.rs +++ b/pallets/funding/src/instantiator/types.rs @@ -184,7 +184,7 @@ impl From<(AccountIdOf, BalanceOf, AssetIdOf)> for UserToFor } impl From<(AccountIdOf, BalanceOf)> for UserToForeignAssets { fn from((account, asset_amount): (AccountIdOf, BalanceOf)) -> Self { - UserToForeignAssets::::new(account, asset_amount, AcceptedFundingAsset::USDT.to_assethub_id()) + UserToForeignAssets::::new(account, asset_amount, AcceptedFundingAsset::USDT.id()) } } impl Accounts for Vec> { @@ -205,16 +205,19 @@ impl AccountMerge for Vec> { let mut btree = BTreeMap::new(); for UserToForeignAssets { 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)| UserToForeignAssets::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 49041f1aa..3274137d6 100644 --- a/pallets/funding/src/runtime_api.rs +++ b/pallets/funding/src/runtime_api.rs @@ -72,18 +72,22 @@ impl Pallet { Bids::::iter_prefix_values((project_id,)) .sorted_by(|a, b| { let a_final_ct_amount = match a.status { - BidStatus::YetUnknown => {panic!("Something on-chain went very wrong")} - BidStatus::Accepted => {a.original_ct_amount} - BidStatus::Rejected => {Zero::zero()} - BidStatus::PartiallyAccepted(amount) => {amount} - } ; + BidStatus::YetUnknown => { + panic!("Something on-chain went very wrong") + }, + BidStatus::Accepted => a.original_ct_amount, + BidStatus::Rejected => Zero::zero(), + BidStatus::PartiallyAccepted(amount) => amount, + }; let b_final_ct_amount = match b.status { - BidStatus::YetUnknown => {panic!("Something on-chain went very wrong")} - BidStatus::Accepted => {b.original_ct_amount} - BidStatus::Rejected => {Zero::zero()} - BidStatus::PartiallyAccepted(amount) => {amount} - } ; + BidStatus::YetUnknown => { + panic!("Something on-chain went very wrong") + }, + BidStatus::Accepted => b.original_ct_amount, + BidStatus::Rejected => Zero::zero(), + BidStatus::PartiallyAccepted(amount) => amount, + }; b_final_ct_amount.cmp(&a_final_ct_amount) }) .take(amount as usize) @@ -148,7 +152,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 4a7423b10..28cc08451 100644 --- a/pallets/funding/src/tests/1_application.rs +++ b/pallets/funding/src/tests/1_application.rs @@ -1,1269 +1,1280 @@ -// use super::*; -// use polimec_common::credentials::InvestorType; -// use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt_with_cid}; -// -// #[cfg(test)] -// mod round_flow { -// use super::*; -// -// #[cfg(test)] -// mod success { -// use super::*; -// -// #[test] -// fn application_round_completed() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// -// let issuer = ISSUER_1; -// let project_metadata = default_project_metadata(issuer); -// -// inst.create_evaluating_project(project_metadata, issuer, None); -// } -// } -// } -// -// #[cfg(test)] -// mod create_project_extrinsic { -// use super::*; -// -// #[cfg(test)] -// mod success { -// use super::*; -// use polimec_common_test_utils::get_mock_jwt_with_cid; -// -// #[test] -// fn project_id_autoincrement_works() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_1 = default_project_metadata(ISSUER_1); -// let project_2 = default_project_metadata(ISSUER_2); -// let project_3 = default_project_metadata(ISSUER_3); -// -// let created_project_1_id = inst.create_evaluating_project(project_1, ISSUER_1, None); -// let created_project_2_id = inst.create_evaluating_project(project_2, ISSUER_2, None); -// let created_project_3_id = inst.create_evaluating_project(project_3, ISSUER_3, None); -// -// assert_eq!(created_project_1_id, 0); -// assert_eq!(created_project_2_id, 1); -// assert_eq!(created_project_3_id, 2); -// } -// -// #[test] -// fn multiple_creations_different_issuers() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let mut issuer = ISSUER_1; -// for _ in 0..512 { -// let project_metadata = default_project_metadata(issuer); -// inst.create_evaluating_project(project_metadata, issuer, None); -// inst.advance_time(1u64).unwrap(); -// issuer += 1; -// } -// } -// -// #[test] -// fn multiple_funding_currencies() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let default_project_metadata = default_project_metadata(ISSUER_1); -// -// let mut one_currency_1 = default_project_metadata.clone(); -// one_currency_1.participation_currencies = vec![AcceptedFundingAsset::USDT].try_into().unwrap(); -// -// let mut one_currency_2 = default_project_metadata.clone(); -// one_currency_2.participation_currencies = vec![AcceptedFundingAsset::USDC].try_into().unwrap(); -// -// let mut one_currency_3 = default_project_metadata.clone(); -// one_currency_3.participation_currencies = vec![AcceptedFundingAsset::DOT].try_into().unwrap(); -// -// let mut two_currencies_1 = default_project_metadata.clone(); -// two_currencies_1.participation_currencies = -// vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC].try_into().unwrap(); -// -// let mut two_currencies_2 = default_project_metadata.clone(); -// two_currencies_2.participation_currencies = -// vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT].try_into().unwrap(); -// -// let mut two_currencies_3 = default_project_metadata.clone(); -// two_currencies_3.participation_currencies = -// vec![AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT].try_into().unwrap(); -// -// let mut three_currencies = default_project_metadata.clone(); -// three_currencies.participation_currencies = -// vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT] -// .try_into() -// .unwrap(); -// -// let projects = vec![ -// one_currency_1.clone(), -// one_currency_2.clone(), -// one_currency_3, -// two_currencies_1, -// two_currencies_2, -// two_currencies_3, -// three_currencies, -// ]; -// -// let mut issuer = ISSUER_1; -// for project in projects { -// issuer += 1; -// let issuer_mint = (issuer, 1000 * PLMC).into(); -// inst.mint_plmc_to(vec![issuer_mint]); -// assert_ok!(inst.execute(|| { -// Pallet::::do_create_project(&issuer, project, generate_did_from_account(issuer)) -// })); -// } -// } -// -// #[test] -// fn issuer_can_create_second_project_after_first_is_inactive() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let issuer: AccountId = ISSUER_1; -// let did: Did = BoundedVec::new(); -// let project_metadata: ProjectMetadataOf = default_project_metadata(issuer); -// let jwt: UntrustedToken = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Institutional, -// did, -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// -// 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()); -// -// // Cannot create 2 projects consecutively -// inst.execute(|| { -// assert_ok!(Pallet::::create_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_metadata.clone() -// )); -// }); -// inst.execute(|| { -// assert_noop!( -// Pallet::::create_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_metadata.clone() -// ), -// Error::::HasActiveProject -// ); -// }); -// -// // A Project is "inactive" after the evaluation fails -// inst.start_evaluation(0, ISSUER_1).unwrap(); -// inst.execute(|| { -// assert_noop!( -// Pallet::::create_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_metadata.clone() -// ), -// Error::::HasActiveProject -// ); -// }); -// inst.advance_time(::EvaluationDuration::get() + 1).unwrap(); -// assert_eq!( -// inst.get_project_details(0).status, -// ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed) -// ); -// inst.execute(|| { -// assert_ok!(Pallet::::create_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_metadata.clone() -// )); -// }); -// -// // A Project is "inactive" after the funding fails -// inst.start_evaluation(1, ISSUER_1).unwrap(); -// inst.evaluate_for_users(1, default_evaluations()).unwrap(); -// inst.start_auction(1, ISSUER_1).unwrap(); -// inst.bid_for_users(1, failing_bids).unwrap(); -// inst.start_community_funding(1).unwrap(); -// inst.execute(|| { -// assert_noop!( -// Pallet::::create_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_metadata.clone() -// ), -// Error::::HasActiveProject -// ); -// }); -// inst.finish_funding(1, None).unwrap(); -// assert_eq!(inst.get_project_details(1).status, ProjectStatus::FundingFailed); -// inst.execute(|| { -// assert_ok!(Pallet::::create_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_metadata.clone() -// )); -// }); -// -// // A project is "inactive" after the funding succeeds -// inst.start_evaluation(2, ISSUER_1).unwrap(); -// inst.evaluate_for_users(2, default_evaluations()).unwrap(); -// inst.start_auction(2, ISSUER_1).unwrap(); -// 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); -// assert_ok!(inst.execute(|| crate::Pallet::::create_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_metadata.clone() -// ))); -// } -// -// #[test] -// fn shitcoin_tokenomics() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let mut project_metadata = default_project_metadata(ISSUER_1); -// -// // funding target of 1000 USD at 100 trillion supply -// const QUADRILLION_SUPPLY: u128 = 100_000_000_000_000 * CT_UNIT; -// // at the lowest possible price, which makes a funding target of 1 bn USD -// const LOW_PRICE: f64 = 0.00001f64; -// -// project_metadata.mainnet_token_max_supply = QUADRILLION_SUPPLY; -// project_metadata.total_allocation_size = QUADRILLION_SUPPLY; -// project_metadata.minimum_price = PriceProviderOf::::calculate_decimals_aware_price( -// FixedU128::from_float(LOW_PRICE), -// USD_DECIMALS, -// CT_DECIMALS, -// ) -// .unwrap(); -// -// inst.mint_plmc_to(default_plmc_balances()); -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// inst.execute(|| { -// assert_ok!(crate::Pallet::::create_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt, -// project_metadata -// )); -// }); -// } -// } -// -// #[cfg(test)] -// mod failure { -// use super::*; -// -// #[test] -// fn non_institutional_credential_fails() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// inst.mint_plmc_to(default_plmc_balances()); -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Retail, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// inst.execute(|| { -// assert_noop!( -// crate::Pallet::::create_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt, -// project_metadata.clone() -// ), -// Error::::WrongInvestorType -// ); -// }); -// -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Professional, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// inst.execute(|| { -// assert_noop!( -// crate::Pallet::::create_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt, -// project_metadata -// ), -// Error::::WrongInvestorType -// ); -// }); -// } -// -// #[test] -// fn did_cannot_have_2_active_projects() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// let ed = inst.get_ed(); -// let issuer_mint: UserToPLMCBalance = (ISSUER_1, ed * 2).into(); -// // Create a first project -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// inst.mint_plmc_to(vec![issuer_mint.clone()]); -// inst.execute(|| { -// assert_ok!(Pallet::::create_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_metadata.clone() -// )); -// }); -// -// // different account, same did -// let jwt = get_mock_jwt_with_cid( -// ISSUER_2, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// inst.execute(|| { -// assert_noop!( -// Pallet::::create_project( -// RuntimeOrigin::signed(ISSUER_2), -// jwt.clone(), -// project_metadata.clone() -// ), -// Error::::HasActiveProject -// ); -// }); -// } -// -// #[test] -// fn not_enough_plmc_for_escrow_ed() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// let ed = inst.get_ed(); -// inst.mint_plmc_to(vec![UserToPLMCBalance::new(ISSUER_1, ed)]); -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// inst.execute(|| { -// assert_noop!( -// Pallet::::create_project(RuntimeOrigin::signed(ISSUER_1), jwt, project_metadata,), -// Error::::IssuerNotEnoughFunds -// ); -// }); -// } -// -// // Invalid metadata tests: -// #[test] -// fn mainnet_supply_less_than_allocation() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let mut project_metadata = default_project_metadata(ISSUER_1); -// project_metadata.total_allocation_size = 100_000_001 * CT_UNIT; -// project_metadata.mainnet_token_max_supply = 100_000_000 * CT_UNIT; -// inst.mint_plmc_to(default_plmc_balances()); -// inst.execute(|| { -// assert_noop!( -// Pallet::::do_create_project( -// &ISSUER_1, -// project_metadata, -// generate_did_from_account(ISSUER_1), -// ), -// Error::::AllocationSizeError -// ); -// }); -// } -// -// #[test] -// fn invalid_ticket_sizes() { -// let correct_project = default_project_metadata(ISSUER_1); -// -// // min in bidding below 5k -// let mut wrong_project_1 = correct_project.clone(); -// wrong_project_1.bidding_ticket_sizes.professional = TicketSize::new(4999 * USD_UNIT, None); -// -// let mut wrong_project_2 = correct_project.clone(); -// wrong_project_2.bidding_ticket_sizes.institutional = TicketSize::new(4999 * USD_UNIT, None); -// -// let mut wrong_project_3 = correct_project.clone(); -// wrong_project_3.bidding_ticket_sizes.professional = TicketSize::new(3000 * USD_UNIT, None); -// wrong_project_3.bidding_ticket_sizes.institutional = TicketSize::new(0 * USD_UNIT, None); -// -// let mut wrong_project_4 = correct_project.clone(); -// wrong_project_4.bidding_ticket_sizes.professional = TicketSize::new(USD_UNIT, None); -// wrong_project_4.bidding_ticket_sizes.institutional = TicketSize::new(USD_UNIT, None); -// -// // min in contributing below 1 USD -// let mut wrong_project_5 = correct_project.clone(); -// wrong_project_5.contributing_ticket_sizes.retail = TicketSize::new(USD_UNIT / 2, None); -// -// let mut wrong_project_6 = correct_project.clone(); -// wrong_project_6.contributing_ticket_sizes.professional = TicketSize::new(USD_UNIT / 2, None); -// -// let mut wrong_project_7 = correct_project.clone(); -// wrong_project_7.contributing_ticket_sizes.institutional = TicketSize::new(USD_UNIT / 2, None); -// -// // min higher than max -// let mut wrong_project_8 = correct_project.clone(); -// wrong_project_8.bidding_ticket_sizes.professional = TicketSize::new(5000 * USD_UNIT, Some(4990 * USD_UNIT)); -// -// let mut wrong_project_9 = correct_project.clone(); -// wrong_project_9.bidding_ticket_sizes.institutional = -// TicketSize::new(6000 * USD_UNIT, Some(5500 * USD_UNIT)); -// -// let mut wrong_project_10 = correct_project.clone(); -// wrong_project_10.contributing_ticket_sizes.retail = TicketSize::new(6000 * USD_UNIT, Some(5500 * USD_UNIT)); -// -// let mut wrong_project_11 = correct_project.clone(); -// wrong_project_11.contributing_ticket_sizes.professional = -// TicketSize::new(6000 * USD_UNIT, Some(5500 * USD_UNIT)); -// -// let mut wrong_project_12 = correct_project.clone(); -// wrong_project_12.contributing_ticket_sizes.professional = -// TicketSize::new(6000 * USD_UNIT, Some(5500 * USD_UNIT)); -// -// let wrong_projects = vec![ -// wrong_project_1.clone(), -// wrong_project_2, -// wrong_project_3.clone(), -// wrong_project_4, -// wrong_project_5, -// wrong_project_6, -// wrong_project_7, -// wrong_project_8, -// wrong_project_9, -// wrong_project_10, -// wrong_project_11, -// wrong_project_12, -// ]; -// -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// inst.mint_plmc_to(default_plmc_balances()); -// -// for project in wrong_projects { -// let project_err = inst.execute(|| { -// Pallet::::do_create_project(&ISSUER_1, project, generate_did_from_account(ISSUER_1)) -// .unwrap_err() -// }); -// assert_eq!(project_err, Error::::TicketSizeError.into()); -// } -// } -// -// #[test] -// fn duplicated_participation_currencies() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let mut issuer = ISSUER_1; -// let default_project_metadata = default_project_metadata(ISSUER_1); -// -// let mut wrong_project_1 = default_project_metadata.clone(); -// wrong_project_1.participation_currencies = -// vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDT].try_into().unwrap(); -// -// let mut wrong_project_2 = default_project_metadata.clone(); -// wrong_project_2.participation_currencies = -// vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDT] -// .try_into() -// .unwrap(); -// -// let mut wrong_project_3 = default_project_metadata.clone(); -// wrong_project_3.participation_currencies = -// vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::USDT] -// .try_into() -// .unwrap(); -// -// let mut wrong_project_4 = default_project_metadata.clone(); -// wrong_project_4.participation_currencies = -// vec![AcceptedFundingAsset::DOT, AcceptedFundingAsset::DOT, AcceptedFundingAsset::USDC] -// .try_into() -// .unwrap(); -// -// let wrong_projects = vec![wrong_project_1, wrong_project_2, wrong_project_3, wrong_project_4]; -// for project in wrong_projects { -// issuer += 1; -// let issuer_mint = (issuer, 1000 * PLMC).into(); -// inst.mint_plmc_to(vec![issuer_mint]); -// let project_err = inst.execute(|| { -// Pallet::::do_create_project(&issuer, project, generate_did_from_account(issuer)) -// .unwrap_err() -// }); -// assert_eq!(project_err, Error::::ParticipationCurrenciesError.into()); -// } -// } -// -// #[test] -// fn price_zero() { -// let mut project_metadata = default_project_metadata(ISSUER_1); -// project_metadata.minimum_price = 0_u128.into(); -// -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// inst.mint_plmc_to(default_plmc_balances()); -// let project_err = inst.execute(|| { -// Pallet::::do_create_project( -// &ISSUER_1, -// project_metadata, -// generate_did_from_account(ISSUER_1), -// ) -// .unwrap_err() -// }); -// assert_eq!(project_err, Error::::PriceTooLow.into()); -// } -// -// #[test] -// fn allocation_zero() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let mut project_metadata = default_project_metadata(ISSUER_1); -// project_metadata.total_allocation_size = 0; -// -// inst.mint_plmc_to(default_plmc_balances()); -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// inst.execute(|| { -// assert_noop!( -// Pallet::::create_project(RuntimeOrigin::signed(ISSUER_1), jwt, project_metadata), -// Error::::AllocationSizeError -// ); -// }); -// } -// -// #[test] -// fn auction_round_percentage_zero() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let mut project_metadata = default_project_metadata(ISSUER_1); -// project_metadata.auction_round_allocation_percentage = Percent::from_percent(0); -// -// inst.mint_plmc_to(default_plmc_balances()); -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// inst.execute(|| { -// assert_noop!( -// Pallet::::create_project(RuntimeOrigin::signed(ISSUER_1), jwt, project_metadata), -// Error::::AuctionRoundPercentageError -// ); -// }); -// } -// -// #[test] -// fn target_funding_less_than_1000_usd() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let mut project_metadata = default_project_metadata(ISSUER_1); -// project_metadata.minimum_price = >::calculate_decimals_aware_price( -// PriceOf::::from_float(1.0), -// USD_DECIMALS, -// CT_DECIMALS, -// ) -// .unwrap(); -// project_metadata.total_allocation_size = 999 * CT_UNIT; -// project_metadata.mainnet_token_max_supply = 999 * CT_UNIT; -// -// inst.mint_plmc_to(default_plmc_balances()); -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// inst.execute(|| { -// assert_noop!( -// Pallet::::create_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_metadata.clone() -// ), -// Error::::FundingTargetTooLow -// ); -// }); -// -// project_metadata.minimum_price = >::calculate_decimals_aware_price( -// PriceOf::::from_float(0.0001), -// USD_DECIMALS, -// CT_DECIMALS, -// ) -// .unwrap(); -// project_metadata.total_allocation_size = 9999999u128 * CT_UNIT; -// project_metadata.mainnet_token_max_supply = 9999999u128 * CT_UNIT; -// inst.execute(|| { -// assert_noop!( -// Pallet::::create_project(RuntimeOrigin::signed(ISSUER_1), jwt, project_metadata), -// Error::::FundingTargetTooLow -// ); -// }); -// } -// -// #[test] -// fn unaccepted_decimal_ranges() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// -// let mut fail_with_decimals = |decimals: u8| { -// let mut project_metadata = default_project_metadata(ISSUER_1); -// project_metadata.token_information.decimals = decimals; -// project_metadata.total_allocation_size = 100_000 * 10u128.pow(decimals.into()); -// project_metadata.mainnet_token_max_supply = project_metadata.total_allocation_size; -// project_metadata.minimum_price = -// ::PriceProvider::calculate_decimals_aware_price( -// PriceOf::::from_float(10_000.0f64), -// USD_DECIMALS, -// project_metadata.token_information.decimals, -// ) -// .unwrap(); -// -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// inst.execute(|| { -// assert_noop!( -// Pallet::::create_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_metadata.clone() -// ), -// Error::::BadDecimals -// ); -// }); -// }; -// -// // less than 6 should fail -// for i in 0..=5 { -// fail_with_decimals(i); -// } -// -// // more than 18 should fail -// for i in 19..=24 { -// fail_with_decimals(i); -// } -// -// let mut issuer = ISSUER_2; -// let mut succeed_with_decimals = |decimals: u8| { -// let mut project_metadata = default_project_metadata(issuer); -// project_metadata.token_information.decimals = decimals; -// project_metadata.total_allocation_size = 100_000 * 10u128.pow(decimals.into()); -// project_metadata.mainnet_token_max_supply = project_metadata.total_allocation_size; -// project_metadata.minimum_price = -// ::PriceProvider::calculate_decimals_aware_price( -// PriceOf::::from_float(1.0), -// USD_DECIMALS, -// project_metadata.token_information.decimals, -// ) -// .unwrap(); -// let jwt = get_mock_jwt_with_cid( -// issuer, -// InvestorType::Institutional, -// generate_did_from_account(issuer), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// -// inst.mint_plmc_to(vec![(issuer, 1000 * PLMC).into()]); -// inst.execute(|| { -// assert_ok!(Pallet::::create_project( -// RuntimeOrigin::signed(issuer), -// jwt.clone(), -// project_metadata.clone() -// )); -// }); -// issuer += 1; -// }; -// // 5 to 20 succeeds -// for i in 6..=18 { -// succeed_with_decimals(i); -// } -// } -// -// #[test] -// fn unaccepted_prices() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let mut issuer = ISSUER_1; -// let mut assert_price = |price: f64, fail: bool| { -// inst.mint_plmc_to(vec![(issuer, 1000 * PLMC).into()]); -// let mut project_metadata = default_project_metadata(issuer); -// -// // Need this helper function because the price provider does not allow prices below 10^-6 -// let calculate_decimals_aware_price = |price: f64, decimals: u8| { -// let price = PriceOf::::from_float(price); -// let usd_unit = 10u128.checked_pow(USD_DECIMALS.into()).unwrap(); -// let usd_price_with_decimals = price.checked_mul_int(usd_unit * 1_000_000).unwrap(); -// let asset_unit = 10u128.checked_pow(decimals.into()).unwrap(); -// -// let divisor = FixedU128::from_float(1_000_000f64); -// -// FixedU128::checked_from_rational(usd_price_with_decimals, asset_unit).unwrap().div(divisor) -// }; -// -// project_metadata.minimum_price = -// calculate_decimals_aware_price(price, project_metadata.token_information.decimals); -// project_metadata.total_allocation_size = -// project_metadata.minimum_price.reciprocal().unwrap().saturating_mul_int(100_000 * USD_UNIT); -// project_metadata.mainnet_token_max_supply = project_metadata.total_allocation_size; -// -// let jwt = get_mock_jwt_with_cid( -// issuer, -// InvestorType::Institutional, -// generate_did_from_account(issuer), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// -// let run_extrinsic = || { -// Pallet::::create_project( -// RuntimeOrigin::signed(issuer), -// jwt.clone(), -// project_metadata.clone(), -// ) -// }; -// inst.execute(|| { -// if fail { -// assert_noop!(run_extrinsic(), Error::::BadTokenomics,); -// } else { -// assert_ok!(run_extrinsic()); -// } -// }); -// issuer += 1; -// }; -// -// let low_prices = vec![0.0000001, 0.000001]; -// let high_prices = vec![10_000f64, 100_000f64]; -// let right_prices = vec![0.00001, 0.001, 0.01, 0.1, 1.0, 10.0, 100f64, 1_000f64]; -// -// for price in low_prices { -// assert_price(price, true); -// } -// for price in high_prices { -// assert_price(price, true); -// } -// for price in right_prices { -// assert_price(price, false); -// } -// } -// -// #[test] -// fn allocation_smaller_than_decimals() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let mut project_metadata = default_project_metadata(ISSUER_1); -// project_metadata.total_allocation_size = 2_000_000; -// project_metadata.token_information.decimals = 8; -// project_metadata.minimum_price = ::PriceProvider::calculate_decimals_aware_price( -// PriceOf::::from_float(100_000.0f64), -// USD_DECIMALS, -// project_metadata.token_information.decimals, -// ) -// .unwrap(); -// -// inst.mint_plmc_to(default_plmc_balances()); -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// -// inst.execute(|| { -// assert_noop!( -// Pallet::::create_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_metadata.clone() -// ), -// Error::::AllocationSizeError -// ); -// }); -// } -// } -// } -// -// #[cfg(test)] -// mod edit_project_extrinsic { -// use super::*; -// -// #[cfg(test)] -// mod success { -// use super::*; -// use polimec_common::USD_DECIMALS; -// use polimec_common_test_utils::get_mock_jwt; -// #[test] -// fn project_id_stays_the_same() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let mut project_metadata = default_project_metadata(ISSUER_1); -// inst.mint_plmc_to(default_plmc_balances()); -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); -// -// project_metadata.minimum_price = PriceProviderOf::::calculate_decimals_aware_price( -// PriceOf::::from_float(15.0), -// USD_DECIMALS, -// CT_DECIMALS, -// ) -// .unwrap(); -// assert_ok!(inst.execute(|| crate::Pallet::::edit_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_id, -// project_metadata.clone() -// ))); -// let next_project_id = inst.execute(|| NextProjectId::::get()); -// assert_eq!(project_id, next_project_id - 1); -// let projects_details = inst.execute(|| ProjectsDetails::::iter_keys().collect_vec()); -// let project_metadatas = inst.execute(|| ProjectsMetadata::::iter_keys().collect_vec()); -// assert_eq!(projects_details, vec![project_id]); -// assert_eq!(project_metadatas, vec![project_id]); -// } -// -// #[test] -// fn multiple_fields_edited() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// inst.mint_plmc_to(default_plmc_balances()); -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); -// let mut new_metadata_1 = project_metadata.clone(); -// let new_policy_hash = ipfs_hash(); -// new_metadata_1.minimum_price = PriceProviderOf::::calculate_decimals_aware_price( -// PriceOf::::from_float(15.0), -// USD_DECIMALS, -// CT_DECIMALS, -// ) -// .unwrap(); -// let new_metadata_2 = ProjectMetadataOf:: { -// token_information: CurrencyMetadata { -// name: BoundedVec::try_from("Changed Name".as_bytes().to_vec()).unwrap(), -// symbol: BoundedVec::try_from("CN".as_bytes().to_vec()).unwrap(), -// decimals: 12, -// }, -// mainnet_token_max_supply: 100_000_000 * CT_UNIT, -// total_allocation_size: 5_000_000 * CT_UNIT, -// auction_round_allocation_percentage: Percent::from_percent(30u8), -// minimum_price: PriceProviderOf::::calculate_decimals_aware_price( -// PriceOf::::from_float(20.0), -// USD_DECIMALS, -// CT_DECIMALS, -// ) -// .unwrap(), -// bidding_ticket_sizes: BiddingTicketSizes { -// professional: TicketSize::new(10_000 * USD_UNIT, Some(20_000 * USD_UNIT)), -// institutional: TicketSize::new(20_000 * USD_UNIT, Some(30_000 * USD_UNIT)), -// phantom: Default::default(), -// }, -// contributing_ticket_sizes: ContributingTicketSizes { -// retail: TicketSize::new(1_000 * USD_UNIT, Some(2_000 * USD_UNIT)), -// professional: TicketSize::new(2_000 * USD_UNIT, Some(3_000 * USD_UNIT)), -// institutional: TicketSize::new(3_000 * USD_UNIT, Some(4_000 * USD_UNIT)), -// phantom: Default::default(), -// }, -// participation_currencies: vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC] -// .try_into() -// .unwrap(), -// -// funding_destination_account: ISSUER_2, -// policy_ipfs_cid: Some(new_policy_hash), -// }; -// -// // No fields changed -// assert_ok!(inst.execute(|| crate::Pallet::::edit_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_id, -// project_metadata.clone() -// ))); -// inst.execute(|| { -// find_event!(TestRuntime, Event::::MetadataEdited{project_id, ref metadata}, project_id == 0, metadata == &project_metadata); -// }); -// -// // Just one field changed -// assert_ok!(inst.execute(|| crate::Pallet::::edit_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_id, -// new_metadata_1.clone() -// ))); -// assert_eq!(inst.get_project_metadata(project_id), new_metadata_1); -// inst.execute(|| { -// find_event!(TestRuntime, Event::::MetadataEdited{project_id, ref metadata}, project_id == 0, metadata == &new_metadata_1); -// }); -// -// // All fields changed -// assert_ok!(inst.execute(|| crate::Pallet::::edit_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_id, -// new_metadata_2.clone() -// ))); -// assert_eq!(inst.get_project_metadata(project_id), new_metadata_2); -// inst.execute(|| { -// find_event!(TestRuntime, Event::::MetadataEdited{project_id, ref metadata}, project_id == 0, metadata == &new_metadata_2); -// }); -// } -// -// #[test] -// fn adding_project_policy() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let mut project_metadata = default_project_metadata(ISSUER_1); -// project_metadata.policy_ipfs_cid = None; -// inst.mint_plmc_to(default_plmc_balances()); -// let jwt = get_mock_jwt(ISSUER_1, InvestorType::Institutional, generate_did_from_account(ISSUER_1)); -// let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); -// let mut new_metadata = project_metadata.clone(); -// let new_policy_hash = ipfs_hash(); -// new_metadata.policy_ipfs_cid = Some(new_policy_hash); -// assert_ok!(inst.execute(|| crate::Pallet::::edit_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_id, -// new_metadata.clone() -// ))); -// assert_eq!(inst.get_project_metadata(project_id), new_metadata); -// } -// -// #[test] -// fn storage_changes() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// inst.mint_plmc_to(default_plmc_balances()); -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); -// let mut new_metadata = project_metadata.clone(); -// -// let new_price = PriceOf::::from_float(1f64); -// new_metadata.minimum_price = ::PriceProvider::calculate_decimals_aware_price( -// new_price, -// USD_DECIMALS, -// new_metadata.token_information.decimals, -// ) -// .unwrap(); -// new_metadata.total_allocation_size = 100_000 * CT_UNIT; -// assert_ok!(inst.execute(|| crate::Pallet::::edit_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_id, -// new_metadata.clone() -// ))); -// // Project details reflect changes -// assert_eq!(inst.get_project_details(project_id).fundraising_target_usd, 100_000 * USD_UNIT); -// // Bucket reflects changes -// let new_bucket = Pallet::::create_bucket_from_metadata(&new_metadata).unwrap(); -// let stored_bucket = inst.execute(|| Buckets::::get(project_id).unwrap()); -// assert_eq!(stored_bucket, new_bucket); -// // Event emitted -// inst.execute(|| { -// find_event!(TestRuntime, Event::::MetadataEdited{project_id, ref metadata}, project_id == 0, metadata == &new_metadata); -// }); -// } -// } -// -// #[cfg(test)] -// mod failure { -// use super::*; -// -// #[test] -// fn called_by_different_issuer() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let ed = inst.get_ed(); -// let issuer_1_mint: UserToPLMCBalance = (ISSUER_1, ed).into(); -// let issuer_2_mint: UserToPLMCBalance = (ISSUER_2, ed).into(); -// -// let project_metadata_1 = default_project_metadata(ISSUER_1); -// let project_metadata_2 = default_project_metadata(ISSUER_2); -// -// inst.mint_plmc_to(vec![issuer_1_mint.clone(), issuer_2_mint.clone()]); -// -// let jwt_1 = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_1), -// project_metadata_1.clone().policy_ipfs_cid.unwrap(), -// ); -// let jwt_2 = get_mock_jwt_with_cid( -// ISSUER_2, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_2), -// project_metadata_2.clone().policy_ipfs_cid.unwrap(), -// ); -// -// let project_id_1 = inst.create_new_project(project_metadata_1.clone(), ISSUER_1, None); -// let project_id_2 = inst.create_new_project(project_metadata_2.clone(), ISSUER_2, None); -// -// inst.execute(|| { -// assert_noop!( -// Pallet::::edit_project( -// RuntimeOrigin::signed(ISSUER_2), -// jwt_2, -// project_id_1, -// project_metadata_2 -// ), -// Error::::NotIssuer -// ); -// assert_noop!( -// Pallet::::edit_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt_1, -// project_id_2, -// project_metadata_1 -// ), -// Error::::NotIssuer -// ); -// }); -// } -// -// #[test] -// fn evaluation_already_started() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// inst.mint_plmc_to(default_plmc_balances()); -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_1), -// 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(); -// inst.execute(|| { -// assert_noop!( -// crate::Pallet::::edit_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_id, -// project_metadata.clone() -// ), -// Error::::ProjectIsFrozen -// ); -// }); -// } -// -// #[test] -// fn non_institutional_credential() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// inst.mint_plmc_to(default_plmc_balances()); -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Retail, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// -// let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); -// -// inst.execute(|| { -// assert_noop!( -// crate::Pallet::::edit_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt, -// project_id, -// project_metadata.clone() -// ), -// Error::::WrongInvestorType -// ); -// }); -// -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Professional, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// inst.execute(|| { -// assert_noop!( -// crate::Pallet::::edit_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt, -// project_id, -// project_metadata -// ), -// Error::::WrongInvestorType -// ); -// }); -// } -// } -// } -// -// #[cfg(test)] -// mod remove_project_extrinsic { -// use super::*; -// -// #[cfg(test)] -// mod success { -// use super::*; -// -// #[test] -// fn normal_remove() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// inst.mint_plmc_to(default_plmc_balances()); -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); -// assert_ok!(inst.execute(|| crate::Pallet::::remove_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_id -// ))); -// inst.execute(|| { -// assert!(ProjectsDetails::::get(project_id).is_none()); -// assert!(ProjectsMetadata::::get(project_id).is_none()); -// assert!(Buckets::::get(project_id).is_none()); -// assert!(DidWithActiveProjects::::get(generate_did_from_account(ISSUER_1)).is_none()); -// }); -// } -// -// #[test] -// fn can_create_after_remove() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// let ed = inst.get_ed(); -// let issuer_mint: UserToPLMCBalance = (ISSUER_1, ed * 2).into(); -// // Create a first project -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// inst.mint_plmc_to(vec![issuer_mint.clone()]); -// inst.execute(|| { -// assert_ok!(Pallet::::create_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_metadata.clone() -// )); -// }); -// -// // Same account same did -// inst.mint_plmc_to(vec![issuer_mint.clone()]); -// inst.execute(|| { -// assert_noop!( -// Pallet::::create_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_metadata.clone() -// ), -// Error::::HasActiveProject -// ); -// }); -// -// // Remove the first project -// inst.execute(|| { -// assert_ok!(Pallet::::remove_project(RuntimeOrigin::signed(ISSUER_1), jwt.clone(), 0)); -// }); -// -// // Create a second project -// inst.execute(|| { -// assert_ok!(Pallet::::create_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_metadata.clone() -// )); -// }); -// } -// } -// -// #[cfg(test)] -// mod failure { -// use super::*; -// -// #[test] -// fn non_issuer_credential() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// inst.mint_plmc_to(default_plmc_balances()); -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Professional, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); -// inst.execute(|| { -// assert_noop!( -// crate::Pallet::::remove_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_id -// ), -// Error::::WrongInvestorType -// ); -// }); -// } -// -// #[test] -// fn different_account() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// inst.mint_plmc_to(default_plmc_balances()); -// let jwt = get_mock_jwt_with_cid( -// ISSUER_2, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_2), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// -// let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); -// -// inst.execute(|| { -// assert_noop!( -// crate::Pallet::::remove_project( -// RuntimeOrigin::signed(ISSUER_2), -// jwt.clone(), -// project_id -// ), -// Error::::NotIssuer -// ); -// }); -// } -// -// #[test] -// fn evaluation_already_started() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// inst.mint_plmc_to(default_plmc_balances()); -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_1), -// 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(); -// inst.execute(|| { -// assert_noop!( -// crate::Pallet::::remove_project( -// RuntimeOrigin::signed(ISSUER_1), -// jwt.clone(), -// project_id, -// ), -// Error::::ProjectIsFrozen -// ); -// }); -// } -// } -// } +use super::*; +use polimec_common::credentials::InvestorType; +use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt_with_cid}; + +#[cfg(test)] +mod round_flow { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + + #[test] + fn application_round_completed() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + + let issuer = ISSUER_1; + let project_metadata = default_project_metadata(issuer); + + inst.create_evaluating_project(project_metadata, issuer, None); + } + } +} + +#[cfg(test)] +mod create_project_extrinsic { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + use polimec_common_test_utils::get_mock_jwt_with_cid; + + #[test] + fn project_id_autoincrement_works() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_1 = default_project_metadata(ISSUER_1); + let project_2 = default_project_metadata(ISSUER_2); + let project_3 = default_project_metadata(ISSUER_3); + + let created_project_1_id = inst.create_evaluating_project(project_1, ISSUER_1, None); + let created_project_2_id = inst.create_evaluating_project(project_2, ISSUER_2, None); + let created_project_3_id = inst.create_evaluating_project(project_3, ISSUER_3, None); + + assert_eq!(created_project_1_id, 0); + assert_eq!(created_project_2_id, 1); + assert_eq!(created_project_3_id, 2); + } + + #[test] + fn multiple_creations_different_issuers() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let mut issuer = ISSUER_1; + for _ in 0..512 { + let project_metadata = default_project_metadata(issuer); + inst.create_evaluating_project(project_metadata, issuer, None); + inst.advance_time(1u64); + issuer += 1; + } + } + + #[test] + fn multiple_funding_currencies() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let default_project_metadata = default_project_metadata(ISSUER_1); + + let mut one_currency_1 = default_project_metadata.clone(); + one_currency_1.participation_currencies = vec![AcceptedFundingAsset::USDT].try_into().unwrap(); + + let mut one_currency_2 = default_project_metadata.clone(); + one_currency_2.participation_currencies = vec![AcceptedFundingAsset::USDC].try_into().unwrap(); + + let mut one_currency_3 = default_project_metadata.clone(); + one_currency_3.participation_currencies = vec![AcceptedFundingAsset::DOT].try_into().unwrap(); + + let mut two_currencies_1 = default_project_metadata.clone(); + two_currencies_1.participation_currencies = + vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC].try_into().unwrap(); + + let mut two_currencies_2 = default_project_metadata.clone(); + two_currencies_2.participation_currencies = + vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT].try_into().unwrap(); + + let mut two_currencies_3 = default_project_metadata.clone(); + two_currencies_3.participation_currencies = + vec![AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT].try_into().unwrap(); + + let mut three_currencies = default_project_metadata.clone(); + three_currencies.participation_currencies = + vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT] + .try_into() + .unwrap(); + + let projects = vec![ + one_currency_1.clone(), + one_currency_2.clone(), + one_currency_3, + two_currencies_1, + two_currencies_2, + two_currencies_3, + three_currencies, + ]; + + let mut issuer = ISSUER_1; + for project in projects { + issuer += 1; + let issuer_mint = (issuer, 1000 * PLMC).into(); + inst.mint_plmc_to(vec![issuer_mint]); + assert_ok!(inst.execute(|| { + Pallet::::do_create_project(&issuer, project, generate_did_from_account(issuer)) + })); + } + } + + #[test] + fn issuer_can_create_second_project_after_first_is_inactive() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let issuer: AccountId = ISSUER_1; + let did: Did = BoundedVec::new(); + let project_metadata: ProjectMetadataOf = default_project_metadata(issuer); + let jwt: UntrustedToken = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Institutional, + did, + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + + 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_funding_asset_to(default_usdt_balances()); + + // Cannot create 2 projects consecutively + inst.execute(|| { + assert_ok!(Pallet::::create_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_metadata.clone() + )); + }); + inst.execute(|| { + assert_noop!( + Pallet::::create_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_metadata.clone() + ), + Error::::HasActiveProject + ); + }); + + // A Project is "inactive" after the evaluation fails + assert_eq!(inst.go_to_next_state(0), ProjectStatus::EvaluationRound); + + inst.execute(|| { + assert_noop!( + Pallet::::create_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_metadata.clone() + ), + Error::::HasActiveProject + ); + }); + assert_eq!(inst.go_to_next_state(0), ProjectStatus::FundingFailed); + + inst.execute(|| { + assert_ok!(Pallet::::create_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_metadata.clone() + )); + }); + + // A Project is "inactive" after the funding fails + assert_eq!(inst.go_to_next_state(1), ProjectStatus::EvaluationRound); + + inst.evaluate_for_users(1, default_evaluations()).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(); + + assert!(matches!(inst.go_to_next_state(1), ProjectStatus::CommunityRound(_))); + inst.execute(|| { + assert_noop!( + Pallet::::create_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_metadata.clone() + ), + Error::::HasActiveProject + ); + }); + assert_eq!(inst.go_to_next_state(1), ProjectStatus::FundingFailed); + inst.execute(|| { + assert_ok!(Pallet::::create_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_metadata.clone() + )); + }); + + // A project is "inactive" after the funding succeeds + assert_eq!(inst.go_to_next_state(2), ProjectStatus::EvaluationRound); + inst.evaluate_for_users(2, default_evaluations()).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(); + + 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(), + project_metadata.clone() + ))); + } + + #[test] + fn shitcoin_tokenomics() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let mut project_metadata = default_project_metadata(ISSUER_1); + + // funding target of 1000 USD at 100 trillion supply + const QUADRILLION_SUPPLY: u128 = 100_000_000_000_000 * CT_UNIT; + // at the lowest possible price, which makes a funding target of 1 bn USD + const LOW_PRICE: f64 = 0.00001f64; + + project_metadata.mainnet_token_max_supply = QUADRILLION_SUPPLY; + project_metadata.total_allocation_size = QUADRILLION_SUPPLY; + project_metadata.minimum_price = PriceProviderOf::::calculate_decimals_aware_price( + FixedU128::from_float(LOW_PRICE), + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + + inst.mint_plmc_to(default_plmc_balances()); + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Institutional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + inst.execute(|| { + assert_ok!(crate::Pallet::::create_project( + RuntimeOrigin::signed(ISSUER_1), + jwt, + project_metadata + )); + }); + } + } + + #[cfg(test)] + mod failure { + use super::*; + + #[test] + fn non_institutional_credential_fails() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + inst.mint_plmc_to(default_plmc_balances()); + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Retail, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + inst.execute(|| { + assert_noop!( + crate::Pallet::::create_project( + RuntimeOrigin::signed(ISSUER_1), + jwt, + project_metadata.clone() + ), + Error::::WrongInvestorType + ); + }); + + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Professional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + inst.execute(|| { + assert_noop!( + crate::Pallet::::create_project( + RuntimeOrigin::signed(ISSUER_1), + jwt, + project_metadata + ), + Error::::WrongInvestorType + ); + }); + } + + #[test] + fn did_cannot_have_2_active_projects() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + let ed = inst.get_ed(); + let issuer_mint: UserToPLMCBalance = (ISSUER_1, ed * 2).into(); + // Create a first project + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Institutional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + inst.mint_plmc_to(vec![issuer_mint.clone()]); + inst.execute(|| { + assert_ok!(Pallet::::create_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_metadata.clone() + )); + }); + + // different account, same did + let jwt = get_mock_jwt_with_cid( + ISSUER_2, + InvestorType::Institutional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + inst.execute(|| { + assert_noop!( + Pallet::::create_project( + RuntimeOrigin::signed(ISSUER_2), + jwt.clone(), + project_metadata.clone() + ), + Error::::HasActiveProject + ); + }); + } + + #[test] + fn not_enough_plmc_for_escrow_ed() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + let ed = inst.get_ed(); + inst.mint_plmc_to(vec![UserToPLMCBalance::new(ISSUER_1, ed)]); + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Institutional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + inst.execute(|| { + assert_noop!( + Pallet::::create_project(RuntimeOrigin::signed(ISSUER_1), jwt, project_metadata,), + Error::::IssuerNotEnoughFunds + ); + }); + } + + // Invalid metadata tests: + #[test] + fn mainnet_supply_less_than_allocation() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.total_allocation_size = 100_000_001 * CT_UNIT; + project_metadata.mainnet_token_max_supply = 100_000_000 * CT_UNIT; + inst.mint_plmc_to(default_plmc_balances()); + inst.execute(|| { + assert_noop!( + Pallet::::do_create_project( + &ISSUER_1, + project_metadata, + generate_did_from_account(ISSUER_1), + ), + Error::::AllocationSizeError + ); + }); + } + + #[test] + fn invalid_ticket_sizes() { + let correct_project = default_project_metadata(ISSUER_1); + + // min in bidding below 5k + let mut wrong_project_1 = correct_project.clone(); + wrong_project_1.bidding_ticket_sizes.professional = TicketSize::new(4999 * USD_UNIT, None); + + let mut wrong_project_2 = correct_project.clone(); + wrong_project_2.bidding_ticket_sizes.institutional = TicketSize::new(4999 * USD_UNIT, None); + + let mut wrong_project_3 = correct_project.clone(); + wrong_project_3.bidding_ticket_sizes.professional = TicketSize::new(3000 * USD_UNIT, None); + wrong_project_3.bidding_ticket_sizes.institutional = TicketSize::new(0 * USD_UNIT, None); + + let mut wrong_project_4 = correct_project.clone(); + wrong_project_4.bidding_ticket_sizes.professional = TicketSize::new(USD_UNIT, None); + wrong_project_4.bidding_ticket_sizes.institutional = TicketSize::new(USD_UNIT, None); + + // min in contributing below 1 USD + let mut wrong_project_5 = correct_project.clone(); + wrong_project_5.contributing_ticket_sizes.retail = TicketSize::new(USD_UNIT / 2, None); + + let mut wrong_project_6 = correct_project.clone(); + wrong_project_6.contributing_ticket_sizes.professional = TicketSize::new(USD_UNIT / 2, None); + + let mut wrong_project_7 = correct_project.clone(); + wrong_project_7.contributing_ticket_sizes.institutional = TicketSize::new(USD_UNIT / 2, None); + + // min higher than max + let mut wrong_project_8 = correct_project.clone(); + wrong_project_8.bidding_ticket_sizes.professional = TicketSize::new(5000 * USD_UNIT, Some(4990 * USD_UNIT)); + + let mut wrong_project_9 = correct_project.clone(); + wrong_project_9.bidding_ticket_sizes.institutional = + TicketSize::new(6000 * USD_UNIT, Some(5500 * USD_UNIT)); + + let mut wrong_project_10 = correct_project.clone(); + wrong_project_10.contributing_ticket_sizes.retail = TicketSize::new(6000 * USD_UNIT, Some(5500 * USD_UNIT)); + + let mut wrong_project_11 = correct_project.clone(); + wrong_project_11.contributing_ticket_sizes.professional = + TicketSize::new(6000 * USD_UNIT, Some(5500 * USD_UNIT)); + + let mut wrong_project_12 = correct_project.clone(); + wrong_project_12.contributing_ticket_sizes.professional = + TicketSize::new(6000 * USD_UNIT, Some(5500 * USD_UNIT)); + + let wrong_projects = vec![ + wrong_project_1.clone(), + wrong_project_2, + wrong_project_3.clone(), + wrong_project_4, + wrong_project_5, + wrong_project_6, + wrong_project_7, + wrong_project_8, + wrong_project_9, + wrong_project_10, + wrong_project_11, + wrong_project_12, + ]; + + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + inst.mint_plmc_to(default_plmc_balances()); + + for project in wrong_projects { + let project_err = inst.execute(|| { + Pallet::::do_create_project(&ISSUER_1, project, generate_did_from_account(ISSUER_1)) + .unwrap_err() + }); + assert_eq!(project_err, Error::::TicketSizeError.into()); + } + } + + #[test] + fn duplicated_participation_currencies() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let mut issuer = ISSUER_1; + let default_project_metadata = default_project_metadata(ISSUER_1); + + let mut wrong_project_1 = default_project_metadata.clone(); + wrong_project_1.participation_currencies = + vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDT].try_into().unwrap(); + + let mut wrong_project_2 = default_project_metadata.clone(); + wrong_project_2.participation_currencies = + vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDT] + .try_into() + .unwrap(); + + let mut wrong_project_3 = default_project_metadata.clone(); + wrong_project_3.participation_currencies = + vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::USDT] + .try_into() + .unwrap(); + + let mut wrong_project_4 = default_project_metadata.clone(); + wrong_project_4.participation_currencies = + vec![AcceptedFundingAsset::DOT, AcceptedFundingAsset::DOT, AcceptedFundingAsset::USDC] + .try_into() + .unwrap(); + + let wrong_projects = vec![wrong_project_1, wrong_project_2, wrong_project_3, wrong_project_4]; + for project in wrong_projects { + issuer += 1; + let issuer_mint = (issuer, 1000 * PLMC).into(); + inst.mint_plmc_to(vec![issuer_mint]); + let project_err = inst.execute(|| { + Pallet::::do_create_project(&issuer, project, generate_did_from_account(issuer)) + .unwrap_err() + }); + assert_eq!(project_err, Error::::ParticipationCurrenciesError.into()); + } + } + + #[test] + fn price_zero() { + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.minimum_price = 0_u128.into(); + + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + inst.mint_plmc_to(default_plmc_balances()); + let project_err = inst.execute(|| { + Pallet::::do_create_project( + &ISSUER_1, + project_metadata, + generate_did_from_account(ISSUER_1), + ) + .unwrap_err() + }); + assert_eq!(project_err, Error::::PriceTooLow.into()); + } + + #[test] + fn allocation_zero() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.total_allocation_size = 0; + + inst.mint_plmc_to(default_plmc_balances()); + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Institutional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + inst.execute(|| { + assert_noop!( + Pallet::::create_project(RuntimeOrigin::signed(ISSUER_1), jwt, project_metadata), + Error::::AllocationSizeError + ); + }); + } + + #[test] + fn auction_round_percentage_zero() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.auction_round_allocation_percentage = Percent::from_percent(0); + + inst.mint_plmc_to(default_plmc_balances()); + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Institutional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + inst.execute(|| { + assert_noop!( + Pallet::::create_project(RuntimeOrigin::signed(ISSUER_1), jwt, project_metadata), + Error::::AuctionRoundPercentageError + ); + }); + } + + #[test] + fn target_funding_less_than_1000_usd() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.minimum_price = >::calculate_decimals_aware_price( + PriceOf::::from_float(1.0), + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + project_metadata.total_allocation_size = 999 * CT_UNIT; + project_metadata.mainnet_token_max_supply = 999 * CT_UNIT; + + inst.mint_plmc_to(default_plmc_balances()); + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Institutional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + inst.execute(|| { + assert_noop!( + Pallet::::create_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_metadata.clone() + ), + Error::::FundingTargetTooLow + ); + }); + + project_metadata.minimum_price = >::calculate_decimals_aware_price( + PriceOf::::from_float(0.0001), + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + project_metadata.total_allocation_size = 9999999u128 * CT_UNIT; + project_metadata.mainnet_token_max_supply = 9999999u128 * CT_UNIT; + inst.execute(|| { + assert_noop!( + Pallet::::create_project(RuntimeOrigin::signed(ISSUER_1), jwt, project_metadata), + Error::::FundingTargetTooLow + ); + }); + } + + #[test] + fn unaccepted_decimal_ranges() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + + let mut fail_with_decimals = |decimals: u8| { + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.token_information.decimals = decimals; + project_metadata.total_allocation_size = 100_000 * 10u128.pow(decimals.into()); + project_metadata.mainnet_token_max_supply = project_metadata.total_allocation_size; + project_metadata.minimum_price = + ::PriceProvider::calculate_decimals_aware_price( + PriceOf::::from_float(10_000.0f64), + USD_DECIMALS, + project_metadata.token_information.decimals, + ) + .unwrap(); + + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Institutional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + inst.execute(|| { + assert_noop!( + Pallet::::create_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_metadata.clone() + ), + Error::::BadDecimals + ); + }); + }; + + // less than 6 should fail + for i in 0..=5 { + fail_with_decimals(i); + } + + // more than 18 should fail + for i in 19..=24 { + fail_with_decimals(i); + } + + let mut issuer = ISSUER_2; + let mut succeed_with_decimals = |decimals: u8| { + let mut project_metadata = default_project_metadata(issuer); + project_metadata.token_information.decimals = decimals; + project_metadata.total_allocation_size = 100_000 * 10u128.pow(decimals.into()); + project_metadata.mainnet_token_max_supply = project_metadata.total_allocation_size; + project_metadata.minimum_price = + ::PriceProvider::calculate_decimals_aware_price( + PriceOf::::from_float(1.0), + USD_DECIMALS, + project_metadata.token_information.decimals, + ) + .unwrap(); + let jwt = get_mock_jwt_with_cid( + issuer, + InvestorType::Institutional, + generate_did_from_account(issuer), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + + inst.mint_plmc_to(vec![(issuer, 1000 * PLMC).into()]); + inst.execute(|| { + assert_ok!(Pallet::::create_project( + RuntimeOrigin::signed(issuer), + jwt.clone(), + project_metadata.clone() + )); + }); + issuer += 1; + }; + // 5 to 20 succeeds + for i in 6..=18 { + succeed_with_decimals(i); + } + } + + #[test] + fn unaccepted_prices() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let mut issuer = ISSUER_1; + let mut assert_price = |price: f64, fail: bool| { + inst.mint_plmc_to(vec![(issuer, 1000 * PLMC).into()]); + let mut project_metadata = default_project_metadata(issuer); + + // Need this helper function because the price provider does not allow prices below 10^-6 + let calculate_decimals_aware_price = |price: f64, decimals: u8| { + let price = PriceOf::::from_float(price); + let usd_unit = 10u128.checked_pow(USD_DECIMALS.into()).unwrap(); + let usd_price_with_decimals = price.checked_mul_int(usd_unit * 1_000_000).unwrap(); + let asset_unit = 10u128.checked_pow(decimals.into()).unwrap(); + + let divisor = FixedU128::from_float(1_000_000f64); + + FixedU128::checked_from_rational(usd_price_with_decimals, asset_unit).unwrap().div(divisor) + }; + + project_metadata.minimum_price = + calculate_decimals_aware_price(price, project_metadata.token_information.decimals); + project_metadata.total_allocation_size = + project_metadata.minimum_price.reciprocal().unwrap().saturating_mul_int(100_000 * USD_UNIT); + project_metadata.mainnet_token_max_supply = project_metadata.total_allocation_size; + + let jwt = get_mock_jwt_with_cid( + issuer, + InvestorType::Institutional, + generate_did_from_account(issuer), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + + let run_extrinsic = || { + Pallet::::create_project( + RuntimeOrigin::signed(issuer), + jwt.clone(), + project_metadata.clone(), + ) + }; + inst.execute(|| { + if fail { + assert_noop!(run_extrinsic(), Error::::BadTokenomics,); + } else { + assert_ok!(run_extrinsic()); + } + }); + issuer += 1; + }; + + let low_prices = vec![0.0000001, 0.000001]; + let high_prices = vec![10_000f64, 100_000f64]; + let right_prices = vec![0.00001, 0.001, 0.01, 0.1, 1.0, 10.0, 100f64, 1_000f64]; + + for price in low_prices { + assert_price(price, true); + } + for price in high_prices { + assert_price(price, true); + } + for price in right_prices { + assert_price(price, false); + } + } + + #[test] + fn allocation_smaller_than_decimals() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.total_allocation_size = 2_000_000; + project_metadata.token_information.decimals = 8; + project_metadata.minimum_price = ::PriceProvider::calculate_decimals_aware_price( + PriceOf::::from_float(100_000.0f64), + USD_DECIMALS, + project_metadata.token_information.decimals, + ) + .unwrap(); + + inst.mint_plmc_to(default_plmc_balances()); + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Institutional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + + inst.execute(|| { + assert_noop!( + Pallet::::create_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_metadata.clone() + ), + Error::::AllocationSizeError + ); + }); + } + } +} + +#[cfg(test)] +mod edit_project_extrinsic { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + use polimec_common::USD_DECIMALS; + use polimec_common_test_utils::get_mock_jwt; + #[test] + fn project_id_stays_the_same() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let mut project_metadata = default_project_metadata(ISSUER_1); + inst.mint_plmc_to(default_plmc_balances()); + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Institutional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); + + project_metadata.minimum_price = PriceProviderOf::::calculate_decimals_aware_price( + PriceOf::::from_float(15.0), + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + assert_ok!(inst.execute(|| crate::Pallet::::edit_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_id, + project_metadata.clone() + ))); + let next_project_id = inst.execute(|| NextProjectId::::get()); + assert_eq!(project_id, next_project_id - 1); + let projects_details = inst.execute(|| ProjectsDetails::::iter_keys().collect_vec()); + let project_metadatas = inst.execute(|| ProjectsMetadata::::iter_keys().collect_vec()); + assert_eq!(projects_details, vec![project_id]); + assert_eq!(project_metadatas, vec![project_id]); + } + + #[test] + fn multiple_fields_edited() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + inst.mint_plmc_to(default_plmc_balances()); + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Institutional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); + let mut new_metadata_1 = project_metadata.clone(); + let new_policy_hash = ipfs_hash(); + new_metadata_1.minimum_price = PriceProviderOf::::calculate_decimals_aware_price( + PriceOf::::from_float(15.0), + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + let new_metadata_2 = ProjectMetadataOf:: { + token_information: CurrencyMetadata { + name: BoundedVec::try_from("Changed Name".as_bytes().to_vec()).unwrap(), + symbol: BoundedVec::try_from("CN".as_bytes().to_vec()).unwrap(), + decimals: 12, + }, + mainnet_token_max_supply: 100_000_000 * CT_UNIT, + total_allocation_size: 5_000_000 * CT_UNIT, + auction_round_allocation_percentage: Percent::from_percent(30u8), + minimum_price: PriceProviderOf::::calculate_decimals_aware_price( + PriceOf::::from_float(20.0), + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(), + bidding_ticket_sizes: BiddingTicketSizes { + professional: TicketSize::new(10_000 * USD_UNIT, Some(20_000 * USD_UNIT)), + institutional: TicketSize::new(20_000 * USD_UNIT, Some(30_000 * USD_UNIT)), + phantom: Default::default(), + }, + contributing_ticket_sizes: ContributingTicketSizes { + retail: TicketSize::new(1_000 * USD_UNIT, Some(2_000 * USD_UNIT)), + professional: TicketSize::new(2_000 * USD_UNIT, Some(3_000 * USD_UNIT)), + institutional: TicketSize::new(3_000 * USD_UNIT, Some(4_000 * USD_UNIT)), + phantom: Default::default(), + }, + participation_currencies: vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC] + .try_into() + .unwrap(), + + funding_destination_account: ISSUER_2, + policy_ipfs_cid: Some(new_policy_hash), + }; + + // No fields changed + assert_ok!(inst.execute(|| crate::Pallet::::edit_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_id, + project_metadata.clone() + ))); + inst.execute(|| { + find_event!(TestRuntime, Event::::MetadataEdited{project_id, ref metadata}, project_id == 0, metadata == &project_metadata); + }); + + // Just one field changed + assert_ok!(inst.execute(|| crate::Pallet::::edit_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_id, + new_metadata_1.clone() + ))); + assert_eq!(inst.get_project_metadata(project_id), new_metadata_1); + inst.execute(|| { + find_event!(TestRuntime, Event::::MetadataEdited{project_id, ref metadata}, project_id == 0, metadata == &new_metadata_1); + }); + + // All fields changed + assert_ok!(inst.execute(|| crate::Pallet::::edit_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_id, + new_metadata_2.clone() + ))); + assert_eq!(inst.get_project_metadata(project_id), new_metadata_2); + inst.execute(|| { + find_event!(TestRuntime, Event::::MetadataEdited{project_id, ref metadata}, project_id == 0, metadata == &new_metadata_2); + }); + } + + #[test] + fn adding_project_policy() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.policy_ipfs_cid = None; + inst.mint_plmc_to(default_plmc_balances()); + let jwt = get_mock_jwt(ISSUER_1, InvestorType::Institutional, generate_did_from_account(ISSUER_1)); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); + let mut new_metadata = project_metadata.clone(); + let new_policy_hash = ipfs_hash(); + new_metadata.policy_ipfs_cid = Some(new_policy_hash); + assert_ok!(inst.execute(|| crate::Pallet::::edit_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_id, + new_metadata.clone() + ))); + assert_eq!(inst.get_project_metadata(project_id), new_metadata); + } + + #[test] + fn storage_changes() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + inst.mint_plmc_to(default_plmc_balances()); + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Institutional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); + let mut new_metadata = project_metadata.clone(); + + let new_price = PriceOf::::from_float(1f64); + new_metadata.minimum_price = ::PriceProvider::calculate_decimals_aware_price( + new_price, + USD_DECIMALS, + new_metadata.token_information.decimals, + ) + .unwrap(); + new_metadata.total_allocation_size = 100_000 * CT_UNIT; + assert_ok!(inst.execute(|| crate::Pallet::::edit_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_id, + new_metadata.clone() + ))); + // Project details reflect changes + assert_eq!(inst.get_project_details(project_id).fundraising_target_usd, 100_000 * USD_UNIT); + // Bucket reflects changes + let new_bucket = Pallet::::create_bucket_from_metadata(&new_metadata).unwrap(); + let stored_bucket = inst.execute(|| Buckets::::get(project_id).unwrap()); + assert_eq!(stored_bucket, new_bucket); + // Event emitted + inst.execute(|| { + find_event!(TestRuntime, Event::::MetadataEdited{project_id, ref metadata}, project_id == 0, metadata == &new_metadata); + }); + } + } + + #[cfg(test)] + mod failure { + use super::*; + + #[test] + fn called_by_different_issuer() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let ed = inst.get_ed(); + let issuer_1_mint: UserToPLMCBalance = (ISSUER_1, ed).into(); + let issuer_2_mint: UserToPLMCBalance = (ISSUER_2, ed).into(); + + let project_metadata_1 = default_project_metadata(ISSUER_1); + let project_metadata_2 = default_project_metadata(ISSUER_2); + + inst.mint_plmc_to(vec![issuer_1_mint.clone(), issuer_2_mint.clone()]); + + let jwt_1 = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Institutional, + generate_did_from_account(ISSUER_1), + project_metadata_1.clone().policy_ipfs_cid.unwrap(), + ); + let jwt_2 = get_mock_jwt_with_cid( + ISSUER_2, + InvestorType::Institutional, + generate_did_from_account(ISSUER_2), + project_metadata_2.clone().policy_ipfs_cid.unwrap(), + ); + + let project_id_1 = inst.create_new_project(project_metadata_1.clone(), ISSUER_1, None); + let project_id_2 = inst.create_new_project(project_metadata_2.clone(), ISSUER_2, None); + + inst.execute(|| { + assert_noop!( + Pallet::::edit_project( + RuntimeOrigin::signed(ISSUER_2), + jwt_2, + project_id_1, + project_metadata_2 + ), + Error::::NotIssuer + ); + assert_noop!( + Pallet::::edit_project( + RuntimeOrigin::signed(ISSUER_1), + jwt_1, + project_id_2, + project_metadata_1 + ), + Error::::NotIssuer + ); + }); + } + + #[test] + fn evaluation_already_started() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + inst.mint_plmc_to(default_plmc_balances()); + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Institutional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::EvaluationRound); + + inst.execute(|| { + assert_noop!( + crate::Pallet::::edit_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_id, + project_metadata.clone() + ), + Error::::ProjectIsFrozen + ); + }); + } + + #[test] + fn non_institutional_credential() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + inst.mint_plmc_to(default_plmc_balances()); + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Retail, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); + + inst.execute(|| { + assert_noop!( + crate::Pallet::::edit_project( + RuntimeOrigin::signed(ISSUER_1), + jwt, + project_id, + project_metadata.clone() + ), + Error::::WrongInvestorType + ); + }); + + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Professional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + inst.execute(|| { + assert_noop!( + crate::Pallet::::edit_project( + RuntimeOrigin::signed(ISSUER_1), + jwt, + project_id, + project_metadata + ), + Error::::WrongInvestorType + ); + }); + } + } +} + +#[cfg(test)] +mod remove_project_extrinsic { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + + #[test] + fn normal_remove() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + inst.mint_plmc_to(default_plmc_balances()); + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Institutional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); + assert_ok!(inst.execute(|| crate::Pallet::::remove_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_id + ))); + inst.execute(|| { + assert!(ProjectsDetails::::get(project_id).is_none()); + assert!(ProjectsMetadata::::get(project_id).is_none()); + assert!(Buckets::::get(project_id).is_none()); + assert!(DidWithActiveProjects::::get(generate_did_from_account(ISSUER_1)).is_none()); + }); + } + + #[test] + fn can_create_after_remove() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + let ed = inst.get_ed(); + let issuer_mint: UserToPLMCBalance = (ISSUER_1, ed * 2).into(); + // Create a first project + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Institutional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + inst.mint_plmc_to(vec![issuer_mint.clone()]); + inst.execute(|| { + assert_ok!(Pallet::::create_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_metadata.clone() + )); + }); + + // Same account same did + inst.mint_plmc_to(vec![issuer_mint.clone()]); + inst.execute(|| { + assert_noop!( + Pallet::::create_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_metadata.clone() + ), + Error::::HasActiveProject + ); + }); + + // Remove the first project + inst.execute(|| { + assert_ok!(Pallet::::remove_project(RuntimeOrigin::signed(ISSUER_1), jwt.clone(), 0)); + }); + + // Create a second project + inst.execute(|| { + assert_ok!(Pallet::::create_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_metadata.clone() + )); + }); + } + } + + #[cfg(test)] + mod failure { + use super::*; + + #[test] + fn non_issuer_credential() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + inst.mint_plmc_to(default_plmc_balances()); + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Professional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); + inst.execute(|| { + assert_noop!( + crate::Pallet::::remove_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_id + ), + Error::::WrongInvestorType + ); + }); + } + + #[test] + fn different_account() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + inst.mint_plmc_to(default_plmc_balances()); + let jwt = get_mock_jwt_with_cid( + ISSUER_2, + InvestorType::Institutional, + generate_did_from_account(ISSUER_2), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); + + inst.execute(|| { + assert_noop!( + crate::Pallet::::remove_project( + RuntimeOrigin::signed(ISSUER_2), + jwt.clone(), + project_id + ), + Error::::NotIssuer + ); + }); + } + + #[test] + fn evaluation_already_started() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + inst.mint_plmc_to(default_plmc_balances()); + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Institutional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::EvaluationRound); + inst.execute(|| { + assert_noop!( + crate::Pallet::::remove_project( + RuntimeOrigin::signed(ISSUER_1), + jwt.clone(), + project_id, + ), + Error::::ProjectIsFrozen + ); + }); + } + } +} diff --git a/pallets/funding/src/tests/2_evaluation.rs b/pallets/funding/src/tests/2_evaluation.rs index d51696b39..38fbd1d6d 100644 --- a/pallets/funding/src/tests/2_evaluation.rs +++ b/pallets/funding/src/tests/2_evaluation.rs @@ -1,1010 +1,990 @@ -// use super::*; -// -// #[cfg(test)] -// mod round_flow { -// use super::*; -// -// #[cfg(test)] -// mod success { -// use super::*; -// use std::collections::HashSet; -// -// #[test] -// fn evaluation_round_completed() { -// 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(); -// -// inst.create_auctioning_project(project_metadata, issuer, None, evaluations); -// } -// -// #[test] -// fn multiple_evaluating_projects() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project1 = default_project_metadata(ISSUER_1); -// let project2 = default_project_metadata(ISSUER_2); -// let project3 = default_project_metadata(ISSUER_3); -// let project4 = default_project_metadata(ISSUER_4); -// let evaluations = default_evaluations(); -// -// inst.create_auctioning_project(project1, ISSUER_1, None, evaluations.clone()); -// inst.create_auctioning_project(project2, ISSUER_2, None, evaluations.clone()); -// inst.create_auctioning_project(project3, ISSUER_3, None, evaluations.clone()); -// inst.create_auctioning_project(project4, ISSUER_4, None, evaluations); -// } -// -// #[test] -// fn plmc_price_change_doesnt_affect_evaluation_end() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// -// // Decreasing the price before the end doesn't make a project over the threshold fail. -// let target_funding = -// project_metadata.minimum_price.saturating_mul_int(project_metadata.total_allocation_size); -// let target_evaluation_usd = Percent::from_percent(10) * target_funding; -// -// let evaluations = vec![(EVALUATOR_1, target_evaluation_usd).into()]; -// let evaluation_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); -// inst.mint_plmc_to(evaluation_plmc); -// -// let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); -// inst.evaluate_for_users(project_id, evaluations.clone()).unwrap(); -// -// 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(); -// -// // 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()]; -// let evaluation_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); -// inst.mint_plmc_to(evaluation_plmc); -// -// let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_2, None); -// inst.evaluate_for_users(project_id, evaluations.clone()).unwrap(); -// -// 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)); -// } -// -// #[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 min_evaluation_amount_usd = ::MinUsdPerEvaluation::get(); -// let stored_plmc_price = -// inst.execute(|| ::PriceProvider::get_price(PLMC_FOREIGN_ID).unwrap()); -// let usable_plmc_price = inst.execute(|| { -// ::PriceProvider::get_decimals_aware_price( -// PLMC_FOREIGN_ID, -// USD_DECIMALS, -// PLMC_DECIMALS, -// ) -// .unwrap() -// }); -// let min_evaluation_amount_plmc = -// usable_plmc_price.reciprocal().unwrap().checked_mul_int(min_evaluation_amount_usd).unwrap(); -// -// // Test independent of CT decimals - Right PLMC conversion is stored. -// // We move comma 4 places to the left since PLMC has 4 more decimals than USD. -// assert_eq!(stored_plmc_price, FixedU128::from_float(8.4)); -// assert_eq!(usable_plmc_price, FixedU128::from_float(0.00084)); -// -// let mut evaluation_ct_thresholds = Vec::new(); -// let mut evaluation_usd_thresholds = Vec::new(); -// let mut evaluation_plmc_thresholds = Vec::new(); -// -// let mut decimal_test = |decimals: u8| { -// 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; -// -// let issuer: AccountIdOf = (10_000 + inst.get_new_nonce()).try_into().unwrap(); -// let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer, None); -// -// let evaluation_threshold = inst.execute(|| ::EvaluationSuccessThreshold::get()); -// let evaluation_threshold_ct = evaluation_threshold * project_metadata.total_allocation_size; -// evaluation_ct_thresholds.push(evaluation_threshold_ct); -// -// let evaluation_threshold_usd = -// project_metadata.minimum_price.saturating_mul_int(evaluation_threshold_ct); -// evaluation_usd_thresholds.push(evaluation_threshold_usd); -// -// let evaluation_threshold_plmc = -// usable_plmc_price.reciprocal().unwrap().checked_mul_int(evaluation_threshold_usd).unwrap(); -// evaluation_plmc_thresholds.push(evaluation_threshold_plmc); -// -// // CT price should be multiplied or divided by the amount of decimal difference with USD. -// let decimal_abs_diff = USD_DECIMALS.abs_diff(decimals); -// let original_price_as_usd = original_price.saturating_mul_int(10u128.pow(USD_DECIMALS as u32)); -// let min_price_as_usd = project_metadata.minimum_price.saturating_mul_int(USD_UNIT); -// if decimals < USD_DECIMALS { -// assert_eq!(min_price_as_usd, original_price_as_usd * 10u128.pow(decimal_abs_diff as u32)); -// } else { -// assert_eq!(min_price_as_usd, original_price_as_usd / 10u128.pow(decimal_abs_diff as u32)); -// } -// -// // A minimum evaluation goes through. This is a fixed USD/PLMC value, so independent of CT decimals. -// inst.mint_plmc_to(vec![UserToPLMCBalance::new(EVALUATOR_1, min_evaluation_amount_plmc + ed)]); -// assert_ok!(inst.execute(|| PolimecFunding::evaluate( -// RuntimeOrigin::signed(EVALUATOR_1), -// get_mock_jwt_with_cid( -// EVALUATOR_1, -// InvestorType::Retail, -// generate_did_from_account(EVALUATOR_1), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// min_evaluation_amount_usd -// ))); -// -// // Try bonding up to the threshold with a second evaluation -// inst.mint_plmc_to(vec![UserToPLMCBalance::new( -// EVALUATOR_2, -// evaluation_threshold_plmc + ed - min_evaluation_amount_plmc, -// )]); -// assert_ok!(inst.execute(|| PolimecFunding::evaluate( -// RuntimeOrigin::signed(EVALUATOR_2), -// get_mock_jwt_with_cid( -// EVALUATOR_2, -// InvestorType::Retail, -// generate_did_from_account(EVALUATOR_2), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// evaluation_threshold_usd - min_evaluation_amount_usd -// ))); -// -// // The evaluation should succeed when we bond the threshold PLMC amount in total. -// inst.start_auction(project_id, issuer).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!(evaluation_usd_thresholds.iter().all(|x| *x == evaluation_usd_thresholds[0])); -// assert!(evaluation_plmc_thresholds.iter().all(|x| *x == evaluation_plmc_thresholds[0])); -// -// // CT amounts however should be different from each other -// let mut hash_set = HashSet::new(); -// for amount in evaluation_ct_thresholds { -// assert!(!hash_set.contains(&amount)); -// hash_set.insert(amount); -// } -// } -// } -// -// #[cfg(test)] -// mod failure { -// use super::*; -// -// #[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(); -// let plmc_eval_deposits: Vec> = -// inst.calculate_evaluation_plmc_spent(evaluations, false); -// let plmc_existential_deposits = plmc_eval_deposits.accounts().existential_deposits(); -// -// let expected_evaluator_balances = inst.generic_map_operation( -// vec![plmc_eval_deposits.clone(), plmc_existential_deposits.clone()], -// MergeOperation::Add, -// ); -// -// inst.mint_plmc_to(plmc_eval_deposits.clone()); -// inst.mint_plmc_to(plmc_existential_deposits.clone()); -// -// 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); -// -// inst.settle_project(project_id).unwrap(); -// inst.do_free_plmc_assertions(expected_evaluator_balances); -// } -// } -// } -// -// #[cfg(test)] -// mod start_evaluation_extrinsic { -// use super::*; -// -// #[cfg(test)] -// mod success { -// use super::*; -// -// #[test] -// fn evaluation_starts() { -// 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_new_project(project_metadata.clone(), issuer, None); -// let jwt = get_mock_jwt_with_cid( -// issuer, -// InvestorType::Institutional, -// generate_did_from_account(issuer), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Application); -// assert_ok!(inst.execute(|| PolimecFunding::start_evaluation( -// RuntimeOrigin::signed(issuer), -// jwt, -// project_id -// ))); -// assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::EvaluationRound); -// } -// -// #[test] -// fn storage_is_updated() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let issuer = ISSUER_1; -// let issuer_did = generate_did_from_account(issuer); -// let project_metadata = default_project_metadata(issuer); -// -// let project_id = inst.create_new_project(project_metadata.clone(), issuer, None); -// let jwt = get_mock_jwt_with_cid( -// issuer, -// InvestorType::Institutional, -// issuer_did.clone(), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// let expected_details = ProjectDetailsOf:: { -// issuer_account: ISSUER_1, -// issuer_did, -// is_frozen: true, -// weighted_average_price: None, -// status: ProjectStatus::EvaluationRound, -// round_duration: BlockNumberPair::new(None, None), -// random_end_block: None, -// fundraising_target_usd: project_metadata -// .minimum_price -// .saturating_mul_int(project_metadata.total_allocation_size), -// remaining_contribution_tokens: project_metadata.total_allocation_size, -// funding_amount_reached_usd: 0u128, -// evaluation_round_info: EvaluationRoundInfoOf:: { -// total_bonded_usd: 0u128, -// total_bonded_plmc: 0u128, -// evaluators_outcome: EvaluatorsOutcome::Unchanged, -// }, -// usd_bid_on_oversubscription: None, -// funding_end_block: None, -// migration_type: None, -// }; -// assert_ok!(inst.execute(|| PolimecFunding::start_evaluation( -// RuntimeOrigin::signed(issuer), -// jwt, -// project_id -// ))); -// -// assert_eq!(inst.get_project_details(project_id), expected_details); -// } -// } -// -// #[cfg(test)] -// mod failure { -// use super::*; -// use polimec_common_test_utils::get_mock_jwt; -// -// #[test] -// fn non_institutional_jwt() { -// 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_new_project(project_metadata.clone(), issuer, None); -// assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Application); -// -// inst.execute(|| { -// assert_noop!( -// PolimecFunding::start_evaluation( -// RuntimeOrigin::signed(issuer), -// get_mock_jwt_with_cid( -// issuer, -// InvestorType::Professional, -// generate_did_from_account(issuer), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// ), -// Error::::WrongInvestorType -// ); -// }); -// -// inst.execute(|| { -// assert_noop!( -// PolimecFunding::start_evaluation( -// RuntimeOrigin::signed(issuer), -// get_mock_jwt_with_cid( -// issuer, -// InvestorType::Retail, -// generate_did_from_account(issuer), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// ), -// Error::::WrongInvestorType -// ); -// }); -// } -// -// #[test] -// fn evaluation_started_already() { -// 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_new_project(project_metadata.clone(), issuer, None); -// let jwt = get_mock_jwt_with_cid( -// issuer, -// InvestorType::Institutional, -// generate_did_from_account(issuer), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Application); -// assert_ok!(inst.execute(|| PolimecFunding::start_evaluation( -// RuntimeOrigin::signed(issuer), -// jwt.clone(), -// project_id -// ))); -// assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::EvaluationRound); -// -// inst.execute(|| { -// assert_noop!( -// PolimecFunding::start_evaluation(RuntimeOrigin::signed(issuer), jwt, project_id), -// Error::::IncorrectRound -// ); -// }); -// } -// -// #[test] -// fn no_policy_provided() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let issuer = ISSUER_1; -// let mut project_metadata = default_project_metadata(issuer); -// project_metadata.policy_ipfs_cid = None; -// -// let project_id = inst.create_new_project(project_metadata.clone(), issuer, None); -// let jwt = get_mock_jwt(issuer, InvestorType::Institutional, generate_did_from_account(issuer)); -// assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Application); -// inst.execute(|| { -// assert_noop!( -// PolimecFunding::start_evaluation(RuntimeOrigin::signed(issuer), jwt, project_id), -// Error::::CidNotProvided -// ); -// }); -// } -// -// #[test] -// fn different_account() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// -// let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); -// let jwt = get_mock_jwt_with_cid( -// ISSUER_1, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Application); -// assert_ok!(inst.execute(|| PolimecFunding::start_evaluation( -// RuntimeOrigin::signed(ISSUER_1), -// jwt, -// project_id -// ))); -// assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::EvaluationRound); -// -// inst.execute(|| { -// assert_noop!( -// PolimecFunding::start_evaluation( -// RuntimeOrigin::signed(ISSUER_2), -// get_mock_jwt_with_cid( -// ISSUER_2, -// InvestorType::Institutional, -// generate_did_from_account(ISSUER_2), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// ), -// Error::::NotIssuer -// ); -// }); -// } -// } -// } -// -// #[cfg(test)] -// mod evaluate_extrinsic { -// use super::*; -// -// #[cfg(test)] -// mod success { -// use super::*; -// use frame_support::traits::fungible::InspectFreeze; -// use pallet_balances::AccountData; -// -// #[test] -// fn all_investor_types() { -// 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_evaluating_project(project_metadata.clone(), issuer, None); -// -// let evaluations = vec![ -// (EVALUATOR_1, 500 * USD_UNIT).into(), -// (EVALUATOR_2, 1000 * USD_UNIT).into(), -// (EVALUATOR_3, 20_000 * USD_UNIT).into(), -// ]; -// let necessary_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); -// -// inst.mint_plmc_to(necessary_plmc); -// -// assert_ok!(inst.execute(|| PolimecFunding::evaluate( -// RuntimeOrigin::signed(evaluations[0].account), -// get_mock_jwt_with_cid( -// evaluations[0].account, -// InvestorType::Institutional, -// generate_did_from_account(evaluations[0].account), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// evaluations[0].usd_amount, -// ))); -// -// assert_ok!(inst.execute(|| PolimecFunding::evaluate( -// RuntimeOrigin::signed(evaluations[1].account), -// get_mock_jwt_with_cid( -// evaluations[1].account, -// InvestorType::Professional, -// generate_did_from_account(evaluations[1].account), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// evaluations[1].usd_amount, -// ))); -// -// assert_ok!(inst.execute(|| PolimecFunding::evaluate( -// RuntimeOrigin::signed(evaluations[2].account), -// get_mock_jwt_with_cid( -// evaluations[2].account, -// InvestorType::Retail, -// generate_did_from_account(evaluations[2].account), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// evaluations[2].usd_amount, -// ))); -// } -// -// #[test] -// fn using_frozen_tokens() { -// 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_evaluating_project(project_metadata.clone(), issuer, None); -// -// let evaluation = UserToUSDBalance::new(EVALUATOR_1, 500 * USD_UNIT); -// let necessary_plmc = inst.calculate_evaluation_plmc_spent(vec![evaluation.clone()], true); -// -// inst.mint_plmc_to(necessary_plmc.clone()); -// -// inst.execute(|| { -// mock::Balances::set_freeze(&(), &EVALUATOR_1, necessary_plmc[0].plmc_amount).unwrap(); -// }); -// -// assert_ok!(inst.execute(|| PolimecFunding::evaluate( -// RuntimeOrigin::signed(evaluation.account), -// get_mock_jwt_with_cid( -// evaluation.account, -// InvestorType::Retail, -// generate_did_from_account(evaluation.account), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// evaluation.usd_amount -// ))); -// } -// -// #[test] -// fn storage_check() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); -// let evaluation = UserToUSDBalance::new(EVALUATOR_1, 500 * USD_UNIT); -// let necessary_plmc = inst.calculate_evaluation_plmc_spent(vec![evaluation.clone()], false); -// let plmc_existential_deposits = necessary_plmc.accounts().existential_deposits(); -// -// inst.mint_plmc_to(necessary_plmc.clone()); -// inst.mint_plmc_to(plmc_existential_deposits.clone()); -// -// inst.execute(|| { -// assert_eq!(Evaluations::::iter_values().collect_vec(), vec![]); -// }); -// -// let did = generate_did_from_account(evaluation.account); -// -// assert_ok!(inst.execute(|| PolimecFunding::evaluate( -// RuntimeOrigin::signed(evaluation.account), -// get_mock_jwt_with_cid( -// evaluation.account, -// InvestorType::Retail, -// did.clone(), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// evaluation.usd_amount -// ))); -// -// inst.execute(|| { -// let evaluations = Evaluations::::iter_prefix_values((project_id,)).collect_vec(); -// assert_eq!(evaluations.len(), 1); -// let stored_evaluation = &evaluations[0]; -// let expected_evaluation_item = EvaluationInfoOf:: { -// id: 0, -// did, -// project_id: 0, -// evaluator: EVALUATOR_1, -// original_plmc_bond: necessary_plmc[0].plmc_amount, -// current_plmc_bond: necessary_plmc[0].plmc_amount, -// early_usd_amount: evaluation.usd_amount, -// late_usd_amount: 0, -// when: 1, -// }; -// assert_eq!(stored_evaluation, &expected_evaluation_item); -// }); -// } -// -// #[test] -// fn can_evaluate_with_frozen_tokens() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let issuer = ISSUER_1; -// let project_metadata = default_project_metadata(issuer); -// -// let evaluation = UserToUSDBalance::new(EVALUATOR_4, 1_000_000 * USD_UNIT); -// let plmc_required = inst.calculate_evaluation_plmc_spent(vec![evaluation.clone()], false); -// let plmc_existential_deposits = plmc_required.accounts().existential_deposits(); -// let frozen_amount = plmc_required[0].plmc_amount; -// -// inst.mint_plmc_to(plmc_required.clone()); -// inst.mint_plmc_to(plmc_existential_deposits.clone()); -// -// inst.execute(|| { -// mock::Balances::set_freeze(&(), &EVALUATOR_4, plmc_required[0].plmc_amount).unwrap(); -// }); -// -// inst.execute(|| { -// assert_noop!( -// Balances::transfer_allow_death(RuntimeOrigin::signed(EVALUATOR_4), ISSUER_1, frozen_amount,), -// TokenError::Frozen -// ); -// }); -// -// let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer, None); -// inst.execute(|| { -// assert_ok!(PolimecFunding::evaluate( -// RuntimeOrigin::signed(EVALUATOR_4), -// get_mock_jwt_with_cid( -// EVALUATOR_4, -// InvestorType::Retail, -// generate_did_from_account(EVALUATOR_4), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// evaluation.usd_amount -// )); -// }); -// -// let new_evaluations = default_evaluations(); -// let new_plmc_required = inst.calculate_evaluation_plmc_spent(new_evaluations.clone(), true); -// 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.get_project_details(project_id).status, ProjectStatus::FundingFailed); -// -// let free_balance = inst.get_free_plmc_balance_for(EVALUATOR_4); -// let evaluation_held_balance = -// inst.get_reserved_plmc_balance_for(EVALUATOR_4, HoldReason::Evaluation(project_id).into()); -// let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &EVALUATOR_4)); -// -// assert_eq!(free_balance, inst.get_ed()); -// assert_eq!(evaluation_held_balance, frozen_amount); -// assert_eq!(frozen_balance, frozen_amount); -// -// 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); -// -// inst.execute(|| { -// PolimecFunding::settle_failed_evaluation( -// RuntimeOrigin::signed(EVALUATOR_4), -// project_id, -// EVALUATOR_4, -// 0, -// ) -// .unwrap(); -// }); -// -// let post_slash_treasury_balance = inst.get_free_plmc_balance_for(treasury_account); -// let free_balance = inst.get_free_plmc_balance_for(EVALUATOR_4); -// let evaluation_held_balance = -// inst.get_reserved_plmc_balance_for(EVALUATOR_4, HoldReason::Evaluation(project_id).into()); -// let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &EVALUATOR_4)); -// let account_data = inst.execute(|| System::account(&EVALUATOR_4)).data; -// -// let post_slash_evaluation_plmc = -// frozen_amount - (::EvaluatorSlash::get() * frozen_amount); -// assert_eq!(free_balance, inst.get_ed() + post_slash_evaluation_plmc); -// assert_eq!(evaluation_held_balance, Zero::zero()); -// assert_eq!(frozen_balance, frozen_amount); -// let expected_account_data = AccountData { -// free: inst.get_ed() + post_slash_evaluation_plmc, -// reserved: Zero::zero(), -// frozen: frozen_amount, -// flags: Default::default(), -// }; -// assert_eq!(account_data, expected_account_data); -// -// assert!(account_data.frozen > account_data.free); -// assert_eq!( -// post_slash_treasury_balance, -// pre_slash_treasury_balance + ::EvaluatorSlash::get() * frozen_amount -// ); -// } -// } -// -// #[cfg(test)] -// mod failure { -// use super::*; -// -// #[test] -// fn project_is_not_in_evaluation_round() { -// 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_auctioning_project(project_metadata.clone(), issuer, None, default_evaluations()); -// -// inst.execute(|| { -// assert_noop!( -// PolimecFunding::evaluate( -// RuntimeOrigin::signed(EVALUATOR_1), -// get_mock_jwt_with_cid( -// EVALUATOR_1, -// InvestorType::Retail, -// generate_did_from_account(EVALUATOR_1), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// 500 * USD_UNIT, -// ), -// Error::::IncorrectRound -// ); -// }); -// } -// -// #[test] -// fn insufficient_plmc_for_desired_evaluation() { -// 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 insufficient_eval_deposits = inst -// .calculate_evaluation_plmc_spent(evaluations.clone(), false) -// .iter() -// .map(|UserToPLMCBalance { account, plmc_amount }| UserToPLMCBalance::new(*account, plmc_amount / 2)) -// .collect::>>(); -// -// let plmc_existential_deposits = insufficient_eval_deposits.accounts().existential_deposits(); -// -// inst.mint_plmc_to(insufficient_eval_deposits); -// inst.mint_plmc_to(plmc_existential_deposits); -// -// let project_id = inst.create_evaluating_project(project_metadata, issuer, None); -// -// let dispatch_error = inst.evaluate_for_users(project_id, evaluations); -// assert_err!(dispatch_error, TokenError::FundsUnavailable) -// } -// -// #[test] -// fn evaluation_placing_user_balance_under_ed() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let issuer = ISSUER_1; -// let project_metadata = default_project_metadata(issuer); -// let evaluations = vec![UserToUSDBalance::new(EVALUATOR_1, 1000 * USD_UNIT)]; -// let evaluating_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), false); -// let mut plmc_insufficient_existential_deposit = evaluating_plmc.accounts().existential_deposits(); -// -// plmc_insufficient_existential_deposit[0].plmc_amount = -// plmc_insufficient_existential_deposit[0].plmc_amount / 2; -// -// inst.mint_plmc_to(evaluating_plmc); -// inst.mint_plmc_to(plmc_insufficient_existential_deposit); -// -// let project_id = inst.create_evaluating_project(project_metadata, issuer, None); -// -// let dispatch_error = inst.evaluate_for_users(project_id, evaluations); -// assert_err!(dispatch_error, TokenError::FundsUnavailable) -// } -// -// #[test] -// fn cannot_evaluate_more_than_project_limit() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// let evaluations = (0u32..::MaxEvaluationsPerProject::get()) -// .map(|i| UserToUSDBalance::::new(i as u32 + 420u32, (100u128 * CT_UNIT).into())) -// .collect_vec(); -// let failing_evaluation = UserToUSDBalance::new(EVALUATOR_1, 1000 * CT_UNIT); -// -// let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); -// -// let plmc_for_evaluating = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); -// -// inst.mint_plmc_to(plmc_for_evaluating.clone()); -// -// inst.evaluate_for_users(project_id, evaluations.clone()).unwrap(); -// -// let plmc_for_failing_evaluating = -// inst.calculate_evaluation_plmc_spent(vec![failing_evaluation.clone()], true); -// -// inst.mint_plmc_to(plmc_for_failing_evaluating.clone()); -// -// assert_err!( -// inst.evaluate_for_users(project_id, vec![failing_evaluation]), -// Error::::TooManyProjectParticipations -// ); -// } -// -// #[test] -// fn cannot_evaluate_more_than_user_limit() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// let evaluations = (0u32..::MaxEvaluationsPerUser::get()) -// .map(|_| UserToUSDBalance::::new(EVALUATOR_1, (100u128 * USD_UNIT).into())) -// .collect_vec(); -// let failing_evaluation = UserToUSDBalance::new(EVALUATOR_1, 100 * USD_UNIT); -// -// let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); -// -// let plmc_for_evaluating = inst.calculate_evaluation_plmc_spent(evaluations.clone(), false); -// let plmc_existential_deposits = evaluations.accounts().existential_deposits(); -// -// inst.mint_plmc_to(plmc_for_evaluating.clone()); -// inst.mint_plmc_to(plmc_existential_deposits.clone()); -// -// inst.evaluate_for_users(project_id, evaluations.clone()).unwrap(); -// -// let plmc_for_failing_evaluating = -// inst.calculate_evaluation_plmc_spent(vec![failing_evaluation.clone()], true); -// -// inst.mint_plmc_to(plmc_for_failing_evaluating.clone()); -// -// assert_err!( -// inst.evaluate_for_users(project_id, vec![failing_evaluation]), -// Error::::TooManyUserParticipations -// ); -// } -// -// #[test] -// fn cannot_use_balance_on_hold() { -// 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_evaluating_project(project_metadata.clone(), issuer, None); -// -// let evaluation = UserToUSDBalance::new(EVALUATOR_1, 500 * USD_UNIT); -// let necessary_plmc = inst.calculate_evaluation_plmc_spent(vec![evaluation.clone()], false); -// let ed = necessary_plmc.accounts().existential_deposits(); -// -// inst.mint_plmc_to(necessary_plmc.clone()); -// inst.mint_plmc_to(ed.clone()); -// -// inst.execute(|| { -// ::NativeCurrency::hold( -// &RuntimeHoldReason::PolimecFunding(HoldReason::Evaluation(69)), -// &EVALUATOR_1, -// necessary_plmc[0].plmc_amount, -// ) -// .unwrap(); -// }); -// -// inst.execute(|| { -// assert_noop!( -// PolimecFunding::evaluate( -// RuntimeOrigin::signed(evaluation.account), -// get_mock_jwt_with_cid( -// evaluation.account, -// InvestorType::Retail, -// generate_did_from_account(evaluation.account), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// evaluation.usd_amount, -// ), -// TokenError::FundsUnavailable -// ); -// }); -// } -// -// #[test] -// fn issuer_cannot_evaluate_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_evaluating_project(project_metadata.clone(), ISSUER_1, None); -// assert_err!( -// inst.execute(|| crate::Pallet::::do_evaluate( -// &(&ISSUER_1 + 1), -// project_id, -// 500 * USD_UNIT, -// generate_did_from_account(ISSUER_1), -// InvestorType::Institutional, -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// )), -// Error::::ParticipationToOwnProject -// ); -// } -// -// #[test] -// fn cannot_use_same_plmc_for_2_evaluations() { -// 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_evaluating_project(project_metadata.clone(), issuer, None); -// -// let evaluation = UserToUSDBalance::new(EVALUATOR_1, 500 * USD_UNIT); -// let necessary_plmc = inst.calculate_evaluation_plmc_spent(vec![evaluation.clone()], true); -// -// inst.mint_plmc_to(necessary_plmc.clone()); -// -// inst.execute(|| { -// assert_ok!(PolimecFunding::evaluate( -// RuntimeOrigin::signed(evaluation.account), -// get_mock_jwt_with_cid( -// evaluation.account, -// InvestorType::Retail, -// generate_did_from_account(evaluation.account), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// evaluation.usd_amount, -// )); -// }); -// -// inst.execute(|| { -// assert_noop!( -// PolimecFunding::evaluate( -// RuntimeOrigin::signed(evaluation.account), -// get_mock_jwt_with_cid( -// evaluation.account, -// InvestorType::Retail, -// generate_did_from_account(evaluation.account), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// evaluation.usd_amount, -// ), -// TokenError::FundsUnavailable -// ); -// }); -// } -// -// #[test] -// fn cannot_evaluate_with_less_than_100_usd() { -// 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_evaluating_project(project_metadata.clone(), issuer, None); -// let evaluator = EVALUATOR_1; -// let jwt = get_mock_jwt_with_cid( -// evaluator, -// InvestorType::Retail, -// generate_did_from_account(evaluator), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// -// inst.mint_plmc_to(vec![(evaluator.clone(), 2000 * PLMC).into()]); -// -// // Cannot evaluate with 0 USD -// inst.execute(|| { -// assert_noop!( -// Pallet::::evaluate( -// RuntimeOrigin::signed(evaluator.clone()), -// jwt.clone(), -// project_id, -// 0 -// ), -// Error::::TooLow -// ); -// }); -// -// // Cannot evaluate with less than 99 USD -// inst.execute(|| { -// assert_noop!( -// Pallet::::evaluate( -// RuntimeOrigin::signed(evaluator.clone()), -// jwt.clone(), -// project_id, -// 99 * USD_UNIT -// ), -// Error::::TooLow -// ); -// }); -// } -// -// #[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_evaluating_project(project_metadata.clone(), ISSUER_1, None); -// -// inst.execute(|| { -// assert_noop!( -// PolimecFunding::evaluate( -// RuntimeOrigin::signed(EVALUATOR_1), -// get_mock_jwt_with_cid( -// EVALUATOR_1, -// InvestorType::Retail, -// generate_did_from_account(EVALUATOR_1), -// "wrong_cid".as_bytes().to_vec().try_into().unwrap() -// ), -// project_id, -// 500 * USD_UNIT, -// ), -// Error::::PolicyMismatch -// ); -// }); -// } -// } -// } +use super::*; + +#[cfg(test)] +mod round_flow { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + use std::collections::HashSet; + + #[test] + fn evaluation_round_completed() { + 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(); + + inst.create_auctioning_project(project_metadata, issuer, None, evaluations); + } + + #[test] + fn multiple_evaluating_projects() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project1 = default_project_metadata(ISSUER_1); + let project2 = default_project_metadata(ISSUER_2); + let project3 = default_project_metadata(ISSUER_3); + let project4 = default_project_metadata(ISSUER_4); + let evaluations = default_evaluations(); + + inst.create_auctioning_project(project1, ISSUER_1, None, evaluations.clone()); + inst.create_auctioning_project(project2, ISSUER_2, None, evaluations.clone()); + inst.create_auctioning_project(project3, ISSUER_3, None, evaluations.clone()); + inst.create_auctioning_project(project4, ISSUER_4, None, evaluations); + } + + #[test] + fn plmc_price_change_doesnt_affect_evaluation_end() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + + // Decreasing the price before the end doesn't make a project over the threshold fail. + let target_funding = + project_metadata.minimum_price.saturating_mul_int(project_metadata.total_allocation_size); + let target_evaluation_usd = Percent::from_percent(10) * target_funding; + + let evaluations = vec![(EVALUATOR_1, target_evaluation_usd).into()]; + let evaluation_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); + inst.mint_plmc_to(evaluation_plmc); + + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); + inst.evaluate_for_users(project_id, evaluations.clone()).unwrap(); + + 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())); + + 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()]; + let evaluation_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); + inst.mint_plmc_to(evaluation_plmc); + + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_2, None); + inst.evaluate_for_users(project_id, evaluations.clone()).unwrap(); + + 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())); + + 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] + 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 min_evaluation_amount_usd = ::MinUsdPerEvaluation::get(); + let stored_plmc_price = + inst.execute(|| ::PriceProvider::get_price(PLMC_FOREIGN_ID).unwrap()); + let usable_plmc_price = inst.execute(|| { + ::PriceProvider::get_decimals_aware_price( + PLMC_FOREIGN_ID, + USD_DECIMALS, + PLMC_DECIMALS, + ) + .unwrap() + }); + let min_evaluation_amount_plmc = + usable_plmc_price.reciprocal().unwrap().checked_mul_int(min_evaluation_amount_usd).unwrap(); + + // Test independent of CT decimals - Right PLMC conversion is stored. + // We move comma 4 places to the left since PLMC has 4 more decimals than USD. + assert_eq!(stored_plmc_price, FixedU128::from_float(8.4)); + assert_eq!(usable_plmc_price, FixedU128::from_float(0.00084)); + + let mut evaluation_ct_thresholds = Vec::new(); + let mut evaluation_usd_thresholds = Vec::new(); + let mut evaluation_plmc_thresholds = Vec::new(); + + let mut decimal_test = |decimals: u8| { + 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; + + let issuer: AccountIdOf = (10_000 + inst.get_new_nonce()).try_into().unwrap(); + let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer, None); + + let evaluation_threshold = inst.execute(|| ::EvaluationSuccessThreshold::get()); + let evaluation_threshold_ct = evaluation_threshold * project_metadata.total_allocation_size; + evaluation_ct_thresholds.push(evaluation_threshold_ct); + + let evaluation_threshold_usd = + project_metadata.minimum_price.saturating_mul_int(evaluation_threshold_ct); + evaluation_usd_thresholds.push(evaluation_threshold_usd); + + let evaluation_threshold_plmc = + usable_plmc_price.reciprocal().unwrap().checked_mul_int(evaluation_threshold_usd).unwrap(); + evaluation_plmc_thresholds.push(evaluation_threshold_plmc); + + // CT price should be multiplied or divided by the amount of decimal difference with USD. + let decimal_abs_diff = USD_DECIMALS.abs_diff(decimals); + let original_price_as_usd = original_price.saturating_mul_int(10u128.pow(USD_DECIMALS as u32)); + let min_price_as_usd = project_metadata.minimum_price.saturating_mul_int(USD_UNIT); + if decimals < USD_DECIMALS { + assert_eq!(min_price_as_usd, original_price_as_usd * 10u128.pow(decimal_abs_diff as u32)); + } else { + assert_eq!(min_price_as_usd, original_price_as_usd / 10u128.pow(decimal_abs_diff as u32)); + } + + // A minimum evaluation goes through. This is a fixed USD/PLMC value, so independent of CT decimals. + inst.mint_plmc_to(vec![UserToPLMCBalance::new(EVALUATOR_1, min_evaluation_amount_plmc + ed)]); + assert_ok!(inst.execute(|| PolimecFunding::evaluate( + RuntimeOrigin::signed(EVALUATOR_1), + get_mock_jwt_with_cid( + EVALUATOR_1, + InvestorType::Retail, + generate_did_from_account(EVALUATOR_1), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + min_evaluation_amount_usd + ))); + + // Try bonding up to the threshold with a second evaluation + inst.mint_plmc_to(vec![UserToPLMCBalance::new( + EVALUATOR_2, + evaluation_threshold_plmc + ed - min_evaluation_amount_plmc, + )]); + assert_ok!(inst.execute(|| PolimecFunding::evaluate( + RuntimeOrigin::signed(EVALUATOR_2), + get_mock_jwt_with_cid( + EVALUATOR_2, + InvestorType::Retail, + generate_did_from_account(EVALUATOR_2), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + evaluation_threshold_usd - min_evaluation_amount_usd + ))); + + // The evaluation should succeed when we bond the threshold PLMC amount in total. + 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 { + 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!(evaluation_usd_thresholds.iter().all(|x| *x == evaluation_usd_thresholds[0])); + assert!(evaluation_plmc_thresholds.iter().all(|x| *x == evaluation_plmc_thresholds[0])); + + // CT amounts however should be different from each other + let mut hash_set = HashSet::new(); + for amount in evaluation_ct_thresholds { + assert!(!hash_set.contains(&amount)); + hash_set.insert(amount); + } + } + } + + #[cfg(test)] + mod failure { + use super::*; + + #[test] + fn round_fails_after_not_enough_bonds() { + 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_failing_evaluations(); + let plmc_eval_deposits: Vec> = + inst.calculate_evaluation_plmc_spent(evaluations, false); + let plmc_existential_deposits = plmc_eval_deposits.accounts().existential_deposits(); + + let expected_evaluator_balances = inst.generic_map_operation( + vec![plmc_eval_deposits.clone(), plmc_existential_deposits.clone()], + MergeOperation::Add, + ); + + inst.mint_plmc_to(plmc_eval_deposits.clone()); + inst.mint_plmc_to(plmc_existential_deposits.clone()); + + let project_id = inst.create_evaluating_project(project_metadata, issuer, None); + + 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()); + + 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); + inst.do_free_plmc_assertions(expected_evaluator_balances); + } + } +} + +#[cfg(test)] +mod start_evaluation_extrinsic { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + + #[test] + fn evaluation_starts() { + 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_new_project(project_metadata.clone(), issuer, None); + let jwt = get_mock_jwt_with_cid( + issuer, + InvestorType::Institutional, + generate_did_from_account(issuer), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Application); + assert_ok!(inst.execute(|| PolimecFunding::start_evaluation( + RuntimeOrigin::signed(issuer), + jwt, + project_id + ))); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::EvaluationRound); + } + + #[test] + fn storage_is_updated() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let issuer = ISSUER_1; + let issuer_did = generate_did_from_account(issuer); + let project_metadata = default_project_metadata(issuer); + + let project_id = inst.create_new_project(project_metadata.clone(), issuer, None); + let jwt = get_mock_jwt_with_cid( + issuer, + InvestorType::Institutional, + issuer_did.clone(), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + let expected_details = ProjectDetailsOf:: { + issuer_account: ISSUER_1, + issuer_did, + is_frozen: true, + weighted_average_price: None, + status: ProjectStatus::EvaluationRound, + round_duration: BlockNumberPair::new(Some(1), Some(::EvaluationDuration::get())), + random_end_block: None, + fundraising_target_usd: project_metadata + .minimum_price + .saturating_mul_int(project_metadata.total_allocation_size), + remaining_contribution_tokens: project_metadata.total_allocation_size, + funding_amount_reached_usd: 0u128, + evaluation_round_info: EvaluationRoundInfoOf:: { + total_bonded_usd: 0u128, + total_bonded_plmc: 0u128, + evaluators_outcome: None, + }, + usd_bid_on_oversubscription: None, + funding_end_block: None, + migration_type: None, + }; + assert_ok!(inst.execute(|| PolimecFunding::start_evaluation( + RuntimeOrigin::signed(issuer), + jwt, + project_id + ))); + + assert_eq!(inst.get_project_details(project_id), expected_details); + } + } + + #[cfg(test)] + mod failure { + use super::*; + use polimec_common_test_utils::get_mock_jwt; + + #[test] + fn non_institutional_jwt() { + 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_new_project(project_metadata.clone(), issuer, None); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Application); + + inst.execute(|| { + assert_noop!( + PolimecFunding::start_evaluation( + RuntimeOrigin::signed(issuer), + get_mock_jwt_with_cid( + issuer, + InvestorType::Professional, + generate_did_from_account(issuer), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + ), + Error::::WrongInvestorType + ); + }); + + inst.execute(|| { + assert_noop!( + PolimecFunding::start_evaluation( + RuntimeOrigin::signed(issuer), + get_mock_jwt_with_cid( + issuer, + InvestorType::Retail, + generate_did_from_account(issuer), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + ), + Error::::WrongInvestorType + ); + }); + } + + #[test] + fn evaluation_started_already() { + 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_new_project(project_metadata.clone(), issuer, None); + let jwt = get_mock_jwt_with_cid( + issuer, + InvestorType::Institutional, + generate_did_from_account(issuer), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Application); + assert_ok!(inst.execute(|| PolimecFunding::start_evaluation( + RuntimeOrigin::signed(issuer), + jwt.clone(), + project_id + ))); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::EvaluationRound); + + inst.execute(|| { + assert_noop!( + PolimecFunding::start_evaluation(RuntimeOrigin::signed(issuer), jwt, project_id), + Error::::ProjectAlreadyFrozen + ); + }); + } + + #[test] + fn no_policy_provided() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let issuer = ISSUER_1; + let mut project_metadata = default_project_metadata(issuer); + project_metadata.policy_ipfs_cid = None; + + let project_id = inst.create_new_project(project_metadata.clone(), issuer, None); + let jwt = get_mock_jwt(issuer, InvestorType::Institutional, generate_did_from_account(issuer)); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Application); + inst.execute(|| { + assert_noop!( + PolimecFunding::start_evaluation(RuntimeOrigin::signed(issuer), jwt, project_id), + Error::::CidNotProvided + ); + }); + } + + #[test] + fn different_account() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); + let jwt = get_mock_jwt_with_cid( + ISSUER_1, + InvestorType::Institutional, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Application); + assert_ok!(inst.execute(|| PolimecFunding::start_evaluation( + RuntimeOrigin::signed(ISSUER_1), + jwt, + project_id + ))); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::EvaluationRound); + + inst.execute(|| { + assert_noop!( + PolimecFunding::start_evaluation( + RuntimeOrigin::signed(ISSUER_2), + get_mock_jwt_with_cid( + ISSUER_2, + InvestorType::Institutional, + generate_did_from_account(ISSUER_2), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + ), + Error::::NotIssuer + ); + }); + } + } +} + +#[cfg(test)] +mod evaluate_extrinsic { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + use frame_support::traits::fungible::InspectFreeze; + use pallet_balances::AccountData; + + #[test] + fn all_investor_types() { + 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_evaluating_project(project_metadata.clone(), issuer, None); + + let evaluations = vec![ + (EVALUATOR_1, 500 * USD_UNIT).into(), + (EVALUATOR_2, 1000 * USD_UNIT).into(), + (EVALUATOR_3, 20_000 * USD_UNIT).into(), + ]; + let necessary_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); + + inst.mint_plmc_to(necessary_plmc); + + assert_ok!(inst.execute(|| PolimecFunding::evaluate( + RuntimeOrigin::signed(evaluations[0].account), + get_mock_jwt_with_cid( + evaluations[0].account, + InvestorType::Institutional, + generate_did_from_account(evaluations[0].account), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + evaluations[0].usd_amount, + ))); + + assert_ok!(inst.execute(|| PolimecFunding::evaluate( + RuntimeOrigin::signed(evaluations[1].account), + get_mock_jwt_with_cid( + evaluations[1].account, + InvestorType::Professional, + generate_did_from_account(evaluations[1].account), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + evaluations[1].usd_amount, + ))); + + assert_ok!(inst.execute(|| PolimecFunding::evaluate( + RuntimeOrigin::signed(evaluations[2].account), + get_mock_jwt_with_cid( + evaluations[2].account, + InvestorType::Retail, + generate_did_from_account(evaluations[2].account), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + evaluations[2].usd_amount, + ))); + } + + #[test] + fn using_frozen_tokens() { + 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_evaluating_project(project_metadata.clone(), issuer, None); + + let evaluation = UserToUSDBalance::new(EVALUATOR_1, 500 * USD_UNIT); + let necessary_plmc = inst.calculate_evaluation_plmc_spent(vec![evaluation.clone()], true); + + inst.mint_plmc_to(necessary_plmc.clone()); + + inst.execute(|| { + mock::Balances::set_freeze(&(), &EVALUATOR_1, necessary_plmc[0].plmc_amount).unwrap(); + }); + + assert_ok!(inst.execute(|| PolimecFunding::evaluate( + RuntimeOrigin::signed(evaluation.account), + get_mock_jwt_with_cid( + evaluation.account, + InvestorType::Retail, + generate_did_from_account(evaluation.account), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + evaluation.usd_amount + ))); + } + + #[test] + fn storage_check() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); + let evaluation = UserToUSDBalance::new(EVALUATOR_1, 500 * USD_UNIT); + let necessary_plmc = inst.calculate_evaluation_plmc_spent(vec![evaluation.clone()], false); + let plmc_existential_deposits = necessary_plmc.accounts().existential_deposits(); + + inst.mint_plmc_to(necessary_plmc.clone()); + inst.mint_plmc_to(plmc_existential_deposits.clone()); + + inst.execute(|| { + assert_eq!(Evaluations::::iter_values().collect_vec(), vec![]); + }); + + let did = generate_did_from_account(evaluation.account); + + assert_ok!(inst.execute(|| PolimecFunding::evaluate( + RuntimeOrigin::signed(evaluation.account), + get_mock_jwt_with_cid( + evaluation.account, + InvestorType::Retail, + did.clone(), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + evaluation.usd_amount + ))); + + inst.execute(|| { + let evaluations = Evaluations::::iter_prefix_values((project_id,)).collect_vec(); + assert_eq!(evaluations.len(), 1); + let stored_evaluation = &evaluations[0]; + let expected_evaluation_item = EvaluationInfoOf:: { + id: 0, + did, + project_id: 0, + evaluator: EVALUATOR_1, + original_plmc_bond: necessary_plmc[0].plmc_amount, + current_plmc_bond: necessary_plmc[0].plmc_amount, + early_usd_amount: evaluation.usd_amount, + late_usd_amount: 0, + when: 1, + }; + assert_eq!(stored_evaluation, &expected_evaluation_item); + }); + } + + #[test] + fn can_evaluate_with_frozen_tokens() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let issuer = ISSUER_1; + let project_metadata = default_project_metadata(issuer); + + let evaluation = UserToUSDBalance::new(EVALUATOR_4, 1_000_000 * USD_UNIT); + let plmc_required = inst.calculate_evaluation_plmc_spent(vec![evaluation.clone()], false); + let plmc_existential_deposits = plmc_required.accounts().existential_deposits(); + let frozen_amount = plmc_required[0].plmc_amount; + + inst.mint_plmc_to(plmc_required.clone()); + inst.mint_plmc_to(plmc_existential_deposits.clone()); + + inst.execute(|| { + mock::Balances::set_freeze(&(), &EVALUATOR_4, plmc_required[0].plmc_amount).unwrap(); + }); + + inst.execute(|| { + assert_noop!( + Balances::transfer_allow_death(RuntimeOrigin::signed(EVALUATOR_4), ISSUER_1, frozen_amount,), + TokenError::Frozen + ); + }); + + let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer, None); + inst.execute(|| { + assert_ok!(PolimecFunding::evaluate( + RuntimeOrigin::signed(EVALUATOR_4), + get_mock_jwt_with_cid( + EVALUATOR_4, + InvestorType::Retail, + generate_did_from_account(EVALUATOR_4), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + evaluation.usd_amount + )); + }); + + let new_evaluations = default_evaluations(); + let new_plmc_required = inst.calculate_evaluation_plmc_spent(new_evaluations.clone(), true); + inst.mint_plmc_to(new_plmc_required.clone()); + inst.evaluate_for_users(project_id, new_evaluations).unwrap(); + + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionInitializePeriod); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionRound); + + 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 = + inst.get_reserved_plmc_balance_for(EVALUATOR_4, HoldReason::Evaluation(project_id).into()); + let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &EVALUATOR_4)); + + assert_eq!(free_balance, inst.get_ed()); + assert_eq!(evaluation_held_balance, frozen_amount); + assert_eq!(frozen_balance, frozen_amount); + + let treasury_account = ::BlockchainOperationTreasury::get(); + let pre_slash_treasury_balance = inst.get_free_plmc_balance_for(treasury_account); + + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); + + inst.execute(|| { + 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); + let free_balance = inst.get_free_plmc_balance_for(EVALUATOR_4); + let evaluation_held_balance = + inst.get_reserved_plmc_balance_for(EVALUATOR_4, HoldReason::Evaluation(project_id).into()); + let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &EVALUATOR_4)); + let account_data = inst.execute(|| System::account(&EVALUATOR_4)).data; + + let post_slash_evaluation_plmc = + frozen_amount - (::EvaluatorSlash::get() * frozen_amount); + assert_eq!(free_balance, inst.get_ed() + post_slash_evaluation_plmc); + assert_eq!(evaluation_held_balance, Zero::zero()); + assert_eq!(frozen_balance, frozen_amount); + let expected_account_data = AccountData { + free: inst.get_ed() + post_slash_evaluation_plmc, + reserved: Zero::zero(), + frozen: frozen_amount, + flags: Default::default(), + }; + assert_eq!(account_data, expected_account_data); + + assert!(account_data.frozen > account_data.free); + assert_eq!( + post_slash_treasury_balance, + pre_slash_treasury_balance + ::EvaluatorSlash::get() * frozen_amount + ); + } + } + + #[cfg(test)] + mod failure { + use super::*; + + #[test] + fn project_is_not_in_evaluation_round() { + 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_auctioning_project(project_metadata.clone(), issuer, None, default_evaluations()); + + inst.execute(|| { + assert_noop!( + PolimecFunding::evaluate( + RuntimeOrigin::signed(EVALUATOR_1), + get_mock_jwt_with_cid( + EVALUATOR_1, + InvestorType::Retail, + generate_did_from_account(EVALUATOR_1), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + 500 * USD_UNIT, + ), + Error::::IncorrectRound + ); + }); + } + + #[test] + fn insufficient_plmc_for_desired_evaluation() { + 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 insufficient_eval_deposits = inst + .calculate_evaluation_plmc_spent(evaluations.clone(), false) + .iter() + .map(|UserToPLMCBalance { account, plmc_amount }| UserToPLMCBalance::new(*account, plmc_amount / 2)) + .collect::>>(); + + let plmc_existential_deposits = insufficient_eval_deposits.accounts().existential_deposits(); + + inst.mint_plmc_to(insufficient_eval_deposits); + inst.mint_plmc_to(plmc_existential_deposits); + + let project_id = inst.create_evaluating_project(project_metadata, issuer, None); + + let dispatch_error = inst.evaluate_for_users(project_id, evaluations); + assert_err!(dispatch_error, TokenError::FundsUnavailable) + } + + #[test] + fn evaluation_placing_user_balance_under_ed() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let issuer = ISSUER_1; + let project_metadata = default_project_metadata(issuer); + let evaluations = vec![UserToUSDBalance::new(EVALUATOR_1, 1000 * USD_UNIT)]; + let evaluating_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), false); + let mut plmc_insufficient_existential_deposit = evaluating_plmc.accounts().existential_deposits(); + + plmc_insufficient_existential_deposit[0].plmc_amount = + plmc_insufficient_existential_deposit[0].plmc_amount / 2; + + inst.mint_plmc_to(evaluating_plmc); + inst.mint_plmc_to(plmc_insufficient_existential_deposit); + + let project_id = inst.create_evaluating_project(project_metadata, issuer, None); + + let dispatch_error = inst.evaluate_for_users(project_id, evaluations); + assert_err!(dispatch_error, TokenError::FundsUnavailable) + } + + #[test] + fn cannot_evaluate_more_than_project_limit() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + let evaluations = (0u32..::MaxEvaluationsPerProject::get()) + .map(|i| UserToUSDBalance::::new(i as u32 + 420u32, (100u128 * CT_UNIT).into())) + .collect_vec(); + let failing_evaluation = UserToUSDBalance::new(EVALUATOR_1, 1000 * CT_UNIT); + + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); + + let plmc_for_evaluating = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); + + inst.mint_plmc_to(plmc_for_evaluating.clone()); + + inst.evaluate_for_users(project_id, evaluations.clone()).unwrap(); + + let plmc_for_failing_evaluating = + inst.calculate_evaluation_plmc_spent(vec![failing_evaluation.clone()], true); + + inst.mint_plmc_to(plmc_for_failing_evaluating.clone()); + + assert_err!( + inst.evaluate_for_users(project_id, vec![failing_evaluation]), + Error::::TooManyProjectParticipations + ); + } + + #[test] + fn cannot_evaluate_more_than_user_limit() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + let evaluations = (0u32..::MaxEvaluationsPerUser::get()) + .map(|_| UserToUSDBalance::::new(EVALUATOR_1, (100u128 * USD_UNIT).into())) + .collect_vec(); + let failing_evaluation = UserToUSDBalance::new(EVALUATOR_1, 100 * USD_UNIT); + + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); + + let plmc_for_evaluating = inst.calculate_evaluation_plmc_spent(evaluations.clone(), false); + let plmc_existential_deposits = evaluations.accounts().existential_deposits(); + + inst.mint_plmc_to(plmc_for_evaluating.clone()); + inst.mint_plmc_to(plmc_existential_deposits.clone()); + + inst.evaluate_for_users(project_id, evaluations.clone()).unwrap(); + + let plmc_for_failing_evaluating = + inst.calculate_evaluation_plmc_spent(vec![failing_evaluation.clone()], true); + + inst.mint_plmc_to(plmc_for_failing_evaluating.clone()); + + assert_err!( + inst.evaluate_for_users(project_id, vec![failing_evaluation]), + Error::::TooManyUserParticipations + ); + } + + #[test] + fn cannot_use_balance_on_hold() { + 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_evaluating_project(project_metadata.clone(), issuer, None); + + let evaluation = UserToUSDBalance::new(EVALUATOR_1, 500 * USD_UNIT); + let necessary_plmc = inst.calculate_evaluation_plmc_spent(vec![evaluation.clone()], false); + let ed = necessary_plmc.accounts().existential_deposits(); + + inst.mint_plmc_to(necessary_plmc.clone()); + inst.mint_plmc_to(ed.clone()); + + inst.execute(|| { + ::NativeCurrency::hold( + &RuntimeHoldReason::PolimecFunding(HoldReason::Evaluation(69)), + &EVALUATOR_1, + necessary_plmc[0].plmc_amount, + ) + .unwrap(); + }); + + inst.execute(|| { + assert_noop!( + PolimecFunding::evaluate( + RuntimeOrigin::signed(evaluation.account), + get_mock_jwt_with_cid( + evaluation.account, + InvestorType::Retail, + generate_did_from_account(evaluation.account), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + evaluation.usd_amount, + ), + TokenError::FundsUnavailable + ); + }); + } + + #[test] + fn issuer_cannot_evaluate_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_evaluating_project(project_metadata.clone(), ISSUER_1, None); + assert_err!( + inst.execute(|| crate::Pallet::::do_evaluate( + &(&ISSUER_1 + 1), + project_id, + 500 * USD_UNIT, + generate_did_from_account(ISSUER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + )), + Error::::ParticipationToOwnProject + ); + } + + #[test] + fn cannot_use_same_plmc_for_2_evaluations() { + 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_evaluating_project(project_metadata.clone(), issuer, None); + + let evaluation = UserToUSDBalance::new(EVALUATOR_1, 500 * USD_UNIT); + let necessary_plmc = inst.calculate_evaluation_plmc_spent(vec![evaluation.clone()], true); + + inst.mint_plmc_to(necessary_plmc.clone()); + + inst.execute(|| { + assert_ok!(PolimecFunding::evaluate( + RuntimeOrigin::signed(evaluation.account), + get_mock_jwt_with_cid( + evaluation.account, + InvestorType::Retail, + generate_did_from_account(evaluation.account), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + evaluation.usd_amount, + )); + }); + + inst.execute(|| { + assert_noop!( + PolimecFunding::evaluate( + RuntimeOrigin::signed(evaluation.account), + get_mock_jwt_with_cid( + evaluation.account, + InvestorType::Retail, + generate_did_from_account(evaluation.account), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + evaluation.usd_amount, + ), + TokenError::FundsUnavailable + ); + }); + } + + #[test] + fn cannot_evaluate_with_less_than_100_usd() { + 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_evaluating_project(project_metadata.clone(), issuer, None); + let evaluator = EVALUATOR_1; + let jwt = get_mock_jwt_with_cid( + evaluator, + InvestorType::Retail, + generate_did_from_account(evaluator), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + + inst.mint_plmc_to(vec![(evaluator.clone(), 2000 * PLMC).into()]); + + // Cannot evaluate with 0 USD + inst.execute(|| { + assert_noop!( + Pallet::::evaluate( + RuntimeOrigin::signed(evaluator.clone()), + jwt.clone(), + project_id, + 0 + ), + Error::::TooLow + ); + }); + + // Cannot evaluate with less than 99 USD + inst.execute(|| { + assert_noop!( + Pallet::::evaluate( + RuntimeOrigin::signed(evaluator.clone()), + jwt.clone(), + project_id, + 99 * USD_UNIT + ), + Error::::TooLow + ); + }); + } + + #[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_evaluating_project(project_metadata.clone(), ISSUER_1, None); + + inst.execute(|| { + assert_noop!( + PolimecFunding::evaluate( + RuntimeOrigin::signed(EVALUATOR_1), + get_mock_jwt_with_cid( + EVALUATOR_1, + InvestorType::Retail, + generate_did_from_account(EVALUATOR_1), + "wrong_cid".as_bytes().to_vec().try_into().unwrap() + ), + project_id, + 500 * USD_UNIT, + ), + Error::::PolicyMismatch + ); + }); + } + } +} diff --git a/pallets/funding/src/tests/3_auction.rs b/pallets/funding/src/tests/3_auction.rs index ed9133003..18748e9c7 100644 --- a/pallets/funding/src/tests/3_auction.rs +++ b/pallets/funding/src/tests/3_auction.rs @@ -1,2120 +1,1976 @@ -// use super::*; -// -// #[cfg(test)] -// mod round_flow { -// use super::*; -// -// #[cfg(test)] -// mod success { -// use super::*; -// use frame_support::traits::fungibles::metadata::Inspect; -// use sp_core::bounded_vec; -// use std::{collections::HashSet, ops::Not}; -// -// #[test] -// fn auction_round_completed() { -// 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_community_contributing_project(project_metadata, ISSUER_1, None, evaluations, bids); -// } -// -// #[test] -// fn multiple_auction_projects_completed() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project1 = default_project_metadata(ISSUER_1); -// let project2 = default_project_metadata(ISSUER_2); -// let project3 = default_project_metadata(ISSUER_3); -// let project4 = default_project_metadata(ISSUER_4); -// let evaluations = default_evaluations(); -// let bids = default_bids(); -// -// inst.create_community_contributing_project(project1, ISSUER_1, None, evaluations.clone(), bids.clone()); -// inst.create_community_contributing_project(project2, ISSUER_2, None, evaluations.clone(), bids.clone()); -// inst.create_community_contributing_project(project3, ISSUER_3, None, evaluations.clone(), bids.clone()); -// inst.create_community_contributing_project(project4, ISSUER_4, None, evaluations, bids); -// } -// -// #[test] -// fn wap_is_accurate() { -// // From the knowledge hub: https://hub.polimec.org/learn/calculation-example#auction-round-calculation-example -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// -// const ADAM: u32 = 60; -// const TOM: u32 = 61; -// const SOFIA: u32 = 62; -// const FRED: u32 = 63; -// const ANNA: u32 = 64; -// const DAMIAN: u32 = 65; -// -// let accounts = vec![ADAM, TOM, SOFIA, FRED, ANNA, DAMIAN]; -// -// let bounded_name = bounded_name(); -// let bounded_symbol = bounded_symbol(); -// let metadata_hash = ipfs_hash(); -// let normalized_price = PriceOf::::from_float(10.0); -// let decimal_aware_price = PriceProviderOf::::calculate_decimals_aware_price( -// normalized_price, -// USD_DECIMALS, -// CT_DECIMALS, -// ) -// .unwrap(); -// let project_metadata = ProjectMetadata { -// token_information: CurrencyMetadata { -// name: bounded_name, -// symbol: bounded_symbol, -// decimals: CT_DECIMALS, -// }, -// mainnet_token_max_supply: 8_000_000 * CT_UNIT, -// total_allocation_size: 100_000 * CT_UNIT, -// auction_round_allocation_percentage: Percent::from_percent(50u8), -// minimum_price: decimal_aware_price, -// 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, None), -// professional: TicketSize::new(USD_UNIT, None), -// institutional: TicketSize::new(USD_UNIT, None), -// phantom: Default::default(), -// }, -// participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), -// funding_destination_account: ISSUER_1, -// policy_ipfs_cid: Some(metadata_hash), -// }; -// -// // overfund with plmc -// let plmc_fundings = accounts -// .iter() -// .map(|acc| UserToPLMCBalance { account: acc.clone(), plmc_amount: PLMC * 1_000_000 }) -// .collect_vec(); -// let usdt_fundings = accounts -// .iter() -// .map(|acc| UserToForeignAssets { -// account: acc.clone(), -// asset_amount: USD_UNIT * 1_000_000, -// asset_id: AcceptedFundingAsset::USDT.to_assethub_id(), -// }) -// .collect_vec(); -// inst.mint_plmc_to(plmc_fundings); -// inst.mint_foreign_asset_to(usdt_fundings); -// -// let project_id = -// inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); -// -// let bids = vec![ -// (ADAM, 10_000 * CT_UNIT).into(), -// (TOM, 20_000 * CT_UNIT).into(), -// (SOFIA, 20_000 * CT_UNIT).into(), -// (FRED, 10_000 * CT_UNIT).into(), -// (ANNA, 5_000 * CT_UNIT).into(), -// (DAMIAN, 5_000 * CT_UNIT).into(), -// ]; -// -// inst.bid_for_users(project_id, bids).unwrap(); -// -// inst.start_community_funding(project_id).unwrap(); -// -// 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) -// .unwrap(); -// let desired_price = PriceOf::::from_float(11.1818f64); -// -// assert_close_enough!( -// normalized_wap.saturating_mul_int(CT_UNIT), -// desired_price.saturating_mul_int(CT_UNIT), -// Perquintill::from_float(0.99) -// ); -// } -// -// #[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()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// let evaluations = default_evaluations(); -// let auction_percentage = project_metadata.auction_round_allocation_percentage; -// let total_allocation = project_metadata.total_allocation_size; -// -// let auction_allocation = auction_percentage * total_allocation; -// -// let bids = vec![(BIDDER_1, auction_allocation).into()]; -// let project_id = inst.create_community_contributing_project( -// project_metadata.clone(), -// ISSUER_1, -// None, -// evaluations.clone(), -// bids, -// ); -// 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); -// -// let project_metadata = default_project_metadata(ISSUER_2); -// let bids = vec![(BIDDER_1, auction_allocation).into(), (BIDDER_1, 1000 * CT_UNIT).into()]; -// let project_id = inst.create_community_contributing_project( -// project_metadata.clone(), -// ISSUER_2, -// None, -// 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()); -// assert!(inst.execute(|| bid_infos.next().is_none())); -// assert_eq!( -// bid_info_1.final_ct_amount + bid_info_2.final_ct_amount, -// auction_allocation, -// "Should not be able to buy more than 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() { -// 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 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); -// // 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 -------------| -// // post wap ~ 1.0557252: -// // (Accepted, 5k) - (Partially, 32k) - (Rejected, 5k) - (Accepted, 5k) - (Accepted - 5k) - (Accepted - 1k) - (Accepted - 2k) -// -// let bids = vec![bid_1, bid_2, bid_3, bid_4, bid_5]; -// -// 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( -// &bids, -// project_metadata.clone(), -// None, -// false, -// ); -// let usdt_fundings = 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(); -// -// inst.mint_plmc_to(plmc_fundings.clone()); -// inst.mint_plmc_to(plmc_existential_amounts.clone()); -// inst.mint_foreign_asset_to(usdt_fundings.clone()); -// -// inst.bid_for_users(project_id, bids.clone()).unwrap(); -// -// inst.do_free_plmc_assertions(vec![ -// 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.start_community_funding(project_id).unwrap(); -// -// let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); -// let returned_auction_plmc = -// inst.calculate_auction_plmc_returned_from_all_bids_made(&bids, project_metadata.clone(), wap); -// let returned_funding_assets = -// inst.calculate_auction_funding_asset_returned_from_all_bids_made(&bids, project_metadata, wap); -// -// let expected_free_plmc = inst.generic_map_operation( -// vec![returned_auction_plmc.clone(), plmc_existential_amounts], -// MergeOperation::Add, -// ); -// 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, -// ); -// -// 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, -// ) -// .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 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, -// ); -// 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); -// -// 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(); -// -// 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(); -// -// 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)); -// } -// -// #[test] -// fn all_bids_rejected() { -// 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(); -// -// 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); -// } -// -// #[test] -// fn wap_from_different_funding_assets() { -// // From the knowledge hub: https://hub.polimec.org/learn/calculation-example#auction-round-calculation-example -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// -// const ADAM: u32 = 60; -// const TOM: u32 = 61; -// const SOFIA: u32 = 62; -// const FRED: u32 = 63; -// const ANNA: u32 = 64; -// const DAMIAN: u32 = 65; -// -// let accounts = vec![ADAM, TOM, SOFIA, FRED, ANNA, DAMIAN]; -// let mut project_metadata = default_project_metadata(ISSUER_1); -// project_metadata.total_allocation_size = 100_000 * CT_UNIT; -// project_metadata.participation_currencies = -// bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT,]; -// -// // overfund with plmc -// let plmc_fundings = accounts -// .iter() -// .map(|acc| UserToPLMCBalance { account: acc.clone(), plmc_amount: PLMC * 1_000_000 }) -// .collect_vec(); -// -// let fundings = [AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT]; -// assert_eq!(fundings.len(), AcceptedFundingAsset::VARIANT_COUNT); -// let mut fundings = fundings.into_iter().cycle(); -// -// let usdt_fundings = accounts -// .iter() -// .map(|acc| { -// let accepted_asset = fundings.next().unwrap(); -// let asset_id = accepted_asset.to_assethub_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 } -// }) -// .collect_vec(); -// inst.mint_plmc_to(plmc_fundings); -// inst.mint_foreign_asset_to(usdt_fundings); -// -// let project_id = inst.create_auctioning_project(project_metadata, ISSUER_1, None, default_evaluations()); -// -// let bids = vec![ -// (ADAM, 10_000 * CT_UNIT, 1, AcceptedFundingAsset::USDT).into(), -// (TOM, 20_000 * CT_UNIT, 1, AcceptedFundingAsset::USDC).into(), -// (SOFIA, 20_000 * CT_UNIT, 1, AcceptedFundingAsset::DOT).into(), -// (FRED, 10_000 * CT_UNIT, 1, AcceptedFundingAsset::USDT).into(), -// (ANNA, 5_000 * CT_UNIT, 1, AcceptedFundingAsset::USDC).into(), -// (DAMIAN, 5_000 * CT_UNIT, 1, AcceptedFundingAsset::DOT).into(), -// ]; -// -// inst.bid_for_users(project_id, bids).unwrap(); -// -// inst.start_community_funding(project_id).unwrap(); -// -// 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) -// .unwrap(); -// -// let desired_price = PriceOf::::from_float(11.1818f64); -// -// assert_close_enough!( -// normalized_wap.saturating_mul_int(USD_UNIT), -// desired_price.saturating_mul_int(USD_UNIT), -// Perquintill::from_float(0.99) -// ); -// } -// -// #[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 min_bid_amounts_ct = Vec::new(); -// let mut min_bid_amounts_usd = Vec::new(); -// let mut auction_allocations_ct = Vec::new(); -// let mut auction_allocations_usd = 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_auctioning_project(project_metadata.clone(), issuer, None, evaluations); -// -// let auction_allocation_percentage = project_metadata.auction_round_allocation_percentage; -// let auction_allocation_ct = auction_allocation_percentage * project_metadata.total_allocation_size; -// auction_allocations_ct.push(auction_allocation_ct); -// let auction_allocation_usd = project_metadata.minimum_price.saturating_mul_int(auction_allocation_ct); -// auction_allocations_usd.push(auction_allocation_usd); -// -// let min_professional_bid_usd = -// project_metadata.bidding_ticket_sizes.professional.usd_minimum_per_participation; -// min_bid_amounts_usd.push(min_professional_bid_usd); -// let min_professional_bid_ct = -// project_metadata.minimum_price.reciprocal().unwrap().saturating_mul_int(min_professional_bid_usd); -// let min_professional_bid_plmc = -// usable_plmc_price.reciprocal().unwrap().saturating_mul_int(min_professional_bid_usd); -// min_bid_amounts_ct.push(min_professional_bid_ct); -// let min_professional_bid_funding_asset = -// funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(min_professional_bid_usd); -// -// // Every project should want to raise 5MM USD on the auction round regardless of CT decimals -// assert_eq!(auction_allocation_usd, 5_000_000 * USD_UNIT); -// -// // 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( -// BIDDER_1, -// min_professional_bid_funding_asset, -// funding_asset.to_assethub_id(), -// )]); -// -// assert_ok!(inst.execute(|| PolimecFunding::bid( -// RuntimeOrigin::signed(BIDDER_1), -// get_mock_jwt_with_cid( -// BIDDER_1, -// InvestorType::Professional, -// generate_did_from_account(BIDDER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// min_professional_bid_ct, -// 1u8.try_into().unwrap(), -// funding_asset, -// ))); -// -// // The bucket should have 50% of 1MM * 10^decimals CT minus what we just bid -// let bucket = inst.execute(|| Buckets::::get(project_id).unwrap()); -// assert_eq!(bucket.amount_left, 500_000u128 * 10u128.pow(decimals as u32) - min_professional_bid_ct); -// }; -// -// for decimals in 6..=18 { -// decimal_test(decimals); -// } -// -// // Since we use the same original price and allocation size and adjust for decimals, -// // the USD amounts should be the same -// assert!(min_bid_amounts_usd.iter().all(|x| *x == min_bid_amounts_usd[0])); -// assert!(auction_allocations_usd.iter().all(|x| *x == auction_allocations_usd[0])); -// -// // CT amounts however should be different from each other -// let mut hash_set_1 = HashSet::new(); -// for amount in min_bid_amounts_ct { -// assert!(!hash_set_1.contains(&amount)); -// hash_set_1.insert(amount); -// } -// let mut hash_set_2 = HashSet::new(); -// for amount in auction_allocations_ct { -// assert!(!hash_set_2.contains(&amount)); -// hash_set_2.insert(amount); -// } -// } -// -// #[test] -// fn all_bids_but_one_have_price_higher_than_wap() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let total_allocation = 10_000_000 * CT_UNIT; -// let min_bid_ct = 500 * CT_UNIT; // 5k USD at 10USD/CT -// let max_bids_per_project: u32 = ::MaxBidsPerProject::get(); -// let big_bid: BidParams = (BIDDER_1, total_allocation).into(); -// let small_bids: Vec> = -// (0..max_bids_per_project - 1).map(|i| (i + BIDDER_1, min_bid_ct).into()).collect(); -// let all_bids = vec![vec![big_bid.clone()], small_bids.clone()].into_iter().flatten().collect_vec(); -// -// let mut project_metadata = default_project_metadata(ISSUER_1); -// project_metadata.mainnet_token_max_supply = total_allocation; -// project_metadata.total_allocation_size = total_allocation; -// project_metadata.auction_round_allocation_percentage = Percent::from_percent(100); -// -// let project_id = inst.create_community_contributing_project( -// project_metadata.clone(), -// ISSUER_1, -// None, -// inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()), -// all_bids, -// ); -// -// let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); -// -// let all_bids = inst.execute(|| Bids::::iter_prefix_values((project_id,)).collect_vec()); -// -// let higher_than_wap_bids = all_bids.iter().filter(|bid| bid.original_ct_usd_price > wap).collect_vec(); -// assert_eq!(higher_than_wap_bids.len(), (max_bids_per_project - 1u32) as usize); -// } -// -// #[test] -// fn auction_oversubscription() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// let auction_allocation = -// project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; -// let bucket_size = Percent::from_percent(10) * auction_allocation; -// let bids = vec![ -// (BIDDER_1, auction_allocation).into(), -// (BIDDER_2, bucket_size).into(), -// (BIDDER_3, bucket_size).into(), -// (BIDDER_4, bucket_size).into(), -// (BIDDER_5, bucket_size).into(), -// (BIDDER_6, bucket_size).into(), -// ]; -// -// let project_id = inst.create_community_contributing_project( -// project_metadata.clone(), -// ISSUER_1, -// None, -// default_evaluations(), -// bids, -// ); -// -// let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); -// dbg!(wap); -// assert!(wap > project_metadata.minimum_price); -// } -// } -// -// #[cfg(test)] -// mod failure { -// use super::*; -// -// #[test] -// fn contribute_does_not_work() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); -// let did = generate_did_from_account(ISSUER_1); -// let investor_type = InvestorType::Retail; -// inst.execute(|| { -// assert_noop!( -// PolimecFunding::do_contribute( -// &BIDDER_1, -// project_id, -// 100, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// did, -// investor_type, -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ), -// Error::::IncorrectRound -// ); -// }); -// } -// } -// } -// -// #[cfg(test)] -// mod start_auction_extrinsic { -// use super::*; -// -// #[cfg(test)] -// mod success { -// use super::*; -// -// #[test] -// fn pallet_can_start_auction_automatically() { -// 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(); -// -// 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(); -// -// 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(); -// } -// -// #[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); -// } -// -// #[test] -// fn stranger_cannot_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(); -// -// for account in 6000..6010 { -// inst.execute(|| { -// let response = Pallet::::do_start_auction(account, project_id); -// assert_noop!(response, Error::::NotIssuer); -// }); -// } -// } -// } -// -// #[cfg(test)] -// mod failure { -// use super::*; -// -// #[test] -// fn 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.execute(|| { -// assert_noop!( -// PolimecFunding::do_start_auction(ISSUER_1, project_id), -// Error::::TransitionPointNotSet -// ); -// }); -// } -// -// #[test] -// 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); -// inst.advance_time(::EvaluationDuration::get() + 1).unwrap(); -// inst.execute(|| { -// assert_noop!( -// PolimecFunding::do_start_auction(ISSUER_1, project_id), -// Error::::TransitionPointNotSet -// ); -// }); -// 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 -// 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 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); -// } -// } -// } -// -// #[cfg(test)] -// mod bid_extrinsic { -// use super::*; -// -// #[cfg(test)] -// mod success { -// use super::*; -// use frame_support::{dispatch::DispatchResultWithPostInfo, traits::fungible::InspectFreeze}; -// -// #[test] -// fn evaluation_bond_counts_towards_bid() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let issuer = ISSUER_1; -// let project_metadata = default_project_metadata(issuer); -// let mut evaluations = default_evaluations(); -// let evaluator_bidder = 69; -// let evaluation_amount = 420 * USD_UNIT; -// let evaluator_bid = BidParams::new(evaluator_bidder, 600 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); -// evaluations.push((evaluator_bidder, evaluation_amount).into()); -// -// let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); -// -// let already_bonded_plmc = inst -// .calculate_evaluation_plmc_spent(vec![(evaluator_bidder, evaluation_amount).into()], false)[0] -// .plmc_amount; -// -// let usable_evaluation_plmc = -// already_bonded_plmc - ::EvaluatorSlash::get() * already_bonded_plmc; -// -// let necessary_plmc_for_bid = inst.calculate_auction_plmc_charged_with_given_price( -// &vec![evaluator_bid.clone()], -// project_metadata.minimum_price, -// false, -// )[0] -// .plmc_amount; -// -// let necessary_usdt_for_bid = inst.calculate_auction_funding_asset_charged_with_given_price( -// &vec![evaluator_bid.clone()], -// project_metadata.minimum_price, -// ); -// -// inst.mint_plmc_to(vec![UserToPLMCBalance::new( -// evaluator_bidder, -// necessary_plmc_for_bid - usable_evaluation_plmc, -// )]); -// inst.mint_foreign_asset_to(necessary_usdt_for_bid); -// -// inst.bid_for_users(project_id, vec![evaluator_bid]).unwrap(); -// -// let evaluation_items = inst.execute(|| { -// Evaluations::::iter_prefix_values((project_id, evaluator_bidder)).collect_vec() -// }); -// assert_eq!(evaluation_items.len(), 1); -// assert_eq!(evaluation_items[0].current_plmc_bond, already_bonded_plmc - usable_evaluation_plmc); -// -// let bid_items = -// inst.execute(|| Bids::::iter_prefix_values((project_id, evaluator_bidder)).collect_vec()); -// assert_eq!(bid_items.len(), 1); -// assert_eq!(bid_items[0].plmc_bond, necessary_plmc_for_bid); -// -// inst.do_reserved_plmc_assertions( -// vec![UserToPLMCBalance::new(evaluator_bidder, necessary_plmc_for_bid)], -// HoldReason::Participation(project_id).into(), -// ); -// inst.do_reserved_plmc_assertions( -// vec![UserToPLMCBalance::new(evaluator_bidder, already_bonded_plmc - usable_evaluation_plmc)], -// HoldReason::Evaluation(project_id).into(), -// ); -// } -// -// #[test] -// fn bid_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_bid = BidParams::new(BIDDER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); -// let usdc_bid = BidParams::new(BIDDER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDC); -// let dot_bid = BidParams::new(BIDDER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::DOT); -// -// let plmc_fundings = inst.calculate_auction_plmc_charged_with_given_price( -// &vec![usdt_bid.clone(), usdc_bid.clone(), dot_bid.clone()], -// project_metadata_all.minimum_price, -// true, -// ); -// -// inst.mint_plmc_to(plmc_fundings.clone()); -// inst.mint_plmc_to(plmc_fundings.clone()); -// inst.mint_plmc_to(plmc_fundings.clone()); -// -// let usdt_fundings = inst.calculate_auction_funding_asset_charged_with_given_price( -// &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()); -// -// let project_id_all = -// inst.create_auctioning_project(project_metadata_all, ISSUER_1, None, evaluations.clone()); -// assert_ok!(inst.bid_for_users(project_id_all, vec![usdt_bid.clone(), usdc_bid.clone(), dot_bid.clone()])); -// -// let project_id_usdt = -// inst.create_auctioning_project(project_metadata_usdt, ISSUER_2, None, evaluations.clone()); -// assert_ok!(inst.bid_for_users(project_id_usdt, vec![usdt_bid.clone()])); -// assert_err!( -// inst.bid_for_users(project_id_usdt, vec![usdc_bid.clone()]), -// Error::::FundingAssetNotAccepted -// ); -// assert_err!( -// inst.bid_for_users(project_id_usdt, vec![dot_bid.clone()]), -// Error::::FundingAssetNotAccepted -// ); -// -// let project_id_usdc = -// inst.create_auctioning_project(project_metadata_usdc, ISSUER_3, None, evaluations.clone()); -// assert_err!( -// inst.bid_for_users(project_id_usdc, vec![usdt_bid.clone()]), -// Error::::FundingAssetNotAccepted -// ); -// assert_ok!(inst.bid_for_users(project_id_usdc, vec![usdc_bid.clone()])); -// assert_err!( -// inst.bid_for_users(project_id_usdc, vec![dot_bid.clone()]), -// Error::::FundingAssetNotAccepted -// ); -// -// let project_id_dot = -// inst.create_auctioning_project(project_metadata_dot, ISSUER_4, None, evaluations.clone()); -// assert_err!( -// inst.bid_for_users(project_id_dot, vec![usdt_bid.clone()]), -// Error::::FundingAssetNotAccepted -// ); -// assert_err!( -// inst.bid_for_users(project_id_dot, vec![usdc_bid.clone()]), -// Error::::FundingAssetNotAccepted -// ); -// assert_ok!(inst.bid_for_users(project_id_dot, vec![dot_bid.clone()])); -// } -// -// fn test_bid_setup( -// inst: &mut MockInstantiator, -// project_id: ProjectId, -// bidder: 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( -// bidder.clone(), -// investor_type, -// generate_did_from_account(BIDDER_1), -// project_policy, -// ); -// let amount = 1000 * CT_UNIT; -// let multiplier = Multiplier::force_new(u8_multiplier); -// -// if u8_multiplier > 0 { -// let bid = BidParams:: { -// bidder: bidder.clone(), -// amount, -// multiplier, -// asset: AcceptedFundingAsset::USDT, -// }; -// let min_price = inst.get_project_metadata(project_id).minimum_price; -// let necessary_plmc = -// inst.calculate_auction_plmc_charged_with_given_price(&vec![bid.clone()], min_price, true); -// let necessary_usdt = -// 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.execute(|| { -// Pallet::::bid( -// RuntimeOrigin::signed(bidder), -// jwt, -// project_id, -// amount, -// multiplier, -// AcceptedFundingAsset::USDT, -// ) -// }) -// } -// -// #[test] -// fn multiplier_limits() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// let evaluations = -// inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); -// let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); -// // Professional bids: 0x multiplier should fail -// assert_err!( -// test_bid_setup(&mut inst, project_id, BIDDER_1, InvestorType::Professional, 0), -// Error::::ForbiddenMultiplier -// ); -// // Professional bids: 1 - 10x multiplier should work -// for multiplier in 1..=10u8 { -// assert_ok!(test_bid_setup(&mut inst, project_id, BIDDER_1, InvestorType::Professional, multiplier)); -// } -// // Professional bids: >=11x multiplier should fail -// for multiplier in 11..=50u8 { -// assert_err!( -// test_bid_setup(&mut inst, project_id, BIDDER_1, InvestorType::Professional, multiplier), -// Error::::ForbiddenMultiplier -// ); -// } -// -// // Institutional bids: 0x multiplier should fail -// assert_err!( -// test_bid_setup(&mut inst, project_id, BIDDER_2, InvestorType::Institutional, 0), -// Error::::ForbiddenMultiplier -// ); -// // Institutional bids: 1 - 25x multiplier should work -// for multiplier in 1..=25u8 { -// assert_ok!(test_bid_setup(&mut inst, project_id, BIDDER_2, InvestorType::Institutional, multiplier)); -// } -// // Institutional bids: >=26x multiplier should fail -// for multiplier in 26..=50u8 { -// assert_err!( -// test_bid_setup(&mut inst, project_id, BIDDER_2, InvestorType::Institutional, multiplier), -// Error::::ForbiddenMultiplier -// ); -// } -// } -// -// #[test] -// fn bid_split_into_multiple_buckets() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// -// let mut project_metadata = default_project_metadata(ISSUER_1); -// project_metadata.minimum_price = PriceProviderOf::::calculate_decimals_aware_price( -// PriceOf::::from_float(1.0), -// USD_DECIMALS, -// project_metadata.clone().token_information.decimals, -// ) -// .unwrap(); -// project_metadata.auction_round_allocation_percentage = Percent::from_percent(50u8); -// -// let evaluations = default_evaluations(); -// let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); -// -// // bid that fills 80% of the first bucket -// let bid_40_percent = inst.generate_bids_from_total_ct_percent( -// project_metadata.clone(), -// 40u8, -// vec![100], -// vec![BIDDER_1], -// vec![8u8], -// ); -// -// // Note: 5% of total CTs is one bucket, i.e 10% of the auction allocation -// // This bid fills last 20% of the first bucket, -// // and gets split into 3 more bids of 2 more full and one partially full buckets. -// // 10% + 5% + 5% + 3% = 23% -// let bid_23_percent = inst.generate_bids_from_total_ct_percent( -// project_metadata.clone(), -// 23u8, -// vec![100], -// vec![BIDDER_2], -// vec![7u8], -// ); -// -// let all_bids = vec![bid_40_percent[0].clone(), bid_23_percent[0].clone()]; -// -// let necessary_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( -// &all_bids, -// project_metadata.clone(), -// None, -// true, -// ); -// let necessary_usdt = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( -// &all_bids, -// project_metadata.clone(), -// None, -// ); -// inst.mint_plmc_to(necessary_plmc.clone()); -// inst.mint_foreign_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()); -// assert_eq!(stored_bids.len(), 1); -// -// inst.bid_for_users(project_id, bid_23_percent.clone()).unwrap(); -// let mut stored_bids = inst.execute(|| Bids::::iter_prefix_values((project_id,)).collect_vec()); -// stored_bids.sort_by(|a, b| a.id.cmp(&b.id)); -// // 40% + 10% + 5% + 5% + 3% = 5 total bids -// assert_eq!(stored_bids.len(), 5); -// -// let normalize_price = |decimal_aware_price| { -// PriceProviderOf::::convert_back_to_normal_price( -// decimal_aware_price, -// USD_DECIMALS, -// project_metadata.clone().token_information.decimals, -// ) -// .unwrap() -// }; -// assert_eq!(normalize_price(stored_bids[1].original_ct_usd_price), PriceOf::::from_float(1.0)); -// assert_eq!( -// stored_bids[1].original_ct_amount, -// Percent::from_percent(10) * project_metadata.total_allocation_size -// ); -// assert_eq!( -// normalize_price(stored_bids[2].original_ct_usd_price), -// PriceOf::::from_rational(11, 10) -// ); -// assert_eq!( -// stored_bids[2].original_ct_amount, -// Percent::from_percent(5) * project_metadata.total_allocation_size -// ); -// -// assert_eq!(normalize_price(stored_bids[3].original_ct_usd_price), PriceOf::::from_float(1.2)); -// assert_eq!( -// stored_bids[3].original_ct_amount, -// Percent::from_percent(5) * project_metadata.total_allocation_size -// ); -// -// assert_eq!(normalize_price(stored_bids[4].original_ct_usd_price), PriceOf::::from_float(1.3)); -// assert_eq!( -// stored_bids[4].original_ct_amount, -// Percent::from_percent(3) * project_metadata.total_allocation_size -// ); -// let current_bucket = inst.execute(|| Buckets::::get(project_id)).unwrap(); -// assert_eq!(normalize_price(current_bucket.current_price), PriceOf::::from_float(1.3)); -// assert_eq!(current_bucket.amount_left, Percent::from_percent(2) * project_metadata.total_allocation_size); -// assert_eq!(normalize_price(current_bucket.delta_price), PriceOf::::from_float(0.1)); -// } -// -// #[test] -// fn can_bid_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_auctioning_project(project_metadata.clone(), issuer, None, default_evaluations()); -// -// let bid = BidParams::new(BIDDER_4, 500 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); -// let plmc_required = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( -// &vec![bid.clone()], -// project_metadata.clone(), -// None, -// 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(&(), &BIDDER_4, plmc_required[0].plmc_amount).unwrap(); -// }); -// -// let usdt_required = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( -// &vec![bid.clone()], -// project_metadata.clone(), -// None, -// ); -// inst.mint_foreign_asset_to(usdt_required); -// -// inst.execute(|| { -// assert_noop!( -// Balances::transfer_allow_death(RuntimeOrigin::signed(BIDDER_4), ISSUER_1, frozen_amount,), -// TokenError::Frozen -// ); -// }); -// -// inst.execute(|| { -// assert_ok!(PolimecFunding::bid( -// RuntimeOrigin::signed(BIDDER_4), -// get_mock_jwt_with_cid( -// BIDDER_4, -// InvestorType::Institutional, -// generate_did_from_account(BIDDER_4), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// bid.amount, -// bid.multiplier, -// bid.asset -// )); -// }); -// -// 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); -// -// let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); -// let bid_held_balance = -// inst.get_reserved_plmc_balance_for(BIDDER_4, HoldReason::Participation(project_id).into()); -// let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BIDDER_4)); -// -// assert_eq!(free_balance, inst.get_ed()); -// 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); -// -// inst.execute(|| { -// PolimecFunding::settle_failed_bid(RuntimeOrigin::signed(BIDDER_4), project_id, BIDDER_4, 0).unwrap(); -// }); -// -// let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); -// let bid_held_balance = -// inst.get_reserved_plmc_balance_for(BIDDER_4, HoldReason::Evaluation(project_id).into()); -// let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BIDDER_4)); -// -// assert_eq!(free_balance, inst.get_ed() + frozen_amount); -// assert_eq!(bid_held_balance, Zero::zero()); -// assert_eq!(frozen_balance, frozen_amount); -// } -// -// #[test] -// fn can_bid_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_auctioning_project(project_metadata.clone(), issuer, None, default_evaluations()); -// -// let bid = BidParams::new(BIDDER_4, 500 * CT_UNIT, 5u8, AcceptedFundingAsset::USDT); -// let plmc_required = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( -// &vec![bid.clone()], -// project_metadata.clone(), -// None, -// 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(&(), &BIDDER_4, plmc_required[0].plmc_amount).unwrap(); -// }); -// -// let usdt_required = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( -// &vec![bid.clone()], -// project_metadata.clone(), -// None, -// ); -// inst.mint_foreign_asset_to(usdt_required); -// -// inst.execute(|| { -// assert_noop!( -// Balances::transfer_allow_death(RuntimeOrigin::signed(BIDDER_4), ISSUER_1, frozen_amount,), -// TokenError::Frozen -// ); -// }); -// -// inst.execute(|| { -// assert_ok!(PolimecFunding::bid( -// RuntimeOrigin::signed(BIDDER_4), -// get_mock_jwt_with_cid( -// BIDDER_4, -// InvestorType::Institutional, -// generate_did_from_account(BIDDER_4), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// bid.amount, -// bid.multiplier, -// bid.asset -// )); -// }); -// -// inst.start_community_funding(project_id).unwrap(); -// let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); -// -// let contributions = inst.generate_contributions_from_total_ct_percent( -// project_metadata.clone(), -// 90u8, -// default_weights(), -// default_community_contributors(), -// default_multipliers(), -// ); -// let plmc_required = inst.calculate_contributed_plmc_spent(contributions.clone(), wap, false); -// let plmc_existential_deposits = plmc_required.accounts().existential_deposits(); -// inst.mint_plmc_to(plmc_required.clone()); -// 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.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); -// -// let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); -// let bid_held_balance = -// inst.get_reserved_plmc_balance_for(BIDDER_4, HoldReason::Participation(project_id).into()); -// let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BIDDER_4)); -// -// assert_eq!(free_balance, inst.get_ed()); -// assert_eq!(bid_held_balance, frozen_amount); -// assert_eq!(frozen_balance, frozen_amount); -// -// inst.execute(|| { -// PolimecFunding::settle_successful_bid(RuntimeOrigin::signed(BIDDER_4), project_id, BIDDER_4, 0) -// .unwrap(); -// }); -// -// let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); -// let bid_held_balance = -// inst.get_reserved_plmc_balance_for(BIDDER_4, HoldReason::Participation(project_id).into()); -// let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BIDDER_4)); -// -// assert_eq!(free_balance, inst.get_ed()); -// assert_eq!(bid_held_balance, frozen_amount); -// assert_eq!(frozen_balance, frozen_amount); -// -// 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(BIDDER_4), -// HoldReason::Participation(project_id).into() -// )); -// }); -// -// let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); -// let bid_held_balance = -// inst.get_reserved_plmc_balance_for(BIDDER_4, HoldReason::Participation(project_id).into()); -// let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BIDDER_4)); -// -// assert_eq!(free_balance, inst.get_ed() + frozen_amount); -// assert_eq!(bid_held_balance, Zero::zero()); -// assert_eq!(frozen_balance, frozen_amount); -// } -// } -// -// #[cfg(test)] -// mod failure { -// use super::*; -// -// #[test] -// fn cannot_use_all_of_evaluation_bond_on_bid() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let issuer = ISSUER_1; -// let project_metadata = default_project_metadata(issuer); -// let mut evaluations = default_evaluations(); -// let evaluator_bidder = 69; -// let evaluation_amount = 420 * USD_UNIT; -// let evaluator_bid = BidParams::new(evaluator_bidder, 600 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); -// evaluations.push((evaluator_bidder, evaluation_amount).into()); -// -// let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); -// -// let necessary_usdt_for_bid = inst.calculate_auction_funding_asset_charged_with_given_price( -// &vec![evaluator_bid.clone()], -// project_metadata.minimum_price, -// ); -// -// inst.mint_foreign_asset_to(necessary_usdt_for_bid); -// -// assert_err!( -// inst.bid_for_users(project_id, vec![evaluator_bid]), -// Error::::ParticipantNotEnoughFunds -// ); -// } -// -// #[test] -// fn cannot_use_evaluation_bond_on_another_project_bid() { -// 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_bidder = 69; -// let evaluation_amount = 420 * USD_UNIT; -// let evaluator_bid = BidParams::new(evaluator_bidder, 600 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); -// evaluations_1.push((evaluator_bidder, evaluation_amount).into()); -// -// let _project_id_1 = -// inst.create_auctioning_project(project_metadata_1.clone(), ISSUER_1, None, evaluations_1); -// let project_id_2 = -// inst.create_auctioning_project(project_metadata_2.clone(), ISSUER_2, None, evaluations_2); -// -// // Necessary Mints -// let already_bonded_plmc = inst -// .calculate_evaluation_plmc_spent(vec![(evaluator_bidder, evaluation_amount).into()], false)[0] -// .plmc_amount; -// let usable_evaluation_plmc = -// already_bonded_plmc - ::EvaluatorSlash::get() * already_bonded_plmc; -// let necessary_plmc_for_bid = inst.calculate_auction_plmc_charged_with_given_price( -// &vec![evaluator_bid.clone()], -// project_metadata_2.minimum_price, -// false, -// )[0] -// .plmc_amount; -// let necessary_usdt_for_bid = inst.calculate_auction_funding_asset_charged_with_given_price( -// &vec![evaluator_bid.clone()], -// project_metadata_2.minimum_price, -// ); -// inst.mint_plmc_to(vec![UserToPLMCBalance::new( -// evaluator_bidder, -// necessary_plmc_for_bid - usable_evaluation_plmc, -// )]); -// inst.mint_foreign_asset_to(necessary_usdt_for_bid); -// -// inst.execute(|| { -// assert_noop!( -// PolimecFunding::bid( -// RuntimeOrigin::signed(evaluator_bidder), -// get_mock_jwt_with_cid( -// evaluator_bidder, -// InvestorType::Professional, -// generate_did_from_account(evaluator_bidder), -// project_metadata_2.clone().policy_ipfs_cid.unwrap() -// ), -// project_id_2, -// evaluator_bid.amount, -// evaluator_bid.multiplier, -// evaluator_bid.asset -// ), -// Error::::ParticipantNotEnoughFunds -// ); -// }); -// } -// -// #[test] -// fn cannot_bid_before_auction_round() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// let _ = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); -// let did = generate_did_from_account(BIDDER_2); -// let investor_type = InvestorType::Institutional; -// -// inst.execute(|| { -// assert_noop!( -// PolimecFunding::do_bid( -// &BIDDER_2, -// 0, -// 1, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// did, -// investor_type, -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ), -// Error::::IncorrectRound -// ); -// }); -// } -// -// #[test] -// fn cannot_bid_more_than_project_limit_count() { -// 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 = 1_000_000_000 * CT_UNIT; -// project_metadata.total_allocation_size = 100_000_000 * CT_UNIT; -// -// let evaluations = -// inst.generate_successful_evaluations(project_metadata.clone(), vec![EVALUATOR_1], vec![100u8]); -// let max_bids_per_project: u32 = ::MaxBidsPerProject::get(); -// let bids = -// (0u32..max_bids_per_project - 1).map(|i| (i as u32 + 420u32, 5000 * CT_UNIT).into()).collect_vec(); -// -// let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); -// -// let plmc_for_bidding = inst.calculate_auction_plmc_charged_with_given_price( -// &bids.clone(), -// project_metadata.minimum_price, -// true, -// ); -// let usdt_for_bidding = inst.calculate_auction_funding_asset_charged_with_given_price( -// &bids.clone(), -// project_metadata.minimum_price, -// ); -// -// inst.mint_plmc_to(plmc_for_bidding.clone()); -// inst.mint_foreign_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(); -// let remaining_ct = current_bucket.amount_left; -// -// // This bid should be split in 2, but the second one should fail, making the whole extrinsic fail and roll back storage -// let failing_bid = -// BidParams::::new(BIDDER_1, remaining_ct + 5000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); -// let plmc_for_failing_bid = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( -// &vec![failing_bid.clone()], -// project_metadata.clone(), -// Some(current_bucket), -// true, -// ); -// -// let usdt_for_bidding = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( -// &vec![failing_bid.clone()], -// project_metadata.clone(), -// Some(current_bucket), -// ); -// -// inst.mint_plmc_to(plmc_for_failing_bid.clone()); -// inst.mint_foreign_asset_to(usdt_for_bidding.clone()); -// -// inst.execute(|| { -// assert_noop!( -// PolimecFunding::bid( -// RuntimeOrigin::signed(failing_bid.bidder), -// get_mock_jwt_with_cid( -// failing_bid.bidder, -// InvestorType::Professional, -// generate_did_from_account(failing_bid.bidder), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// failing_bid.amount, -// failing_bid.multiplier, -// failing_bid.asset -// ), -// Error::::TooManyProjectParticipations -// ); -// }); -// -// // Now we test that after reaching the limit, just one bid is also not allowed -// inst.execute(|| { -// assert_ok!(PolimecFunding::bid( -// RuntimeOrigin::signed(failing_bid.bidder), -// get_mock_jwt_with_cid( -// failing_bid.bidder, -// InvestorType::Professional, -// generate_did_from_account(failing_bid.bidder), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// remaining_ct, -// failing_bid.multiplier, -// failing_bid.asset -// )); -// }); -// inst.execute(|| { -// assert_noop!( -// PolimecFunding::bid( -// RuntimeOrigin::signed(failing_bid.bidder), -// get_mock_jwt_with_cid( -// failing_bid.bidder, -// InvestorType::Professional, -// generate_did_from_account(failing_bid.bidder), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// 5000 * CT_UNIT, -// failing_bid.multiplier, -// failing_bid.asset -// ), -// Error::::TooManyProjectParticipations -// ); -// }); -// } -// -// #[test] -// fn cannot_bid_more_than_user_limit_count() { -// 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 = 1_000_000_000 * CT_UNIT; -// project_metadata.total_allocation_size = 100_000_000 * CT_UNIT; -// -// let evaluations = -// inst.generate_successful_evaluations(project_metadata.clone(), vec![EVALUATOR_1], vec![100u8]); -// let max_bids_per_user: u32 = ::MaxBidsPerUser::get(); -// let bids = (0u32..max_bids_per_user - 1u32).map(|_| (BIDDER_1, 5000 * CT_UNIT).into()).collect_vec(); -// -// let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); -// -// let plmc_for_bidding = inst.calculate_auction_plmc_charged_with_given_price( -// &bids.clone(), -// project_metadata.minimum_price, -// true, -// ); -// let usdt_for_bidding = inst.calculate_auction_funding_asset_charged_with_given_price( -// &bids.clone(), -// project_metadata.minimum_price, -// ); -// -// inst.mint_plmc_to(plmc_for_bidding.clone()); -// inst.mint_foreign_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(); -// let remaining_ct = current_bucket.amount_left; -// -// // This bid should be split in 2, but the second one should fail, making the whole extrinsic fail and roll back storage -// let failing_bid = -// BidParams::::new(BIDDER_1, remaining_ct + 5000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); -// let plmc_for_failing_bid = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( -// &vec![failing_bid.clone()], -// project_metadata.clone(), -// Some(current_bucket), -// true, -// ); -// let usdt_for_bidding = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( -// &vec![failing_bid.clone()], -// project_metadata.clone(), -// Some(current_bucket), -// ); -// inst.mint_plmc_to(plmc_for_failing_bid.clone()); -// inst.mint_foreign_asset_to(usdt_for_bidding.clone()); -// -// inst.execute(|| { -// assert_noop!( -// PolimecFunding::bid( -// RuntimeOrigin::signed(failing_bid.bidder), -// get_mock_jwt_with_cid( -// failing_bid.bidder, -// InvestorType::Professional, -// generate_did_from_account(failing_bid.bidder), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// failing_bid.amount, -// failing_bid.multiplier, -// failing_bid.asset -// ), -// Error::::TooManyUserParticipations -// ); -// }); -// -// // Now we test that after reaching the limit, just one bid is also not allowed -// inst.execute(|| { -// assert_ok!(PolimecFunding::bid( -// RuntimeOrigin::signed(failing_bid.bidder), -// get_mock_jwt_with_cid( -// failing_bid.bidder, -// InvestorType::Professional, -// generate_did_from_account(failing_bid.bidder), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// remaining_ct, -// failing_bid.multiplier, -// failing_bid.asset -// )); -// }); -// inst.execute(|| { -// assert_noop!( -// PolimecFunding::bid( -// RuntimeOrigin::signed(failing_bid.bidder), -// get_mock_jwt_with_cid( -// failing_bid.bidder, -// InvestorType::Professional, -// generate_did_from_account(failing_bid.bidder), -// project_metadata.clone().policy_ipfs_cid.unwrap() -// ), -// project_id, -// 5000 * CT_UNIT, -// failing_bid.multiplier, -// failing_bid.asset -// ), -// Error::::TooManyUserParticipations -// ); -// }); -// } -// -// #[test] -// fn per_credential_type_ticket_size_minimums() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let mut project_metadata = default_project_metadata(ISSUER_1); -// project_metadata.total_allocation_size = 100_000 * CT_UNIT; -// project_metadata.bidding_ticket_sizes = BiddingTicketSizes { -// professional: TicketSize::new(8_000 * USD_UNIT, None), -// institutional: TicketSize::new(20_000 * USD_UNIT, None), -// phantom: Default::default(), -// }; -// -// let evaluations = default_evaluations(); -// -// let project_id = -// inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations.clone()); -// -// 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![ -// (BIDDER_1, 50_000 * USD_UNIT).into(), -// (BIDDER_2, 50_000 * USD_UNIT).into(), -// ]); -// -// // bid below 800 CT (8k USD) should fail for professionals -// inst.execute(|| { -// assert_noop!( -// Pallet::::do_bid( -// &BIDDER_1, -// project_id, -// 799 * CT_UNIT, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// generate_did_from_account(BIDDER_1), -// InvestorType::Professional, -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ), -// Error::::TooLow -// ); -// }); -// // bid below 2000 CT (20k USD) should fail for institutionals -// inst.execute(|| { -// assert_noop!( -// Pallet::::do_bid( -// &BIDDER_2, -// project_id, -// 1999 * CT_UNIT, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// generate_did_from_account(BIDDER_1), -// InvestorType::Institutional, -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ), -// Error::::TooLow -// ); -// }); -// } -// -// #[test] -// fn ticket_size_minimums_use_current_bucket_price() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let mut project_metadata = default_project_metadata(ISSUER_1); -// project_metadata.total_allocation_size = 100_000 * CT_UNIT; -// project_metadata.bidding_ticket_sizes = BiddingTicketSizes { -// professional: TicketSize::new(8_000 * USD_UNIT, None), -// institutional: TicketSize::new(20_000 * USD_UNIT, None), -// phantom: Default::default(), -// }; -// project_metadata.minimum_price = PriceProviderOf::::calculate_decimals_aware_price( -// PriceOf::::from_float(1.0), -// USD_DECIMALS, -// project_metadata.clone().token_information.decimals, -// ) -// .unwrap(); -// -// let evaluations = default_evaluations(); -// -// let project_id = -// inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations.clone()); -// -// inst.mint_plmc_to(vec![ -// (BIDDER_1, 200_000 * PLMC).into(), -// (BIDDER_2, 200_000 * PLMC).into(), -// (BIDDER_3, 200_000 * PLMC).into(), -// ]); -// inst.mint_foreign_asset_to(vec![ -// (BIDDER_1, 200_000 * USDT_UNIT).into(), -// (BIDDER_2, 200_000 * USDT_UNIT).into(), -// (BIDDER_3, 200_000 * USDT_UNIT).into(), -// ]); -// -// // First bucket is covered by one bidder -// let big_bid: BidParams = (BIDDER_1, 50_000 * CT_UNIT).into(); -// inst.bid_for_users(project_id, vec![big_bid.clone()]).unwrap(); -// -// // A bid at the min price of 1 should require a min of 8k CT, but with a new price of 1.1, we can now bid with less -// let bucket_increase_price = PriceProviderOf::::calculate_decimals_aware_price( -// PriceOf::::from_float(1.1), -// USD_DECIMALS, -// project_metadata.clone().token_information.decimals, -// ) -// .unwrap(); -// let smallest_ct_amount_at_8k_usd = bucket_increase_price -// .reciprocal() -// .unwrap() -// .checked_mul_int(8000 * USD_UNIT) -// // add 1 because result could be .99999 of what we expect -// .unwrap() + 1; -// assert!(smallest_ct_amount_at_8k_usd < 8000 * CT_UNIT); -// inst.execute(|| { -// assert_ok!(Pallet::::do_bid( -// &BIDDER_1, -// project_id, -// smallest_ct_amount_at_8k_usd, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// generate_did_from_account(BIDDER_1), -// InvestorType::Professional, -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// )); -// }); -// let smallest_ct_amount_at_20k_usd = bucket_increase_price -// .reciprocal() -// .unwrap() -// .checked_mul_int(20_000 * USD_UNIT) -// // add 1 because result could be .99999 of what we expect -// .unwrap() + 1; -// assert!(smallest_ct_amount_at_20k_usd < 20_000 * CT_UNIT); -// inst.execute(|| { -// assert_ok!(Pallet::::do_bid( -// &BIDDER_2, -// project_id, -// smallest_ct_amount_at_20k_usd, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// generate_did_from_account(BIDDER_1), -// InvestorType::Institutional, -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// )); -// }); -// } -// -// #[test] -// fn per_credential_type_ticket_size_maximums() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let mut project_metadata = default_project_metadata(ISSUER_1); -// project_metadata.bidding_ticket_sizes = BiddingTicketSizes { -// professional: TicketSize::new(8_000 * USD_UNIT, Some(100_000 * USD_UNIT)), -// institutional: TicketSize::new(20_000 * USD_UNIT, Some(500_000 * USD_UNIT)), -// phantom: Default::default(), -// }; -// project_metadata.contributing_ticket_sizes = ContributingTicketSizes { -// retail: TicketSize::new(USD_UNIT, Some(100_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(), -// }; -// let evaluations = default_evaluations(); -// -// let project_id = -// inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations.clone()); -// -// inst.mint_plmc_to(vec![ -// (BIDDER_1, 500_000 * CT_UNIT).into(), -// (BIDDER_2, 500_000 * CT_UNIT).into(), -// (BIDDER_3, 500_000 * CT_UNIT).into(), -// (BIDDER_4, 500_000 * CT_UNIT).into(), -// ]); -// -// inst.mint_foreign_asset_to(vec![ -// (BIDDER_1, 500_000 * USD_UNIT).into(), -// (BIDDER_2, 500_000 * USD_UNIT).into(), -// (BIDDER_3, 500_000 * USD_UNIT).into(), -// (BIDDER_4, 500_000 * USD_UNIT).into(), -// ]); -// -// let bidder_1_jwt = get_mock_jwt_with_cid( -// BIDDER_1, -// InvestorType::Professional, -// generate_did_from_account(BIDDER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// let bidder_2_jwt_same_did = get_mock_jwt_with_cid( -// BIDDER_2, -// InvestorType::Professional, -// generate_did_from_account(BIDDER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// // total bids with same DID above 10k CT (100k USD) should fail for professionals -// inst.execute(|| { -// assert_ok!(Pallet::::bid( -// RuntimeOrigin::signed(BIDDER_1), -// bidder_1_jwt, -// project_id, -// 8000 * CT_UNIT, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// )); -// }); -// inst.execute(|| { -// assert_noop!( -// Pallet::::bid( -// RuntimeOrigin::signed(BIDDER_2), -// bidder_2_jwt_same_did.clone(), -// project_id, -// 3000 * CT_UNIT, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT -// ), -// Error::::TooHigh -// ); -// }); -// // bidding 10k total works -// inst.execute(|| { -// assert_ok!(Pallet::::bid( -// RuntimeOrigin::signed(BIDDER_2), -// bidder_2_jwt_same_did, -// project_id, -// 2000 * CT_UNIT, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// )); -// }); -// -// let bidder_3_jwt = get_mock_jwt_with_cid( -// BIDDER_3, -// InvestorType::Institutional, -// generate_did_from_account(BIDDER_3), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// let bidder_4_jwt_same_did = get_mock_jwt_with_cid( -// BIDDER_4, -// InvestorType::Institutional, -// generate_did_from_account(BIDDER_3), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// // total bids with same DID above 50k CT (500k USD) should fail for institutionals -// inst.execute(|| { -// assert_ok!(Pallet::::bid( -// RuntimeOrigin::signed(BIDDER_3), -// bidder_3_jwt, -// project_id, -// 40_000 * CT_UNIT, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// )); -// }); -// inst.execute(|| { -// assert_noop!( -// Pallet::::bid( -// RuntimeOrigin::signed(BIDDER_4), -// bidder_4_jwt_same_did.clone(), -// project_id, -// 11_000 * CT_UNIT, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// ), -// Error::::TooHigh -// ); -// }); -// // bidding 50k total works -// inst.execute(|| { -// assert_ok!(Pallet::::bid( -// RuntimeOrigin::signed(BIDDER_4), -// bidder_4_jwt_same_did, -// project_id, -// 10_000 * CT_UNIT, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// )); -// }); -// } -// -// #[test] -// fn issuer_cannot_bid_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_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); -// assert_err!( -// inst.execute(|| crate::Pallet::::do_bid( -// &(&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 bid_with_asset_not_accepted() { -// 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 bids = vec![BidParams::::new(BIDDER_1, 10_000, 1u8, AcceptedFundingAsset::USDC)]; -// -// let did = generate_did_from_account(bids[0].bidder); -// let investor_type = InvestorType::Institutional; -// -// let outcome = inst.execute(|| { -// Pallet::::do_bid( -// &bids[0].bidder, -// project_id, -// bids[0].amount, -// bids[0].multiplier, -// bids[0].asset, -// did, -// investor_type, -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ) -// }); -// frame_support::assert_err!(outcome, Error::::FundingAssetNotAccepted); -// } -// -// #[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_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); -// -// inst.execute(|| { -// assert_noop!( -// PolimecFunding::bid( -// RuntimeOrigin::signed(BIDDER_1), -// get_mock_jwt_with_cid( -// BIDDER_1, -// InvestorType::Professional, -// generate_did_from_account(BIDDER_1), -// "wrong_cid".as_bytes().to_vec().try_into().unwrap() -// ), -// project_id, -// 5000 * CT_UNIT, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT -// ), -// Error::::PolicyMismatch -// ); -// }); -// } -// } -// } +use super::*; + +#[cfg(test)] +mod round_flow { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + use frame_support::traits::fungibles::metadata::Inspect; + use sp_core::bounded_vec; + use std::{collections::HashSet}; + + #[test] + fn auction_round_completed() { + 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_community_contributing_project(project_metadata, ISSUER_1, None, evaluations, bids); + } + + #[test] + fn multiple_auction_projects_completed() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project1 = default_project_metadata(ISSUER_1); + let project2 = default_project_metadata(ISSUER_2); + let project3 = default_project_metadata(ISSUER_3); + let project4 = default_project_metadata(ISSUER_4); + let evaluations = default_evaluations(); + let bids = default_bids(); + + inst.create_community_contributing_project(project1, ISSUER_1, None, evaluations.clone(), bids.clone()); + inst.create_community_contributing_project(project2, ISSUER_2, None, evaluations.clone(), bids.clone()); + inst.create_community_contributing_project(project3, ISSUER_3, None, evaluations.clone(), bids.clone()); + inst.create_community_contributing_project(project4, ISSUER_4, None, evaluations, bids); + } + + #[test] + fn wap_is_accurate() { + // From the knowledge hub: https://hub.polimec.org/learn/calculation-example#auction-round-calculation-example + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + + const ADAM: u32 = 60; + const TOM: u32 = 61; + const SOFIA: u32 = 62; + const FRED: u32 = 63; + const ANNA: u32 = 64; + const DAMIAN: u32 = 65; + + let accounts = vec![ADAM, TOM, SOFIA, FRED, ANNA, DAMIAN]; + + let bounded_name = bounded_name(); + let bounded_symbol = bounded_symbol(); + let metadata_hash = ipfs_hash(); + let normalized_price = PriceOf::::from_float(10.0); + let decimal_aware_price = PriceProviderOf::::calculate_decimals_aware_price( + normalized_price, + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + let project_metadata = ProjectMetadata { + token_information: CurrencyMetadata { + name: bounded_name, + symbol: bounded_symbol, + decimals: CT_DECIMALS, + }, + mainnet_token_max_supply: 8_000_000 * CT_UNIT, + total_allocation_size: 100_000 * CT_UNIT, + auction_round_allocation_percentage: Percent::from_percent(50u8), + minimum_price: decimal_aware_price, + 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, None), + professional: TicketSize::new(USD_UNIT, None), + institutional: TicketSize::new(USD_UNIT, None), + phantom: Default::default(), + }, + participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), + funding_destination_account: ISSUER_1, + policy_ipfs_cid: Some(metadata_hash), + }; + + // overfund with plmc + let plmc_fundings = accounts + .iter() + .map(|acc| UserToPLMCBalance { account: acc.clone(), plmc_amount: PLMC * 1_000_000 }) + .collect_vec(); + let usdt_fundings = accounts + .iter() + .map(|acc| UserToForeignAssets { + account: acc.clone(), + asset_amount: USD_UNIT * 1_000_000, + asset_id: AcceptedFundingAsset::USDT.id(), + }) + .collect_vec(); + inst.mint_plmc_to(plmc_fundings); + inst.mint_funding_asset_to(usdt_fundings); + + let project_id = + inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); + + let bids = vec![ + (ADAM, 10_000 * CT_UNIT).into(), + (TOM, 20_000 * CT_UNIT).into(), + (SOFIA, 20_000 * CT_UNIT).into(), + (FRED, 10_000 * CT_UNIT).into(), + (ANNA, 5_000 * CT_UNIT).into(), + (DAMIAN, 5_000 * CT_UNIT).into(), + ]; + + inst.bid_for_users(project_id, bids).unwrap(); + + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); + + let project_details = inst.get_project_details(project_id); + dbg!(&project_details); + 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) + .unwrap(); + let desired_price = PriceOf::::from_float(11.1818f64); + + assert_close_enough!( + normalized_wap.saturating_mul_int(CT_UNIT), + desired_price.saturating_mul_int(CT_UNIT), + Perquintill::from_float(0.99) + ); + } + + #[test] + fn auction_gets_percentage_of_ct_total_allocation() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + let evaluations = default_evaluations(); + let auction_percentage = project_metadata.auction_round_allocation_percentage; + let total_allocation = project_metadata.total_allocation_size; + + let auction_allocation = auction_percentage * total_allocation; + + let bids = vec![(BIDDER_1, auction_allocation).into()]; + let project_id = inst.create_community_contributing_project( + project_metadata.clone(), + ISSUER_1, + None, + evaluations.clone(), + bids, + ); + 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.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()]; + let project_id = inst.create_community_contributing_project( + project_metadata.clone(), + ISSUER_2, + None, + evaluations.clone(), + bids, + ); + 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.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 not be able to buy more than auction allocation" + ); + 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() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let issuer = ISSUER_1; + 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(); + + // 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) + // | -------------------- 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) + + let bids = vec![bid_1, bid_2, bid_3, bid_4, bid_5]; + + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); + + let plmc_amounts = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + &bids, + project_metadata.clone(), + None, + false, + ); + 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_amounts.accounts().existential_deposits(); + + inst.mint_plmc_to(plmc_amounts.clone()); + inst.mint_plmc_to(plmc_existential_amounts.clone()); + inst.mint_funding_asset_to(funding_asset_amounts.clone()); + + inst.bid_for_users(project_id, bids.clone()).unwrap(); + + inst.do_free_plmc_assertions(vec![ + UserToPLMCBalance::new(BIDDER_1, inst.get_ed()), + UserToPLMCBalance::new(BIDDER_2, inst.get_ed()), + ]); + inst.do_reserved_plmc_assertions(plmc_amounts.clone(), HoldReason::Participation(project_id).into()); + + 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 = + inst.calculate_auction_plmc_returned_from_all_bids_made(&bids, project_metadata.clone(), wap); + let returned_funding_assets = + inst.calculate_auction_funding_asset_returned_from_all_bids_made(&bids, project_metadata, wap); + + let expected_free_plmc = inst.generic_map_operation( + vec![returned_auction_plmc.clone(), plmc_existential_amounts], + MergeOperation::Add, + ); + 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_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_foreign_asset_mappings(vec![expected_final_funding_spent]); + + // 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(); + 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 + ); + + // 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) + ); + 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.settle_project(project_id); + + 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); + + 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 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!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); + + assert_eq!( + inst.get_project_details(project_id).weighted_average_price, + Some(project_metadata.minimum_price) + ); + } + + #[test] + fn wap_from_different_funding_assets() { + // From the knowledge hub: https://hub.polimec.org/learn/calculation-example#auction-round-calculation-example + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + + const ADAM: u32 = 60; + const TOM: u32 = 61; + const SOFIA: u32 = 62; + const FRED: u32 = 63; + const ANNA: u32 = 64; + const DAMIAN: u32 = 65; + + let accounts = vec![ADAM, TOM, SOFIA, FRED, ANNA, DAMIAN]; + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.total_allocation_size = 100_000 * CT_UNIT; + project_metadata.participation_currencies = + bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT,]; + + // overfund with plmc + let plmc_fundings = accounts + .iter() + .map(|acc| UserToPLMCBalance { account: acc.clone(), plmc_amount: PLMC * 1_000_000 }) + .collect_vec(); + + let fundings = [AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT]; + assert_eq!(fundings.len(), AcceptedFundingAsset::VARIANT_COUNT); + let mut fundings = fundings.into_iter().cycle(); + + let usdt_fundings = accounts + .iter() + .map(|acc| { + let accepted_asset = fundings.next().unwrap(); + 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 } + }) + .collect_vec(); + inst.mint_plmc_to(plmc_fundings); + inst.mint_funding_asset_to(usdt_fundings); + + let project_id = inst.create_auctioning_project(project_metadata, ISSUER_1, None, default_evaluations()); + + let bids = vec![ + (ADAM, 10_000 * CT_UNIT, 1, AcceptedFundingAsset::USDT).into(), + (TOM, 20_000 * CT_UNIT, 1, AcceptedFundingAsset::USDC).into(), + (SOFIA, 20_000 * CT_UNIT, 1, AcceptedFundingAsset::DOT).into(), + (FRED, 10_000 * CT_UNIT, 1, AcceptedFundingAsset::USDT).into(), + (ANNA, 5_000 * CT_UNIT, 1, AcceptedFundingAsset::USDC).into(), + (DAMIAN, 5_000 * CT_UNIT, 1, AcceptedFundingAsset::DOT).into(), + ]; + + inst.bid_for_users(project_id, bids).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 = + PriceProviderOf::::convert_back_to_normal_price(token_price, USD_DECIMALS, CT_DECIMALS) + .unwrap(); + + let desired_price = PriceOf::::from_float(11.1818f64); + + assert_close_enough!( + normalized_wap.saturating_mul_int(USD_UNIT), + desired_price.saturating_mul_int(USD_UNIT), + Perquintill::from_float(0.99) + ); + } + + #[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.id(), + USD_DECIMALS, + ForeignAssets::decimals(AcceptedFundingAsset::USDT.id()), + ) + .unwrap() + }); + let usdc_price = inst.execute(|| { + ::PriceProvider::get_decimals_aware_price( + AcceptedFundingAsset::USDC.id(), + USD_DECIMALS, + ForeignAssets::decimals(AcceptedFundingAsset::USDC.id()), + ) + .unwrap() + }); + let dot_price = inst.execute(|| { + ::PriceProvider::get_decimals_aware_price( + AcceptedFundingAsset::DOT.id(), + USD_DECIMALS, + ForeignAssets::decimals(AcceptedFundingAsset::DOT.id()), + ) + .unwrap() + }); + + let mut funding_assets_cycle = + vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT] + .into_iter() + .cycle(); + + let mut min_bid_amounts_ct = Vec::new(); + let mut min_bid_amounts_usd = Vec::new(); + let mut auction_allocations_ct = Vec::new(); + let mut auction_allocations_usd = 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_auctioning_project(project_metadata.clone(), issuer, None, evaluations); + + let auction_allocation_percentage = project_metadata.auction_round_allocation_percentage; + let auction_allocation_ct = auction_allocation_percentage * project_metadata.total_allocation_size; + auction_allocations_ct.push(auction_allocation_ct); + let auction_allocation_usd = project_metadata.minimum_price.saturating_mul_int(auction_allocation_ct); + auction_allocations_usd.push(auction_allocation_usd); + + let min_professional_bid_usd = + project_metadata.bidding_ticket_sizes.professional.usd_minimum_per_participation; + min_bid_amounts_usd.push(min_professional_bid_usd); + let min_professional_bid_ct = + project_metadata.minimum_price.reciprocal().unwrap().saturating_mul_int(min_professional_bid_usd); + let min_professional_bid_plmc = + usable_plmc_price.reciprocal().unwrap().saturating_mul_int(min_professional_bid_usd); + min_bid_amounts_ct.push(min_professional_bid_ct); + let min_professional_bid_funding_asset = + funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(min_professional_bid_usd); + + // Every project should want to raise 5MM USD on the auction round regardless of CT decimals + assert_eq!(auction_allocation_usd, 5_000_000 * USD_UNIT); + + // 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_funding_asset_to(vec![UserToForeignAssets::new( + BIDDER_1, + min_professional_bid_funding_asset, + funding_asset.id(), + )]); + + assert_ok!(inst.execute(|| PolimecFunding::bid( + RuntimeOrigin::signed(BIDDER_1), + get_mock_jwt_with_cid( + BIDDER_1, + InvestorType::Professional, + generate_did_from_account(BIDDER_1), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + min_professional_bid_ct, + 1u8.try_into().unwrap(), + funding_asset, + ))); + + // The bucket should have 50% of 1MM * 10^decimals CT minus what we just bid + let bucket = inst.execute(|| Buckets::::get(project_id).unwrap()); + assert_eq!(bucket.amount_left, 500_000u128 * 10u128.pow(decimals as u32) - min_professional_bid_ct); + }; + + for decimals in 6..=18 { + decimal_test(decimals); + } + + // Since we use the same original price and allocation size and adjust for decimals, + // the USD amounts should be the same + assert!(min_bid_amounts_usd.iter().all(|x| *x == min_bid_amounts_usd[0])); + assert!(auction_allocations_usd.iter().all(|x| *x == auction_allocations_usd[0])); + + // CT amounts however should be different from each other + let mut hash_set_1 = HashSet::new(); + for amount in min_bid_amounts_ct { + assert!(!hash_set_1.contains(&amount)); + hash_set_1.insert(amount); + } + let mut hash_set_2 = HashSet::new(); + for amount in auction_allocations_ct { + assert!(!hash_set_2.contains(&amount)); + hash_set_2.insert(amount); + } + } + + #[test] + fn all_bids_but_one_have_price_higher_than_wap() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let total_allocation = 10_000_000 * CT_UNIT; + let min_bid_ct = 500 * CT_UNIT; // 5k USD at 10USD/CT + let max_bids_per_project: u32 = ::MaxBidsPerProject::get(); + let big_bid: BidParams = (BIDDER_1, total_allocation).into(); + let small_bids: Vec> = + (0..max_bids_per_project - 1).map(|i| (i + BIDDER_1, min_bid_ct).into()).collect(); + let all_bids = vec![vec![big_bid.clone()], small_bids.clone()].into_iter().flatten().collect_vec(); + + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.mainnet_token_max_supply = total_allocation; + project_metadata.total_allocation_size = total_allocation; + project_metadata.auction_round_allocation_percentage = Percent::from_percent(100); + + let project_id = inst.create_community_contributing_project( + project_metadata.clone(), + ISSUER_1, + None, + inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()), + all_bids, + ); + + let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); + + let all_bids = inst.execute(|| Bids::::iter_prefix_values((project_id,)).collect_vec()); + + let higher_than_wap_bids = all_bids.iter().filter(|bid| bid.original_ct_usd_price > wap).collect_vec(); + assert_eq!(higher_than_wap_bids.len(), (max_bids_per_project - 1u32) as usize); + } + + #[test] + fn auction_oversubscription() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + let auction_allocation = + project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let bucket_size = Percent::from_percent(10) * auction_allocation; + let bids = vec![ + (BIDDER_1, auction_allocation).into(), + (BIDDER_2, bucket_size).into(), + (BIDDER_3, bucket_size).into(), + (BIDDER_4, bucket_size).into(), + (BIDDER_5, bucket_size).into(), + (BIDDER_6, bucket_size).into(), + ]; + + let project_id = inst.create_community_contributing_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + bids, + ); + + let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); + dbg!(wap); + assert!(wap > project_metadata.minimum_price); + } + } + + #[cfg(test)] + mod failure { + use super::*; + + #[test] + fn contribute_does_not_work() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); + let did = generate_did_from_account(ISSUER_1); + let investor_type = InvestorType::Retail; + inst.execute(|| { + assert_noop!( + PolimecFunding::do_contribute( + &BIDDER_1, + project_id, + 100, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + did, + investor_type, + project_metadata.clone().policy_ipfs_cid.unwrap(), + ), + Error::::IncorrectRound + ); + }); + } + } +} + +#[cfg(test)] +mod start_auction_extrinsic { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + + #[test] + 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(); + 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); + let end_block = inst.get_project_details(project_id).round_duration.end().unwrap(); + inst.jump_to_block(end_block); + + inst.execute(|| PolimecFunding::start_auction(RuntimeOrigin::signed(420u32), project_id)).unwrap(); + + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionRound); + } + + #[test] + 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(); + + 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); + } + } + + #[cfg(test)] + mod failure { + use super::*; + + #[test] + 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::start_auction(RuntimeOrigin::signed(420u32), project_id), + Error::::TooEarlyForRound + ); + }); + + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionInitializePeriod); + } + + #[test] + 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.execute(|| { + assert_noop!( + PolimecFunding::start_auction(RuntimeOrigin::signed(ISSUER_1), project_id), + Error::::IncorrectRound + ); + }); + } + + #[test] + 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); + + inst.execute(|| { + assert_noop!( + PolimecFunding::start_auction(RuntimeOrigin::signed(ISSUER_1), project_id), + Error::::IncorrectRound + ); + }); + } + } +} + +#[cfg(test)] +mod bid_extrinsic { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + + #[test] + fn evaluation_bond_counts_towards_bid() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let issuer = ISSUER_1; + let project_metadata = default_project_metadata(issuer); + let mut evaluations = default_evaluations(); + let evaluator_bidder = 69; + let evaluation_amount = 420 * USD_UNIT; + let evaluator_bid = BidParams::new(evaluator_bidder, 600 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + evaluations.push((evaluator_bidder, evaluation_amount).into()); + + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); + + let already_bonded_plmc = inst + .calculate_evaluation_plmc_spent(vec![(evaluator_bidder, evaluation_amount).into()], false)[0] + .plmc_amount; + + let usable_evaluation_plmc = + already_bonded_plmc - ::EvaluatorSlash::get() * already_bonded_plmc; + + let necessary_plmc_for_bid = inst.calculate_auction_plmc_charged_with_given_price( + &vec![evaluator_bid.clone()], + project_metadata.minimum_price, + false, + )[0] + .plmc_amount; + + let necessary_usdt_for_bid = inst.calculate_auction_funding_asset_charged_with_given_price( + &vec![evaluator_bid.clone()], + project_metadata.minimum_price, + ); + + inst.mint_plmc_to(vec![UserToPLMCBalance::new( + evaluator_bidder, + necessary_plmc_for_bid - usable_evaluation_plmc, + )]); + inst.mint_funding_asset_to(necessary_usdt_for_bid); + + inst.bid_for_users(project_id, vec![evaluator_bid]).unwrap(); + + let evaluation_items = inst.execute(|| { + Evaluations::::iter_prefix_values((project_id, evaluator_bidder)).collect_vec() + }); + assert_eq!(evaluation_items.len(), 1); + assert_eq!(evaluation_items[0].current_plmc_bond, already_bonded_plmc - usable_evaluation_plmc); + + let bid_items = + inst.execute(|| Bids::::iter_prefix_values((project_id, evaluator_bidder)).collect_vec()); + assert_eq!(bid_items.len(), 1); + assert_eq!(bid_items[0].plmc_bond, necessary_plmc_for_bid); + + inst.do_reserved_plmc_assertions( + vec![UserToPLMCBalance::new(evaluator_bidder, necessary_plmc_for_bid)], + HoldReason::Participation(project_id).into(), + ); + inst.do_reserved_plmc_assertions( + vec![UserToPLMCBalance::new(evaluator_bidder, already_bonded_plmc - usable_evaluation_plmc)], + HoldReason::Evaluation(project_id).into(), + ); + } + + // #[test] + // fn bid_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_bid = BidParams::new(BIDDER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + // let usdc_bid = BidParams::new(BIDDER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDC); + // let dot_bid = BidParams::new(BIDDER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::DOT); + // + // let plmc_fundings = inst.calculate_auction_plmc_charged_with_given_price( + // &vec![usdt_bid.clone(), usdc_bid.clone(), dot_bid.clone()], + // project_metadata_all.minimum_price, + // true, + // ); + // + // inst.mint_plmc_to(plmc_fundings.clone()); + // inst.mint_plmc_to(plmc_fundings.clone()); + // inst.mint_plmc_to(plmc_fundings.clone()); + // + // let usdt_fundings = inst.calculate_auction_funding_asset_charged_with_given_price( + // &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()); + // + // let project_id_all = + // inst.create_auctioning_project(project_metadata_all, ISSUER_1, None, evaluations.clone()); + // assert_ok!(inst.bid_for_users(project_id_all, vec![usdt_bid.clone(), usdc_bid.clone(), dot_bid.clone()])); + // + // let project_id_usdt = + // inst.create_auctioning_project(project_metadata_usdt, ISSUER_2, None, evaluations.clone()); + // assert_ok!(inst.bid_for_users(project_id_usdt, vec![usdt_bid.clone()])); + // assert_err!( + // inst.bid_for_users(project_id_usdt, vec![usdc_bid.clone()]), + // Error::::FundingAssetNotAccepted + // ); + // assert_err!( + // inst.bid_for_users(project_id_usdt, vec![dot_bid.clone()]), + // Error::::FundingAssetNotAccepted + // ); + // + // let project_id_usdc = + // inst.create_auctioning_project(project_metadata_usdc, ISSUER_3, None, evaluations.clone()); + // assert_err!( + // inst.bid_for_users(project_id_usdc, vec![usdt_bid.clone()]), + // Error::::FundingAssetNotAccepted + // ); + // assert_ok!(inst.bid_for_users(project_id_usdc, vec![usdc_bid.clone()])); + // assert_err!( + // inst.bid_for_users(project_id_usdc, vec![dot_bid.clone()]), + // Error::::FundingAssetNotAccepted + // ); + // + // let project_id_dot = + // inst.create_auctioning_project(project_metadata_dot, ISSUER_4, None, evaluations.clone()); + // assert_err!( + // inst.bid_for_users(project_id_dot, vec![usdt_bid.clone()]), + // Error::::FundingAssetNotAccepted + // ); + // assert_err!( + // inst.bid_for_users(project_id_dot, vec![usdc_bid.clone()]), + // Error::::FundingAssetNotAccepted + // ); + // assert_ok!(inst.bid_for_users(project_id_dot, vec![dot_bid.clone()])); + // } + // + // fn test_bid_setup( + // inst: &mut MockInstantiator, + // project_id: ProjectId, + // bidder: 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( + // bidder.clone(), + // investor_type, + // generate_did_from_account(BIDDER_1), + // project_policy, + // ); + // let amount = 1000 * CT_UNIT; + // let multiplier = Multiplier::force_new(u8_multiplier); + // + // if u8_multiplier > 0 { + // let bid = BidParams:: { + // bidder: bidder.clone(), + // amount, + // multiplier, + // asset: AcceptedFundingAsset::USDT, + // }; + // let min_price = inst.get_project_metadata(project_id).minimum_price; + // let necessary_plmc = + // inst.calculate_auction_plmc_charged_with_given_price(&vec![bid.clone()], min_price, true); + // let necessary_usdt = + // 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.execute(|| { + // Pallet::::bid( + // RuntimeOrigin::signed(bidder), + // jwt, + // project_id, + // amount, + // multiplier, + // AcceptedFundingAsset::USDT, + // ) + // }) + // } + // + // #[test] + // fn multiplier_limits() { + // let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + // let project_metadata = default_project_metadata(ISSUER_1); + // let evaluations = + // inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); + // let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); + // // Professional bids: 0x multiplier should fail + // assert_err!( + // test_bid_setup(&mut inst, project_id, BIDDER_1, InvestorType::Professional, 0), + // Error::::ForbiddenMultiplier + // ); + // // Professional bids: 1 - 10x multiplier should work + // for multiplier in 1..=10u8 { + // assert_ok!(test_bid_setup(&mut inst, project_id, BIDDER_1, InvestorType::Professional, multiplier)); + // } + // // Professional bids: >=11x multiplier should fail + // for multiplier in 11..=50u8 { + // assert_err!( + // test_bid_setup(&mut inst, project_id, BIDDER_1, InvestorType::Professional, multiplier), + // Error::::ForbiddenMultiplier + // ); + // } + // + // // Institutional bids: 0x multiplier should fail + // assert_err!( + // test_bid_setup(&mut inst, project_id, BIDDER_2, InvestorType::Institutional, 0), + // Error::::ForbiddenMultiplier + // ); + // // Institutional bids: 1 - 25x multiplier should work + // for multiplier in 1..=25u8 { + // assert_ok!(test_bid_setup(&mut inst, project_id, BIDDER_2, InvestorType::Institutional, multiplier)); + // } + // // Institutional bids: >=26x multiplier should fail + // for multiplier in 26..=50u8 { + // assert_err!( + // test_bid_setup(&mut inst, project_id, BIDDER_2, InvestorType::Institutional, multiplier), + // Error::::ForbiddenMultiplier + // ); + // } + // } + // + // #[test] + // fn bid_split_into_multiple_buckets() { + // let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + // + // let mut project_metadata = default_project_metadata(ISSUER_1); + // project_metadata.minimum_price = PriceProviderOf::::calculate_decimals_aware_price( + // PriceOf::::from_float(1.0), + // USD_DECIMALS, + // project_metadata.clone().token_information.decimals, + // ) + // .unwrap(); + // project_metadata.auction_round_allocation_percentage = Percent::from_percent(50u8); + // + // let evaluations = default_evaluations(); + // let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); + // + // // bid that fills 80% of the first bucket + // let bid_40_percent = inst.generate_bids_from_total_ct_percent( + // project_metadata.clone(), + // 40u8, + // vec![100], + // vec![BIDDER_1], + // vec![8u8], + // ); + // + // // Note: 5% of total CTs is one bucket, i.e 10% of the auction allocation + // // This bid fills last 20% of the first bucket, + // // and gets split into 3 more bids of 2 more full and one partially full buckets. + // // 10% + 5% + 5% + 3% = 23% + // let bid_23_percent = inst.generate_bids_from_total_ct_percent( + // project_metadata.clone(), + // 23u8, + // vec![100], + // vec![BIDDER_2], + // vec![7u8], + // ); + // + // let all_bids = vec![bid_40_percent[0].clone(), bid_23_percent[0].clone()]; + // + // let necessary_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + // &all_bids, + // project_metadata.clone(), + // None, + // true, + // ); + // let necessary_usdt = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( + // &all_bids, + // project_metadata.clone(), + // None, + // ); + // inst.mint_plmc_to(necessary_plmc.clone()); + // inst.mint_foreign_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()); + // assert_eq!(stored_bids.len(), 1); + // + // inst.bid_for_users(project_id, bid_23_percent.clone()).unwrap(); + // let mut stored_bids = inst.execute(|| Bids::::iter_prefix_values((project_id,)).collect_vec()); + // stored_bids.sort_by(|a, b| a.id.cmp(&b.id)); + // // 40% + 10% + 5% + 5% + 3% = 5 total bids + // assert_eq!(stored_bids.len(), 5); + // + // let normalize_price = |decimal_aware_price| { + // PriceProviderOf::::convert_back_to_normal_price( + // decimal_aware_price, + // USD_DECIMALS, + // project_metadata.clone().token_information.decimals, + // ) + // .unwrap() + // }; + // assert_eq!(normalize_price(stored_bids[1].original_ct_usd_price), PriceOf::::from_float(1.0)); + // assert_eq!( + // stored_bids[1].original_ct_amount, + // Percent::from_percent(10) * project_metadata.total_allocation_size + // ); + // assert_eq!( + // normalize_price(stored_bids[2].original_ct_usd_price), + // PriceOf::::from_rational(11, 10) + // ); + // assert_eq!( + // stored_bids[2].original_ct_amount, + // Percent::from_percent(5) * project_metadata.total_allocation_size + // ); + // + // assert_eq!(normalize_price(stored_bids[3].original_ct_usd_price), PriceOf::::from_float(1.2)); + // assert_eq!( + // stored_bids[3].original_ct_amount, + // Percent::from_percent(5) * project_metadata.total_allocation_size + // ); + // + // assert_eq!(normalize_price(stored_bids[4].original_ct_usd_price), PriceOf::::from_float(1.3)); + // assert_eq!( + // stored_bids[4].original_ct_amount, + // Percent::from_percent(3) * project_metadata.total_allocation_size + // ); + // let current_bucket = inst.execute(|| Buckets::::get(project_id)).unwrap(); + // assert_eq!(normalize_price(current_bucket.current_price), PriceOf::::from_float(1.3)); + // assert_eq!(current_bucket.amount_left, Percent::from_percent(2) * project_metadata.total_allocation_size); + // assert_eq!(normalize_price(current_bucket.delta_price), PriceOf::::from_float(0.1)); + // } + // + // #[test] + // fn can_bid_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_auctioning_project(project_metadata.clone(), issuer, None, default_evaluations()); + // + // let bid = BidParams::new(BIDDER_4, 500 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + // let plmc_required = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + // &vec![bid.clone()], + // project_metadata.clone(), + // None, + // 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(&(), &BIDDER_4, plmc_required[0].plmc_amount).unwrap(); + // }); + // + // let usdt_required = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( + // &vec![bid.clone()], + // project_metadata.clone(), + // None, + // ); + // inst.mint_foreign_asset_to(usdt_required); + // + // inst.execute(|| { + // assert_noop!( + // Balances::transfer_allow_death(RuntimeOrigin::signed(BIDDER_4), ISSUER_1, frozen_amount,), + // TokenError::Frozen + // ); + // }); + // + // inst.execute(|| { + // assert_ok!(PolimecFunding::bid( + // RuntimeOrigin::signed(BIDDER_4), + // get_mock_jwt_with_cid( + // BIDDER_4, + // InvestorType::Institutional, + // generate_did_from_account(BIDDER_4), + // project_metadata.clone().policy_ipfs_cid.unwrap() + // ), + // project_id, + // bid.amount, + // bid.multiplier, + // bid.asset + // )); + // }); + // + // 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); + // + // let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); + // let bid_held_balance = + // inst.get_reserved_plmc_balance_for(BIDDER_4, HoldReason::Participation(project_id).into()); + // let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BIDDER_4)); + // + // assert_eq!(free_balance, inst.get_ed()); + // 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); + // + // inst.execute(|| { + // PolimecFunding::settle_failed_bid(RuntimeOrigin::signed(BIDDER_4), project_id, BIDDER_4, 0).unwrap(); + // }); + // + // let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); + // let bid_held_balance = + // inst.get_reserved_plmc_balance_for(BIDDER_4, HoldReason::Evaluation(project_id).into()); + // let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BIDDER_4)); + // + // assert_eq!(free_balance, inst.get_ed() + frozen_amount); + // assert_eq!(bid_held_balance, Zero::zero()); + // assert_eq!(frozen_balance, frozen_amount); + // } + // + // #[test] + // fn can_bid_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_auctioning_project(project_metadata.clone(), issuer, None, default_evaluations()); + // + // let bid = BidParams::new(BIDDER_4, 500 * CT_UNIT, 5u8, AcceptedFundingAsset::USDT); + // let plmc_required = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + // &vec![bid.clone()], + // project_metadata.clone(), + // None, + // 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(&(), &BIDDER_4, plmc_required[0].plmc_amount).unwrap(); + // }); + // + // let usdt_required = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( + // &vec![bid.clone()], + // project_metadata.clone(), + // None, + // ); + // inst.mint_foreign_asset_to(usdt_required); + // + // inst.execute(|| { + // assert_noop!( + // Balances::transfer_allow_death(RuntimeOrigin::signed(BIDDER_4), ISSUER_1, frozen_amount,), + // TokenError::Frozen + // ); + // }); + // + // inst.execute(|| { + // assert_ok!(PolimecFunding::bid( + // RuntimeOrigin::signed(BIDDER_4), + // get_mock_jwt_with_cid( + // BIDDER_4, + // InvestorType::Institutional, + // generate_did_from_account(BIDDER_4), + // project_metadata.clone().policy_ipfs_cid.unwrap() + // ), + // project_id, + // bid.amount, + // bid.multiplier, + // bid.asset + // )); + // }); + // + // inst.start_community_funding(project_id).unwrap(); + // let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); + // + // let contributions = inst.generate_contributions_from_total_ct_percent( + // project_metadata.clone(), + // 90u8, + // default_weights(), + // default_community_contributors(), + // default_multipliers(), + // ); + // let plmc_required = inst.calculate_contributed_plmc_spent(contributions.clone(), wap, false); + // let plmc_existential_deposits = plmc_required.accounts().existential_deposits(); + // inst.mint_plmc_to(plmc_required.clone()); + // 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.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); + // + // let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); + // let bid_held_balance = + // inst.get_reserved_plmc_balance_for(BIDDER_4, HoldReason::Participation(project_id).into()); + // let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BIDDER_4)); + // + // assert_eq!(free_balance, inst.get_ed()); + // assert_eq!(bid_held_balance, frozen_amount); + // assert_eq!(frozen_balance, frozen_amount); + // + // inst.execute(|| { + // PolimecFunding::settle_successful_bid(RuntimeOrigin::signed(BIDDER_4), project_id, BIDDER_4, 0) + // .unwrap(); + // }); + // + // let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); + // let bid_held_balance = + // inst.get_reserved_plmc_balance_for(BIDDER_4, HoldReason::Participation(project_id).into()); + // let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BIDDER_4)); + // + // assert_eq!(free_balance, inst.get_ed()); + // assert_eq!(bid_held_balance, frozen_amount); + // assert_eq!(frozen_balance, frozen_amount); + // + // 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(BIDDER_4), + // HoldReason::Participation(project_id).into() + // )); + // }); + // + // let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); + // let bid_held_balance = + // inst.get_reserved_plmc_balance_for(BIDDER_4, HoldReason::Participation(project_id).into()); + // let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BIDDER_4)); + // + // assert_eq!(free_balance, inst.get_ed() + frozen_amount); + // assert_eq!(bid_held_balance, Zero::zero()); + // assert_eq!(frozen_balance, frozen_amount); + // } + } + + #[cfg(test)] + mod failure { + use super::*; + + #[test] + fn cannot_use_all_of_evaluation_bond_on_bid() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let issuer = ISSUER_1; + let project_metadata = default_project_metadata(issuer); + let mut evaluations = default_evaluations(); + let evaluator_bidder = 69; + let evaluation_amount = 420 * USD_UNIT; + let evaluator_bid = BidParams::new(evaluator_bidder, 600 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + evaluations.push((evaluator_bidder, evaluation_amount).into()); + + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); + + let necessary_usdt_for_bid = inst.calculate_auction_funding_asset_charged_with_given_price( + &vec![evaluator_bid.clone()], + project_metadata.minimum_price, + ); + + inst.mint_funding_asset_to(necessary_usdt_for_bid); + + assert_err!( + inst.bid_for_users(project_id, vec![evaluator_bid]), + Error::::ParticipantNotEnoughFunds + ); + } + + #[test] + fn cannot_use_evaluation_bond_on_another_project_bid() { + 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_bidder = 69; + let evaluation_amount = 420 * USD_UNIT; + let evaluator_bid = BidParams::new(evaluator_bidder, 600 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + evaluations_1.push((evaluator_bidder, evaluation_amount).into()); + + let _project_id_1 = + inst.create_auctioning_project(project_metadata_1.clone(), ISSUER_1, None, evaluations_1); + let project_id_2 = + inst.create_auctioning_project(project_metadata_2.clone(), ISSUER_2, None, evaluations_2); + + // Necessary Mints + let already_bonded_plmc = inst + .calculate_evaluation_plmc_spent(vec![(evaluator_bidder, evaluation_amount).into()], false)[0] + .plmc_amount; + let usable_evaluation_plmc = + already_bonded_plmc - ::EvaluatorSlash::get() * already_bonded_plmc; + let necessary_plmc_for_bid = inst.calculate_auction_plmc_charged_with_given_price( + &vec![evaluator_bid.clone()], + project_metadata_2.minimum_price, + false, + )[0] + .plmc_amount; + let necessary_usdt_for_bid = inst.calculate_auction_funding_asset_charged_with_given_price( + &vec![evaluator_bid.clone()], + project_metadata_2.minimum_price, + ); + inst.mint_plmc_to(vec![UserToPLMCBalance::new( + evaluator_bidder, + necessary_plmc_for_bid - usable_evaluation_plmc, + )]); + inst.mint_funding_asset_to(necessary_usdt_for_bid); + + inst.execute(|| { + assert_noop!( + PolimecFunding::bid( + RuntimeOrigin::signed(evaluator_bidder), + get_mock_jwt_with_cid( + evaluator_bidder, + InvestorType::Professional, + generate_did_from_account(evaluator_bidder), + project_metadata_2.clone().policy_ipfs_cid.unwrap() + ), + project_id_2, + evaluator_bid.amount, + evaluator_bid.multiplier, + evaluator_bid.asset + ), + Error::::ParticipantNotEnoughFunds + ); + }); + } + + #[test] + fn cannot_bid_before_auction_round() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + let _ = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); + let did = generate_did_from_account(BIDDER_2); + let investor_type = InvestorType::Institutional; + + inst.execute(|| { + assert_noop!( + PolimecFunding::do_bid( + &BIDDER_2, + 0, + 1, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + did, + investor_type, + project_metadata.clone().policy_ipfs_cid.unwrap(), + ), + Error::::IncorrectRound + ); + }); + } + + #[test] + fn cannot_bid_more_than_project_limit_count() { + 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 = 1_000_000_000 * CT_UNIT; + project_metadata.total_allocation_size = 100_000_000 * CT_UNIT; + + let evaluations = + inst.generate_successful_evaluations(project_metadata.clone(), vec![EVALUATOR_1], vec![100u8]); + let max_bids_per_project: u32 = ::MaxBidsPerProject::get(); + let bids = + (0u32..max_bids_per_project - 1).map(|i| (i as u32 + 420u32, 5000 * CT_UNIT).into()).collect_vec(); + + let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); + + let plmc_for_bidding = inst.calculate_auction_plmc_charged_with_given_price( + &bids.clone(), + project_metadata.minimum_price, + true, + ); + let usdt_for_bidding = inst.calculate_auction_funding_asset_charged_with_given_price( + &bids.clone(), + project_metadata.minimum_price, + ); + + inst.mint_plmc_to(plmc_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(); + let remaining_ct = current_bucket.amount_left; + + // This bid should be split in 2, but the second one should fail, making the whole extrinsic fail and roll back storage + let failing_bid = + BidParams::::new(BIDDER_1, remaining_ct + 5000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + let plmc_for_failing_bid = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + &vec![failing_bid.clone()], + project_metadata.clone(), + Some(current_bucket), + true, + ); + + let usdt_for_bidding = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( + &vec![failing_bid.clone()], + project_metadata.clone(), + Some(current_bucket), + ); + + inst.mint_plmc_to(plmc_for_failing_bid.clone()); + inst.mint_funding_asset_to(usdt_for_bidding.clone()); + + inst.execute(|| { + assert_noop!( + PolimecFunding::bid( + RuntimeOrigin::signed(failing_bid.bidder), + get_mock_jwt_with_cid( + failing_bid.bidder, + InvestorType::Professional, + generate_did_from_account(failing_bid.bidder), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + failing_bid.amount, + failing_bid.multiplier, + failing_bid.asset + ), + Error::::TooManyProjectParticipations + ); + }); + + // Now we test that after reaching the limit, just one bid is also not allowed + inst.execute(|| { + assert_ok!(PolimecFunding::bid( + RuntimeOrigin::signed(failing_bid.bidder), + get_mock_jwt_with_cid( + failing_bid.bidder, + InvestorType::Professional, + generate_did_from_account(failing_bid.bidder), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + remaining_ct, + failing_bid.multiplier, + failing_bid.asset + )); + }); + inst.execute(|| { + assert_noop!( + PolimecFunding::bid( + RuntimeOrigin::signed(failing_bid.bidder), + get_mock_jwt_with_cid( + failing_bid.bidder, + InvestorType::Professional, + generate_did_from_account(failing_bid.bidder), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + 5000 * CT_UNIT, + failing_bid.multiplier, + failing_bid.asset + ), + Error::::TooManyProjectParticipations + ); + }); + } + + #[test] + fn cannot_bid_more_than_user_limit_count() { + 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 = 1_000_000_000 * CT_UNIT; + project_metadata.total_allocation_size = 100_000_000 * CT_UNIT; + + let evaluations = + inst.generate_successful_evaluations(project_metadata.clone(), vec![EVALUATOR_1], vec![100u8]); + let max_bids_per_user: u32 = ::MaxBidsPerUser::get(); + let bids = (0u32..max_bids_per_user - 1u32).map(|_| (BIDDER_1, 5000 * CT_UNIT).into()).collect_vec(); + + let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); + + let plmc_for_bidding = inst.calculate_auction_plmc_charged_with_given_price( + &bids.clone(), + project_metadata.minimum_price, + true, + ); + let usdt_for_bidding = inst.calculate_auction_funding_asset_charged_with_given_price( + &bids.clone(), + project_metadata.minimum_price, + ); + + inst.mint_plmc_to(plmc_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(); + let remaining_ct = current_bucket.amount_left; + + // This bid should be split in 2, but the second one should fail, making the whole extrinsic fail and roll back storage + let failing_bid = + BidParams::::new(BIDDER_1, remaining_ct + 5000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + let plmc_for_failing_bid = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + &vec![failing_bid.clone()], + project_metadata.clone(), + Some(current_bucket), + true, + ); + let usdt_for_bidding = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( + &vec![failing_bid.clone()], + project_metadata.clone(), + Some(current_bucket), + ); + inst.mint_plmc_to(plmc_for_failing_bid.clone()); + inst.mint_funding_asset_to(usdt_for_bidding.clone()); + + inst.execute(|| { + assert_noop!( + PolimecFunding::bid( + RuntimeOrigin::signed(failing_bid.bidder), + get_mock_jwt_with_cid( + failing_bid.bidder, + InvestorType::Professional, + generate_did_from_account(failing_bid.bidder), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + failing_bid.amount, + failing_bid.multiplier, + failing_bid.asset + ), + Error::::TooManyUserParticipations + ); + }); + + // Now we test that after reaching the limit, just one bid is also not allowed + inst.execute(|| { + assert_ok!(PolimecFunding::bid( + RuntimeOrigin::signed(failing_bid.bidder), + get_mock_jwt_with_cid( + failing_bid.bidder, + InvestorType::Professional, + generate_did_from_account(failing_bid.bidder), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + remaining_ct, + failing_bid.multiplier, + failing_bid.asset + )); + }); + inst.execute(|| { + assert_noop!( + PolimecFunding::bid( + RuntimeOrigin::signed(failing_bid.bidder), + get_mock_jwt_with_cid( + failing_bid.bidder, + InvestorType::Professional, + generate_did_from_account(failing_bid.bidder), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + 5000 * CT_UNIT, + failing_bid.multiplier, + failing_bid.asset + ), + Error::::TooManyUserParticipations + ); + }); + } + + #[test] + fn per_credential_type_ticket_size_minimums() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.total_allocation_size = 100_000 * CT_UNIT; + project_metadata.bidding_ticket_sizes = BiddingTicketSizes { + professional: TicketSize::new(8_000 * USD_UNIT, None), + institutional: TicketSize::new(20_000 * USD_UNIT, None), + phantom: Default::default(), + }; + + let evaluations = default_evaluations(); + + let project_id = + inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations.clone()); + + inst.mint_plmc_to(vec![(BIDDER_1, 50_000 * CT_UNIT).into(), (BIDDER_2, 50_000 * CT_UNIT).into()]); + + inst.mint_funding_asset_to(vec![ + (BIDDER_1, 50_000 * USD_UNIT).into(), + (BIDDER_2, 50_000 * USD_UNIT).into(), + ]); + + // bid below 800 CT (8k USD) should fail for professionals + inst.execute(|| { + assert_noop!( + Pallet::::do_bid( + &BIDDER_1, + project_id, + 799 * CT_UNIT, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + generate_did_from_account(BIDDER_1), + InvestorType::Professional, + project_metadata.clone().policy_ipfs_cid.unwrap(), + ), + Error::::TooLow + ); + }); + // bid below 2000 CT (20k USD) should fail for institutionals + inst.execute(|| { + assert_noop!( + Pallet::::do_bid( + &BIDDER_2, + project_id, + 1999 * CT_UNIT, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + generate_did_from_account(BIDDER_1), + InvestorType::Institutional, + project_metadata.clone().policy_ipfs_cid.unwrap(), + ), + Error::::TooLow + ); + }); + } + + #[test] + fn ticket_size_minimums_use_current_bucket_price() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.total_allocation_size = 100_000 * CT_UNIT; + project_metadata.bidding_ticket_sizes = BiddingTicketSizes { + professional: TicketSize::new(8_000 * USD_UNIT, None), + institutional: TicketSize::new(20_000 * USD_UNIT, None), + phantom: Default::default(), + }; + project_metadata.minimum_price = PriceProviderOf::::calculate_decimals_aware_price( + PriceOf::::from_float(1.0), + USD_DECIMALS, + project_metadata.clone().token_information.decimals, + ) + .unwrap(); + + let evaluations = default_evaluations(); + + let project_id = + inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations.clone()); + + inst.mint_plmc_to(vec![ + (BIDDER_1, 200_000 * PLMC).into(), + (BIDDER_2, 200_000 * PLMC).into(), + (BIDDER_3, 200_000 * PLMC).into(), + ]); + 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(), + ]); + + // First bucket is covered by one bidder + let big_bid: BidParams = (BIDDER_1, 50_000 * CT_UNIT).into(); + inst.bid_for_users(project_id, vec![big_bid.clone()]).unwrap(); + + // A bid at the min price of 1 should require a min of 8k CT, but with a new price of 1.1, we can now bid with less + let bucket_increase_price = PriceProviderOf::::calculate_decimals_aware_price( + PriceOf::::from_float(1.1), + USD_DECIMALS, + project_metadata.clone().token_information.decimals, + ) + .unwrap(); + let smallest_ct_amount_at_8k_usd = bucket_increase_price + .reciprocal() + .unwrap() + .checked_mul_int(8000 * USD_UNIT) + // add 1 because result could be .99999 of what we expect + .unwrap() + 1; + assert!(smallest_ct_amount_at_8k_usd < 8000 * CT_UNIT); + inst.execute(|| { + assert_ok!(Pallet::::do_bid( + &BIDDER_1, + project_id, + smallest_ct_amount_at_8k_usd, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + generate_did_from_account(BIDDER_1), + InvestorType::Professional, + project_metadata.clone().policy_ipfs_cid.unwrap(), + )); + }); + let smallest_ct_amount_at_20k_usd = bucket_increase_price + .reciprocal() + .unwrap() + .checked_mul_int(20_000 * USD_UNIT) + // add 1 because result could be .99999 of what we expect + .unwrap() + 1; + assert!(smallest_ct_amount_at_20k_usd < 20_000 * CT_UNIT); + inst.execute(|| { + assert_ok!(Pallet::::do_bid( + &BIDDER_2, + project_id, + smallest_ct_amount_at_20k_usd, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + generate_did_from_account(BIDDER_1), + InvestorType::Institutional, + project_metadata.clone().policy_ipfs_cid.unwrap(), + )); + }); + } + + #[test] + fn per_credential_type_ticket_size_maximums() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.bidding_ticket_sizes = BiddingTicketSizes { + professional: TicketSize::new(8_000 * USD_UNIT, Some(100_000 * USD_UNIT)), + institutional: TicketSize::new(20_000 * USD_UNIT, Some(500_000 * USD_UNIT)), + phantom: Default::default(), + }; + project_metadata.contributing_ticket_sizes = ContributingTicketSizes { + retail: TicketSize::new(USD_UNIT, Some(100_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(), + }; + let evaluations = default_evaluations(); + + let project_id = + inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations.clone()); + + inst.mint_plmc_to(vec![ + (BIDDER_1, 500_000 * CT_UNIT).into(), + (BIDDER_2, 500_000 * CT_UNIT).into(), + (BIDDER_3, 500_000 * CT_UNIT).into(), + (BIDDER_4, 500_000 * CT_UNIT).into(), + ]); + + 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(), + (BIDDER_4, 500_000 * USD_UNIT).into(), + ]); + + let bidder_1_jwt = get_mock_jwt_with_cid( + BIDDER_1, + InvestorType::Professional, + generate_did_from_account(BIDDER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + let bidder_2_jwt_same_did = get_mock_jwt_with_cid( + BIDDER_2, + InvestorType::Professional, + generate_did_from_account(BIDDER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + // total bids with same DID above 10k CT (100k USD) should fail for professionals + inst.execute(|| { + assert_ok!(Pallet::::bid( + RuntimeOrigin::signed(BIDDER_1), + bidder_1_jwt, + project_id, + 8000 * CT_UNIT, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + )); + }); + inst.execute(|| { + assert_noop!( + Pallet::::bid( + RuntimeOrigin::signed(BIDDER_2), + bidder_2_jwt_same_did.clone(), + project_id, + 3000 * CT_UNIT, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT + ), + Error::::TooHigh + ); + }); + // bidding 10k total works + inst.execute(|| { + assert_ok!(Pallet::::bid( + RuntimeOrigin::signed(BIDDER_2), + bidder_2_jwt_same_did, + project_id, + 2000 * CT_UNIT, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + )); + }); + + let bidder_3_jwt = get_mock_jwt_with_cid( + BIDDER_3, + InvestorType::Institutional, + generate_did_from_account(BIDDER_3), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + let bidder_4_jwt_same_did = get_mock_jwt_with_cid( + BIDDER_4, + InvestorType::Institutional, + generate_did_from_account(BIDDER_3), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + // total bids with same DID above 50k CT (500k USD) should fail for institutionals + inst.execute(|| { + assert_ok!(Pallet::::bid( + RuntimeOrigin::signed(BIDDER_3), + bidder_3_jwt, + project_id, + 40_000 * CT_UNIT, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + )); + }); + inst.execute(|| { + assert_noop!( + Pallet::::bid( + RuntimeOrigin::signed(BIDDER_4), + bidder_4_jwt_same_did.clone(), + project_id, + 11_000 * CT_UNIT, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + ), + Error::::TooHigh + ); + }); + // bidding 50k total works + inst.execute(|| { + assert_ok!(Pallet::::bid( + RuntimeOrigin::signed(BIDDER_4), + bidder_4_jwt_same_did, + project_id, + 10_000 * CT_UNIT, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + )); + }); + } + + #[test] + fn issuer_cannot_bid_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_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); + assert_err!( + inst.execute(|| crate::Pallet::::do_bid( + &(&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 bid_with_asset_not_accepted() { + 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 bids = vec![BidParams::::new(BIDDER_1, 10_000, 1u8, AcceptedFundingAsset::USDC)]; + + let did = generate_did_from_account(bids[0].bidder); + let investor_type = InvestorType::Institutional; + + let outcome = inst.execute(|| { + Pallet::::do_bid( + &bids[0].bidder, + project_id, + bids[0].amount, + bids[0].multiplier, + bids[0].asset, + did, + investor_type, + project_metadata.clone().policy_ipfs_cid.unwrap(), + ) + }); + frame_support::assert_err!(outcome, Error::::FundingAssetNotAccepted); + } + + #[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_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); + + inst.execute(|| { + assert_noop!( + PolimecFunding::bid( + RuntimeOrigin::signed(BIDDER_1), + get_mock_jwt_with_cid( + BIDDER_1, + InvestorType::Professional, + generate_did_from_account(BIDDER_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/4_community.rs b/pallets/funding/src/tests/4_community.rs deleted file mode 100644 index 0aa7dd876..000000000 --- a/pallets/funding/src/tests/4_community.rs +++ /dev/null @@ -1,1794 +0,0 @@ -// use super::*; -// -// #[cfg(test)] -// mod round_flow { -// use super::*; -// -// #[cfg(test)] -// mod success { -// use super::*; -// use frame_support::traits::fungibles::metadata::Inspect; -// use sp_runtime::bounded_vec; -// use std::collections::HashSet; -// -// #[test] -// fn community_round_completed() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let _ = inst.create_remainder_contributing_project( -// default_project_metadata(ISSUER_1), -// ISSUER_1, -// None, -// default_evaluations(), -// default_bids(), -// default_community_buys(), -// ); -// } -// -// #[test] -// fn multiple_contribution_projects_completed() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project1 = default_project_metadata(ISSUER_1); -// let project2 = default_project_metadata(ISSUER_2); -// let project3 = default_project_metadata(ISSUER_3); -// let project4 = default_project_metadata(ISSUER_4); -// let evaluations = default_evaluations(); -// let bids = default_bids(); -// let community_buys = default_community_buys(); -// -// inst.create_remainder_contributing_project( -// project1, -// ISSUER_1, -// None, -// evaluations.clone(), -// bids.clone(), -// community_buys.clone(), -// ); -// inst.create_remainder_contributing_project( -// project2, -// ISSUER_2, -// None, -// evaluations.clone(), -// bids.clone(), -// community_buys.clone(), -// ); -// inst.create_remainder_contributing_project( -// project3, -// ISSUER_3, -// None, -// evaluations.clone(), -// bids.clone(), -// community_buys.clone(), -// ); -// inst.create_remainder_contributing_project(project4, ISSUER_4, None, evaluations, bids, community_buys); -// } -// -// #[test] -// fn community_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), -// BidParams::new_with_defaults(BIDDER_2, 10_000 * CT_UNIT), -// ]; -// let project_id = inst.create_community_contributing_project( -// default_project_metadata(ISSUER_1), -// ISSUER_1, -// None, -// default_evaluations(), -// bids, -// ); -// 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 = plmc_fundings.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 project_id = inst.create_community_contributing_project( -// project_metadata.clone(), -// ISSUER_1, -// None, -// default_evaluations(), -// default_bids(), -// ); -// 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_community_contributing_project( -// project_metadata.clone(), -// issuer, -// None, -// evaluations, -// 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::{dispatch::DispatchResultWithPostInfo, traits::fungible::InspectFreeze}; -// use polimec_common_test_utils::get_mock_jwt; -// -// #[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_community_contributing_project( -// project_metadata.clone(), -// ISSUER_1, -// None, -// evaluations, -// default_bids(), -// ); -// 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(..))); -// -// 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 project_id = inst.create_community_contributing_project( -// project_metadata_all.clone(), -// ISSUER_1, -// None, -// default_evaluations(), -// default_bids(), -// ); -// 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 wap = inst.get_project_details(project_id).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()); -// -// let asset_hub_fundings = inst.calculate_contributed_funding_asset_spent( -// vec![usdt_contribution.clone(), usdc_contribution.clone(), dot_contribution.clone()], -// wap, -// ); -// inst.mint_foreign_asset_to(asset_hub_fundings.clone()); -// -// assert_ok!(inst.contribute_for_users( -// project_id, -// vec![usdt_contribution.clone(), usdc_contribution.clone(), 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_community_contributing_project(project_metadata.clone(), ISSUER_1, None, evaluations, bids); -// -// // 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_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()))); -// 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() - -// 1, -// ) -// .unwrap(); -// inst.bid_for_users(project_id, failing_bids_after_random_end).unwrap(); -// inst.start_community_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_community_contributing_project( -// project_metadata.clone(), -// issuer, -// None, -// default_evaluations(), -// 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.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); -// -// 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)); -// -// assert_eq!(free_balance, inst.get_ed()); -// 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); -// -// 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)); -// -// assert_eq!(free_balance, inst.get_ed() + frozen_amount); -// assert_eq!(bid_held_balance, Zero::zero()); -// assert_eq!(frozen_balance, frozen_amount); -// } -// -// #[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_community_contributing_project( -// project_metadata.clone(), -// issuer, -// None, -// default_evaluations(), -// default_bids(), -// ); -// 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.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); -// -// 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)); -// -// assert_eq!(free_balance, inst.get_ed()); -// assert_eq!(bid_held_balance, frozen_amount); -// assert_eq!(frozen_balance, frozen_amount); -// -// 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)); -// -// assert_eq!(free_balance, inst.get_ed()); -// assert_eq!(bid_held_balance, frozen_amount); -// assert_eq!(frozen_balance, frozen_amount); -// -// 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)); -// -// assert_eq!(free_balance, inst.get_ed() + frozen_amount); -// assert_eq!(bid_held_balance, Zero::zero()); -// assert_eq!(frozen_balance, frozen_amount); -// } -// } -// -// #[cfg(test)] -// mod failure { -// use super::*; -// use frame_support::traits::{ -// fungible::Mutate as MutateFungible, -// fungibles::Mutate as MutateFungibles, -// tokens::{Fortitude, Precision}, -// }; -// use sp_runtime::bounded_vec; -// -// #[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_community_contributing_project( -// default_project_metadata(ISSUER_1), -// ISSUER_1, -// None, -// default_evaluations(), -// default_bids(), -// ); -// 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_community_contributing_project( -// project_metadata.clone(), -// ISSUER_1, -// None, -// default_evaluations(), -// default_bids(), -// ); -// 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 did_with_winning_bid_cannot_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_2, 1337 * USD_UNIT).into()); -// let bids = vec![ -// BidParams::new(BIDDER_1, 400_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// BidParams::new(BIDDER_2, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// // Partially accepted bid. Only the 50k of the second bid will be accepted. -// BidParams::new(BIDDER_3, 100_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ]; -// -// let project_id = inst.create_community_contributing_project( -// project_metadata.clone(), -// ISSUER_1, -// None, -// default_evaluations(), -// bids, -// ); -// -// let mut bid_should_fail = |account, investor_type, did_acc| { -// inst.execute(|| { -// assert_noop!( -// 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, -// ), -// Error::::UserHasWinningBid -// ); -// }); -// }; -// -// // Bidder 1 has a winning bid, his did should not be able to contribute regardless of what investor type -// // or account he uses to sign the transaction -// bid_should_fail(BIDDER_1, InvestorType::Institutional, BIDDER_1); -// bid_should_fail(BUYER_1, InvestorType::Institutional, BIDDER_1); -// bid_should_fail(BIDDER_1, InvestorType::Professional, BIDDER_1); -// bid_should_fail(BUYER_1, InvestorType::Professional, BIDDER_1); -// bid_should_fail(BIDDER_1, InvestorType::Retail, BIDDER_1); -// bid_should_fail(BUYER_1, InvestorType::Retail, BIDDER_1); -// -// // Bidder 2 has a winning bid, and he was also an evaluator. Same conditions as before should apply. -// bid_should_fail(BIDDER_2, InvestorType::Institutional, BIDDER_2); -// bid_should_fail(BUYER_2, InvestorType::Institutional, BIDDER_2); -// bid_should_fail(BIDDER_2, InvestorType::Professional, BIDDER_2); -// bid_should_fail(BUYER_2, InvestorType::Professional, BIDDER_2); -// bid_should_fail(BIDDER_2, InvestorType::Retail, BIDDER_2); -// bid_should_fail(BUYER_2, InvestorType::Retail, BIDDER_2); -// -// // Bidder 3 has a partial winning bid. Same conditions as before should apply. -// bid_should_fail(BIDDER_3, InvestorType::Institutional, BIDDER_3); -// bid_should_fail(BUYER_3, InvestorType::Institutional, BIDDER_3); -// bid_should_fail(BIDDER_3, InvestorType::Professional, BIDDER_3); -// bid_should_fail(BUYER_3, InvestorType::Professional, BIDDER_3); -// bid_should_fail(BIDDER_3, InvestorType::Retail, BIDDER_3); -// bid_should_fail(BUYER_3, InvestorType::Retail, BIDDER_3); -// } -// -// #[test] -// fn per_credential_type_ticket_size_minimums() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let mut project_metadata = default_project_metadata(ISSUER_1); -// project_metadata.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(), -// }; -// -// let project_id = inst.create_community_contributing_project( -// project_metadata.clone(), -// ISSUER_1, -// None, -// default_evaluations(), -// default_bids(), -// ); -// -// inst.mint_plmc_to(vec![ -// (BUYER_1, 50_000 * CT_UNIT).into(), -// (BUYER_2, 50_000 * CT_UNIT).into(), -// (BUYER_3, 50_000 * CT_UNIT).into(), -// ]); -// -// inst.mint_foreign_asset_to(vec![ -// (BUYER_1, 50_000 * USD_UNIT).into(), -// (BUYER_2, 50_000 * USD_UNIT).into(), -// (BUYER_3, 50_000 * USD_UNIT).into(), -// ]); -// -// // contribution below 1 CT (10 USD) should fail for retail -// let jwt = get_mock_jwt_with_cid( -// BUYER_1, -// InvestorType::Retail, -// generate_did_from_account(BUYER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// inst.execute(|| { -// assert_noop!( -// Pallet::::contribute( -// RuntimeOrigin::signed(BUYER_1), -// 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_2, -// InvestorType::Professional, -// generate_did_from_account(BUYER_2), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// inst.execute(|| { -// assert_noop!( -// Pallet::::contribute( -// RuntimeOrigin::signed(BUYER_2), -// 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_3, -// InvestorType::Professional, -// generate_did_from_account(BUYER_3), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// inst.execute(|| { -// assert_noop!( -// Pallet::::contribute( -// RuntimeOrigin::signed(BUYER_3), -// 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 mut project_metadata = default_project_metadata(ISSUER_1); -// project_metadata.contributing_ticket_sizes = ContributingTicketSizes { -// retail: TicketSize::new(USD_UNIT, Some(100_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(), -// }; -// -// let project_id = inst.create_community_contributing_project( -// project_metadata.clone(), -// ISSUER_1, -// None, -// default_evaluations(), -// default_bids(), -// ); -// -// inst.mint_plmc_to(vec![ -// (BUYER_1, 500_000 * CT_UNIT).into(), -// (BUYER_2, 500_000 * CT_UNIT).into(), -// (BUYER_3, 500_000 * CT_UNIT).into(), -// (BUYER_4, 500_000 * CT_UNIT).into(), -// (BUYER_5, 500_000 * CT_UNIT).into(), -// (BUYER_6, 500_000 * CT_UNIT).into(), -// ]); -// -// inst.mint_foreign_asset_to(vec![ -// (BUYER_1, 500_000 * USD_UNIT).into(), -// (BUYER_2, 500_000 * USD_UNIT).into(), -// (BUYER_3, 500_000 * USD_UNIT).into(), -// (BUYER_4, 500_000 * USD_UNIT).into(), -// (BUYER_5, 500_000 * USD_UNIT).into(), -// (BUYER_6, 500_000 * USD_UNIT).into(), -// ]); -// -// let buyer_1_jwt = get_mock_jwt_with_cid( -// BUYER_1, -// InvestorType::Retail, -// generate_did_from_account(BUYER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// let buyer_2_jwt_same_did = get_mock_jwt_with_cid( -// BUYER_2, -// InvestorType::Retail, -// generate_did_from_account(BUYER_1), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// // total contributions with same DID above 10k CT (100k USD) should fail for retail -// inst.execute(|| { -// assert_ok!(Pallet::::contribute( -// RuntimeOrigin::signed(BUYER_1), -// buyer_1_jwt, -// project_id, -// 9000 * CT_UNIT, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// )); -// }); -// inst.execute(|| { -// assert_noop!( -// Pallet::::contribute( -// RuntimeOrigin::signed(BUYER_2), -// buyer_2_jwt_same_did.clone(), -// project_id, -// 1001 * CT_UNIT, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// ), -// Error::::TooHigh -// ); -// }); -// // bidding 2k total works -// inst.execute(|| { -// assert_ok!(Pallet::::contribute( -// RuntimeOrigin::signed(BUYER_2), -// buyer_2_jwt_same_did, -// project_id, -// 1000 * CT_UNIT, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// )); -// }); -// -// let buyer_3_jwt = get_mock_jwt_with_cid( -// BUYER_3, -// InvestorType::Professional, -// generate_did_from_account(BUYER_3), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// let buyer_4_jwt_same_did = get_mock_jwt_with_cid( -// BUYER_4, -// InvestorType::Professional, -// generate_did_from_account(BUYER_3), -// 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::::contribute( -// RuntimeOrigin::signed(BUYER_3), -// buyer_3_jwt, -// project_id, -// 1800 * CT_UNIT, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// )); -// }); -// inst.execute(|| { -// assert_noop!( -// Pallet::::contribute( -// RuntimeOrigin::signed(BUYER_4), -// buyer_4_jwt_same_did.clone(), -// project_id, -// 201 * CT_UNIT, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// ), -// Error::::TooHigh -// ); -// }); -// // bidding 2k total works -// inst.execute(|| { -// assert_ok!(Pallet::::contribute( -// RuntimeOrigin::signed(BUYER_4), -// buyer_4_jwt_same_did, -// project_id, -// 200 * CT_UNIT, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// )); -// }); -// -// let buyer_5_jwt = get_mock_jwt_with_cid( -// BUYER_5, -// InvestorType::Institutional, -// generate_did_from_account(BUYER_5), -// project_metadata.clone().policy_ipfs_cid.unwrap(), -// ); -// let buyer_6_jwt_same_did = get_mock_jwt_with_cid( -// BUYER_6, -// InvestorType::Institutional, -// generate_did_from_account(BUYER_5), -// 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::::contribute( -// RuntimeOrigin::signed(BUYER_5), -// buyer_5_jwt, -// project_id, -// 4690 * CT_UNIT, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// )); -// }); -// inst.execute(|| { -// assert_noop!( -// Pallet::::contribute( -// RuntimeOrigin::signed(BUYER_6), -// buyer_6_jwt_same_did.clone(), -// project_id, -// 311 * CT_UNIT, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// ), -// Error::::TooHigh -// ); -// }); -// // bidding 5k total works -// inst.execute(|| { -// assert_ok!(Pallet::::contribute( -// RuntimeOrigin::signed(BUYER_6), -// buyer_6_jwt_same_did, -// project_id, -// 310 * CT_UNIT, -// 1u8.try_into().unwrap(), -// AcceptedFundingAsset::USDT, -// )); -// }); -// } -// -// #[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_community_contributing_project( -// project_metadata.clone(), -// ISSUER_1, -// None, -// default_evaluations(), -// default_bids(), -// ); -// -// 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_community_round() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let created_project = inst.create_new_project(default_project_metadata(ISSUER_1), ISSUER_1, None); -// let evaluating_project = inst.create_evaluating_project(default_project_metadata(ISSUER_2), ISSUER_2, None); -// let auctioning_project = inst.create_auctioning_project( -// default_project_metadata(ISSUER_3), -// ISSUER_3, -// 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(), -// ); -// -// let projects = -// vec![created_project, evaluating_project, auctioning_project, remaining_project, finished_project]; -// for project in projects { -// let project_policy = inst.get_project_metadata(project).policy_ipfs_cid.unwrap(); -// 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_policy -// ), -// project, -// 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 = bounded_vec![AcceptedFundingAsset::USDT]; -// -// let mut project_metadata_usdc = default_project_metadata(ISSUER_3); -// project_metadata_usdc.participation_currencies = bounded_vec![AcceptedFundingAsset::USDC]; -// -// 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_community_contributing_project( -// project_metadata_usdc, -// ISSUER_2, -// None, -// evaluations.clone(), -// usdc_bids.clone(), -// ); -// assert_err!( -// inst.contribute_for_users(project_id_usdc, vec![usdt_contribution.clone()]), -// Error::::FundingAssetNotAccepted -// ); -// -// let project_id_usdt = inst.create_community_contributing_project( -// project_metadata_usdt, -// ISSUER_3, -// None, -// evaluations.clone(), -// usdt_bids.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 -// ); -// } -// -// #[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_community_contributing_project( -// project_metadata_1.clone(), -// ISSUER_1, -// None, -// evaluations_1, -// default_bids(), -// ); -// let project_id_2 = inst.create_community_contributing_project( -// project_metadata_2.clone(), -// ISSUER_2, -// None, -// evaluations_2, -// default_bids(), -// ); -// -// 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_community_contributing_project( -// project_metadata.clone(), -// ISSUER_1, -// 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), -// "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/4_contribution.rs b/pallets/funding/src/tests/4_contribution.rs new file mode 100644 index 000000000..9a4662b40 --- /dev/null +++ b/pallets/funding/src/tests/4_contribution.rs @@ -0,0 +1,1754 @@ +use super::*; + +#[cfg(test)] +mod round_flow { + use super::*; + + #[cfg(test)] + mod success { + use super::*; + use frame_support::traits::fungibles::metadata::Inspect; + use sp_runtime::bounded_vec; + use std::collections::HashSet; + + #[test] + fn contribution_round_completed() { + 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_contributions(), + default_remainder_contributions(), + ); + } + + #[test] + fn multiple_contribution_projects_completed() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project1 = default_project_metadata(ISSUER_1); + let project2 = default_project_metadata(ISSUER_2); + let project3 = default_project_metadata(ISSUER_3); + let project4 = default_project_metadata(ISSUER_4); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_buys = default_community_contributions(); + + inst.create_remainder_contributing_project( + project1, + ISSUER_1, + None, + evaluations.clone(), + bids.clone(), + community_buys.clone(), + ); + inst.create_remainder_contributing_project( + project2, + ISSUER_2, + None, + evaluations.clone(), + bids.clone(), + community_buys.clone(), + ); + inst.create_remainder_contributing_project( + project3, + ISSUER_3, + None, + evaluations.clone(), + bids.clone(), + community_buys.clone(), + ); + inst.create_remainder_contributing_project(project4, ISSUER_4, None, evaluations, bids, community_buys); + } + + #[test] + 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), + BidParams::new_with_defaults(BIDDER_2, 10_000 * CT_UNIT), + ]; + let project_id = inst.create_community_contributing_project( + default_project_metadata(ISSUER_1), + ISSUER_1, + None, + default_evaluations(), + bids, + ); + 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 = plmc_fundings.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_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"); + + // 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.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_funding_asset_assertions(vec![UserToForeignAssets::::new( + BOB, + 0_u128, + AcceptedFundingAsset::USDT.id(), + )]); + inst.do_reserved_plmc_assertions( + vec![plmc_fundings[0].clone()], + HoldReason::Participation(project_id).into(), + ); + } + + #[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 project_id = inst.create_community_contributing_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + default_bids(), + ); + 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| { + assert_eq!(bid.status, BidStatus::Accepted); + acc + bid.original_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_funding_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.id(), + USD_DECIMALS, + ForeignAssets::decimals(AcceptedFundingAsset::USDT.id()), + ) + .unwrap() + }); + let usdc_price = inst.execute(|| { + ::PriceProvider::get_decimals_aware_price( + AcceptedFundingAsset::USDC.id(), + USD_DECIMALS, + ForeignAssets::decimals(AcceptedFundingAsset::USDC.id()), + ) + .unwrap() + }); + let dot_price = inst.execute(|| { + ::PriceProvider::get_decimals_aware_price( + AcceptedFundingAsset::DOT.id(), + USD_DECIMALS, + ForeignAssets::decimals(AcceptedFundingAsset::DOT.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_community_contributing_project( + project_metadata.clone(), + issuer, + None, + evaluations, + 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_funding_asset_to(vec![UserToForeignAssets::new( + BUYER_1, + total_funding_funding_asset, + funding_asset.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 + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); + }; + + 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::{dispatch::DispatchResultWithPostInfo, traits::fungible::InspectFreeze}; + + #[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_community_contributing_project( + project_metadata.clone(), + ISSUER_1, + None, + evaluations, + default_bids(), + ); + 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_funding_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_funding_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, 10_000 * USD_UNIT).into(); + evaluations.push(bob_evaluation); + + 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; + + // 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 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 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_funding_assets = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( + &bids, + project_metadata.clone(), + None, + ); + inst.mint_funding_asset_to(bids_funding_assets.clone()); + inst.bid_for_users(project_id, bids).unwrap(); + + assert_eq!(inst.execute(|| Balances::free_balance(&bob)), inst.get_ed()); + + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); + + // 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)); + + // 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, usable_bob_ct).into(); + let contribution_usdt = inst.calculate_contributed_funding_asset_spent(vec![bob_contribution], wap); + inst.mint_funding_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_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] + 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 project_id = inst.create_community_contributing_project( + project_metadata_all.clone(), + ISSUER_1, + None, + default_evaluations(), + default_bids(), + ); + 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 wap = inst.get_project_details(project_id).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()); + + let asset_hub_fundings = inst.calculate_contributed_funding_asset_spent( + vec![usdt_contribution.clone(), usdc_contribution.clone(), dot_contribution.clone()], + wap, + ); + inst.mint_funding_asset_to(asset_hub_fundings.clone()); + + assert_ok!(inst.contribute_for_users( + project_id, + vec![usdt_contribution.clone(), usdc_contribution.clone(), 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_funding_asset_to(necessary_usdt.clone()); + } + inst.execute(|| { + Pallet::::contribute( + RuntimeOrigin::signed(contributor), + jwt, + project_id, + amount, + multiplier, + AcceptedFundingAsset::USDT, + ) + }) + } + + #[test] + 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; + 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_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), + 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 did_with_losing_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), + ]; + + // This bids should fill the first bucket. + 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()).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_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(); + 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![ + (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_funding_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 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); + } + + #[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_community_contributing_project( + project_metadata.clone(), + issuer, + None, + default_evaluations(), + 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_funding_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 + )); + }); + + 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 = + inst.get_reserved_plmc_balance_for(BUYER_4, HoldReason::Participation(project_id).into()); + let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BUYER_4)); + + assert_eq!(free_balance, inst.get_ed()); + assert_eq!(bid_held_balance, frozen_amount); + assert_eq!(frozen_balance, frozen_amount); + + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); + + inst.execute(|| { + PolimecFunding::settle_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)); + + assert_eq!(free_balance, inst.get_ed() + frozen_amount); + assert_eq!(bid_held_balance, Zero::zero()); + assert_eq!(frozen_balance, frozen_amount); + } + + #[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_community_contributing_project( + project_metadata.clone(), + issuer, + None, + default_evaluations(), + default_bids(), + ); + 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_funding_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 + )); + }); + + 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 = + inst.get_reserved_plmc_balance_for(BUYER_4, HoldReason::Participation(project_id).into()); + let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BUYER_4)); + + assert_eq!(free_balance, inst.get_ed()); + assert_eq!(bid_held_balance, frozen_amount); + assert_eq!(frozen_balance, frozen_amount); + + inst.execute(|| { + PolimecFunding::settle_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)); + + assert_eq!(free_balance, inst.get_ed()); + assert_eq!(bid_held_balance, frozen_amount); + assert_eq!(frozen_balance, frozen_amount); + + 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)); + + assert_eq!(free_balance, inst.get_ed() + frozen_amount); + 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)] + mod failure { + use super::*; + use frame_support::traits::{ + fungible::Mutate as MutateFungible, + fungibles::Mutate as MutateFungibles, + tokens::{Fortitude, Precision}, + }; + use sp_runtime::bounded_vec; + + #[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_community_contributing_project( + default_project_metadata(ISSUER_1), + ISSUER_1, + None, + default_evaluations(), + default_bids(), + ); + 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_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()); + + // 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.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_asset_mappings(vec![foreign_funding.clone()])[0].1 + ); + } + + #[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_community_contributing_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + default_bids(), + ); + 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 did_with_winning_bid_cannot_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_2, 1337 * USD_UNIT).into()); + let bids = vec![ + BidParams::new(BIDDER_1, 400_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_2, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + // Partially accepted bid. Only the 50k of the second bid will be accepted. + BidParams::new(BIDDER_3, 100_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ]; + + let project_id = inst.create_community_contributing_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + bids, + ); + + let mut bid_should_fail = |account, investor_type, did_acc| { + inst.execute(|| { + assert_noop!( + 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, + ), + Error::::UserHasWinningBid + ); + }); + }; + + // Bidder 1 has a winning bid, his did should not be able to contribute regardless of what investor type + // or account he uses to sign the transaction + bid_should_fail(BIDDER_1, InvestorType::Institutional, BIDDER_1); + bid_should_fail(BUYER_1, InvestorType::Institutional, BIDDER_1); + bid_should_fail(BIDDER_1, InvestorType::Professional, BIDDER_1); + bid_should_fail(BUYER_1, InvestorType::Professional, BIDDER_1); + bid_should_fail(BIDDER_1, InvestorType::Retail, BIDDER_1); + bid_should_fail(BUYER_1, InvestorType::Retail, BIDDER_1); + + // Bidder 2 has a winning bid, and he was also an evaluator. Same conditions as before should apply. + bid_should_fail(BIDDER_2, InvestorType::Institutional, BIDDER_2); + bid_should_fail(BUYER_2, InvestorType::Institutional, BIDDER_2); + bid_should_fail(BIDDER_2, InvestorType::Professional, BIDDER_2); + bid_should_fail(BUYER_2, InvestorType::Professional, BIDDER_2); + bid_should_fail(BIDDER_2, InvestorType::Retail, BIDDER_2); + bid_should_fail(BUYER_2, InvestorType::Retail, BIDDER_2); + + // Bidder 3 has a partial winning bid. Same conditions as before should apply. + bid_should_fail(BIDDER_3, InvestorType::Institutional, BIDDER_3); + bid_should_fail(BUYER_3, InvestorType::Institutional, BIDDER_3); + bid_should_fail(BIDDER_3, InvestorType::Professional, BIDDER_3); + bid_should_fail(BUYER_3, InvestorType::Professional, BIDDER_3); + bid_should_fail(BIDDER_3, InvestorType::Retail, BIDDER_3); + bid_should_fail(BUYER_3, InvestorType::Retail, BIDDER_3); + } + + #[test] + fn per_credential_type_ticket_size_minimums() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.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(), + }; + + let project_id = inst.create_community_contributing_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + default_bids(), + ); + + inst.mint_plmc_to(vec![ + (BUYER_1, 50_000 * CT_UNIT).into(), + (BUYER_2, 50_000 * CT_UNIT).into(), + (BUYER_3, 50_000 * CT_UNIT).into(), + ]); + + 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(), + ]); + + // contribution below 1 CT (10 USD) should fail for retail + let jwt = get_mock_jwt_with_cid( + BUYER_1, + InvestorType::Retail, + generate_did_from_account(BUYER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + inst.execute(|| { + assert_noop!( + Pallet::::contribute( + RuntimeOrigin::signed(BUYER_1), + 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_2, + InvestorType::Professional, + generate_did_from_account(BUYER_2), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + inst.execute(|| { + assert_noop!( + Pallet::::contribute( + RuntimeOrigin::signed(BUYER_2), + 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_3, + InvestorType::Professional, + generate_did_from_account(BUYER_3), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + inst.execute(|| { + assert_noop!( + Pallet::::contribute( + RuntimeOrigin::signed(BUYER_3), + 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 mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.contributing_ticket_sizes = ContributingTicketSizes { + retail: TicketSize::new(USD_UNIT, Some(100_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(), + }; + + let project_id = inst.create_community_contributing_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + default_bids(), + ); + + inst.mint_plmc_to(vec![ + (BUYER_1, 500_000 * CT_UNIT).into(), + (BUYER_2, 500_000 * CT_UNIT).into(), + (BUYER_3, 500_000 * CT_UNIT).into(), + (BUYER_4, 500_000 * CT_UNIT).into(), + (BUYER_5, 500_000 * CT_UNIT).into(), + (BUYER_6, 500_000 * CT_UNIT).into(), + ]); + + 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(), + (BUYER_4, 500_000 * USD_UNIT).into(), + (BUYER_5, 500_000 * USD_UNIT).into(), + (BUYER_6, 500_000 * USD_UNIT).into(), + ]); + + let buyer_1_jwt = get_mock_jwt_with_cid( + BUYER_1, + InvestorType::Retail, + generate_did_from_account(BUYER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + let buyer_2_jwt_same_did = get_mock_jwt_with_cid( + BUYER_2, + InvestorType::Retail, + generate_did_from_account(BUYER_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + // total contributions with same DID above 10k CT (100k USD) should fail for retail + inst.execute(|| { + assert_ok!(Pallet::::contribute( + RuntimeOrigin::signed(BUYER_1), + buyer_1_jwt, + project_id, + 9000 * CT_UNIT, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + )); + }); + inst.execute(|| { + assert_noop!( + Pallet::::contribute( + RuntimeOrigin::signed(BUYER_2), + buyer_2_jwt_same_did.clone(), + project_id, + 1001 * CT_UNIT, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + ), + Error::::TooHigh + ); + }); + // bidding 2k total works + inst.execute(|| { + assert_ok!(Pallet::::contribute( + RuntimeOrigin::signed(BUYER_2), + buyer_2_jwt_same_did, + project_id, + 1000 * CT_UNIT, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + )); + }); + + let buyer_3_jwt = get_mock_jwt_with_cid( + BUYER_3, + InvestorType::Professional, + generate_did_from_account(BUYER_3), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + let buyer_4_jwt_same_did = get_mock_jwt_with_cid( + BUYER_4, + InvestorType::Professional, + generate_did_from_account(BUYER_3), + 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::::contribute( + RuntimeOrigin::signed(BUYER_3), + buyer_3_jwt, + project_id, + 1800 * CT_UNIT, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + )); + }); + inst.execute(|| { + assert_noop!( + Pallet::::contribute( + RuntimeOrigin::signed(BUYER_4), + buyer_4_jwt_same_did.clone(), + project_id, + 201 * CT_UNIT, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + ), + Error::::TooHigh + ); + }); + // bidding 2k total works + inst.execute(|| { + assert_ok!(Pallet::::contribute( + RuntimeOrigin::signed(BUYER_4), + buyer_4_jwt_same_did, + project_id, + 200 * CT_UNIT, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + )); + }); + + let buyer_5_jwt = get_mock_jwt_with_cid( + BUYER_5, + InvestorType::Institutional, + generate_did_from_account(BUYER_5), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + let buyer_6_jwt_same_did = get_mock_jwt_with_cid( + BUYER_6, + InvestorType::Institutional, + generate_did_from_account(BUYER_5), + 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::::contribute( + RuntimeOrigin::signed(BUYER_5), + buyer_5_jwt, + project_id, + 4690 * CT_UNIT, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + )); + }); + inst.execute(|| { + assert_noop!( + Pallet::::contribute( + RuntimeOrigin::signed(BUYER_6), + buyer_6_jwt_same_did.clone(), + project_id, + 311 * CT_UNIT, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + ), + Error::::TooHigh + ); + }); + // bidding 5k total works + inst.execute(|| { + assert_ok!(Pallet::::contribute( + RuntimeOrigin::signed(BUYER_6), + buyer_6_jwt_same_did, + project_id, + 310 * CT_UNIT, + 1u8.try_into().unwrap(), + AcceptedFundingAsset::USDT, + )); + }); + } + + #[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_community_contributing_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + default_bids(), + ); + + 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_funding_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.id(), &BUYER_1, 0)); + inst.mint_funding_asset_to(foreign_funding.clone()); + + inst.execute(|| { + ForeignAssets::burn_from( + AcceptedFundingAsset::USDT.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_community_round() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let created_project = inst.create_new_project(default_project_metadata(ISSUER_1), ISSUER_1, None); + let evaluating_project = inst.create_evaluating_project(default_project_metadata(ISSUER_2), ISSUER_2, None); + let auctioning_project = inst.create_auctioning_project( + default_project_metadata(ISSUER_3), + ISSUER_3, + None, + default_evaluations(), + ); + let finished_project = inst.create_finished_project( + default_project_metadata(ISSUER_5), + ISSUER_5, + None, + default_evaluations(), + default_bids(), + default_community_contributions(), + default_remainder_contributions(), + ); + + 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(|| { + assert_noop!( + PolimecFunding::contribute( + RuntimeOrigin::signed(BUYER_1), + get_mock_jwt_with_cid( + BUYER_1, + InvestorType::Retail, + generate_did_from_account(BUYER_1), + project_policy + ), + project, + 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 = bounded_vec![AcceptedFundingAsset::USDT]; + + let mut project_metadata_usdc = default_project_metadata(ISSUER_3); + project_metadata_usdc.participation_currencies = bounded_vec![AcceptedFundingAsset::USDC]; + + 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_community_contributing_project( + project_metadata_usdc, + ISSUER_2, + None, + evaluations.clone(), + usdc_bids.clone(), + ); + assert_err!( + inst.contribute_for_users(project_id_usdc, vec![usdt_contribution.clone()]), + Error::::FundingAssetNotAccepted + ); + + let project_id_usdt = inst.create_community_contributing_project( + project_metadata_usdt, + ISSUER_3, + None, + evaluations.clone(), + usdt_bids.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 + ); + } + + #[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_community_contributing_project( + project_metadata_1.clone(), + ISSUER_1, + None, + evaluations_1, + default_bids(), + ); + let project_id_2 = inst.create_community_contributing_project( + project_metadata_2.clone(), + ISSUER_2, + None, + evaluations_2, + default_bids(), + ); + + 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_funding_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_community_contributing_project( + project_metadata.clone(), + ISSUER_1, + 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), + "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/5_funding_end.rs b/pallets/funding/src/tests/5_funding_end.rs new file mode 100644 index 000000000..949b64170 --- /dev/null +++ b/pallets/funding/src/tests/5_funding_end.rs @@ -0,0 +1,119 @@ +use super::*; +#[cfg(test)] +mod round_flow { + use super::*; + #[cfg(test)] + mod success { + use super::*; + + #[test] + 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); + dbg!(percentage); + 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 10..=32u8 { + try_for_percentage(i, true); + } + for i in 33..130u8 { + try_for_percentage(i, false); + } + } + + #[test] + 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::Success) + ); + + // We want to test rewards over the 3 brackets, which means > 5MM USD funded + const USD_REACHED: u128 = 9_500_000 * USD_UNIT; + const FEE_1: Percent = Percent::from_percent(10u8); + const FEE_2: Percent = Percent::from_percent(8); + const FEE_3: Percent = Percent::from_percent(6); + + let fee_1 = FEE_1 * 1_000_000 * USD_UNIT; + let fee_2 = FEE_2 * 4_000_000 * USD_UNIT; + let fee_3 = FEE_3 * 4_500_000 * USD_UNIT; + + let total_fee = Perquintill::from_rational(fee_1 + fee_2 + fee_3, USD_REACHED); + + 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 early_evaluator_reward = Perquintill::from_percent(20u64) * total_evaluator_reward; + + let normal_evaluator_reward = Perquintill::from_percent(80u64) * total_evaluator_reward; + const EARLY_EVALUATOR_TOTAL_USD_BONDED: u128 = 1_000_000 * USD_UNIT; + const NORMAL_EVALUATOR_TOTAL_USD_BONDED: u128 = 1_070_000 * USD_UNIT; + + let expected_reward_info = RewardInfoOf:: { + early_evaluator_reward_pot: early_evaluator_reward, + normal_evaluator_reward_pot: normal_evaluator_reward, + early_evaluator_total_bonded_usd: EARLY_EVALUATOR_TOTAL_USD_BONDED, + normal_evaluator_total_bonded_usd: NORMAL_EVALUATOR_TOTAL_USD_BONDED, + }; + assert_eq!( + inst.get_project_details(project_id).evaluation_round_info.evaluators_outcome, + Some(EvaluatorsOutcome::Rewarded(expected_reward_info)) + ); + } + + #[test] + fn auction_oversubscription() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + let auction_allocation = + project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let bucket_size = Percent::from_percent(10) * auction_allocation; + let bids = vec![ + (BIDDER_1, auction_allocation).into(), + (BIDDER_2, bucket_size).into(), + (BIDDER_3, bucket_size).into(), + (BIDDER_4, bucket_size).into(), + (BIDDER_5, bucket_size).into(), + (BIDDER_6, bucket_size).into(), + ]; + + let project_id = inst.create_finished_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + bids, + default_community_contributions(), + default_remainder_contributions(), + ); + + let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); + dbg!(wap); + assert!(wap > project_metadata.minimum_price); + } + } +} diff --git a/pallets/funding/src/tests/5_remainder.rs b/pallets/funding/src/tests/5_remainder.rs deleted file mode 100644 index bbac497f3..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_funding_end.rs b/pallets/funding/src/tests/6_funding_end.rs deleted file mode 100644 index cf736ac34..000000000 --- a/pallets/funding/src/tests/6_funding_end.rs +++ /dev/null @@ -1,113 +0,0 @@ -// use super::*; -// #[cfg(test)] -// mod round_flow { -// use super::*; -// #[cfg(test)] -// mod success { -// 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 -// ); -// } -// -// #[test] -// fn evaluator_reward_is_decided() { -// let (mut inst, project_id) = create_project_with_funding_percentage(95, None, 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) -// ); -// -// // We want to test rewards over the 3 brackets, which means > 5MM USD funded -// const USD_REACHED: u128 = 9_500_000 * USD_UNIT; -// const FEE_1: Percent = Percent::from_percent(10u8); -// const FEE_2: Percent = Percent::from_percent(8); -// const FEE_3: Percent = Percent::from_percent(6); -// -// let fee_1 = FEE_1 * 1_000_000 * USD_UNIT; -// let fee_2 = FEE_2 * 4_000_000 * USD_UNIT; -// let fee_3 = FEE_3 * 4_500_000 * USD_UNIT; -// -// let total_fee = Perquintill::from_rational(fee_1 + fee_2 + fee_3, USD_REACHED); -// -// 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 early_evaluator_reward = Perquintill::from_percent(20u64) * total_evaluator_reward; -// -// let normal_evaluator_reward = Perquintill::from_percent(80u64) * total_evaluator_reward; -// const EARLY_EVALUATOR_TOTAL_USD_BONDED: u128 = 1_000_000 * USD_UNIT; -// const NORMAL_EVALUATOR_TOTAL_USD_BONDED: u128 = 1_070_000 * USD_UNIT; -// -// let expected_reward_info = RewardInfoOf:: { -// early_evaluator_reward_pot: early_evaluator_reward, -// normal_evaluator_reward_pot: normal_evaluator_reward, -// early_evaluator_total_bonded_usd: EARLY_EVALUATOR_TOTAL_USD_BONDED, -// normal_evaluator_total_bonded_usd: NORMAL_EVALUATOR_TOTAL_USD_BONDED, -// }; -// assert_eq!( -// inst.get_project_details(project_id).evaluation_round_info.evaluators_outcome, -// EvaluatorsOutcome::Rewarded(expected_reward_info) -// ); -// } -// -// #[test] -// fn auction_oversubscription() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// let auction_allocation = -// project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; -// let bucket_size = Percent::from_percent(10) * auction_allocation; -// let bids = vec![ -// (BIDDER_1, auction_allocation).into(), -// (BIDDER_2, bucket_size).into(), -// (BIDDER_3, bucket_size).into(), -// (BIDDER_4, bucket_size).into(), -// (BIDDER_5, bucket_size).into(), -// (BIDDER_6, bucket_size).into(), -// ]; -// -// let project_id = inst.create_finished_project( -// project_metadata.clone(), -// ISSUER_1, -// None, -// default_evaluations(), -// bids, -// default_community_buys(), -// default_remainder_buys(), -// ); -// -// let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); -// dbg!(wap); -// assert!(wap > project_metadata.minimum_price); -// } -// } -// } 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/7_ct_migration.rs b/pallets/funding/src/tests/7_ct_migration.rs new file mode 100644 index 000000000..35cc68a58 --- /dev/null +++ b/pallets/funding/src/tests/7_ct_migration.rs @@ -0,0 +1,287 @@ +use super::*; +use frame_support::{assert_err, traits::fungibles::Inspect}; +use sp_runtime::bounded_vec; +use xcm::latest::MaxPalletNameLen; + +mod pallet_migration { + use super::*; + + #[test] + fn start_pallet_migration() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_id = inst.create_finished_project( + default_project_metadata(ISSUER_1), + ISSUER_1, + None, + default_evaluations(), + default_bids(), + default_community_contributions(), + default_remainder_contributions(), + ); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); + inst.settle_project(project_id); + + inst.execute(|| { + assert_err!( + crate::Pallet::::do_start_pallet_migration( + &EVALUATOR_1, + project_id, + ParaId::from(2006u32), + ), + Error::::NotIssuer + ); + assert_err!( + crate::Pallet::::do_start_pallet_migration(&BIDDER_1, project_id, ParaId::from(2006u32),), + Error::::NotIssuer + ); + assert_err!( + crate::Pallet::::do_start_pallet_migration(&BUYER_1, project_id, ParaId::from(2006u32),), + Error::::NotIssuer + ); + assert_ok!(crate::Pallet::::do_start_pallet_migration( + &ISSUER_1, + project_id, + ParaId::from(2006u32).into(), + )); + }); + + let project_details = inst.get_project_details(project_id); + assert_eq!( + project_details.migration_type, + Some(MigrationType::Pallet(PalletMigrationInfo { + parachain_id: 2006.into(), + hrmp_channel_status: HRMPChannelStatus { + project_to_polimec: ChannelStatus::Closed, + polimec_to_project: ChannelStatus::Closed + }, + migration_readiness_check: None, + })) + ); + assert_eq!(project_details.status, ProjectStatus::CTMigrationStarted); + assert_eq!(inst.execute(|| UnmigratedCounter::::get(project_id)), 10); + } + + fn create_pallet_migration_project(mut inst: MockInstantiator) -> (ProjectId, MockInstantiator) { + let project_id = inst.create_finished_project( + default_project_metadata(ISSUER_1), + ISSUER_1, + None, + default_evaluations(), + default_bids(), + default_community_contributions(), + default_remainder_contributions(), + ); + 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, + project_id, + ParaId::from(6969u32) + )); + }); + (project_id, inst) + } + + fn fake_hrmp_establishment() { + // Notification sent by the relay when the project starts a project->polimec channel + let open_channel_message = xcm::v3::opaque::Instruction::HrmpNewChannelOpenRequest { + sender: 6969, + max_message_size: 102_300, + max_capacity: 1000, + }; + // This makes Polimec send an acceptance + open channel (polimec->project) message back to the relay + assert_ok!(PolimecFunding::do_handle_channel_open_request(open_channel_message)); + + // Finally the relay notifies the channel polimec->project has been accepted by the project + let channel_accepted_message = xcm::v3::opaque::Instruction::HrmpChannelAccepted { recipient: 6969u32 }; + + // We set the hrmp flags as "Open" and start the receiver pallet check + assert_ok!(PolimecFunding::do_handle_channel_accepted(channel_accepted_message)); + } + + #[test] + fn automatic_hrmp_establishment() { + let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let (project_id, mut inst) = create_pallet_migration_project(inst); + + inst.execute(|| fake_hrmp_establishment()); + + let project_details = inst.get_project_details(project_id); + assert_eq!( + project_details.migration_type, + Some(MigrationType::Pallet(PalletMigrationInfo { + parachain_id: 6969.into(), + hrmp_channel_status: HRMPChannelStatus { + project_to_polimec: ChannelStatus::Open, + polimec_to_project: ChannelStatus::Open + }, + migration_readiness_check: Some(PalletMigrationReadinessCheck { + holding_check: (0, CheckOutcome::AwaitingResponse), + pallet_check: (1, CheckOutcome::AwaitingResponse) + }), + })) + ); + } + + /// Check that the polimec sovereign account has the ct issuance on the project chain, and the receiver pallet is in + /// the runtime. + #[test] + fn pallet_readiness_check() { + let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let (project_id, mut inst) = create_pallet_migration_project(inst); + inst.execute(|| fake_hrmp_establishment()); + + // At this point, we sent the pallet check xcm to the project chain, and we are awaiting a query response message. + // query id 0 is the CT balance of the Polimec SA + // query id 1 is the existence of the receiver pallet + + // We simulate the response from the project chain + let ct_issuance = + inst.execute(|| ::ContributionTokenCurrency::total_issuance(project_id)); + let ct_multiassets: MultiAssets = vec![MultiAsset { + id: Concrete(MultiLocation { parents: 1, interior: X1(Parachain(6969)) }), + fun: Fungibility::Fungible(ct_issuance), + }] + .into(); + + inst.execute(|| { + assert_ok!(PolimecFunding::do_pallet_migration_readiness_response( + MultiLocation::new(1u8, X1(Parachain(6969u32))), + 0u64, + Response::Assets(ct_multiassets), + )); + }); + + let module_name: BoundedVec = + BoundedVec::try_from("polimec_receiver".as_bytes().to_vec()).unwrap(); + let pallet_info = xcm::latest::PalletInfo { + // index is used for future `Transact` calls to the pallet for migrating a user + index: 69, + // Doesn't matter + name: module_name.clone(), + // Main check that the receiver pallet is there + module_name, + // These might be useful in the future, but not for now + major: 0, + minor: 0, + patch: 0, + }; + inst.execute(|| { + assert_ok!(PolimecFunding::do_pallet_migration_readiness_response( + MultiLocation::new(1u8, X1(Parachain(6969u32))), + 1u64, + Response::PalletsInfo(bounded_vec![pallet_info]), + )); + }); + + let project_details = inst.get_project_details(project_id); + if let MigrationType::Pallet(info) = project_details.migration_type.unwrap() { + assert_eq!(info.migration_readiness_check.unwrap().holding_check.1, CheckOutcome::Passed(None)); + assert_eq!(info.migration_readiness_check.unwrap().pallet_check.1, CheckOutcome::Passed(Some(69))); + } else { + panic!("Migration type is not Pallet") + } + } +} + +mod offchain_migration { + use super::*; + + #[test] + fn start_offchain_migration() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + // Create migrations for 2 projects, to check the `remaining_participants` is unaffected by other projects + let project_id = inst.create_finished_project( + default_project_metadata(ISSUER_1), + ISSUER_1, + None, + default_evaluations(), + default_bids(), + default_community_contributions(), + default_remainder_contributions(), + ); + 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), + ISSUER_1, + None, + default_evaluations(), + default_bids(), + default_community_contributions(), + default_remainder_contributions(), + ); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); + inst.settle_project(project_id); + + inst.execute(|| { + assert_err!( + crate::Pallet::::do_start_offchain_migration(project_id, EVALUATOR_1,), + Error::::NotIssuer + ); + + assert_ok!(crate::Pallet::::do_start_offchain_migration(project_id, ISSUER_1,)); + }); + + let project_details = inst.get_project_details(project_id); + assert_eq!(inst.execute(|| UnmigratedCounter::::get(project_id)), 10); + assert_eq!(project_details.status, ProjectStatus::CTMigrationStarted); + } + + fn create_offchain_migration_project(mut inst: MockInstantiator) -> (ProjectId, MockInstantiator) { + let project_id = inst.create_finished_project( + default_project_metadata(ISSUER_1), + ISSUER_1, + None, + default_evaluations(), + default_bids(), + default_community_contributions(), + default_remainder_contributions(), + ); + 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,)); + }); + (project_id, inst) + } + + #[test] + fn confirm_offchain_migration() { + let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let (project_id, mut inst) = create_offchain_migration_project(inst); + + let bidder_1_migrations = inst.execute(|| UserMigrations::::get((project_id, BIDDER_1))).unwrap(); + assert_eq!(bidder_1_migrations.0, MigrationStatus::NotStarted); + + inst.execute(|| { + assert_ok!(crate::Pallet::::do_confirm_offchain_migration(project_id, ISSUER_1, BIDDER_1)); + }); + + let bidder_1_migrations = inst.execute(|| UserMigrations::::get((project_id, BIDDER_1))).unwrap(); + assert_eq!(bidder_1_migrations.0, MigrationStatus::Confirmed); + } + + #[test] + fn mark_project_migration_as_finished() { + let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let (project_id, mut inst) = create_offchain_migration_project(inst); + + let participants = inst.execute(|| UserMigrations::::iter_key_prefix((project_id,)).collect_vec()); + for participant in participants { + inst.execute(|| { + assert_ok!(crate::Pallet::::do_confirm_offchain_migration( + project_id, + ISSUER_1, + participant + )); + }); + } + + inst.execute(|| { + assert_ok!(crate::Pallet::::do_mark_project_ct_migration_as_finished(project_id)); + }); + } +} diff --git a/pallets/funding/src/tests/7_settlement.rs b/pallets/funding/src/tests/7_settlement.rs deleted file mode 100644 index 28b8ee977..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/8_ct_migration.rs b/pallets/funding/src/tests/8_ct_migration.rs deleted file mode 100644 index ddeec6ff0..000000000 --- a/pallets/funding/src/tests/8_ct_migration.rs +++ /dev/null @@ -1,289 +0,0 @@ -// use super::*; -// use frame_support::{assert_err, traits::fungibles::Inspect}; -// use sp_runtime::bounded_vec; -// use xcm::latest::MaxPalletNameLen; -// -// mod pallet_migration { -// use super::*; -// -// #[test] -// fn start_pallet_migration() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_id = inst.create_finished_project( -// default_project_metadata(ISSUER_1), -// ISSUER_1, -// None, -// default_evaluations(), -// default_bids(), -// default_community_buys(), -// default_remainder_buys(), -// ); -// inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); -// inst.settle_project(project_id).unwrap(); -// -// inst.execute(|| { -// assert_err!( -// crate::Pallet::::do_start_pallet_migration( -// &EVALUATOR_1, -// project_id, -// ParaId::from(2006u32), -// ), -// Error::::NotIssuer -// ); -// assert_err!( -// crate::Pallet::::do_start_pallet_migration(&BIDDER_1, project_id, ParaId::from(2006u32),), -// Error::::NotIssuer -// ); -// assert_err!( -// crate::Pallet::::do_start_pallet_migration(&BUYER_1, project_id, ParaId::from(2006u32),), -// Error::::NotIssuer -// ); -// assert_ok!(crate::Pallet::::do_start_pallet_migration( -// &ISSUER_1, -// project_id, -// ParaId::from(2006u32).into(), -// )); -// }); -// -// let project_details = inst.get_project_details(project_id); -// assert_eq!( -// project_details.migration_type, -// Some(MigrationType::Pallet(PalletMigrationInfo { -// parachain_id: 2006.into(), -// hrmp_channel_status: HRMPChannelStatus { -// project_to_polimec: ChannelStatus::Closed, -// polimec_to_project: ChannelStatus::Closed -// }, -// migration_readiness_check: None, -// })) -// ); -// assert_eq!(project_details.status, ProjectStatus::CTMigrationStarted); -// assert_eq!(inst.execute(|| UnmigratedCounter::::get(project_id)), 10); -// } -// -// fn create_pallet_migration_project(mut inst: MockInstantiator) -> (ProjectId, MockInstantiator) { -// let project_id = inst.create_finished_project( -// default_project_metadata(ISSUER_1), -// ISSUER_1, -// None, -// default_evaluations(), -// default_bids(), -// default_community_buys(), -// default_remainder_buys(), -// ); -// inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); -// inst.settle_project(project_id).unwrap(); -// inst.execute(|| { -// assert_ok!(crate::Pallet::::do_start_pallet_migration( -// &ISSUER_1, -// project_id, -// ParaId::from(6969u32) -// )); -// }); -// (project_id, inst) -// } -// -// fn fake_hrmp_establishment() { -// // Notification sent by the relay when the project starts a project->polimec channel -// let open_channel_message = xcm::v3::opaque::Instruction::HrmpNewChannelOpenRequest { -// sender: 6969, -// max_message_size: 102_300, -// max_capacity: 1000, -// }; -// // This makes Polimec send an acceptance + open channel (polimec->project) message back to the relay -// assert_ok!(PolimecFunding::do_handle_channel_open_request(open_channel_message)); -// -// // Finally the relay notifies the channel polimec->project has been accepted by the project -// let channel_accepted_message = xcm::v3::opaque::Instruction::HrmpChannelAccepted { recipient: 6969u32 }; -// -// // We set the hrmp flags as "Open" and start the receiver pallet check -// assert_ok!(PolimecFunding::do_handle_channel_accepted(channel_accepted_message)); -// } -// -// #[test] -// fn automatic_hrmp_establishment() { -// let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let (project_id, mut inst) = create_pallet_migration_project(inst); -// -// inst.execute(|| fake_hrmp_establishment()); -// -// let project_details = inst.get_project_details(project_id); -// assert_eq!( -// project_details.migration_type, -// Some(MigrationType::Pallet(PalletMigrationInfo { -// parachain_id: 6969.into(), -// hrmp_channel_status: HRMPChannelStatus { -// project_to_polimec: ChannelStatus::Open, -// polimec_to_project: ChannelStatus::Open -// }, -// migration_readiness_check: Some(PalletMigrationReadinessCheck { -// holding_check: (0, CheckOutcome::AwaitingResponse), -// pallet_check: (1, CheckOutcome::AwaitingResponse) -// }), -// })) -// ); -// } -// -// /// Check that the polimec sovereign account has the ct issuance on the project chain, and the receiver pallet is in -// /// the runtime. -// #[test] -// fn pallet_readiness_check() { -// let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let (project_id, mut inst) = create_pallet_migration_project(inst); -// inst.execute(|| fake_hrmp_establishment()); -// -// // At this point, we sent the pallet check xcm to the project chain, and we are awaiting a query response message. -// // query id 0 is the CT balance of the Polimec SA -// // query id 1 is the existence of the receiver pallet -// -// // We simulate the response from the project chain -// let ct_issuance = -// inst.execute(|| ::ContributionTokenCurrency::total_issuance(project_id)); -// let ct_multiassets: MultiAssets = vec![MultiAsset { -// id: Concrete(MultiLocation { parents: 1, interior: X1(Parachain(6969)) }), -// fun: Fungibility::Fungible(ct_issuance), -// }] -// .into(); -// -// inst.execute(|| { -// assert_ok!(PolimecFunding::do_pallet_migration_readiness_response( -// MultiLocation::new(1u8, X1(Parachain(6969u32))), -// 0u64, -// Response::Assets(ct_multiassets), -// )); -// }); -// -// let module_name: BoundedVec = -// BoundedVec::try_from("polimec_receiver".as_bytes().to_vec()).unwrap(); -// let pallet_info = xcm::latest::PalletInfo { -// // index is used for future `Transact` calls to the pallet for migrating a user -// index: 69, -// // Doesn't matter -// name: module_name.clone(), -// // Main check that the receiver pallet is there -// module_name, -// // These might be useful in the future, but not for now -// major: 0, -// minor: 0, -// patch: 0, -// }; -// inst.execute(|| { -// assert_ok!(PolimecFunding::do_pallet_migration_readiness_response( -// MultiLocation::new(1u8, X1(Parachain(6969u32))), -// 1u64, -// Response::PalletsInfo(bounded_vec![pallet_info]), -// )); -// }); -// -// let project_details = inst.get_project_details(project_id); -// if let MigrationType::Pallet(info) = project_details.migration_type.unwrap() { -// assert_eq!(info.migration_readiness_check.unwrap().holding_check.1, CheckOutcome::Passed(None)); -// assert_eq!(info.migration_readiness_check.unwrap().pallet_check.1, CheckOutcome::Passed(Some(69))); -// } else { -// panic!("Migration type is not Pallet") -// } -// } -// } -// -// mod offchain_migration { -// use super::*; -// -// #[test] -// fn start_offchain_migration() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// // Create migrations for 2 projects, to check the `remaining_participants` is unaffected by other projects -// let project_id = inst.create_finished_project( -// default_project_metadata(ISSUER_1), -// ISSUER_1, -// None, -// default_evaluations(), -// default_bids(), -// default_community_buys(), -// default_remainder_buys(), -// ); -// inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); -// inst.settle_project(project_id).unwrap(); -// -// let project_id = inst.create_finished_project( -// default_project_metadata(ISSUER_1), -// ISSUER_1, -// None, -// default_evaluations(), -// default_bids(), -// default_community_buys(), -// default_remainder_buys(), -// ); -// inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); -// inst.settle_project(project_id).unwrap(); -// -// inst.execute(|| { -// assert_err!( -// crate::Pallet::::do_start_offchain_migration(project_id, EVALUATOR_1,), -// Error::::NotIssuer -// ); -// -// assert_ok!(crate::Pallet::::do_start_offchain_migration(project_id, ISSUER_1,)); -// }); -// -// let project_details = inst.get_project_details(project_id); -// assert_eq!(inst.execute(|| UnmigratedCounter::::get(project_id)), 10); -// assert_eq!(project_details.status, ProjectStatus::CTMigrationStarted); -// } -// -// fn create_offchain_migration_project(mut inst: MockInstantiator) -> (ProjectId, MockInstantiator) { -// let project_id = inst.create_finished_project( -// default_project_metadata(ISSUER_1), -// ISSUER_1, -// None, -// default_evaluations(), -// default_bids(), -// default_community_buys(), -// default_remainder_buys(), -// ); -// inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); -// inst.settle_project(project_id).unwrap(); -// inst.execute(|| { -// assert_ok!(crate::Pallet::::do_start_offchain_migration(project_id, ISSUER_1,)); -// }); -// (project_id, inst) -// } -// -// #[test] -// fn confirm_offchain_migration() { -// let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let (project_id, mut inst) = create_offchain_migration_project(inst); -// -// let bidder_1_migrations = inst.execute(|| UserMigrations::::get((project_id, BIDDER_1))).unwrap(); -// assert_eq!(bidder_1_migrations.0, MigrationStatus::NotStarted); -// -// inst.execute(|| { -// assert_ok!(crate::Pallet::::do_confirm_offchain_migration(project_id, ISSUER_1, BIDDER_1)); -// }); -// -// let bidder_1_migrations = inst.execute(|| UserMigrations::::get((project_id, BIDDER_1))).unwrap(); -// assert_eq!(bidder_1_migrations.0, MigrationStatus::Confirmed); -// } -// -// #[test] -// fn mark_project_migration_as_finished() { -// let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let (project_id, mut inst) = create_offchain_migration_project(inst); -// -// let participants = inst.execute(|| UserMigrations::::iter_key_prefix((project_id,)).collect_vec()); -// for participant in participants { -// inst.execute(|| { -// assert_ok!(crate::Pallet::::do_confirm_offchain_migration( -// project_id, -// ISSUER_1, -// participant -// )); -// }); -// } -// -// inst.execute(|| { -// 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/misc.rs b/pallets/funding/src/tests/misc.rs index 0e8f47c0c..602680a52 100644 --- a/pallets/funding/src/tests/misc.rs +++ b/pallets/funding/src/tests/misc.rs @@ -1,368 +1,368 @@ -// use super::*; -// -// // check that functions created to facilitate testing return the expected results -// mod helper_functions { -// use super::*; -// use polimec_common::USD_DECIMALS; -// -// #[test] -// fn test_usd_price_decimal_aware() { -// let submitted_price = FixedU128::from_float(1.85); -// let asset_decimals = 4; -// let expected_price = FixedU128::from_float(185.0); -// type PriceProvider = ::PriceProvider; -// assert_eq!( -// PriceProvider::calculate_decimals_aware_price(submitted_price, USD_DECIMALS, asset_decimals).unwrap(), -// expected_price -// ); -// -// let submitted_price = FixedU128::from_float(1.0); -// let asset_decimals = 12; -// let expected_price = FixedU128::from_float(0.000001); -// -// assert_eq!( -// PriceProvider::calculate_decimals_aware_price(submitted_price, USD_DECIMALS, asset_decimals).unwrap(), -// expected_price -// ); -// } -// -// #[test] -// fn test_convert_from_decimal_aware_back_to_normal() { -// // Test with an asset with less decimals than USD -// let original_price = FixedU128::from_float(1.85); -// let asset_decimals = 4; -// let decimal_aware = ::PriceProvider::calculate_decimals_aware_price( -// original_price, -// USD_DECIMALS, -// asset_decimals, -// ) -// .unwrap(); -// let converted_back = ::PriceProvider::convert_back_to_normal_price( -// decimal_aware, -// USD_DECIMALS, -// asset_decimals, -// ) -// .unwrap(); -// assert_eq!(converted_back, original_price); -// -// // Test with an asset with more decimals than USD -// let original_price = FixedU128::from_float(1.85); -// let asset_decimals = 12; -// let decimal_aware = ::PriceProvider::calculate_decimals_aware_price( -// original_price, -// USD_DECIMALS, -// asset_decimals, -// ) -// .unwrap(); -// let converted_back = ::PriceProvider::convert_back_to_normal_price( -// decimal_aware, -// USD_DECIMALS, -// asset_decimals, -// ) -// .unwrap(); -// assert_eq!(converted_back, original_price); -// } -// -// #[test] -// fn calculate_evaluation_plmc_spent() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// const EVALUATOR_1: AccountIdOf = 1u32; -// const USD_AMOUNT_1: BalanceOf = 150_000 * USD_UNIT; -// const EXPECTED_PLMC_AMOUNT_1: f64 = 17_857.1428571428f64; -// -// const EVALUATOR_2: AccountIdOf = 2u32; -// const USD_AMOUNT_2: BalanceOf = 50_000 * USD_UNIT; -// const EXPECTED_PLMC_AMOUNT_2: f64 = 5_952.3809523809f64; -// -// const EVALUATOR_3: AccountIdOf = 3u32; -// const USD_AMOUNT_3: BalanceOf = 75_000 * USD_UNIT; -// const EXPECTED_PLMC_AMOUNT_3: f64 = 8_928.5714285714f64; -// -// const EVALUATOR_4: AccountIdOf = 4u32; -// const USD_AMOUNT_4: BalanceOf = 100 * USD_UNIT; -// const EXPECTED_PLMC_AMOUNT_4: f64 = 11.9047619047f64; -// -// const EVALUATOR_5: AccountIdOf = 5u32; -// -// // 123.7 USD -// const USD_AMOUNT_5: BalanceOf = 1237 * USD_UNIT / 10; -// const EXPECTED_PLMC_AMOUNT_5: f64 = 14.7261904761f64; -// -// const PLMC_PRICE: f64 = 8.4f64; -// -// assert_eq!( -// ::PriceProvider::get_price(PLMC_FOREIGN_ID).unwrap(), -// PriceOf::::from_float(PLMC_PRICE) -// ); -// -// let evaluations = vec![ -// UserToUSDBalance::::new(EVALUATOR_1, USD_AMOUNT_1), -// UserToUSDBalance::::new(EVALUATOR_2, USD_AMOUNT_2), -// UserToUSDBalance::::new(EVALUATOR_3, USD_AMOUNT_3), -// UserToUSDBalance::::new(EVALUATOR_4, USD_AMOUNT_4), -// UserToUSDBalance::::new(EVALUATOR_5, USD_AMOUNT_5), -// ]; -// -// let expected_plmc_spent = vec![ -// (EVALUATOR_1, EXPECTED_PLMC_AMOUNT_1), -// (EVALUATOR_2, EXPECTED_PLMC_AMOUNT_2), -// (EVALUATOR_3, EXPECTED_PLMC_AMOUNT_3), -// (EVALUATOR_4, EXPECTED_PLMC_AMOUNT_4), -// (EVALUATOR_5, EXPECTED_PLMC_AMOUNT_5), -// ]; -// -// let calculated_plmc_spent = inst -// .calculate_evaluation_plmc_spent(evaluations, false) -// .into_iter() -// .sorted_by(|a, b| a.account.cmp(&b.account)) -// .map(|map| map.plmc_amount) -// .collect_vec(); -// let expected_plmc_spent = expected_plmc_spent -// .into_iter() -// .sorted_by(|a, b| a.0.cmp(&b.0)) -// .map(|map| { -// let f64_amount = map.1; -// let fixed_amount = FixedU128::from_float(f64_amount); -// fixed_amount.checked_mul_int(PLMC).unwrap() -// }) -// .collect_vec(); -// for (expected, calculated) in zip(expected_plmc_spent, calculated_plmc_spent) { -// assert_close_enough!(expected, calculated, Perquintill::from_float(0.999)); -// } -// } -// -// #[test] -// fn calculate_auction_plmc_returned() { -// const CT_AMOUNT_1: u128 = 5000 * CT_UNIT; -// const CT_AMOUNT_2: u128 = 40_000 * CT_UNIT; -// const CT_AMOUNT_3: u128 = 10_000 * CT_UNIT; -// const CT_AMOUNT_4: u128 = 6000 * CT_UNIT; -// const CT_AMOUNT_5: u128 = 2000 * CT_UNIT; -// -// let bid_1 = BidParams::new(BIDDER_1, CT_AMOUNT_1, 1u8, AcceptedFundingAsset::USDT); -// let bid_2 = BidParams::new(BIDDER_2, CT_AMOUNT_2, 1u8, AcceptedFundingAsset::USDT); -// let bid_3 = BidParams::new(BIDDER_1, CT_AMOUNT_3, 1u8, AcceptedFundingAsset::USDT); -// let bid_4 = BidParams::new(BIDDER_3, CT_AMOUNT_4, 1u8, AcceptedFundingAsset::USDT); -// let bid_5 = BidParams::new(BIDDER_4, CT_AMOUNT_5, 1u8, AcceptedFundingAsset::USDT); -// -// // 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 -------------| -// // post wap ~ 1.0557252: -// // (Accepted, 5k) - (Partially, 32k) - (Rejected, 5k) - (Accepted, 5k) - (Accepted - 5k) - (Accepted - 1k) - (Accepted - 2k) -// -// const ORIGINAL_PLMC_CHARGED_BIDDER_1: f64 = 18_452.3809523790; -// const ORIGINAL_PLMC_CHARGED_BIDDER_2: f64 = 47_619.0476190470; -// const ORIGINAL_PLMC_CHARGED_BIDDER_3: f64 = 86_90.4761904760; -// const ORIGINAL_PLMC_CHARGED_BIDDER_4: f64 = 30_95.2380952380; -// -// const FINAL_PLMC_CHARGED_BIDDER_1: f64 = 12_236.4594692840; -// const FINAL_PLMC_CHARGED_BIDDER_2: f64 = 38_095.2380952380; -// const FINAL_PLMC_CHARGED_BIDDER_3: f64 = 75_40.8942202840; -// const FINAL_PLMC_CHARGED_BIDDER_4: f64 = 2_513.6314067610; -// -// let bids = vec![bid_1, bid_2, bid_3, bid_4, bid_5]; -// -// 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: 100_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, None), -// professional: TicketSize::new(USD_UNIT, None), -// institutional: TicketSize::new(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_community_contributing_project( -// project_metadata.clone(), -// ISSUER_1, -// None, -// default_evaluations(), -// bids.clone(), -// ); -// -// let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); -// -// let expected_returns = vec![ -// ORIGINAL_PLMC_CHARGED_BIDDER_1 - FINAL_PLMC_CHARGED_BIDDER_1, -// ORIGINAL_PLMC_CHARGED_BIDDER_2 - FINAL_PLMC_CHARGED_BIDDER_2, -// ORIGINAL_PLMC_CHARGED_BIDDER_3 - FINAL_PLMC_CHARGED_BIDDER_3, -// ORIGINAL_PLMC_CHARGED_BIDDER_4 - FINAL_PLMC_CHARGED_BIDDER_4, -// ]; -// -// let mut returned_plmc_mappings = -// inst.calculate_auction_plmc_returned_from_all_bids_made(&bids, project_metadata.clone(), wap); -// returned_plmc_mappings.sort_by(|b1, b2| b1.account.cmp(&b2.account)); -// -// let returned_plmc_balances = returned_plmc_mappings.into_iter().map(|map| map.plmc_amount).collect_vec(); -// -// for (expected_return, returned_balance) in zip(expected_returns, returned_plmc_balances) { -// let expected_value = FixedU128::from_float(expected_return).checked_mul_int(PLMC).unwrap(); -// -// assert_close_enough!(expected_value, returned_balance, Perquintill::from_float(0.99)); -// } -// } -// -// #[test] -// fn bucket_wap_calculation() { -// let initial_price = FixedU128::from_float(10.0); -// let mut bucket = Bucket::new(100u32, initial_price, FixedU128::from_float(1.0), 10u32); -// let wap = bucket.calculate_wap(100u32); -// assert!(wap == initial_price); -// -// // Initial token amount: 100 -// // Simulate total bidding amount of 128 -// bucket.update(100u32); -// bucket.update(10u32); -// bucket.update(10u32); -// bucket.update(8u32); -// let wap = bucket.calculate_wap(100u32); -// let expected = FixedU128::from_float(10.628); -// let diff = if wap > expected { wap - expected } else { expected - wap }; -// assert!(diff <= FixedU128::from_float(0.001)); -// } -// -// #[test] -// fn calculate_contributed_plmc_spent() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// const PLMC_PRICE: f64 = 8.4f64; -// const CT_PRICE: f64 = 16.32f64; -// -// const CONTRIBUTOR_1: AccountIdOf = 1u32; -// const TOKEN_AMOUNT_1: u128 = 120 * CT_UNIT; -// const MULTIPLIER_1: u8 = 1u8; -// const _TICKET_SIZE_USD_1: u128 = 1_958_4_000_000_000_u128; -// const EXPECTED_PLMC_AMOUNT_1: f64 = 233.1_428_571_428f64; -// -// const CONTRIBUTOR_2: AccountIdOf = 2u32; -// const TOKEN_AMOUNT_2: u128 = 5023 * CT_UNIT; -// const MULTIPLIER_2: u8 = 2u8; -// const _TICKET_SIZE_USD_2: u128 = 81_975_3_600_000_000_u128; -// const EXPECTED_PLMC_AMOUNT_2: f64 = 4_879.4_857_142_857f64; -// -// const CONTRIBUTOR_3: AccountIdOf = 3u32; -// const TOKEN_AMOUNT_3: u128 = 20_000 * CT_UNIT; -// const MULTIPLIER_3: u8 = 17u8; -// const _TICKET_SIZE_USD_3: u128 = 326_400_0_000_000_000_u128; -// const EXPECTED_PLMC_AMOUNT_3: f64 = 2_285.7_142_857_142f64; -// -// const CONTRIBUTOR_4: AccountIdOf = 4u32; -// const TOKEN_AMOUNT_4: u128 = 1_000_000 * CT_UNIT; -// const MULTIPLIER_4: u8 = 25u8; -// const _TICKET_SIZE_4: u128 = 16_320_000_0_000_000_000_u128; -// const EXPECTED_PLMC_AMOUNT_4: f64 = 77_714.2_857_142_857f64; -// -// const CONTRIBUTOR_5: AccountIdOf = 5u32; -// // 0.1233 CTs -// const TOKEN_AMOUNT_5: u128 = 1_233 * CT_UNIT / 10_000; -// const MULTIPLIER_5: u8 = 10u8; -// const _TICKET_SIZE_5: u128 = 2_0_122_562_000_u128; -// const EXPECTED_PLMC_AMOUNT_5: f64 = 0.0_239_554_285f64; -// -// assert_eq!( -// ::PriceProvider::get_price(PLMC_FOREIGN_ID).unwrap(), -// PriceOf::::from_float(PLMC_PRICE) -// ); -// -// let contributions = vec![ -// ContributionParams::new(CONTRIBUTOR_1, TOKEN_AMOUNT_1, MULTIPLIER_1, AcceptedFundingAsset::USDT), -// ContributionParams::new(CONTRIBUTOR_2, TOKEN_AMOUNT_2, MULTIPLIER_2, AcceptedFundingAsset::USDT), -// ContributionParams::new(CONTRIBUTOR_3, TOKEN_AMOUNT_3, MULTIPLIER_3, AcceptedFundingAsset::USDT), -// ContributionParams::new(CONTRIBUTOR_4, TOKEN_AMOUNT_4, MULTIPLIER_4, AcceptedFundingAsset::USDT), -// ContributionParams::new(CONTRIBUTOR_5, TOKEN_AMOUNT_5, MULTIPLIER_5, AcceptedFundingAsset::USDT), -// ]; -// -// let expected_plmc_spent = vec![ -// (CONTRIBUTOR_1, EXPECTED_PLMC_AMOUNT_1), -// (CONTRIBUTOR_2, EXPECTED_PLMC_AMOUNT_2), -// (CONTRIBUTOR_3, EXPECTED_PLMC_AMOUNT_3), -// (CONTRIBUTOR_4, EXPECTED_PLMC_AMOUNT_4), -// (CONTRIBUTOR_5, EXPECTED_PLMC_AMOUNT_5), -// ]; -// -// let calculated_plmc_spent = inst -// .calculate_contributed_plmc_spent( -// contributions, -// PriceProviderOf::::calculate_decimals_aware_price( -// PriceOf::::from_float(CT_PRICE), -// USD_DECIMALS, -// CT_DECIMALS, -// ) -// .unwrap(), -// false, -// ) -// .into_iter() -// .sorted_by(|a, b| a.account.cmp(&b.account)) -// .map(|map| map.plmc_amount) -// .collect_vec(); -// let expected_plmc_spent = expected_plmc_spent -// .into_iter() -// .sorted_by(|a, b| a.0.cmp(&b.0)) -// .map(|map| { -// let fixed_amount = FixedU128::from_float(map.1); -// fixed_amount.checked_mul_int(PLMC).unwrap() -// }) -// .collect_vec(); -// for (expected, calculated) in zip(expected_plmc_spent, calculated_plmc_spent) { -// assert_close_enough!(expected, calculated, Perquintill::from_float(0.999)); -// } -// } -// } -// -// // logic of small functions that extrinsics use to process data or interact with storage -// mod inner_functions { -// use super::*; -// -// #[test] -// fn calculate_vesting_duration() { -// let default_multiplier = MultiplierOf::::default(); -// let default_multiplier_duration = default_multiplier.calculate_vesting_duration::(); -// assert_eq!(default_multiplier_duration, 1u64); -// -// let multiplier_1 = MultiplierOf::::new(1u8).unwrap(); -// let multiplier_1_duration = multiplier_1.calculate_vesting_duration::(); -// assert_eq!(multiplier_1_duration, 1u64); -// -// let multiplier_2 = MultiplierOf::::new(2u8).unwrap(); -// let multiplier_2_duration = multiplier_2.calculate_vesting_duration::(); -// assert_eq!(multiplier_2_duration, FixedU128::from_rational(2167, 1000).saturating_mul_int((DAYS * 7) as u64)); -// -// let multiplier_3 = MultiplierOf::::new(3u8).unwrap(); -// let multiplier_3_duration = multiplier_3.calculate_vesting_duration::(); -// assert_eq!(multiplier_3_duration, FixedU128::from_rational(4334, 1000).saturating_mul_int((DAYS * 7) as u64)); -// -// let multiplier_19 = MultiplierOf::::new(19u8).unwrap(); -// let multiplier_19_duration = multiplier_19.calculate_vesting_duration::(); -// assert_eq!(multiplier_19_duration, FixedU128::from_rational(39006, 1000).saturating_mul_int((DAYS * 7) as u64)); -// -// let multiplier_20 = MultiplierOf::::new(20u8).unwrap(); -// let multiplier_20_duration = multiplier_20.calculate_vesting_duration::(); -// assert_eq!(multiplier_20_duration, FixedU128::from_rational(41173, 1000).saturating_mul_int((DAYS * 7) as u64)); -// -// let multiplier_24 = MultiplierOf::::new(24u8).unwrap(); -// let multiplier_24_duration = multiplier_24.calculate_vesting_duration::(); -// assert_eq!(multiplier_24_duration, FixedU128::from_rational(49841, 1000).saturating_mul_int((DAYS * 7) as u64)); -// -// let multiplier_25 = MultiplierOf::::new(25u8).unwrap(); -// let multiplier_25_duration = multiplier_25.calculate_vesting_duration::(); -// assert_eq!(multiplier_25_duration, FixedU128::from_rational(52008, 1000).saturating_mul_int((DAYS * 7) as u64)); -// } -// } +use super::*; + +// check that functions created to facilitate testing return the expected results +mod helper_functions { + use super::*; + use polimec_common::USD_DECIMALS; + + #[test] + fn test_usd_price_decimal_aware() { + let submitted_price = FixedU128::from_float(1.85); + let asset_decimals = 4; + let expected_price = FixedU128::from_float(185.0); + type PriceProvider = ::PriceProvider; + assert_eq!( + PriceProvider::calculate_decimals_aware_price(submitted_price, USD_DECIMALS, asset_decimals).unwrap(), + expected_price + ); + + let submitted_price = FixedU128::from_float(1.0); + let asset_decimals = 12; + let expected_price = FixedU128::from_float(0.000001); + + assert_eq!( + PriceProvider::calculate_decimals_aware_price(submitted_price, USD_DECIMALS, asset_decimals).unwrap(), + expected_price + ); + } + + #[test] + fn test_convert_from_decimal_aware_back_to_normal() { + // Test with an asset with less decimals than USD + let original_price = FixedU128::from_float(1.85); + let asset_decimals = 4; + let decimal_aware = ::PriceProvider::calculate_decimals_aware_price( + original_price, + USD_DECIMALS, + asset_decimals, + ) + .unwrap(); + let converted_back = ::PriceProvider::convert_back_to_normal_price( + decimal_aware, + USD_DECIMALS, + asset_decimals, + ) + .unwrap(); + assert_eq!(converted_back, original_price); + + // Test with an asset with more decimals than USD + let original_price = FixedU128::from_float(1.85); + let asset_decimals = 12; + let decimal_aware = ::PriceProvider::calculate_decimals_aware_price( + original_price, + USD_DECIMALS, + asset_decimals, + ) + .unwrap(); + let converted_back = ::PriceProvider::convert_back_to_normal_price( + decimal_aware, + USD_DECIMALS, + asset_decimals, + ) + .unwrap(); + assert_eq!(converted_back, original_price); + } + + #[test] + fn calculate_evaluation_plmc_spent() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + const EVALUATOR_1: AccountIdOf = 1u32; + const USD_AMOUNT_1: BalanceOf = 150_000 * USD_UNIT; + const EXPECTED_PLMC_AMOUNT_1: f64 = 17_857.1428571428f64; + + const EVALUATOR_2: AccountIdOf = 2u32; + const USD_AMOUNT_2: BalanceOf = 50_000 * USD_UNIT; + const EXPECTED_PLMC_AMOUNT_2: f64 = 5_952.3809523809f64; + + const EVALUATOR_3: AccountIdOf = 3u32; + const USD_AMOUNT_3: BalanceOf = 75_000 * USD_UNIT; + const EXPECTED_PLMC_AMOUNT_3: f64 = 8_928.5714285714f64; + + const EVALUATOR_4: AccountIdOf = 4u32; + const USD_AMOUNT_4: BalanceOf = 100 * USD_UNIT; + const EXPECTED_PLMC_AMOUNT_4: f64 = 11.9047619047f64; + + const EVALUATOR_5: AccountIdOf = 5u32; + + // 123.7 USD + const USD_AMOUNT_5: BalanceOf = 1237 * USD_UNIT / 10; + const EXPECTED_PLMC_AMOUNT_5: f64 = 14.7261904761f64; + + const PLMC_PRICE: f64 = 8.4f64; + + assert_eq!( + ::PriceProvider::get_price(PLMC_FOREIGN_ID).unwrap(), + PriceOf::::from_float(PLMC_PRICE) + ); + + let evaluations = vec![ + UserToUSDBalance::::new(EVALUATOR_1, USD_AMOUNT_1), + UserToUSDBalance::::new(EVALUATOR_2, USD_AMOUNT_2), + UserToUSDBalance::::new(EVALUATOR_3, USD_AMOUNT_3), + UserToUSDBalance::::new(EVALUATOR_4, USD_AMOUNT_4), + UserToUSDBalance::::new(EVALUATOR_5, USD_AMOUNT_5), + ]; + + let expected_plmc_spent = vec![ + (EVALUATOR_1, EXPECTED_PLMC_AMOUNT_1), + (EVALUATOR_2, EXPECTED_PLMC_AMOUNT_2), + (EVALUATOR_3, EXPECTED_PLMC_AMOUNT_3), + (EVALUATOR_4, EXPECTED_PLMC_AMOUNT_4), + (EVALUATOR_5, EXPECTED_PLMC_AMOUNT_5), + ]; + + let calculated_plmc_spent = inst + .calculate_evaluation_plmc_spent(evaluations, false) + .into_iter() + .sorted_by(|a, b| a.account.cmp(&b.account)) + .map(|map| map.plmc_amount) + .collect_vec(); + let expected_plmc_spent = expected_plmc_spent + .into_iter() + .sorted_by(|a, b| a.0.cmp(&b.0)) + .map(|map| { + let f64_amount = map.1; + let fixed_amount = FixedU128::from_float(f64_amount); + fixed_amount.checked_mul_int(PLMC).unwrap() + }) + .collect_vec(); + for (expected, calculated) in zip(expected_plmc_spent, calculated_plmc_spent) { + assert_close_enough!(expected, calculated, Perquintill::from_float(0.999)); + } + } + + #[test] + fn calculate_auction_plmc_returned() { + const CT_AMOUNT_1: u128 = 5000 * CT_UNIT; + const CT_AMOUNT_2: u128 = 40_000 * CT_UNIT; + const CT_AMOUNT_3: u128 = 10_000 * CT_UNIT; + const CT_AMOUNT_4: u128 = 6000 * CT_UNIT; + const CT_AMOUNT_5: u128 = 2000 * CT_UNIT; + + let bid_1 = BidParams::new(BIDDER_1, CT_AMOUNT_1, 1u8, AcceptedFundingAsset::USDT); + let bid_2 = BidParams::new(BIDDER_2, CT_AMOUNT_2, 1u8, AcceptedFundingAsset::USDT); + let bid_3 = BidParams::new(BIDDER_1, CT_AMOUNT_3, 1u8, AcceptedFundingAsset::USDT); + let bid_4 = BidParams::new(BIDDER_3, CT_AMOUNT_4, 1u8, AcceptedFundingAsset::USDT); + let bid_5 = BidParams::new(BIDDER_4, CT_AMOUNT_5, 1u8, AcceptedFundingAsset::USDT); + + // 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 -------------| + // post wap ~ 1.0557252: + // (Accepted, 5k) - (Partially, 32k) - (Rejected, 5k) - (Accepted, 5k) - (Accepted - 5k) - (Accepted - 1k) - (Accepted - 2k) + + const ORIGINAL_PLMC_CHARGED_BIDDER_1: f64 = 18_452.3809523790; + const ORIGINAL_PLMC_CHARGED_BIDDER_2: f64 = 47_619.0476190470; + const ORIGINAL_PLMC_CHARGED_BIDDER_3: f64 = 86_90.4761904760; + const ORIGINAL_PLMC_CHARGED_BIDDER_4: f64 = 30_95.2380952380; + + const FINAL_PLMC_CHARGED_BIDDER_1: f64 = 12_236.4594692840; + const FINAL_PLMC_CHARGED_BIDDER_2: f64 = 38_095.2380952380; + const FINAL_PLMC_CHARGED_BIDDER_3: f64 = 75_40.8942202840; + const FINAL_PLMC_CHARGED_BIDDER_4: f64 = 2_513.6314067610; + + let bids = vec![bid_1, bid_2, bid_3, bid_4, bid_5]; + + 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: 100_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, None), + professional: TicketSize::new(USD_UNIT, None), + institutional: TicketSize::new(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_community_contributing_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + bids.clone(), + ); + + let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); + + let expected_returns = vec![ + ORIGINAL_PLMC_CHARGED_BIDDER_1 - FINAL_PLMC_CHARGED_BIDDER_1, + ORIGINAL_PLMC_CHARGED_BIDDER_2 - FINAL_PLMC_CHARGED_BIDDER_2, + ORIGINAL_PLMC_CHARGED_BIDDER_3 - FINAL_PLMC_CHARGED_BIDDER_3, + ORIGINAL_PLMC_CHARGED_BIDDER_4 - FINAL_PLMC_CHARGED_BIDDER_4, + ]; + + let mut returned_plmc_mappings = + inst.calculate_auction_plmc_returned_from_all_bids_made(&bids, project_metadata.clone(), wap); + returned_plmc_mappings.sort_by(|b1, b2| b1.account.cmp(&b2.account)); + + let returned_plmc_balances = returned_plmc_mappings.into_iter().map(|map| map.plmc_amount).collect_vec(); + + for (expected_return, returned_balance) in zip(expected_returns, returned_plmc_balances) { + let expected_value = FixedU128::from_float(expected_return).checked_mul_int(PLMC).unwrap(); + + assert_close_enough!(expected_value, returned_balance, Perquintill::from_float(0.99)); + } + } + + #[test] + fn bucket_wap_calculation() { + let initial_price = FixedU128::from_float(10.0); + let mut bucket = Bucket::new(100u32, initial_price, FixedU128::from_float(1.0), 10u32); + let wap = bucket.calculate_wap(100u32); + assert!(wap == initial_price); + + // Initial token amount: 100 + // Simulate total bidding amount of 128 + bucket.update(100u32); + bucket.update(10u32); + bucket.update(10u32); + bucket.update(8u32); + let wap = bucket.calculate_wap(100u32); + let expected = FixedU128::from_float(10.628); + let diff = if wap > expected { wap - expected } else { expected - wap }; + assert!(diff <= FixedU128::from_float(0.001)); + } + + #[test] + fn calculate_contributed_plmc_spent() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + const PLMC_PRICE: f64 = 8.4f64; + const CT_PRICE: f64 = 16.32f64; + + const CONTRIBUTOR_1: AccountIdOf = 1u32; + const TOKEN_AMOUNT_1: u128 = 120 * CT_UNIT; + const MULTIPLIER_1: u8 = 1u8; + const _TICKET_SIZE_USD_1: u128 = 1_958_4_000_000_000_u128; + const EXPECTED_PLMC_AMOUNT_1: f64 = 233.1_428_571_428f64; + + const CONTRIBUTOR_2: AccountIdOf = 2u32; + const TOKEN_AMOUNT_2: u128 = 5023 * CT_UNIT; + const MULTIPLIER_2: u8 = 2u8; + const _TICKET_SIZE_USD_2: u128 = 81_975_3_600_000_000_u128; + const EXPECTED_PLMC_AMOUNT_2: f64 = 4_879.4_857_142_857f64; + + const CONTRIBUTOR_3: AccountIdOf = 3u32; + const TOKEN_AMOUNT_3: u128 = 20_000 * CT_UNIT; + const MULTIPLIER_3: u8 = 17u8; + const _TICKET_SIZE_USD_3: u128 = 326_400_0_000_000_000_u128; + const EXPECTED_PLMC_AMOUNT_3: f64 = 2_285.7_142_857_142f64; + + const CONTRIBUTOR_4: AccountIdOf = 4u32; + const TOKEN_AMOUNT_4: u128 = 1_000_000 * CT_UNIT; + const MULTIPLIER_4: u8 = 25u8; + const _TICKET_SIZE_4: u128 = 16_320_000_0_000_000_000_u128; + const EXPECTED_PLMC_AMOUNT_4: f64 = 77_714.2_857_142_857f64; + + const CONTRIBUTOR_5: AccountIdOf = 5u32; + // 0.1233 CTs + const TOKEN_AMOUNT_5: u128 = 1_233 * CT_UNIT / 10_000; + const MULTIPLIER_5: u8 = 10u8; + const _TICKET_SIZE_5: u128 = 2_0_122_562_000_u128; + const EXPECTED_PLMC_AMOUNT_5: f64 = 0.0_239_554_285f64; + + assert_eq!( + ::PriceProvider::get_price(PLMC_FOREIGN_ID).unwrap(), + PriceOf::::from_float(PLMC_PRICE) + ); + + let contributions = vec![ + ContributionParams::new(CONTRIBUTOR_1, TOKEN_AMOUNT_1, MULTIPLIER_1, AcceptedFundingAsset::USDT), + ContributionParams::new(CONTRIBUTOR_2, TOKEN_AMOUNT_2, MULTIPLIER_2, AcceptedFundingAsset::USDT), + ContributionParams::new(CONTRIBUTOR_3, TOKEN_AMOUNT_3, MULTIPLIER_3, AcceptedFundingAsset::USDT), + ContributionParams::new(CONTRIBUTOR_4, TOKEN_AMOUNT_4, MULTIPLIER_4, AcceptedFundingAsset::USDT), + ContributionParams::new(CONTRIBUTOR_5, TOKEN_AMOUNT_5, MULTIPLIER_5, AcceptedFundingAsset::USDT), + ]; + + let expected_plmc_spent = vec![ + (CONTRIBUTOR_1, EXPECTED_PLMC_AMOUNT_1), + (CONTRIBUTOR_2, EXPECTED_PLMC_AMOUNT_2), + (CONTRIBUTOR_3, EXPECTED_PLMC_AMOUNT_3), + (CONTRIBUTOR_4, EXPECTED_PLMC_AMOUNT_4), + (CONTRIBUTOR_5, EXPECTED_PLMC_AMOUNT_5), + ]; + + let calculated_plmc_spent = inst + .calculate_contributed_plmc_spent( + contributions, + PriceProviderOf::::calculate_decimals_aware_price( + PriceOf::::from_float(CT_PRICE), + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(), + false, + ) + .into_iter() + .sorted_by(|a, b| a.account.cmp(&b.account)) + .map(|map| map.plmc_amount) + .collect_vec(); + let expected_plmc_spent = expected_plmc_spent + .into_iter() + .sorted_by(|a, b| a.0.cmp(&b.0)) + .map(|map| { + let fixed_amount = FixedU128::from_float(map.1); + fixed_amount.checked_mul_int(PLMC).unwrap() + }) + .collect_vec(); + for (expected, calculated) in zip(expected_plmc_spent, calculated_plmc_spent) { + assert_close_enough!(expected, calculated, Perquintill::from_float(0.999)); + } + } +} + +// logic of small functions that extrinsics use to process data or interact with storage +mod inner_functions { + use super::*; + + #[test] + fn calculate_vesting_duration() { + let default_multiplier = MultiplierOf::::default(); + let default_multiplier_duration = default_multiplier.calculate_vesting_duration::(); + assert_eq!(default_multiplier_duration, 1u64); + + let multiplier_1 = MultiplierOf::::new(1u8).unwrap(); + let multiplier_1_duration = multiplier_1.calculate_vesting_duration::(); + assert_eq!(multiplier_1_duration, 1u64); + + let multiplier_2 = MultiplierOf::::new(2u8).unwrap(); + let multiplier_2_duration = multiplier_2.calculate_vesting_duration::(); + assert_eq!(multiplier_2_duration, FixedU128::from_rational(2167, 1000).saturating_mul_int((DAYS * 7) as u64)); + + let multiplier_3 = MultiplierOf::::new(3u8).unwrap(); + let multiplier_3_duration = multiplier_3.calculate_vesting_duration::(); + assert_eq!(multiplier_3_duration, FixedU128::from_rational(4334, 1000).saturating_mul_int((DAYS * 7) as u64)); + + let multiplier_19 = MultiplierOf::::new(19u8).unwrap(); + let multiplier_19_duration = multiplier_19.calculate_vesting_duration::(); + assert_eq!(multiplier_19_duration, FixedU128::from_rational(39006, 1000).saturating_mul_int((DAYS * 7) as u64)); + + let multiplier_20 = MultiplierOf::::new(20u8).unwrap(); + let multiplier_20_duration = multiplier_20.calculate_vesting_duration::(); + assert_eq!(multiplier_20_duration, FixedU128::from_rational(41173, 1000).saturating_mul_int((DAYS * 7) as u64)); + + let multiplier_24 = MultiplierOf::::new(24u8).unwrap(); + let multiplier_24_duration = multiplier_24.calculate_vesting_duration::(); + assert_eq!(multiplier_24_duration, FixedU128::from_rational(49841, 1000).saturating_mul_int((DAYS * 7) as u64)); + + let multiplier_25 = MultiplierOf::::new(25u8).unwrap(); + let multiplier_25_duration = multiplier_25.calculate_vesting_duration::(); + assert_eq!(multiplier_25_duration, FixedU128::from_rational(52008, 1000).saturating_mul_int((DAYS * 7) as u64)); + } +} diff --git a/pallets/funding/src/tests/mod.rs b/pallets/funding/src/tests/mod.rs index 878578456..a59994747 100644 --- a/pallets/funding/src/tests/mod.rs +++ b/pallets/funding/src/tests/mod.rs @@ -1,505 +1,468 @@ -// use super::*; -// use crate::{ -// instantiator::*, -// mock::*, -// traits::{ProvideAssetPrice, VestingDurationCalculation}, -// CurrencyMetadata, Error, ProjectMetadata, TicketSize, -// }; -// use defaults::*; -// use frame_support::{ -// assert_err, assert_noop, assert_ok, -// traits::{ -// fungible::{Inspect as FungibleInspect, InspectHold as FungibleInspectHold, MutateFreeze, MutateHold}, -// Get, -// }, -// }; -// use itertools::Itertools; -// use parachains_common::DAYS; -// use polimec_common::{ReleaseSchedule, USD_DECIMALS, USD_UNIT}; -// use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt_with_cid}; -// use sp_arithmetic::{traits::Zero, Percent, Perquintill}; -// use sp_runtime::TokenError; -// use sp_std::cell::RefCell; -// use std::iter::zip; -// pub type MockInstantiator = -// Instantiator::AllPalletsWithoutSystem, RuntimeEvent>; -// pub const CT_DECIMALS: u8 = 15; -// pub const CT_UNIT: u128 = 10_u128.pow(CT_DECIMALS as u32); -// pub const USDT_UNIT: u128 = USD_UNIT; -// -// const IPFS_CID: &str = "QmeuJ24ffwLAZppQcgcggJs3n689bewednYkuc8Bx5Gngz"; -// const ISSUER_1: AccountId = 11; -// const ISSUER_2: AccountId = 12; -// const ISSUER_3: AccountId = 13; -// 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; -// const EVALUATOR_4: AccountId = 24; -// const EVALUATOR_5: AccountId = 25; -// const BIDDER_1: AccountId = 31; -// const BIDDER_2: AccountId = 32; -// const BIDDER_3: AccountId = 33; -// const BIDDER_4: AccountId = 34; -// const BIDDER_5: AccountId = 35; -// const BIDDER_6: AccountId = 36; -// const BUYER_1: AccountId = 41; -// const BUYER_2: AccountId = 42; -// const BUYER_3: AccountId = 43; -// 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::*; -// -// pub fn default_token_information() -> CurrencyMetadata>> { -// CurrencyMetadata { name: bounded_name(), symbol: bounded_symbol(), decimals: CT_DECIMALS } -// } -// pub fn default_project_metadata(issuer: AccountId) -> ProjectMetadataOf { -// let bounded_name = bounded_name(); -// let bounded_symbol = bounded_symbol(); -// let metadata_hash = ipfs_hash(); -// let base_price = PriceOf::::from_float(10.0); -// let decimal_aware_price = ::PriceProvider::calculate_decimals_aware_price( -// base_price, -// USD_DECIMALS, -// CT_DECIMALS, -// ) -// .unwrap(); -// ProjectMetadata { -// token_information: CurrencyMetadata { name: bounded_name, symbol: bounded_symbol, decimals: CT_DECIMALS }, -// 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: decimal_aware_price, -// 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, None), -// professional: TicketSize::new(USD_UNIT, None), -// institutional: TicketSize::new(USD_UNIT, None), -// phantom: Default::default(), -// }, -// participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), -// funding_destination_account: issuer, -// policy_ipfs_cid: Some(metadata_hash), -// } -// } -// -// pub fn knowledge_hub_project() -> ProjectMetadataOf { -// let bounded_name = bounded_name(); -// let bounded_symbol = bounded_symbol(); -// let metadata_hash = ipfs_hash(); -// let base_price = PriceOf::::from_float(10.0); -// let decimal_aware_price = ::PriceProvider::calculate_decimals_aware_price( -// base_price, -// USD_DECIMALS, -// CT_DECIMALS, -// ) -// .unwrap(); -// let project_metadata = ProjectMetadataOf:: { -// token_information: CurrencyMetadata { name: bounded_name, symbol: bounded_symbol, decimals: CT_DECIMALS }, -// mainnet_token_max_supply: 8_000_000 * CT_UNIT, -// total_allocation_size: 100_000 * CT_UNIT, -// auction_round_allocation_percentage: Percent::from_percent(50u8), -// minimum_price: decimal_aware_price, -// 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, None), -// professional: TicketSize::new(USD_UNIT, None), -// institutional: TicketSize::new(USD_UNIT, None), -// phantom: Default::default(), -// }, -// participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), -// funding_destination_account: ISSUER_1, -// policy_ipfs_cid: Some(metadata_hash), -// }; -// project_metadata -// } -// -// pub fn default_plmc_balances() -> Vec> { -// vec![ -// UserToPLMCBalance::new(ISSUER_1, 10_000_000 * PLMC), -// UserToPLMCBalance::new(EVALUATOR_1, 10_000_000 * PLMC), -// UserToPLMCBalance::new(EVALUATOR_2, 10_000_000 * PLMC), -// UserToPLMCBalance::new(EVALUATOR_3, 10_000_000 * PLMC), -// UserToPLMCBalance::new(BIDDER_1, 10_000_000 * PLMC), -// UserToPLMCBalance::new(BIDDER_2, 10_000_000 * PLMC), -// UserToPLMCBalance::new(BUYER_1, 10_000_000 * PLMC), -// UserToPLMCBalance::new(BUYER_2, 10_000_000 * PLMC), -// UserToPLMCBalance::new(BUYER_3, 10_000_000 * PLMC), -// UserToPLMCBalance::new(BUYER_4, 10_000_000 * PLMC), -// UserToPLMCBalance::new(BUYER_5, 10_000_000 * PLMC), -// ] -// } -// -// pub fn default_usdt_balances() -> Vec> { -// vec![ -// (ISSUER_1, 10_000_000 * USDT_UNIT).into(), -// (EVALUATOR_1, 10_000_000 * USDT_UNIT).into(), -// (EVALUATOR_2, 10_000_000 * USDT_UNIT).into(), -// (EVALUATOR_3, 10_000_000 * USDT_UNIT).into(), -// (BIDDER_1, 10_000_000 * USDT_UNIT).into(), -// (BIDDER_2, 10_000_000 * USDT_UNIT).into(), -// (BUYER_1, 10_000_000 * USDT_UNIT).into(), -// (BUYER_2, 10_000_000 * USDT_UNIT).into(), -// (BUYER_3, 10_000_000 * USDT_UNIT).into(), -// (BUYER_4, 10_000_000 * USDT_UNIT).into(), -// (BUYER_5, 10_000_000 * USDT_UNIT).into(), -// ] -// } -// -// pub fn default_evaluations() -> Vec> { -// 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), -// ] -// } -// -// pub fn knowledge_hub_evaluations() -> Vec> { -// vec![ -// UserToUSDBalance::new(EVALUATOR_1, 75_000 * USDT_UNIT), -// UserToUSDBalance::new(EVALUATOR_2, 65_000 * USDT_UNIT), -// UserToUSDBalance::new(EVALUATOR_3, 60_000 * USDT_UNIT), -// ] -// } -// -// pub fn default_failing_evaluations() -> Vec> { -// vec![UserToUSDBalance::new(EVALUATOR_1, 3_000 * USD_UNIT), UserToUSDBalance::new(EVALUATOR_2, 1_000 * USD_UNIT)] -// } -// -// pub fn default_bids() -> Vec> { -// vec![ -// BidParams::new(BIDDER_1, 400_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// BidParams::new(BIDDER_2, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ] -// } -// -// pub fn knowledge_hub_bids() -> Vec> { -// // This should reflect the bidding currency, which currently is USDT -// vec![ -// BidParams::new(BIDDER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// BidParams::new(BIDDER_2, 20_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// BidParams::new(BIDDER_3, 20_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// BidParams::new(BIDDER_4, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// BidParams::new(BIDDER_5, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// BidParams::new(BIDDER_6, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ] -// } -// -// pub fn default_community_buys() -> Vec> { -// vec![ -// ContributionParams::new(BUYER_1, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ContributionParams::new(BUYER_2, 130_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ContributionParams::new(BUYER_3, 30_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ContributionParams::new(BUYER_4, 210_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ContributionParams::new(BUYER_5, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ] -// } -// -// pub fn default_remainder_buys() -> Vec> { -// vec![ -// ContributionParams::new(EVALUATOR_2, 20_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ContributionParams::new(BUYER_2, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ContributionParams::new(BIDDER_1, 30_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ] -// } -// -// pub fn knowledge_hub_buys() -> Vec> { -// vec![ -// ContributionParams::new(BUYER_1, 4_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ContributionParams::new(BUYER_2, 2_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ContributionParams::new(BUYER_3, 2_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ContributionParams::new(BUYER_4, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ContributionParams::new(BUYER_5, 30_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ContributionParams::new(BUYER_6, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ContributionParams::new(BUYER_7, 2_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ] -// } -// -// pub fn bounded_name() -> BoundedVec> { -// BoundedVec::try_from("Contribution Token TEST".as_bytes().to_vec()).unwrap() -// } -// pub fn bounded_symbol() -> BoundedVec> { -// BoundedVec::try_from("CTEST".as_bytes().to_vec()).unwrap() -// } -// pub fn ipfs_hash() -> BoundedVec> { -// BoundedVec::try_from(IPFS_CID.as_bytes().to_vec()).unwrap() -// } -// -// pub fn default_weights() -> Vec { -// vec![20u8, 15u8, 10u8, 25u8, 30u8] -// } -// -// pub fn default_evaluators() -> Vec { -// vec![EVALUATOR_1, EVALUATOR_2, EVALUATOR_3, EVALUATOR_4, EVALUATOR_5] -// } -// pub fn default_bidders() -> Vec { -// vec![BIDDER_1, BIDDER_2, BIDDER_3, BIDDER_4, BIDDER_5] -// } -// pub fn default_multipliers() -> Vec { -// vec![1u8, 1u8, 1u8, 1u8, 1u8] -// } -// pub fn default_bidder_multipliers() -> Vec { -// vec![10u8, 3u8, 8u8, 1u8, 4u8] -// } -// pub fn default_community_contributor_multipliers() -> Vec { -// vec![1u8, 1u8, 1u8, 1u8, 1u8] -// } -// pub fn default_remainder_contributor_multipliers() -> Vec { -// vec![1u8, 1u8, 1u8, 1u8, 1u8] -// } -// -// pub fn default_community_contributors() -> Vec { -// vec![BUYER_1, BUYER_2, BUYER_3, BUYER_4, BUYER_5] -// } -// -// pub fn default_remainder_contributors() -> Vec { -// vec![EVALUATOR_1, BIDDER_3, BUYER_4, BUYER_6, BIDDER_6] -// } -// -// pub fn default_all_participants() -> Vec { -// let mut accounts: Vec = default_evaluators() -// .iter() -// .chain(default_bidders().iter()) -// .chain(default_community_contributors().iter()) -// .chain(default_remainder_contributors().iter()) -// .copied() -// .collect(); -// accounts.sort(); -// accounts.dedup(); -// accounts -// } -// -// pub fn project_from_funding_reached(instantiator: &mut MockInstantiator, percent: u64) -> ProjectId { -// let project_metadata = default_project_metadata(ISSUER_1); -// let min_price = project_metadata.minimum_price; -// let usd_to_reach = Perquintill::from_percent(percent) * -// (project_metadata.minimum_price.checked_mul_int(project_metadata.total_allocation_size).unwrap()); -// let evaluations = default_evaluations(); -// let bids = instantiator.generate_bids_from_total_usd( -// Percent::from_percent(50u8) * usd_to_reach, -// min_price, -// default_weights(), -// default_bidders(), -// default_multipliers(), -// ); -// let contributions = instantiator.generate_contributions_from_total_usd( -// Percent::from_percent(50u8) * usd_to_reach, -// min_price, -// default_weights(), -// default_community_contributors(), -// default_multipliers(), -// ); -// instantiator.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids, contributions, vec![]) -// } -// -// pub fn default_bids_from_ct_percent(percent: u8) -> Vec> { -// // Used only to generate values, not for chain interactions -// let inst = MockInstantiator::new(None); -// let project_metadata = default_project_metadata(ISSUER_1); -// inst.generate_bids_from_total_ct_percent( -// project_metadata, -// percent, -// default_weights(), -// default_bidders(), -// default_bidder_multipliers(), -// ) -// } -// -// pub fn default_community_contributions_from_ct_percent(percent: u8) -> Vec> { -// // Used only to generate values, not for chain interactions -// let inst = MockInstantiator::new(None); -// let project_metadata = default_project_metadata(ISSUER_1); -// inst.generate_contributions_from_total_ct_percent( -// project_metadata, -// percent, -// default_weights(), -// default_community_contributors(), -// default_community_contributor_multipliers(), -// ) -// } -// -// pub fn default_remainder_contributions_from_ct_percent(percent: u8) -> Vec> { -// // Used only to generate values, not for chain interactions -// let inst = MockInstantiator::new(None); -// let project_metadata = default_project_metadata(ISSUER_1); -// inst.generate_contributions_from_total_ct_percent( -// project_metadata, -// percent, -// default_weights(), -// default_remainder_contributors(), -// default_remainder_contributor_multipliers(), -// ) -// } -// } -// -// 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()))); -// let project_metadata = default_project_metadata(ISSUER_1); -// let min_price = project_metadata.minimum_price; -// let percentage_funded_usd = Perquintill::from_percent(percentage) * -// (project_metadata.minimum_price.checked_mul_int(project_metadata.total_allocation_size).unwrap()); -// let evaluations = default_evaluations(); -// let bids = inst.generate_bids_from_total_usd( -// Percent::from_percent(50u8) * percentage_funded_usd, -// min_price, -// default_weights(), -// default_bidders(), -// default_multipliers(), -// ); -// let contributions = inst.generate_contributions_from_total_usd( -// Percent::from_percent(50u8) * percentage_funded_usd, -// min_price, -// default_weights(), -// default_community_contributors(), -// default_multipliers(), -// ); -// 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); -// } -// } -// -// (inst, project_id) -// } -// -// pub fn create_finished_project_with_usd_raised( -// mut inst: MockInstantiator, -// usd_raised: BalanceOf, -// usd_target: BalanceOf, -// ) -> (MockInstantiator, ProjectId) { -// let issuer = inst.get_new_nonce() as u32; -// let mut project_metadata = default_project_metadata(issuer); -// project_metadata.total_allocation_size = -// 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 { -// // It's hard to know how much usd was raised on the auction to take the price to `x`. So we calculate -// // the price needed to get the project from 0 to `usd_target` buying 50% of the supply in the contribution round. -// // Later we adjust the exact amount of tokens based on the amount raised in the auction. -// // This means we will never have 100% CTs sold. -// let price_increase_percentage = FixedU128::from_rational(usd_raised, usd_target); -// let required_price = price_increase_percentage * project_metadata.minimum_price; -// -// // Since we want to reach the usd target with half the tokens, and the usd target is first calculated based on -// // 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(); -// -// let bids = inst.generate_bids_that_take_price_to(project_metadata.clone(), required_price, 420, |acc| acc + 1u32); -// -// let project_id = inst.create_community_contributing_project(project_metadata, issuer, None, evaluations, bids); -// -// 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; -// -// let community_contributions = inst.generate_contributions_from_total_usd( -// usd_remaining, -// wap, -// default_weights(), -// default_community_contributors(), -// default_multipliers(), -// ); -// 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.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(); -// } -// -// 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); -// -// (inst, project_id) -// } +use super::*; +use crate::{ + instantiator::*, + mock::*, + traits::{ProvideAssetPrice, VestingDurationCalculation}, + CurrencyMetadata, Error, ProjectMetadata, TicketSize, +}; +use defaults::*; +use frame_support::{ + assert_err, assert_noop, assert_ok, + traits::{ + fungible::{Inspect as FungibleInspect, InspectHold as FungibleInspectHold, MutateFreeze, MutateHold}, + Get, + }, +}; +use itertools::Itertools; +use parachains_common::DAYS; +use polimec_common::{ReleaseSchedule, USD_DECIMALS, USD_UNIT}; +use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt_with_cid}; +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; +pub const CT_UNIT: u128 = 10_u128.pow(CT_DECIMALS as u32); +pub const USDT_UNIT: u128 = USD_UNIT; + +const IPFS_CID: &str = "QmeuJ24ffwLAZppQcgcggJs3n689bewednYkuc8Bx5Gngz"; +const ISSUER_1: AccountId = 11; +const ISSUER_2: AccountId = 12; +const ISSUER_3: AccountId = 13; +const ISSUER_4: AccountId = 14; +const ISSUER_5: AccountId = 15; +const ISSUER_6: AccountId = 16; +const ISSUER_7: AccountId = 17; +const EVALUATOR_1: AccountId = 21; +const EVALUATOR_2: AccountId = 22; +const EVALUATOR_3: AccountId = 23; +const EVALUATOR_4: AccountId = 24; +const EVALUATOR_5: AccountId = 25; +const BIDDER_1: AccountId = 31; +const BIDDER_2: AccountId = 32; +const BIDDER_3: AccountId = 33; +const BIDDER_4: AccountId = 34; +const BIDDER_5: AccountId = 35; +const BIDDER_6: AccountId = 36; +const BUYER_1: AccountId = 41; +const BUYER_2: AccountId = 42; +const BUYER_3: AccountId = 43; +const BUYER_4: AccountId = 44; +const BUYER_5: AccountId = 45; +const BUYER_6: AccountId = 46; +const BUYER_7: AccountId = 47; + +pub mod defaults { + use super::*; + + pub fn default_token_information() -> CurrencyMetadata>> { + CurrencyMetadata { name: bounded_name(), symbol: bounded_symbol(), decimals: CT_DECIMALS } + } + pub fn default_project_metadata(issuer: AccountId) -> ProjectMetadataOf { + let bounded_name = bounded_name(); + let bounded_symbol = bounded_symbol(); + let metadata_hash = ipfs_hash(); + let base_price = PriceOf::::from_float(10.0); + let decimal_aware_price = ::PriceProvider::calculate_decimals_aware_price( + base_price, + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + ProjectMetadata { + token_information: CurrencyMetadata { name: bounded_name, symbol: bounded_symbol, decimals: CT_DECIMALS }, + 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: decimal_aware_price, + 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, None), + professional: TicketSize::new(USD_UNIT, None), + institutional: TicketSize::new(USD_UNIT, None), + phantom: Default::default(), + }, + participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), + funding_destination_account: issuer, + policy_ipfs_cid: Some(metadata_hash), + } + } + + pub fn knowledge_hub_project() -> ProjectMetadataOf { + let bounded_name = bounded_name(); + let bounded_symbol = bounded_symbol(); + let metadata_hash = ipfs_hash(); + let base_price = PriceOf::::from_float(10.0); + let decimal_aware_price = ::PriceProvider::calculate_decimals_aware_price( + base_price, + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + let project_metadata = ProjectMetadataOf:: { + token_information: CurrencyMetadata { name: bounded_name, symbol: bounded_symbol, decimals: CT_DECIMALS }, + mainnet_token_max_supply: 8_000_000 * CT_UNIT, + total_allocation_size: 100_000 * CT_UNIT, + auction_round_allocation_percentage: Percent::from_percent(50u8), + minimum_price: decimal_aware_price, + 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, None), + professional: TicketSize::new(USD_UNIT, None), + institutional: TicketSize::new(USD_UNIT, None), + phantom: Default::default(), + }, + participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), + funding_destination_account: ISSUER_1, + policy_ipfs_cid: Some(metadata_hash), + }; + project_metadata + } + + pub fn default_plmc_balances() -> Vec> { + vec![ + UserToPLMCBalance::new(ISSUER_1, 10_000_000 * PLMC), + UserToPLMCBalance::new(EVALUATOR_1, 10_000_000 * PLMC), + UserToPLMCBalance::new(EVALUATOR_2, 10_000_000 * PLMC), + UserToPLMCBalance::new(EVALUATOR_3, 10_000_000 * PLMC), + UserToPLMCBalance::new(BIDDER_1, 10_000_000 * PLMC), + UserToPLMCBalance::new(BIDDER_2, 10_000_000 * PLMC), + UserToPLMCBalance::new(BUYER_1, 10_000_000 * PLMC), + UserToPLMCBalance::new(BUYER_2, 10_000_000 * PLMC), + UserToPLMCBalance::new(BUYER_3, 10_000_000 * PLMC), + UserToPLMCBalance::new(BUYER_4, 10_000_000 * PLMC), + UserToPLMCBalance::new(BUYER_5, 10_000_000 * PLMC), + ] + } + + pub fn default_usdt_balances() -> Vec> { + vec![ + (ISSUER_1, 10_000_000 * USDT_UNIT).into(), + (EVALUATOR_1, 10_000_000 * USDT_UNIT).into(), + (EVALUATOR_2, 10_000_000 * USDT_UNIT).into(), + (EVALUATOR_3, 10_000_000 * USDT_UNIT).into(), + (BIDDER_1, 10_000_000 * USDT_UNIT).into(), + (BIDDER_2, 10_000_000 * USDT_UNIT).into(), + (BUYER_1, 10_000_000 * USDT_UNIT).into(), + (BUYER_2, 10_000_000 * USDT_UNIT).into(), + (BUYER_3, 10_000_000 * USDT_UNIT).into(), + (BUYER_4, 10_000_000 * USDT_UNIT).into(), + (BUYER_5, 10_000_000 * USDT_UNIT).into(), + ] + } + + pub fn default_evaluations() -> Vec> { + 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), + ] + } + + pub fn knowledge_hub_evaluations() -> Vec> { + vec![ + UserToUSDBalance::new(EVALUATOR_1, 75_000 * USDT_UNIT), + UserToUSDBalance::new(EVALUATOR_2, 65_000 * USDT_UNIT), + UserToUSDBalance::new(EVALUATOR_3, 60_000 * USDT_UNIT), + ] + } + + pub fn default_failing_evaluations() -> Vec> { + vec![UserToUSDBalance::new(EVALUATOR_1, 3_000 * USD_UNIT), UserToUSDBalance::new(EVALUATOR_2, 1_000 * USD_UNIT)] + } + + pub fn default_bids() -> Vec> { + vec![ + BidParams::new(BIDDER_1, 400_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_2, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ] + } + + pub fn knowledge_hub_bids() -> Vec> { + // This should reflect the bidding currency, which currently is USDT + vec![ + BidParams::new(BIDDER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_2, 20_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_3, 20_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_4, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_5, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_6, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ] + } + + 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), + ContributionParams::new(BUYER_3, 30_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_4, 210_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_5, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ] + } + + 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), + ContributionParams::new(BIDDER_1, 30_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ] + } + + pub fn knowledge_hub_buys() -> Vec> { + vec![ + ContributionParams::new(BUYER_1, 4_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_2, 2_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_3, 2_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_4, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_5, 30_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_6, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_7, 2_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ] + } + + pub fn bounded_name() -> BoundedVec> { + BoundedVec::try_from("Contribution Token TEST".as_bytes().to_vec()).unwrap() + } + pub fn bounded_symbol() -> BoundedVec> { + BoundedVec::try_from("CTEST".as_bytes().to_vec()).unwrap() + } + pub fn ipfs_hash() -> BoundedVec> { + BoundedVec::try_from(IPFS_CID.as_bytes().to_vec()).unwrap() + } + + pub fn default_weights() -> Vec { + vec![20u8, 15u8, 10u8, 25u8, 30u8] + } + + pub fn default_evaluators() -> Vec { + vec![EVALUATOR_1, EVALUATOR_2, EVALUATOR_3, EVALUATOR_4, EVALUATOR_5] + } + pub fn default_bidders() -> Vec { + vec![BIDDER_1, BIDDER_2, BIDDER_3, BIDDER_4, BIDDER_5] + } + pub fn default_multipliers() -> Vec { + vec![1u8, 1u8, 1u8, 1u8, 1u8] + } + pub fn default_bidder_multipliers() -> Vec { + vec![10u8, 3u8, 8u8, 1u8, 4u8] + } + pub fn default_community_contributor_multipliers() -> Vec { + vec![1u8, 1u8, 1u8, 1u8, 1u8] + } + pub fn default_remainder_contributor_multipliers() -> Vec { + vec![1u8, 1u8, 1u8, 1u8, 1u8] + } + + pub fn default_community_contributors() -> Vec { + vec![BUYER_1, BUYER_2, BUYER_3, BUYER_4, BUYER_5] + } + + pub fn default_remainder_contributors() -> Vec { + vec![EVALUATOR_1, BIDDER_3, BUYER_4, BUYER_6, BIDDER_6] + } + + pub fn default_all_participants() -> Vec { + let mut accounts: Vec = default_evaluators() + .iter() + .chain(default_bidders().iter()) + .chain(default_community_contributors().iter()) + .chain(default_remainder_contributors().iter()) + .copied() + .collect(); + accounts.sort(); + accounts.dedup(); + accounts + } + + pub fn project_from_funding_reached(instantiator: &mut MockInstantiator, percent: u64) -> ProjectId { + let project_metadata = default_project_metadata(ISSUER_1); + let min_price = project_metadata.minimum_price; + let usd_to_reach = Perquintill::from_percent(percent) * + (project_metadata.minimum_price.checked_mul_int(project_metadata.total_allocation_size).unwrap()); + let evaluations = default_evaluations(); + let bids = instantiator.generate_bids_from_total_usd( + Percent::from_percent(50u8) * usd_to_reach, + min_price, + default_weights(), + default_bidders(), + default_multipliers(), + ); + let contributions = instantiator.generate_contributions_from_total_usd( + Percent::from_percent(50u8) * usd_to_reach, + min_price, + default_weights(), + default_community_contributors(), + default_multipliers(), + ); + instantiator.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids, contributions, vec![]) + } + + pub fn default_bids_from_ct_percent(percent: u8) -> Vec> { + // Used only to generate values, not for chain interactions + let inst = MockInstantiator::new(None); + let project_metadata = default_project_metadata(ISSUER_1); + inst.generate_bids_from_total_ct_percent( + project_metadata, + percent, + default_weights(), + default_bidders(), + default_bidder_multipliers(), + ) + } + + pub fn default_community_contributions_from_ct_percent(percent: u8) -> Vec> { + // Used only to generate values, not for chain interactions + let inst = MockInstantiator::new(None); + let project_metadata = default_project_metadata(ISSUER_1); + inst.generate_contributions_from_total_ct_percent( + project_metadata, + percent, + default_weights(), + default_community_contributors(), + default_community_contributor_multipliers(), + ) + } + + pub fn default_remainder_contributions_from_ct_percent(percent: u8) -> Vec> { + // Used only to generate values, not for chain interactions + let inst = MockInstantiator::new(None); + let project_metadata = default_project_metadata(ISSUER_1); + inst.generate_contributions_from_total_ct_percent( + project_metadata, + percent, + default_weights(), + default_remainder_contributors(), + default_remainder_contributor_multipliers(), + ) + } +} + +pub fn create_project_with_funding_percentage( + percentage: u64, + start_settlement: bool, +) -> (MockInstantiator, ProjectId) { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + let min_price = project_metadata.minimum_price; + let percentage_funded_usd = Perquintill::from_percent(percentage) * + (project_metadata.minimum_price.checked_mul_int(project_metadata.total_allocation_size).unwrap()); + let evaluations = default_evaluations(); + let bids = inst.generate_bids_from_total_usd( + Percent::from_percent(50u8) * percentage_funded_usd, + min_price, + default_weights(), + default_bidders(), + default_multipliers(), + ); + let contributions = inst.generate_contributions_from_total_usd( + Percent::from_percent(50u8) * percentage_funded_usd, + min_price, + default_weights(), + default_community_contributors(), + default_multipliers(), + ); + let project_id = + inst.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids, contributions, vec![]); + + if start_settlement { + 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) +} + +pub fn create_finished_project_with_usd_raised( + mut inst: MockInstantiator, + usd_raised: BalanceOf, + usd_target: BalanceOf, +) -> (MockInstantiator, ProjectId) { + let issuer = inst.get_new_nonce() as u32; + let mut project_metadata = default_project_metadata(issuer); + project_metadata.total_allocation_size = + project_metadata.minimum_price.reciprocal().unwrap().saturating_mul_int(usd_target); + project_metadata.auction_round_allocation_percentage = Percent::from_percent(50u8); + + let required_price = if usd_raised <= usd_target { + project_metadata.minimum_price + } else { + // It's hard to know how much usd was raised on the auction to take the price to `x`. So we calculate + // the price needed to get the project from 0 to `usd_target` buying 50% of the supply in the contribution round. + // Later we adjust the exact amount of tokens based on the amount raised in the auction. + // This means we will never have 100% CTs sold. + let price_increase_percentage = FixedU128::from_rational(usd_raised, usd_target); + let required_price = price_increase_percentage * project_metadata.minimum_price; + + // Since we want to reach the usd target with half the tokens, and the usd target is first calculated based on + // selling all the CTs, we need the price to be double + FixedU128::from_rational(2, 1) * required_price + }; + + let evaluations = default_evaluations(); + + let bids = inst.generate_bids_that_take_price_to(project_metadata.clone(), required_price, 420, |acc| acc + 1u32); + + let project_id = inst.create_community_contributing_project(project_metadata, issuer, None, evaluations, bids); + + let project_details = inst.get_project_details(project_id); + let wap = project_details.weighted_average_price.unwrap(); + + let usd_raised_so_far = project_details.funding_amount_reached_usd; + let usd_remaining = usd_raised - usd_raised_so_far; + + let community_contributions = inst.generate_contributions_from_total_usd( + usd_remaining, + wap, + default_weights(), + default_community_contributors(), + default_multipliers(), + ); + 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_funding_asset_to(usdt_required); + inst.contribute_for_users(project_id, community_contributions).unwrap(); + + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); + + let project_details = inst.get_project_details(project_id); + + // 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); + + (inst, project_id) +} diff --git a/pallets/funding/src/tests/runtime_api.rs b/pallets/funding/src/tests/runtime_api.rs index dad0c7e04..4993b530e 100644 --- a/pallets/funding/src/tests/runtime_api.rs +++ b/pallets/funding/src/tests/runtime_api.rs @@ -1,695 +1,700 @@ -// use super::*; -// use crate::runtime_api::{ExtrinsicHelpers, Leaderboards, ProjectInformation, UserInformation}; -// -// #[test] -// fn top_evaluations() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let evaluations = 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), -// UserToUSDBalance::new(EVALUATOR_4, 1_000_000 * USD_UNIT), -// UserToUSDBalance::new(EVALUATOR_1, 1_000 * USD_UNIT), -// ]; -// let project_id = inst.create_auctioning_project(default_project_metadata(ISSUER_1), ISSUER_1, None, evaluations); -// -// inst.execute(|| { -// let block_hash = System::block_hash(System::block_number()); -// let top_1 = TestRuntime::top_evaluations(&TestRuntime, block_hash, project_id, 1).unwrap(); -// let evaluator_4_evaluation = Evaluations::::get((project_id, EVALUATOR_4, 3)).unwrap(); -// assert!(top_1.len() == 1 && top_1[0] == evaluator_4_evaluation); -// -// let top_4_evaluators = TestRuntime::top_evaluations(&TestRuntime, block_hash, project_id, 4) -// .unwrap() -// .into_iter() -// .map(|evaluation| evaluation.evaluator) -// .collect_vec(); -// assert_eq!(top_4_evaluators, vec![EVALUATOR_4, EVALUATOR_1, EVALUATOR_3, EVALUATOR_2]); -// -// let top_6_evaluators = TestRuntime::top_evaluations(&TestRuntime, block_hash, project_id, 6) -// .unwrap() -// .into_iter() -// .map(|evaluation| evaluation.evaluator) -// .collect_vec(); -// assert_eq!(top_6_evaluators, vec![EVALUATOR_4, EVALUATOR_1, EVALUATOR_3, EVALUATOR_2, EVALUATOR_1]); -// }); -// } -// -// #[test] -// fn top_bids() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let bids = vec![ -// (BIDDER_1, 8000 * CT_UNIT).into(), -// (BIDDER_2, 501 * CT_UNIT).into(), -// (BIDDER_3, 1200 * CT_UNIT).into(), -// (BIDDER_4, 10400 * CT_UNIT).into(), -// (BIDDER_1, 500 * CT_UNIT).into(), -// ]; -// let project_id = inst.create_community_contributing_project( -// default_project_metadata(ISSUER_1), -// ISSUER_1, -// None, -// default_evaluations(), -// bids, -// ); -// -// inst.execute(|| { -// let block_hash = System::block_hash(System::block_number()); -// let top_1 = TestRuntime::top_bids(&TestRuntime, block_hash, project_id, 1).unwrap(); -// let bidder_4_evaluation = Bids::::get((project_id, BIDDER_4, 3)).unwrap(); -// assert!(top_1.len() == 1 && top_1[0] == bidder_4_evaluation); -// -// let top_4_bidders = TestRuntime::top_bids(&TestRuntime, block_hash, project_id, 4) -// .unwrap() -// .into_iter() -// .map(|evaluation| evaluation.bidder) -// .collect_vec(); -// assert_eq!(top_4_bidders, vec![BIDDER_4, BIDDER_1, BIDDER_3, BIDDER_2]); -// -// let top_6_bidders = TestRuntime::top_bids(&TestRuntime, block_hash, project_id, 6) -// .unwrap() -// .into_iter() -// .map(|evaluation| evaluation.bidder) -// .collect_vec(); -// assert_eq!(top_6_bidders, vec![BIDDER_4, BIDDER_1, BIDDER_3, BIDDER_2, BIDDER_1]); -// }); -// } -// -// #[test] -// fn top_contributions() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let community_contributors = -// vec![(BUYER_1, 8000 * CT_UNIT).into(), (BUYER_2, 501 * CT_UNIT).into(), (BUYER_3, 1200 * CT_UNIT).into()]; -// let remainder_contributors = vec![(BUYER_4, 10400 * CT_UNIT).into(), (BUYER_1, 500 * CT_UNIT).into()]; -// let project_id = inst.create_finished_project( -// default_project_metadata(ISSUER_1), -// ISSUER_1, -// None, -// default_evaluations(), -// default_bids(), -// community_contributors, -// remainder_contributors, -// ); -// -// inst.execute(|| { -// let block_hash = System::block_hash(System::block_number()); -// let top_1 = TestRuntime::top_contributions(&TestRuntime, block_hash, project_id, 1).unwrap(); -// let contributor_4_evaluation = Contributions::::get((project_id, BUYER_4, 3)).unwrap(); -// assert!(top_1.len() == 1 && top_1[0] == contributor_4_evaluation); -// -// let top_4_contributors = TestRuntime::top_contributions(&TestRuntime, block_hash, project_id, 4) -// .unwrap() -// .into_iter() -// .map(|evaluation| evaluation.contributor) -// .collect_vec(); -// assert_eq!(top_4_contributors, vec![BUYER_4, BUYER_1, BUYER_3, BUYER_2]); -// -// let top_6_contributors = TestRuntime::top_contributions(&TestRuntime, block_hash, project_id, 6) -// .unwrap() -// .into_iter() -// .map(|evaluation| evaluation.contributor) -// .collect_vec(); -// assert_eq!(top_6_contributors, vec![BUYER_4, BUYER_1, BUYER_3, BUYER_2, BUYER_1]); -// }); -// } -// -// #[test] -// fn top_projects_by_usd_raised() { -// let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// -// let (inst, project_id_1) = create_finished_project_with_usd_raised(inst, 400_000 * USD_UNIT, 1_000_000 * USD_UNIT); -// let (inst, project_id_2) = -// create_finished_project_with_usd_raised(inst, 1_200_000 * USD_UNIT, 1_000_000 * USD_UNIT); -// let (inst, project_id_3) = -// create_finished_project_with_usd_raised(inst, 3_000_000 * USD_UNIT, 1_000_000 * USD_UNIT); -// let (inst, project_id_4) = create_finished_project_with_usd_raised(inst, 840_000 * USD_UNIT, 1_000_000 * USD_UNIT); -// let (mut inst, project_id_5) = -// create_finished_project_with_usd_raised(inst, 980_000 * USD_UNIT, 1_000_000 * USD_UNIT); -// -// inst.execute(|| { -// let block_hash = System::block_hash(System::block_number()); -// let top_1 = TestRuntime::top_projects_by_usd_raised(&TestRuntime, block_hash, 1u32).unwrap(); -// let project_3_details = ProjectsDetails::::get(project_id_3).unwrap(); -// let project_3_metadata = ProjectsMetadata::::get(project_id_3).unwrap(); -// assert!(top_1.len() == 1 && top_1[0] == (project_id_3, project_3_metadata, project_3_details)); -// -// let top_4 = TestRuntime::top_projects_by_usd_raised(&TestRuntime, block_hash, 4u32) -// .unwrap() -// .into_iter() -// .map(|(project_id, project_metadata, project_details)| { -// let stored_metadata = ProjectsMetadata::::get(project_id).unwrap(); -// let stored_details = ProjectsDetails::::get(project_id).unwrap(); -// assert!(project_metadata == stored_metadata && project_details == stored_details); -// project_id -// }) -// .collect_vec(); -// -// assert_eq!(top_4, vec![project_id_3, project_id_2, project_id_5, project_id_4]); -// -// let top_6 = TestRuntime::top_projects_by_usd_raised(&TestRuntime, block_hash, 6u32) -// .unwrap() -// .into_iter() -// .map(|(project_id, project_metadata, project_details)| { -// let stored_metadata = ProjectsMetadata::::get(project_id).unwrap(); -// let stored_details = ProjectsDetails::::get(project_id).unwrap(); -// assert!(project_metadata == stored_metadata && project_details == stored_details); -// project_id -// }) -// .collect_vec(); -// -// assert_eq!(top_6, vec![project_id_3, project_id_2, project_id_5, project_id_4, project_id_1]); -// }); -// } -// -// #[test] -// fn top_projects_by_usd_target_percent_reached() { -// let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let (inst, project_id_1) = -// create_finished_project_with_usd_raised(inst, 2_000_000 * USD_UNIT, 1_000_000 * USD_UNIT); -// let (inst, project_id_2) = create_finished_project_with_usd_raised(inst, 945_000 * USD_UNIT, 1_000_000 * USD_UNIT); -// let (inst, project_id_3) = create_finished_project_with_usd_raised(inst, 500_000 * USD_UNIT, 100_000 * USD_UNIT); -// -// let (mut inst, project_id_4) = create_finished_project_with_usd_raised(inst, 50_000 * USD_UNIT, 100_000 * USD_UNIT); -// -// inst.execute(|| { -// let block_hash = System::block_hash(System::block_number()); -// let top_1 = TestRuntime::top_projects_by_usd_target_percent_reached(&TestRuntime, block_hash, 1u32).unwrap(); -// let project_3_details = ProjectsDetails::::get(project_id_3).unwrap(); -// let project_3_metadata = ProjectsMetadata::::get(project_id_3).unwrap(); -// assert!(top_1.len() == 1 && top_1[0] == (project_id_3, project_3_metadata, project_3_details)); -// -// let top_3 = TestRuntime::top_projects_by_usd_target_percent_reached(&TestRuntime, block_hash, 3u32) -// .unwrap() -// .into_iter() -// .map(|(project_id, project_metadata, project_details)| { -// let stored_metadata = ProjectsMetadata::::get(project_id).unwrap(); -// let stored_details = ProjectsDetails::::get(project_id).unwrap(); -// assert!(project_metadata == stored_metadata && project_details == stored_details); -// project_id -// }) -// .collect_vec(); -// -// assert_eq!(top_3, vec![project_id_3, project_id_1, project_id_2]); -// -// let top_6 = TestRuntime::top_projects_by_usd_target_percent_reached(&TestRuntime, block_hash, 6u32) -// .unwrap() -// .into_iter() -// .map(|(project_id, project_metadata, project_details)| { -// let stored_metadata = ProjectsMetadata::::get(project_id).unwrap(); -// let stored_details = ProjectsDetails::::get(project_id).unwrap(); -// assert!(project_metadata == stored_metadata && project_details == stored_details); -// project_id -// }) -// .collect_vec(); -// -// assert_eq!(top_6, vec![project_id_3, project_id_1, project_id_2, project_id_4]); -// }); -// } -// -// #[test] -// fn contribution_tokens() { -// let bob = 420; -// let mut contributions_with_bob_1 = default_community_buys(); -// 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 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 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 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; -// -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let project_id_1 = inst.create_settled_project( -// default_project_metadata(ISSUER_1), -// ISSUER_1, -// None, -// default_evaluations(), -// default_bids(), -// contributions_with_bob_1, -// default_remainder_buys(), -// ); -// let _project_id_2 = inst.create_settled_project( -// default_project_metadata(ISSUER_2), -// ISSUER_2, -// None, -// default_evaluations(), -// default_bids(), -// default_community_buys(), -// default_remainder_buys(), -// ); -// let _project_id_3 = inst.create_settled_project( -// default_project_metadata(ISSUER_3), -// ISSUER_3, -// None, -// default_evaluations(), -// default_bids(), -// default_community_buys(), -// default_remainder_buys(), -// ); -// let project_id_4 = inst.create_settled_project( -// default_project_metadata(ISSUER_4), -// ISSUER_4, -// None, -// default_evaluations(), -// default_bids(), -// contributions_with_bob_2, -// default_remainder_buys(), -// ); -// let _project_id_5 = inst.create_settled_project( -// default_project_metadata(ISSUER_5), -// ISSUER_5, -// None, -// default_evaluations(), -// default_bids(), -// default_community_buys(), -// default_remainder_buys(), -// ); -// let project_id_6 = inst.create_settled_project( -// default_project_metadata(ISSUER_6), -// ISSUER_6, -// None, -// default_evaluations(), -// default_bids(), -// contributions_with_bob_3, -// default_remainder_buys(), -// ); -// let project_id_7 = inst.create_settled_project( -// default_project_metadata(ISSUER_7), -// ISSUER_7, -// None, -// default_evaluations(), -// default_bids(), -// contributions_with_bob_4, -// default_remainder_buys(), -// ); -// -// let expected_items = vec![ -// (project_id_4, bob_amount_2), -// (project_id_1, bob_amount_1), -// (project_id_6, bob_amount_3), -// (project_id_7, bob_amount_4), -// ]; -// -// inst.execute(|| { -// let block_hash = System::block_hash(System::block_number()); -// let bob_items = TestRuntime::contribution_tokens(&TestRuntime, block_hash, bob.clone()).unwrap(); -// assert_eq!(bob_items, expected_items); -// }); -// } -// -// #[test] -// fn funding_asset_to_ct_amount() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// -// // 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(), -// PriceOf::::from_float(69.0f64) -// ); -// -// let dot_amount: u128 = 1350_0_000_000_000; -// // USD Ticket = 93_150 USD -// -// // Easy case, wap is already calculated, we want to know how many tokens at wap we can buy with `x` USDT -// let project_metadata_1 = default_project_metadata(ISSUER_1); -// let project_id_1 = inst.create_community_contributing_project( -// project_metadata_1.clone(), -// ISSUER_1, -// None, -// default_evaluations(), -// vec![], -// ); -// let wap = project_metadata_1.minimum_price; -// assert_eq!(inst.get_project_details(project_id_1).weighted_average_price.unwrap(), wap); -// -// // Price of ct is min price = 10 USD/CT -// let expected_ct_amount_contribution = 9_315 * CT_UNIT; -// inst.execute(|| { -// let block_hash = System::block_hash(System::block_number()); -// let ct_amount = TestRuntime::funding_asset_to_ct_amount( -// &TestRuntime, -// block_hash, -// project_id_1, -// AcceptedFundingAsset::DOT, -// dot_amount, -// ) -// .unwrap(); -// assert_eq!(ct_amount, expected_ct_amount_contribution); -// }); -// -// // Medium case, contribution at a wap that is not the minimum price. -// let project_metadata_2 = default_project_metadata(ISSUER_2); -// let new_price = PriceOf::::from_float(16.3f64); -// let decimal_aware_price = -// PriceProviderOf::::calculate_decimals_aware_price(new_price, USD_DECIMALS, CT_DECIMALS).unwrap(); -// -// let bids = -// inst.generate_bids_that_take_price_to(project_metadata_2.clone(), decimal_aware_price, 420u32, |acc| acc + 1); -// let project_id_2 = inst.create_community_contributing_project( -// project_metadata_2.clone(), -// ISSUER_2, -// None, -// default_evaluations(), -// bids, -// ); -// // Sanity check -// let project_details = inst.get_project_details(project_id_2); -// assert_eq!(project_details.weighted_average_price.unwrap(), decimal_aware_price); -// -// // 5'714.72... rounded down -// let expected_ct_amount_contribution = 5_714_720_000_000_000_000; -// inst.execute(|| { -// let block_hash = System::block_hash(System::block_number()); -// let ct_amount = TestRuntime::funding_asset_to_ct_amount( -// &TestRuntime, -// block_hash, -// project_id_2, -// AcceptedFundingAsset::DOT, -// dot_amount, -// ) -// .unwrap(); -// assert_close_enough!(ct_amount, expected_ct_amount_contribution, Perquintill::from_float(0.999f64)); -// }); -// -// // Medium case, a bid goes over part of a bucket (bucket after the first one) -// let project_metadata_3 = default_project_metadata(ISSUER_3); -// let project_id_3 = -// inst.create_auctioning_project(project_metadata_3.clone(), ISSUER_3, None, default_evaluations()); -// let mut bucket = inst.execute(|| Buckets::::get(project_id_3)).unwrap(); -// -// // We want a full bucket after filling 6 buckets. (first bucket has full allocation and initial price) -// // Price should be at 16 USD/CT -// bucket.current_price = bucket.initial_price + bucket.delta_price * FixedU128::from_float(6.0f64); -// bucket.amount_left = bucket.delta_amount; -// let bids = inst.generate_bids_from_bucket( -// project_metadata_3.clone(), -// bucket, -// 420u32, -// |acc| acc + 1, -// AcceptedFundingAsset::USDT, -// ); -// let necessary_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( -// &bids, -// project_metadata_3.clone(), -// None, -// true, -// ); -// let necessary_usdt = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( -// &bids, -// project_metadata_3.clone(), -// None, -// ); -// inst.mint_plmc_to(necessary_plmc); -// inst.mint_foreign_asset_to(necessary_usdt); -// inst.bid_for_users(project_id_3, bids).unwrap(); -// -// // Sanity check -// let expected_price = PriceOf::::from_float(16.0f64); -// let decimal_aware_expected_price = -// PriceProviderOf::::calculate_decimals_aware_price(expected_price, USD_DECIMALS, CT_DECIMALS) -// .unwrap(); -// let current_bucket = inst.execute(|| Buckets::::get(project_id_3).unwrap()); -// assert_eq!(current_bucket.current_price, decimal_aware_expected_price); -// -// dbg!(current_bucket.current_price.saturating_mul_int(current_bucket.amount_left)); -// -// let dot_amount: u128 = 217_0_000_000_000; -// let expected_ct_amount: u128 = 935_812_500_000_000_000; -// -// inst.execute(|| { -// let block_hash = System::block_hash(System::block_number()); -// let ct_amount = TestRuntime::funding_asset_to_ct_amount( -// &TestRuntime, -// block_hash, -// project_id_3, -// AcceptedFundingAsset::DOT, -// dot_amount, -// ) -// .unwrap(); -// assert_eq!(ct_amount, expected_ct_amount); -// }); -// -// // Hard case, a bid goes over multiple buckets -// // We take the same project from before, and we add a bid that goes over 3 buckets. -// // Bucket size is 50k CTs, and current price is 16 USD/CT -// // We need to buy 50k at 16 , 50k at 17, and 13.5k at 18 = 1893k USD -// -// // Amount needed to spend 1893k USD through several buckets with DOT at 69 USD/DOT -// let dot_amount = 27_434_7_826_086_956u128; -// let expected_ct_amount = 113_500 * CT_UNIT; -// -// inst.execute(|| { -// let block_hash = System::block_hash(System::block_number()); -// let ct_amount = TestRuntime::funding_asset_to_ct_amount( -// &TestRuntime, -// block_hash, -// project_id_3, -// AcceptedFundingAsset::DOT, -// dot_amount, -// ) -// .unwrap(); -// assert_close_enough!(ct_amount, expected_ct_amount, Perquintill::from_float(0.9999)); -// }); -// } -// -// #[test] -// fn all_project_participations_by_did() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// -// let did_user = generate_did_from_account(420); -// let project_metadata = default_project_metadata(ISSUER_1); -// let cid = project_metadata.clone().policy_ipfs_cid.unwrap(); -// let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); -// -// let evaluations = 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), -// ]; -// let bids = vec![ -// BidParams::new(BIDDER_1, 400_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// BidParams::new(BIDDER_2, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ]; -// let community_contributions = vec![ -// ContributionParams::new(BUYER_1, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ContributionParams::new(BUYER_2, 130_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ContributionParams::new(BUYER_3, 30_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ContributionParams::new(BUYER_4, 210_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ContributionParams::new(BUYER_5, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ]; -// let remainder_contributions = vec![ -// ContributionParams::new(EVALUATOR_2, 20_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ContributionParams::new(BUYER_2, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ContributionParams::new(BIDDER_1, 30_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), -// ]; -// -// let evaluations_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); -// let bids_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( -// &bids, -// project_metadata.clone(), -// None, -// true, -// ); -// let community_contributions_plmc = inst.calculate_contributed_plmc_spent( -// community_contributions.clone(), -// project_metadata.minimum_price.clone(), -// true, -// ); -// let remainder_contributions_plmc = inst.calculate_contributed_plmc_spent( -// remainder_contributions.clone(), -// project_metadata.minimum_price.clone(), -// true, -// ); -// let all_plmc = inst.generic_map_operation( -// vec![evaluations_plmc, bids_plmc, community_contributions_plmc, remainder_contributions_plmc], -// MergeOperation::Add, -// ); -// inst.mint_plmc_to(all_plmc); -// -// let bids_usdt = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( -// &bids, -// project_metadata.clone(), -// None, -// ); -// let community_contributions_usdt = inst.calculate_contributed_funding_asset_spent( -// community_contributions.clone(), -// project_metadata.minimum_price.clone(), -// ); -// let remainder_contributions_usdt = inst.calculate_contributed_funding_asset_spent( -// remainder_contributions.clone(), -// project_metadata.minimum_price.clone(), -// ); -// let all_usdt = inst.generic_map_operation( -// vec![bids_usdt, community_contributions_usdt, remainder_contributions_usdt], -// MergeOperation::Add, -// ); -// inst.mint_foreign_asset_to(all_usdt); -// -// inst.evaluate_for_users(project_id, evaluations[..1].to_vec()).unwrap(); -// for evaluation in evaluations[1..].to_vec() { -// let jwt = get_mock_jwt_with_cid(evaluation.account, InvestorType::Retail, did_user.clone(), cid.clone()); -// inst.execute(|| { -// PolimecFunding::evaluate(RuntimeOrigin::signed(evaluation.account), jwt, project_id, evaluation.usd_amount) -// .unwrap(); -// }); -// } -// -// inst.start_auction(project_id, ISSUER_1).unwrap(); -// -// inst.bid_for_users(project_id, bids[..1].to_vec()).unwrap(); -// for bid in bids[1..].to_vec() { -// let jwt = get_mock_jwt_with_cid(bid.bidder, InvestorType::Institutional, did_user.clone(), cid.clone()); -// inst.execute(|| { -// PolimecFunding::bid( -// RuntimeOrigin::signed(bid.bidder), -// jwt, -// project_id, -// bid.amount, -// bid.multiplier, -// bid.asset, -// ) -// .unwrap(); -// }); -// } -// -// inst.start_community_funding(project_id).unwrap(); -// -// inst.contribute_for_users(project_id, community_contributions).unwrap(); -// -// inst.start_remainder_or_end_funding(project_id).unwrap(); -// -// for contribution in remainder_contributions { -// let jwt = -// get_mock_jwt_with_cid(contribution.contributor, InvestorType::Professional, did_user.clone(), cid.clone()); -// inst.execute(|| { -// PolimecFunding::contribute( -// RuntimeOrigin::signed(contribution.contributor), -// jwt, -// project_id, -// contribution.amount, -// contribution.multiplier, -// contribution.asset, -// ) -// .unwrap(); -// }); -// } -// -// inst.finish_funding(project_id, None).unwrap(); -// -// inst.execute(|| { -// let block_hash = System::block_hash(System::block_number()); -// let items = -// TestRuntime::all_project_participations_by_did(&TestRuntime, block_hash, project_id, did_user).unwrap(); -// dbg!(items); -// }); -// } -// -// #[test] -// fn usd_target_percent_reached() { -// let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let (inst, project_id_1) = -// create_finished_project_with_usd_raised(inst, 2_000_000 * USD_UNIT, 1_000_000 * USD_UNIT); -// let (inst, project_id_2) = create_finished_project_with_usd_raised(inst, 945_000 * USD_UNIT, 1_000_000 * USD_UNIT); -// let (inst, project_id_3) = create_finished_project_with_usd_raised(inst, 517_000 * USD_UNIT, 100_000 * USD_UNIT); -// -// let (mut inst, project_id_4) = create_finished_project_with_usd_raised(inst, 50_000 * USD_UNIT, 100_000 * USD_UNIT); -// -// inst.execute(|| { -// let block_hash = System::block_hash(System::block_number()); -// let percent_200: FixedU128 = -// TestRuntime::usd_target_percent_reached(&TestRuntime, block_hash, project_id_1).unwrap(); -// assert_close_enough!( -// percent_200.into_inner(), -// FixedU128::from_float(2.0f64).into_inner(), -// Perquintill::from_float(0.999) -// ); -// -// let percent_94_5: FixedU128 = -// TestRuntime::usd_target_percent_reached(&TestRuntime, block_hash, project_id_2).unwrap(); -// assert_close_enough!( -// percent_94_5.into_inner(), -// FixedU128::from_float(0.945f64).into_inner(), -// Perquintill::from_float(0.999) -// ); -// -// let percent_517: FixedU128 = -// TestRuntime::usd_target_percent_reached(&TestRuntime, block_hash, project_id_3).unwrap(); -// assert_close_enough!( -// percent_517.into_inner(), -// FixedU128::from_float(5.17f64).into_inner(), -// Perquintill::from_float(0.999) -// ); -// -// let percent_50: FixedU128 = -// TestRuntime::usd_target_percent_reached(&TestRuntime, block_hash, project_id_4).unwrap(); -// assert_close_enough!( -// percent_50.into_inner(), -// FixedU128::from_float(0.5f64).into_inner(), -// Perquintill::from_float(0.999) -// ); -// }); -// } -// -// #[test] -// fn projects_by_did() { -// let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); -// let did_user = generate_did_from_account(420); -// -// let project_id_1 = inst.create_settled_project( -// default_project_metadata(ISSUER_1), -// ISSUER_1, -// Some(did_user.clone()), -// default_evaluations(), -// default_bids(), -// default_community_buys(), -// default_remainder_buys(), -// ); -// -// let _project_id_2 = inst.create_settled_project( -// default_project_metadata(ISSUER_1), -// ISSUER_1, -// None, -// default_evaluations(), -// default_bids(), -// default_community_buys(), -// default_remainder_buys(), -// ); -// -// let project_id_3 = inst.create_settled_project( -// default_project_metadata(ISSUER_2), -// ISSUER_2, -// Some(did_user.clone()), -// default_evaluations(), -// default_bids(), -// default_community_buys(), -// default_remainder_buys(), -// ); -// -// let _project_id_4 = inst.create_settled_project( -// default_project_metadata(ISSUER_3), -// ISSUER_3, -// None, -// default_evaluations(), -// default_bids(), -// default_community_buys(), -// default_remainder_buys(), -// ); -// -// inst.execute(|| { -// 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]); -// }); -// } +use super::*; +use crate::runtime_api::{ExtrinsicHelpers, Leaderboards, ProjectInformation, UserInformation}; + +#[test] +fn top_evaluations() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let evaluations = 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), + UserToUSDBalance::new(EVALUATOR_4, 1_000_000 * USD_UNIT), + UserToUSDBalance::new(EVALUATOR_1, 1_000 * USD_UNIT), + ]; + let project_id = inst.create_auctioning_project(default_project_metadata(ISSUER_1), ISSUER_1, None, evaluations); + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let top_1 = TestRuntime::top_evaluations(&TestRuntime, block_hash, project_id, 1).unwrap(); + let evaluator_4_evaluation = Evaluations::::get((project_id, EVALUATOR_4, 3)).unwrap(); + assert!(top_1.len() == 1 && top_1[0] == evaluator_4_evaluation); + + let top_4_evaluators = TestRuntime::top_evaluations(&TestRuntime, block_hash, project_id, 4) + .unwrap() + .into_iter() + .map(|evaluation| evaluation.evaluator) + .collect_vec(); + assert_eq!(top_4_evaluators, vec![EVALUATOR_4, EVALUATOR_1, EVALUATOR_3, EVALUATOR_2]); + + let top_6_evaluators = TestRuntime::top_evaluations(&TestRuntime, block_hash, project_id, 6) + .unwrap() + .into_iter() + .map(|evaluation| evaluation.evaluator) + .collect_vec(); + assert_eq!(top_6_evaluators, vec![EVALUATOR_4, EVALUATOR_1, EVALUATOR_3, EVALUATOR_2, EVALUATOR_1]); + }); +} + +#[test] +fn top_bids() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let bids = vec![ + (BIDDER_1, 8000 * CT_UNIT).into(), + (BIDDER_2, 501 * CT_UNIT).into(), + (BIDDER_3, 1200 * CT_UNIT).into(), + (BIDDER_4, 10400 * CT_UNIT).into(), + (BIDDER_1, 500 * CT_UNIT).into(), + ]; + let project_id = inst.create_community_contributing_project( + default_project_metadata(ISSUER_1), + ISSUER_1, + None, + default_evaluations(), + bids, + ); + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let top_1 = TestRuntime::top_bids(&TestRuntime, block_hash, project_id, 1).unwrap(); + let bidder_4_evaluation = Bids::::get((project_id, BIDDER_4, 3)).unwrap(); + assert!(top_1.len() == 1 && top_1[0] == bidder_4_evaluation); + + let top_4_bidders = TestRuntime::top_bids(&TestRuntime, block_hash, project_id, 4) + .unwrap() + .into_iter() + .map(|evaluation| evaluation.bidder) + .collect_vec(); + assert_eq!(top_4_bidders, vec![BIDDER_4, BIDDER_1, BIDDER_3, BIDDER_2]); + + let top_6_bidders = TestRuntime::top_bids(&TestRuntime, block_hash, project_id, 6) + .unwrap() + .into_iter() + .map(|evaluation| evaluation.bidder) + .collect_vec(); + assert_eq!(top_6_bidders, vec![BIDDER_4, BIDDER_1, BIDDER_3, BIDDER_2, BIDDER_1]); + }); +} + +#[test] +fn top_contributions() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let community_contributors = + vec![(BUYER_1, 8000 * CT_UNIT).into(), (BUYER_2, 501 * CT_UNIT).into(), (BUYER_3, 1200 * CT_UNIT).into()]; + let remainder_contributors = vec![(BUYER_4, 10400 * CT_UNIT).into(), (BUYER_1, 500 * CT_UNIT).into()]; + let project_id = inst.create_finished_project( + default_project_metadata(ISSUER_1), + ISSUER_1, + None, + default_evaluations(), + default_bids(), + community_contributors, + remainder_contributors, + ); + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let top_1 = TestRuntime::top_contributions(&TestRuntime, block_hash, project_id, 1).unwrap(); + let contributor_4_evaluation = Contributions::::get((project_id, BUYER_4, 3)).unwrap(); + assert!(top_1.len() == 1 && top_1[0] == contributor_4_evaluation); + + let top_4_contributors = TestRuntime::top_contributions(&TestRuntime, block_hash, project_id, 4) + .unwrap() + .into_iter() + .map(|evaluation| evaluation.contributor) + .collect_vec(); + assert_eq!(top_4_contributors, vec![BUYER_4, BUYER_1, BUYER_3, BUYER_2]); + + let top_6_contributors = TestRuntime::top_contributions(&TestRuntime, block_hash, project_id, 6) + .unwrap() + .into_iter() + .map(|evaluation| evaluation.contributor) + .collect_vec(); + assert_eq!(top_6_contributors, vec![BUYER_4, BUYER_1, BUYER_3, BUYER_2, BUYER_1]); + }); +} + +#[test] +fn top_projects_by_usd_raised() { + let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + + let (inst, project_id_1) = create_finished_project_with_usd_raised(inst, 400_000 * USD_UNIT, 1_000_000 * USD_UNIT); + let (inst, project_id_2) = + create_finished_project_with_usd_raised(inst, 1_200_000 * USD_UNIT, 1_000_000 * USD_UNIT); + let (inst, project_id_3) = + create_finished_project_with_usd_raised(inst, 3_000_000 * USD_UNIT, 1_000_000 * USD_UNIT); + let (inst, project_id_4) = create_finished_project_with_usd_raised(inst, 840_000 * USD_UNIT, 1_000_000 * USD_UNIT); + let (mut inst, project_id_5) = + create_finished_project_with_usd_raised(inst, 980_000 * USD_UNIT, 1_000_000 * USD_UNIT); + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let top_1 = TestRuntime::top_projects_by_usd_raised(&TestRuntime, block_hash, 1u32).unwrap(); + let project_3_details = ProjectsDetails::::get(project_id_3).unwrap(); + let project_3_metadata = ProjectsMetadata::::get(project_id_3).unwrap(); + assert!(top_1.len() == 1 && top_1[0] == (project_id_3, project_3_metadata, project_3_details)); + + let top_4 = TestRuntime::top_projects_by_usd_raised(&TestRuntime, block_hash, 4u32) + .unwrap() + .into_iter() + .map(|(project_id, project_metadata, project_details)| { + let stored_metadata = ProjectsMetadata::::get(project_id).unwrap(); + let stored_details = ProjectsDetails::::get(project_id).unwrap(); + assert!(project_metadata == stored_metadata && project_details == stored_details); + project_id + }) + .collect_vec(); + + assert_eq!(top_4, vec![project_id_3, project_id_2, project_id_5, project_id_4]); + + let top_6 = TestRuntime::top_projects_by_usd_raised(&TestRuntime, block_hash, 6u32) + .unwrap() + .into_iter() + .map(|(project_id, project_metadata, project_details)| { + let stored_metadata = ProjectsMetadata::::get(project_id).unwrap(); + let stored_details = ProjectsDetails::::get(project_id).unwrap(); + assert!(project_metadata == stored_metadata && project_details == stored_details); + project_id + }) + .collect_vec(); + + assert_eq!(top_6, vec![project_id_3, project_id_2, project_id_5, project_id_4, project_id_1]); + }); +} + +#[test] +fn top_projects_by_usd_target_percent_reached() { + let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let (inst, project_id_1) = + create_finished_project_with_usd_raised(inst, 2_000_000 * USD_UNIT, 1_000_000 * USD_UNIT); + let (inst, project_id_2) = create_finished_project_with_usd_raised(inst, 945_000 * USD_UNIT, 1_000_000 * USD_UNIT); + let (inst, project_id_3) = create_finished_project_with_usd_raised(inst, 500_000 * USD_UNIT, 100_000 * USD_UNIT); + + let (mut inst, project_id_4) = create_finished_project_with_usd_raised(inst, 50_000 * USD_UNIT, 100_000 * USD_UNIT); + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let top_1 = TestRuntime::top_projects_by_usd_target_percent_reached(&TestRuntime, block_hash, 1u32).unwrap(); + let project_3_details = ProjectsDetails::::get(project_id_3).unwrap(); + let project_3_metadata = ProjectsMetadata::::get(project_id_3).unwrap(); + assert!(top_1.len() == 1 && top_1[0] == (project_id_3, project_3_metadata, project_3_details)); + + let top_3 = TestRuntime::top_projects_by_usd_target_percent_reached(&TestRuntime, block_hash, 3u32) + .unwrap() + .into_iter() + .map(|(project_id, project_metadata, project_details)| { + let stored_metadata = ProjectsMetadata::::get(project_id).unwrap(); + let stored_details = ProjectsDetails::::get(project_id).unwrap(); + assert!(project_metadata == stored_metadata && project_details == stored_details); + project_id + }) + .collect_vec(); + + assert_eq!(top_3, vec![project_id_3, project_id_1, project_id_2]); + + let top_6 = TestRuntime::top_projects_by_usd_target_percent_reached(&TestRuntime, block_hash, 6u32) + .unwrap() + .into_iter() + .map(|(project_id, project_metadata, project_details)| { + let stored_metadata = ProjectsMetadata::::get(project_id).unwrap(); + let stored_details = ProjectsDetails::::get(project_id).unwrap(); + assert!(project_metadata == stored_metadata && project_details == stored_details); + project_id + }) + .collect_vec(); + + assert_eq!(top_6, vec![project_id_3, project_id_1, project_id_2, project_id_4]); + }); +} + +#[test] +fn contribution_tokens() { + let bob = 420; + 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_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_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_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; + + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_id_1 = inst.create_settled_project( + default_project_metadata(ISSUER_1), + ISSUER_1, + None, + default_evaluations(), + default_bids(), + contributions_with_bob_1, + default_remainder_contributions(), + ); + let _project_id_2 = inst.create_settled_project( + default_project_metadata(ISSUER_2), + ISSUER_2, + None, + default_evaluations(), + default_bids(), + default_community_contributions(), + default_remainder_contributions(), + ); + let _project_id_3 = inst.create_settled_project( + default_project_metadata(ISSUER_3), + ISSUER_3, + None, + default_evaluations(), + default_bids(), + default_community_contributions(), + default_remainder_contributions(), + ); + let project_id_4 = inst.create_settled_project( + default_project_metadata(ISSUER_4), + ISSUER_4, + None, + default_evaluations(), + default_bids(), + contributions_with_bob_2, + default_remainder_contributions(), + ); + let _project_id_5 = inst.create_settled_project( + default_project_metadata(ISSUER_5), + ISSUER_5, + None, + default_evaluations(), + default_bids(), + default_community_contributions(), + default_remainder_contributions(), + ); + let project_id_6 = inst.create_settled_project( + default_project_metadata(ISSUER_6), + ISSUER_6, + None, + default_evaluations(), + default_bids(), + contributions_with_bob_3, + default_remainder_contributions(), + ); + let project_id_7 = inst.create_settled_project( + default_project_metadata(ISSUER_7), + ISSUER_7, + None, + default_evaluations(), + default_bids(), + contributions_with_bob_4, + default_remainder_contributions(), + ); + + let expected_items = vec![ + (project_id_4, bob_amount_2), + (project_id_1, bob_amount_1), + (project_id_6, bob_amount_3), + (project_id_7, bob_amount_4), + ]; + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let bob_items = TestRuntime::contribution_tokens(&TestRuntime, block_hash, bob.clone()).unwrap(); + assert_eq!(bob_items, expected_items); + }); +} + +#[test] +fn funding_asset_to_ct_amount() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + + // We want to use a funding asset that is not equal to 1 USD + // Sanity check + assert_eq!( + PriceProviderOf::::get_price(AcceptedFundingAsset::DOT.id()).unwrap(), + PriceOf::::from_float(69.0f64) + ); + + let dot_amount: u128 = 1350_0_000_000_000; + // USD Ticket = 93_150 USD + + // Easy case, wap is already calculated, we want to know how many tokens at wap we can buy with `x` USDT + let project_metadata_1 = default_project_metadata(ISSUER_1); + let project_id_1 = inst.create_community_contributing_project( + project_metadata_1.clone(), + ISSUER_1, + None, + default_evaluations(), + vec![], + ); + let wap = project_metadata_1.minimum_price; + assert_eq!(inst.get_project_details(project_id_1).weighted_average_price.unwrap(), wap); + + // Price of ct is min price = 10 USD/CT + let expected_ct_amount_contribution = 9_315 * CT_UNIT; + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let ct_amount = TestRuntime::funding_asset_to_ct_amount( + &TestRuntime, + block_hash, + project_id_1, + AcceptedFundingAsset::DOT, + dot_amount, + ) + .unwrap(); + assert_eq!(ct_amount, expected_ct_amount_contribution); + }); + + // Medium case, contribution at a wap that is not the minimum price. + let project_metadata_2 = default_project_metadata(ISSUER_2); + let new_price = PriceOf::::from_float(16.3f64); + let decimal_aware_price = + PriceProviderOf::::calculate_decimals_aware_price(new_price, USD_DECIMALS, CT_DECIMALS).unwrap(); + + let bids = + inst.generate_bids_that_take_price_to(project_metadata_2.clone(), decimal_aware_price, 420u32, |acc| acc + 1); + let project_id_2 = inst.create_community_contributing_project( + project_metadata_2.clone(), + ISSUER_2, + None, + default_evaluations(), + bids, + ); + // Sanity check + let project_details = inst.get_project_details(project_id_2); + assert_eq!(project_details.weighted_average_price.unwrap(), decimal_aware_price); + + // 5'714.72... rounded down + let expected_ct_amount_contribution = 5_714_720_000_000_000_000; + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let ct_amount = TestRuntime::funding_asset_to_ct_amount( + &TestRuntime, + block_hash, + project_id_2, + AcceptedFundingAsset::DOT, + dot_amount, + ) + .unwrap(); + assert_close_enough!(ct_amount, expected_ct_amount_contribution, Perquintill::from_float(0.999f64)); + }); + + // Medium case, a bid goes over part of a bucket (bucket after the first one) + let project_metadata_3 = default_project_metadata(ISSUER_3); + let project_id_3 = + inst.create_auctioning_project(project_metadata_3.clone(), ISSUER_3, None, default_evaluations()); + let mut bucket = inst.execute(|| Buckets::::get(project_id_3)).unwrap(); + + // We want a full bucket after filling 6 buckets. (first bucket has full allocation and initial price) + // Price should be at 16 USD/CT + bucket.current_price = bucket.initial_price + bucket.delta_price * FixedU128::from_float(6.0f64); + bucket.amount_left = bucket.delta_amount; + let bids = inst.generate_bids_from_bucket( + project_metadata_3.clone(), + bucket, + 420u32, + |acc| acc + 1, + AcceptedFundingAsset::USDT, + ); + let necessary_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + &bids, + project_metadata_3.clone(), + None, + true, + ); + let necessary_usdt = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( + &bids, + project_metadata_3.clone(), + None, + ); + inst.mint_plmc_to(necessary_plmc); + inst.mint_funding_asset_to(necessary_usdt); + inst.bid_for_users(project_id_3, bids).unwrap(); + + // Sanity check + let expected_price = PriceOf::::from_float(16.0f64); + let decimal_aware_expected_price = + PriceProviderOf::::calculate_decimals_aware_price(expected_price, USD_DECIMALS, CT_DECIMALS) + .unwrap(); + let current_bucket = inst.execute(|| Buckets::::get(project_id_3).unwrap()); + assert_eq!(current_bucket.current_price, decimal_aware_expected_price); + + dbg!(current_bucket.current_price.saturating_mul_int(current_bucket.amount_left)); + + let dot_amount: u128 = 217_0_000_000_000; + let expected_ct_amount: u128 = 935_812_500_000_000_000; + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let ct_amount = TestRuntime::funding_asset_to_ct_amount( + &TestRuntime, + block_hash, + project_id_3, + AcceptedFundingAsset::DOT, + dot_amount, + ) + .unwrap(); + assert_eq!(ct_amount, expected_ct_amount); + }); + + // Hard case, a bid goes over multiple buckets + // We take the same project from before, and we add a bid that goes over 3 buckets. + // Bucket size is 50k CTs, and current price is 16 USD/CT + // We need to buy 50k at 16 , 50k at 17, and 13.5k at 18 = 1893k USD + + // Amount needed to spend 1893k USD through several buckets with DOT at 69 USD/DOT + let dot_amount = 27_434_7_826_086_956u128; + let expected_ct_amount = 113_500 * CT_UNIT; + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let ct_amount = TestRuntime::funding_asset_to_ct_amount( + &TestRuntime, + block_hash, + project_id_3, + AcceptedFundingAsset::DOT, + dot_amount, + ) + .unwrap(); + assert_close_enough!(ct_amount, expected_ct_amount, Perquintill::from_float(0.9999)); + }); +} + +#[test] +fn all_project_participations_by_did() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + + let did_user = generate_did_from_account(420); + let project_metadata = default_project_metadata(ISSUER_1); + let cid = project_metadata.clone().policy_ipfs_cid.unwrap(); + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); + + let evaluations = 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), + ]; + let bids = vec![ + BidParams::new(BIDDER_1, 400_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_2, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ]; + let community_contributions = vec![ + ContributionParams::new(BUYER_1, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_2, 130_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_3, 30_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_4, 210_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_5, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ]; + let remainder_contributions = vec![ + ContributionParams::new(EVALUATOR_2, 20_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_2, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BIDDER_1, 30_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ]; + + let evaluations_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); + let bids_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + &bids, + project_metadata.clone(), + None, + true, + ); + let community_contributions_plmc = inst.calculate_contributed_plmc_spent( + community_contributions.clone(), + project_metadata.minimum_price.clone(), + true, + ); + let remainder_contributions_plmc = inst.calculate_contributed_plmc_spent( + remainder_contributions.clone(), + project_metadata.minimum_price.clone(), + true, + ); + let all_plmc = inst.generic_map_operation( + vec![evaluations_plmc, bids_plmc, community_contributions_plmc, remainder_contributions_plmc], + MergeOperation::Add, + ); + inst.mint_plmc_to(all_plmc); + + let bids_usdt = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( + &bids, + project_metadata.clone(), + None, + ); + let community_contributions_usdt = inst.calculate_contributed_funding_asset_spent( + community_contributions.clone(), + project_metadata.minimum_price.clone(), + ); + let remainder_contributions_usdt = inst.calculate_contributed_funding_asset_spent( + remainder_contributions.clone(), + project_metadata.minimum_price.clone(), + ); + let all_usdt = inst.generic_map_operation( + vec![bids_usdt, community_contributions_usdt, remainder_contributions_usdt], + MergeOperation::Add, + ); + 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() { + let jwt = get_mock_jwt_with_cid(evaluation.account, InvestorType::Retail, did_user.clone(), cid.clone()); + inst.execute(|| { + PolimecFunding::evaluate(RuntimeOrigin::signed(evaluation.account), jwt, project_id, evaluation.usd_amount) + .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() { + let jwt = get_mock_jwt_with_cid(bid.bidder, InvestorType::Institutional, did_user.clone(), cid.clone()); + inst.execute(|| { + PolimecFunding::bid( + RuntimeOrigin::signed(bid.bidder), + jwt, + project_id, + bid.amount, + bid.multiplier, + bid.asset, + ) + .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.jump_to_block(remainder_start); + + for contribution in remainder_contributions { + let jwt = + get_mock_jwt_with_cid(contribution.contributor, InvestorType::Professional, did_user.clone(), cid.clone()); + inst.execute(|| { + PolimecFunding::contribute( + RuntimeOrigin::signed(contribution.contributor), + jwt, + project_id, + contribution.amount, + contribution.multiplier, + contribution.asset, + ) + .unwrap(); + }); + } + + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let items = + TestRuntime::all_project_participations_by_did(&TestRuntime, block_hash, project_id, did_user).unwrap(); + dbg!(items); + }); +} + +#[test] +fn usd_target_percent_reached() { + let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let (inst, project_id_1) = + create_finished_project_with_usd_raised(inst, 2_000_000 * USD_UNIT, 1_000_000 * USD_UNIT); + let (inst, project_id_2) = create_finished_project_with_usd_raised(inst, 945_000 * USD_UNIT, 1_000_000 * USD_UNIT); + let (inst, project_id_3) = create_finished_project_with_usd_raised(inst, 517_000 * USD_UNIT, 100_000 * USD_UNIT); + + let (mut inst, project_id_4) = create_finished_project_with_usd_raised(inst, 50_000 * USD_UNIT, 100_000 * USD_UNIT); + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let percent_200: FixedU128 = + TestRuntime::usd_target_percent_reached(&TestRuntime, block_hash, project_id_1).unwrap(); + assert_close_enough!( + percent_200.into_inner(), + FixedU128::from_float(2.0f64).into_inner(), + Perquintill::from_float(0.999) + ); + + let percent_94_5: FixedU128 = + TestRuntime::usd_target_percent_reached(&TestRuntime, block_hash, project_id_2).unwrap(); + assert_close_enough!( + percent_94_5.into_inner(), + FixedU128::from_float(0.945f64).into_inner(), + Perquintill::from_float(0.999) + ); + + let percent_517: FixedU128 = + TestRuntime::usd_target_percent_reached(&TestRuntime, block_hash, project_id_3).unwrap(); + assert_close_enough!( + percent_517.into_inner(), + FixedU128::from_float(5.17f64).into_inner(), + Perquintill::from_float(0.999) + ); + + let percent_50: FixedU128 = + TestRuntime::usd_target_percent_reached(&TestRuntime, block_hash, project_id_4).unwrap(); + assert_close_enough!( + percent_50.into_inner(), + FixedU128::from_float(0.5f64).into_inner(), + Perquintill::from_float(0.999) + ); + }); +} + +#[test] +fn projects_by_did() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let did_user = generate_did_from_account(420); + dbg!(&did_user); + + let project_id_1 = inst.create_settled_project( + default_project_metadata(ISSUER_1), + ISSUER_1, + Some(did_user.clone()), + default_evaluations(), + default_bids(), + default_community_contributions(), + default_remainder_contributions(), + ); + + let _project_id_2 = inst.create_settled_project( + default_project_metadata(ISSUER_1), + ISSUER_1, + None, + default_evaluations(), + default_bids(), + default_community_contributions(), + default_remainder_contributions(), + ); + + let project_id_3 = inst.create_settled_project( + default_project_metadata(ISSUER_2), + ISSUER_2, + Some(did_user.clone()), + default_evaluations(), + default_bids(), + default_community_contributions(), + default_remainder_contributions(), + ); + + let _project_id_4 = inst.create_settled_project( + default_project_metadata(ISSUER_3), + ISSUER_3, + None, + default_evaluations(), + default_bids(), + 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 bc5fe5e8d..63949e906 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -671,7 +671,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![