diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs index 1991a7396..a6619a3af 100644 --- a/pallets/funding/src/benchmarking.rs +++ b/pallets/funding/src/benchmarking.rs @@ -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>, @@ -877,7 +867,7 @@ mod benchmarks { 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(), @@ -1146,7 +1136,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(), @@ -1432,8 +1422,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()); } @@ -2088,7 +2077,7 @@ mod benchmarks { 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(); @@ -2714,7 +2703,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 +2753,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 +2824,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 +2870,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 +2929,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 +2992,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 +3073,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 +3195,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 +3290,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 +3366,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 +3416,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 f203a6573..664a2e816 100644 --- a/pallets/funding/src/functions/3_auction.rs +++ b/pallets/funding/src/functions/3_auction.rs @@ -34,7 +34,7 @@ impl Pallet { project_id, project_details, ProjectStatus::AuctionInitializePeriod, - ProjectStatus::Auction, + ProjectStatus::AuctionRound, T::AuctionOpeningDuration::get(), skip_round_end_check, ) @@ -60,6 +60,8 @@ impl Pallet { project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size, weighted_token_price, ); + let updated_project_details = + ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; match calculation_result { Err(e) => return Err(DispatchErrorWithPostInfo { post_info: ().into(), error: e }), @@ -68,8 +70,8 @@ impl Pallet { // * Transition Round * Self::transition_project( project_id, - project_details, - ProjectStatus::Auction, + updated_project_details, + ProjectStatus::AuctionRound, ProjectStatus::CommunityRound(now.saturating_add(T::CommunityFundingDuration::get())), T::CommunityFundingDuration::get() + T::RemainderFundingDuration::get(), false, @@ -151,7 +153,7 @@ impl Pallet { ensure!(ct_amount > Zero::zero(), Error::::TooLow); ensure!(did != project_details.issuer_did, Error::::ParticipationToOwnProject); - ensure!(matches!(project_details.status, ProjectStatus::Auction), Error::::IncorrectRound); + ensure!(matches!(project_details.status, ProjectStatus::AuctionRound), Error::::IncorrectRound); ensure!( project_metadata.participation_currencies.contains(&funding_asset), Error::::FundingAssetNotAccepted diff --git a/pallets/funding/src/functions/5_funding_end.rs b/pallets/funding/src/functions/5_funding_end.rs index 7e445d9eb..9c5becbce 100644 --- a/pallets/funding/src/functions/5_funding_end.rs +++ b/pallets/funding/src/functions/5_funding_end.rs @@ -83,6 +83,7 @@ impl Pallet { let round_end = now.saturating_add(duration).saturating_sub(One::one()); project_details.round_duration.update(Some(now), Some(round_end)); project_details.status = next_status; + ProjectsDetails::::insert(project_id, project_details); Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes }) } diff --git a/pallets/funding/src/functions/misc.rs b/pallets/funding/src/functions/misc.rs index 9d393bc37..45aa1b7bf 100644 --- a/pallets/funding/src/functions/misc.rs +++ b/pallets/funding/src/functions/misc.rs @@ -126,6 +126,8 @@ impl Pallet { if let Some(info) = maybe_info { info.remaining_contribution_tokens.saturating_reduce(bid_token_amount_sum); info.funding_amount_reached_usd.saturating_accrue(total_auction_allocation_usd); + info.weighted_average_price = Some(wap); + Ok(()) } else { Err(Error::::ProjectDetailsNotFound.into()) diff --git a/pallets/funding/src/instantiator/calculations.rs b/pallets/funding/src/instantiator/calculations.rs index ea5bac920..3aff6234f 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< @@ -242,7 +243,6 @@ impl< 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 @@ -272,10 +272,9 @@ 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( @@ -293,10 +292,8 @@ 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; @@ -508,14 +505,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, + mut 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 } @@ -705,7 +710,6 @@ 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); if wap < target_wap { @@ -740,7 +744,6 @@ impl< new_wap = self.dry_run_wap(bucket.clone(), auction_allocation); } - dbg!(&bucket); bucket } diff --git a/pallets/funding/src/instantiator/chain_interactions.rs b/pallets/funding/src/instantiator/chain_interactions.rs index 4baaa6b2c..0a3e96a52 100644 --- a/pallets/funding/src/instantiator/chain_interactions.rs +++ b/pallets/funding/src/instantiator/chain_interactions.rs @@ -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"); }); } } @@ -191,7 +191,7 @@ impl< self.execute(|| frame_system::Pallet::::set_block_number(block - One::one())); self.advance_time(One::one()).unwrap(); } else { - panic!("Cannot jump to a block in the present or past") + // panic!("Cannot jump to a block in the present or past") } } @@ -205,46 +205,10 @@ impl< } pub fn do_free_foreign_asset_assertions(&mut self, correct_funds: Vec>) { - for UserToForeignAssets { account, asset_amount, asset_id } in correct_funds { + 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 foreign asset balance expected for user {:?}", account); }); } } @@ -384,9 +348,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( @@ -412,22 +407,15 @@ 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, issuer: AccountIdOf, maybe_did: Option, ) -> ProjectId { - let project_id = self.create_new_project(project_metadata, issuer.clone(), maybe_did); - self.start_evaluation(project_id, issuer).unwrap(); + let project_id = self.create_new_project(project_metadata, issuer.clone(), None); + assert_eq!(self.go_to_next_state(project_id), ProjectStatus::EvaluationRound); + project_id } @@ -452,24 +440,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::Auction); - - Ok(()) - } - pub fn create_auctioning_project( &mut self, project_metadata: ProjectMetadataOf, @@ -502,7 +472,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 } @@ -527,25 +499,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, @@ -557,7 +510,7 @@ 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 } @@ -612,15 +565,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_foreign_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)); 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 } @@ -656,46 +609,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) -> Result<(), DispatchError> { let details = self.get_project_details(project_id); match details.status { @@ -941,61 +854,61 @@ impl< bids.clone(), ); - if contributions.is_empty() { - self.start_remainder_or_end_funding(project_id).unwrap(); - return project_id; - } + if !contributions.is_empty() { + let ct_price = self.get_project_details(project_id).weighted_average_price.unwrap(); - 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.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 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_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 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 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 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 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_foreign_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_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.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.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 } @@ -1019,69 +932,68 @@ 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.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_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 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 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 contributor_balances = - self.sum_balance_mappings(vec![necessary_plmc_mint.clone(), plmc_existential_deposits.clone()]); + 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 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_foreign_asset_to(funding_asset_deposits.clone()); - self.contribute_for_users(project_id, remainder_contributions.clone()) - .expect("Remainder Contributing should work"); + self.contribute_for_users(project_id, remainder_contributions.clone()) + .expect("Remainder Contributing should work"); - 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.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.finish_funding(project_id, None).unwrap(); - 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 @@ -1103,6 +1015,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 @@ -1128,10 +1044,10 @@ 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).unwrap(); + project_id } @@ -1157,7 +1073,7 @@ impl< ), ProjectStatus::CommunityRound(..) => self.create_community_contributing_project(project_metadata, issuer, None, evaluations, bids), - ProjectStatus::Auction => self.create_auctioning_project(project_metadata, issuer, None, evaluations), + ProjectStatus::AuctionRound => self.create_auctioning_project(project_metadata, issuer, None, evaluations), ProjectStatus::EvaluationRound => self.create_evaluating_project(project_metadata, issuer, None), ProjectStatus::Application => self.create_new_project(project_metadata, issuer, None), _ => panic!("unsupported project creation in that status"), diff --git a/pallets/funding/src/instantiator/tests.rs b/pallets/funding/src/instantiator/tests.rs index 1bb3df471..1a3258d1f 100644 --- a/pallets/funding/src/instantiator/tests.rs +++ b/pallets/funding/src/instantiator/tests.rs @@ -84,17 +84,15 @@ 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); assert_eq!(dry_run_price, wap); } @@ -170,18 +168,15 @@ 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); @@ -203,9 +198,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 +206,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 738c8a720..5cda72dba 100644 --- a/pallets/funding/src/instantiator/types.rs +++ b/pallets/funding/src/instantiator/types.rs @@ -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/lib.rs b/pallets/funding/src/lib.rs index 7265e6ebb..b43a04551 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -891,10 +891,8 @@ pub mod pallet { /// Any bids from this point until the auction_closing starts, will be considered as valid. #[pallet::call_index(6)] #[pallet::weight(WeightInfoOf::::start_auction_manually(1))] - pub fn start_auction(origin: OriginFor, jwt: UntrustedToken, project_id: ProjectId) -> DispatchResult { - let (account, _did, investor_type, _cid) = - T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?; - ensure!(investor_type == InvestorType::Institutional, Error::::WrongInvestorType); + pub fn start_auction(origin: OriginFor, project_id: ProjectId) -> DispatchResult { + let account = ensure_signed(origin)?; Self::do_start_auction(account, project_id) } diff --git a/pallets/funding/src/runtime_api.rs b/pallets/funding/src/runtime_api.rs index 4d56fa1f1..b3627a544 100644 --- a/pallets/funding/src/runtime_api.rs +++ b/pallets/funding/src/runtime_api.rs @@ -61,24 +61,21 @@ sp_api::decl_runtime_apis! { } impl Pallet { - pub fn top_evaluations -(project_id: ProjectId, amount: u32) -> Vec> { + pub fn top_evaluations(project_id: ProjectId, amount: u32) -> Vec> { Evaluations::::iter_prefix_values((project_id,)) .sorted_by(|a, b| b.original_plmc_bond.cmp(&a.original_plmc_bond)) .take(amount as usize) .collect_vec() } - pub fn top_bids -(project_id: ProjectId, amount: u32) -> Vec> { + pub fn top_bids(project_id: ProjectId, amount: u32) -> Vec> { Bids::::iter_prefix_values((project_id,)) .sorted_by(|a, b| b.final_ct_amount.cmp(&a.final_ct_amount)) .take(amount as usize) .collect_vec() } - pub fn top_contributions -(project_id: ProjectId, amount: u32) -> Vec> { + pub fn top_contributions(project_id: ProjectId, amount: u32) -> Vec> { Contributions::::iter_prefix_values((project_id,)) .sorted_by(|a, b| b.ct_amount.cmp(&a.ct_amount)) .take(amount as usize) diff --git a/pallets/funding/src/tests/1_application.rs b/pallets/funding/src/tests/1_application.rs index 2f39a2f83..9e4c4347e 100644 --- a/pallets/funding/src/tests/1_application.rs +++ b/pallets/funding/src/tests/1_application.rs @@ -150,7 +150,8 @@ mod create_project_extrinsic { }); // A Project is "inactive" after the evaluation fails - inst.start_evaluation(0, ISSUER_1).unwrap(); + assert_eq!(inst.go_to_next_state(0), ProjectStatus::EvaluationRound); + inst.execute(|| { assert_noop!( Pallet::::create_project( @@ -161,11 +162,8 @@ mod create_project_extrinsic { Error::::HasActiveProject ); }); - inst.advance_time(::EvaluationDuration::get() + 1).unwrap(); - assert_eq!( - inst.get_project_details(0).status, - ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed) - ); + assert_eq!(inst.go_to_next_state(0), ProjectStatus::FundingFailed); + inst.execute(|| { assert_ok!(Pallet::::create_project( RuntimeOrigin::signed(ISSUER_1), @@ -175,11 +173,16 @@ mod create_project_extrinsic { }); // A Project is "inactive" after the funding fails - inst.start_evaluation(1, ISSUER_1).unwrap(); + assert_eq!(inst.go_to_next_state(1), ProjectStatus::EvaluationRound); + inst.evaluate_for_users(1, default_evaluations()).unwrap(); - inst.start_auction(1, ISSUER_1).unwrap(); + + assert_eq!(inst.go_to_next_state(1), ProjectStatus::AuctionInitializePeriod); + assert_eq!(inst.go_to_next_state(1), ProjectStatus::AuctionRound); + inst.bid_for_users(1, failing_bids).unwrap(); - inst.start_community_funding(1).unwrap(); + + assert!(matches!(inst.go_to_next_state(1), ProjectStatus::CommunityRound(_))); inst.execute(|| { assert_noop!( Pallet::::create_project( @@ -190,8 +193,7 @@ mod create_project_extrinsic { Error::::HasActiveProject ); }); - inst.finish_funding(1, None).unwrap(); - assert_eq!(inst.get_project_details(1).status, ProjectStatus::FundingFailed); + assert_eq!(inst.go_to_next_state(1), ProjectStatus::FundingFailed); inst.execute(|| { assert_ok!(Pallet::::create_project( RuntimeOrigin::signed(ISSUER_1), @@ -201,16 +203,24 @@ mod create_project_extrinsic { }); // A project is "inactive" after the funding succeeds - inst.start_evaluation(2, ISSUER_1).unwrap(); + assert_eq!(inst.go_to_next_state(2), ProjectStatus::EvaluationRound); inst.evaluate_for_users(2, default_evaluations()).unwrap(); - inst.start_auction(2, ISSUER_1).unwrap(); + + assert_eq!(inst.go_to_next_state(2), ProjectStatus::AuctionInitializePeriod); + assert_eq!(inst.go_to_next_state(2), ProjectStatus::AuctionRound); + inst.bid_for_users(2, default_bids()).unwrap(); - inst.start_community_funding(2).unwrap(); + + let ProjectStatus::CommunityRound(remainder_start) = inst.go_to_next_state(2) else { + panic!("Expected CommunityRound"); + }; + inst.contribute_for_users(2, default_community_buys()).unwrap(); - inst.start_remainder_or_end_funding(2).unwrap(); + inst.jump_to_block(remainder_start); 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_eq!(inst.go_to_next_state(2), ProjectStatus::FundingSuccessful); + assert_ok!(inst.execute(|| crate::Pallet::::create_project( RuntimeOrigin::signed(ISSUER_1), jwt.clone(), @@ -1042,7 +1052,8 @@ mod edit_project_extrinsic { project_metadata.clone().policy_ipfs_cid.unwrap(), ); let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); - inst.start_evaluation(project_id, ISSUER_1).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::EvaluationRound); + inst.execute(|| { assert_noop!( crate::Pallet::::edit_project( @@ -1253,7 +1264,7 @@ mod remove_project_extrinsic { project_metadata.clone().policy_ipfs_cid.unwrap(), ); let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); - inst.start_evaluation(project_id, ISSUER_1).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::EvaluationRound); inst.execute(|| { assert_noop!( crate::Pallet::::remove_project( diff --git a/pallets/funding/src/tests/2_evaluation.rs b/pallets/funding/src/tests/2_evaluation.rs index 20c3b210e..932fbac27 100644 --- a/pallets/funding/src/tests/2_evaluation.rs +++ b/pallets/funding/src/tests/2_evaluation.rs @@ -54,7 +54,8 @@ mod round_flow { let old_price = ::PriceProvider::get_price(PLMC_FOREIGN_ID).unwrap(); PRICE_MAP.with_borrow_mut(|map| map.insert(PLMC_FOREIGN_ID, old_price / 2.into())); - inst.start_auction(project_id, ISSUER_1).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionInitializePeriod); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionRound); // Increasing the price before the end doesn't make a project under the threshold succeed. let evaluations = vec![(EVALUATOR_1, target_evaluation_usd / 2).into()]; @@ -67,11 +68,11 @@ mod round_flow { let old_price = ::PriceProvider::get_price(PLMC_FOREIGN_ID).unwrap(); PRICE_MAP.with_borrow_mut(|map| map.insert(PLMC_FOREIGN_ID, old_price * 2.into())); - let update_block = inst.get_update_block(project_id, &UpdateType::EvaluationEnd).unwrap(); - let now = inst.current_block(); - inst.advance_time(update_block - now + 1).unwrap(); - let project_status = inst.get_project_details(project_id).status; - assert_eq!(project_status, ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed)); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); + assert_eq!( + inst.go_to_next_state(project_id), + ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed) + ); } #[test] @@ -180,7 +181,8 @@ mod round_flow { ))); // The evaluation should succeed when we bond the threshold PLMC amount in total. - inst.start_auction(project_id, issuer).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionInitializePeriod); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionRound); }; for decimals in 6..=18 { @@ -237,12 +239,11 @@ mod round_flow { inst.do_free_plmc_assertions(plmc_existential_deposits); inst.do_reserved_plmc_assertions(plmc_eval_deposits, HoldReason::Evaluation(project_id).into()); - inst.advance_time(evaluation_end - now + 1).unwrap(); - - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); - - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); + assert_eq!( + inst.go_to_next_state(project_id), + ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed) + ); inst.settle_project(project_id).unwrap(); inst.do_free_plmc_assertions(expected_evaluator_balances); @@ -300,7 +301,7 @@ mod start_evaluation_extrinsic { is_frozen: true, weighted_average_price: None, status: ProjectStatus::EvaluationRound, - round_duration: BlockNumberPair::new(None, None), + round_duration: BlockNumberPair::new(Some(1), Some(::EvaluationDuration::get())), random_end_block: None, fundraising_target_usd: project_metadata .minimum_price @@ -397,7 +398,7 @@ mod start_evaluation_extrinsic { inst.execute(|| { assert_noop!( PolimecFunding::start_evaluation(RuntimeOrigin::signed(issuer), jwt, project_id), - Error::::IncorrectRound + Error::::ProjectAlreadyFrozen ); }); } @@ -645,12 +646,11 @@ mod evaluate_extrinsic { inst.mint_plmc_to(new_plmc_required.clone()); inst.evaluate_for_users(project_id, new_evaluations).unwrap(); - inst.start_auction(project_id, ISSUER_1).unwrap(); - inst.start_community_funding(project_id).unwrap(); - inst.start_remainder_or_end_funding(project_id).unwrap(); - inst.finish_funding(project_id, None).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionInitializePeriod); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionRound); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(_))); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); let free_balance = inst.get_free_plmc_balance_for(EVALUATOR_4); let evaluation_held_balance = @@ -664,8 +664,10 @@ mod evaluate_extrinsic { let treasury_account = ::BlockchainOperationTreasury::get(); let pre_slash_treasury_balance = inst.get_free_plmc_balance_for(treasury_account); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); + assert_eq!( + inst.go_to_next_state(project_id), + ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed) + ); inst.execute(|| { PolimecFunding::settle_failed_evaluation( diff --git a/pallets/funding/src/tests/3_auction.rs b/pallets/funding/src/tests/3_auction.rs index a524a6ca1..2e637f4f8 100644 --- a/pallets/funding/src/tests/3_auction.rs +++ b/pallets/funding/src/tests/3_auction.rs @@ -117,8 +117,10 @@ mod round_flow { inst.bid_for_users(project_id, bids).unwrap(); - inst.start_community_funding(project_id).unwrap(); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); + let project_details = inst.get_project_details(project_id); + 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) @@ -213,20 +215,34 @@ mod round_flow { // Full Acceptance at price > wap (refund due to final price lower than original price paid) // Rejection due to no more tokens left (full refund) #[test] - fn bids_get_rejected_and_refunded_part_one() { + fn bids_get_rejected_and_refunded() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let issuer = ISSUER_1; - let project_metadata = default_project_metadata(issuer); + let mut project_metadata = default_project_metadata(issuer); + project_metadata.total_allocation_size = 100_000 * CT_UNIT; + project_metadata.mainnet_token_max_supply = project_metadata.total_allocation_size; + project_metadata.auction_round_allocation_percentage = Percent::from_percent(50); + project_metadata.minimum_price = ConstPriceProvider::calculate_decimals_aware_price( + FixedU128::from_float(10.0f64), + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + project_metadata.participation_currencies = + bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT]; + let evaluations = default_evaluations(); - let bid_1 = BidParams::new(BIDDER_1, 5000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let bid_2 = BidParams::new(BIDDER_2, 40_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let bid_3 = BidParams::new(BIDDER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let bid_4 = BidParams::new(BIDDER_3, 6000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let bid_5 = BidParams::new(BIDDER_4, 2000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + // We use multiplier > 1 so after settlement, only the refunds defined above are done. The rest will be done + // through the linear release pallet + let bid_1 = BidParams::new(BIDDER_1, 5000 * CT_UNIT, 5u8, AcceptedFundingAsset::USDT); + let bid_2 = BidParams::new(BIDDER_2, 40_000 * CT_UNIT, 5u8, AcceptedFundingAsset::USDC); + let bid_3 = BidParams::new(BIDDER_1, 10_000 * CT_UNIT, 5u8, AcceptedFundingAsset::DOT); + let bid_4 = BidParams::new(BIDDER_3, 6000 * CT_UNIT, 5u8, AcceptedFundingAsset::USDT); + let bid_5 = BidParams::new(BIDDER_4, 2000 * CT_UNIT, 5u8, AcceptedFundingAsset::DOT); // post bucketing, the bids look like this: // (BIDDER_1, 5k) - (BIDDER_2, 40k) - (BIDDER_1, 5k) - (BIDDER_1, 5k) - (BIDDER_3 - 5k) - (BIDDER_3 - 1k) - (BIDDER_4 - 2k) - // | -------------------- 1USD ----------------------|---- 1.1 USD ---|---- 1.2 USD ----|----------- 1.3 USD -------------| + // | -------------------- 10USD ----------------------|---- 11 USD ---|---- 12 USD ----|----------- 13 USD -------------| // post wap ~ 1.0557252: // (Accepted, 5k) - (Partially, 32k) - (Rejected, 5k) - (Accepted, 5k) - (Accepted - 5k) - (Accepted - 1k) - (Accepted - 2k) @@ -234,23 +250,23 @@ mod round_flow { let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); - let plmc_fundings = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + let plmc_amounts = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( &bids, project_metadata.clone(), None, false, ); - let usdt_fundings = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( + let funding_asset_amounts = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( &bids, project_metadata.clone(), None, ); - let plmc_existential_amounts = plmc_fundings.accounts().existential_deposits(); + let plmc_existential_amounts = plmc_amounts.accounts().existential_deposits(); - inst.mint_plmc_to(plmc_fundings.clone()); + inst.mint_plmc_to(plmc_amounts.clone()); inst.mint_plmc_to(plmc_existential_amounts.clone()); - inst.mint_foreign_asset_to(usdt_fundings.clone()); + inst.mint_foreign_asset_to(funding_asset_amounts.clone()); inst.bid_for_users(project_id, bids.clone()).unwrap(); @@ -258,10 +274,9 @@ mod round_flow { UserToPLMCBalance::new(BIDDER_1, inst.get_ed()), UserToPLMCBalance::new(BIDDER_2, inst.get_ed()), ]); - inst.do_reserved_plmc_assertions(plmc_fundings.clone(), HoldReason::Participation(project_id).into()); - inst.do_bid_transferred_foreign_asset_assertions(usdt_fundings.clone(), project_id); + inst.do_reserved_plmc_assertions(plmc_amounts.clone(), HoldReason::Participation(project_id).into()); - inst.start_community_funding(project_id).unwrap(); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(_))); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); let returned_auction_plmc = @@ -275,182 +290,76 @@ mod round_flow { ); let expected_free_funding_assets = inst.generic_map_operation(vec![returned_funding_assets.clone()], MergeOperation::Add); - let expected_reserved_plmc = inst - .generic_map_operation(vec![plmc_fundings.clone(), returned_auction_plmc], MergeOperation::Subtract); - let expected_held_funding_assets = inst - .generic_map_operation(vec![usdt_fundings.clone(), returned_funding_assets], MergeOperation::Subtract); - - inst.do_free_plmc_assertions(expected_free_plmc); - - inst.do_reserved_plmc_assertions(expected_reserved_plmc, HoldReason::Participation(project_id).into()); - - inst.do_free_foreign_asset_assertions(expected_free_funding_assets); - inst.do_bid_transferred_foreign_asset_assertions(expected_held_funding_assets, project_id); - } - - #[test] - // Partial acceptance at price > wap (refund due to less CT bought, and final price lower than original price paid) - // Rejection due to bid being made after random end (full refund) - fn bids_get_rejected_and_refunded_part_two() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = - inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); - - let total_auction_ct_amount = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - - let full_ct_bid_rejected = - BidParams::new(BIDDER_1, total_auction_ct_amount, 1u8, AcceptedFundingAsset::USDT); - let full_ct_bid_partially_accepted = - BidParams::new(BIDDER_2, total_auction_ct_amount, 1u8, AcceptedFundingAsset::USDT); - let oversubscription_bid = BidParams::new(BIDDER_3, 100_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let after_random_end_bid = BidParams::new(BIDDER_4, 100_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - - let all_bids = vec![ - full_ct_bid_rejected.clone(), - full_ct_bid_partially_accepted.clone(), - oversubscription_bid.clone(), - after_random_end_bid.clone(), - ]; - let all_included_bids = - vec![full_ct_bid_rejected.clone(), full_ct_bid_partially_accepted.clone(), oversubscription_bid]; - - let necessary_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &all_bids, - project_metadata.clone(), - None, - false, - ); - let plmc_existential_amounts = necessary_plmc.accounts().existential_deposits(); - let necessary_usdt = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &all_bids, - project_metadata.clone(), - None, + let expected_reserved_plmc = + inst.generic_map_operation(vec![plmc_amounts.clone(), returned_auction_plmc], MergeOperation::Subtract); + let expected_final_funding_spent = inst.generic_map_operation( + vec![funding_asset_amounts.clone(), returned_funding_assets], + MergeOperation::Subtract, ); + let expected_issuer_funding = inst.sum_foreign_asset_mappings(vec![expected_final_funding_spent]); - inst.mint_plmc_to(necessary_plmc.clone()); - inst.mint_plmc_to(plmc_existential_amounts.clone()); - inst.mint_foreign_asset_to(necessary_usdt.clone()); - inst.bid_for_users(project_id, all_included_bids.clone()).unwrap(); - inst.advance_time( - ::AuctionOpeningDuration::get() + - ::AuctionClosingDuration::get() - - 1, - ) + // Assertions about rejected bid + let rejected_bid = inst.execute(|| Bids::::get((project_id, BIDDER_1, 2)).unwrap()); + assert_eq!(rejected_bid.status, BidStatus::Rejected); + let bidder_plmc_pre_balance = inst.get_free_plmc_balance_for(rejected_bid.bidder); + let bidder_funding_asset_pre_balance = inst + .get_free_foreign_asset_balance_for(rejected_bid.funding_asset.to_assethub_id(), rejected_bid.bidder); + inst.execute(|| { + PolimecFunding::settle_failed_bid( + RuntimeOrigin::signed(rejected_bid.bidder), + project_id, + rejected_bid.bidder, + 2, + ) + }) .unwrap(); - - inst.bid_for_users(project_id, vec![after_random_end_bid]).unwrap(); - inst.do_free_plmc_assertions(vec![ - UserToPLMCBalance::new(BIDDER_1, inst.get_ed()), - UserToPLMCBalance::new(BIDDER_2, inst.get_ed()), - UserToPLMCBalance::new(BIDDER_3, inst.get_ed()), - UserToPLMCBalance::new(BIDDER_4, inst.get_ed()), - ]); - inst.do_reserved_plmc_assertions(necessary_plmc.clone(), HoldReason::Participation(project_id).into()); - inst.do_bid_transferred_foreign_asset_assertions(necessary_usdt.clone(), project_id); - inst.start_community_funding(project_id).unwrap(); - - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - let plmc_returned = inst.calculate_auction_plmc_returned_from_all_bids_made( - &all_included_bids, - project_metadata.clone(), - wap, - ); - let usdt_returned = inst.calculate_auction_funding_asset_returned_from_all_bids_made( - &all_included_bids, - project_metadata.clone(), - wap, + let bidder_plmc_post_balance = inst.get_free_plmc_balance_for(rejected_bid.bidder); + let bidder_funding_asset_post_balance = inst + .get_free_foreign_asset_balance_for(rejected_bid.funding_asset.to_assethub_id(), rejected_bid.bidder); + assert!(inst.execute(|| Bids::::get((project_id, BIDDER_1, 2))).is_none()); + assert_eq!(bidder_plmc_post_balance, bidder_plmc_pre_balance + rejected_bid.plmc_bond); + assert_eq!( + bidder_funding_asset_post_balance, + bidder_funding_asset_pre_balance + rejected_bid.funding_asset_amount_locked ); - let rejected_bid_necessary_plmc = &necessary_plmc[3]; - let rejected_bid_necessary_usdt = &necessary_usdt[3]; - - let expected_free = inst.generic_map_operation( - vec![plmc_returned.clone(), plmc_existential_amounts, vec![rejected_bid_necessary_plmc.clone()]], - MergeOperation::Add, - ); - inst.do_free_plmc_assertions(expected_free); - let expected_reserved = inst.generic_map_operation( - vec![necessary_plmc.clone(), plmc_returned.clone(), vec![rejected_bid_necessary_plmc.clone()]], - MergeOperation::Subtract, + // Any refunds on bids that were accepted/partially accepted will be done at the settlement once funding finishes + assert_eq!( + inst.execute(|| Bids::::get((project_id, BIDDER_2, 1)).unwrap()).status, + BidStatus::PartiallyAccepted(32_000 * CT_UNIT) ); - inst.do_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, + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); + assert_eq!( + inst.go_to_next_state(project_id), + ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful) ); - 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); + inst.settle_project(project_id).unwrap(); - 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(); + 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); - let details = inst.get_project_details(project_id); - assert!(matches!(details.status, ProjectStatus::CommunityRound(..))); - assert_eq!(details.weighted_average_price, Some(project_metadata.minimum_price)); + for (asset, expected_amount) in expected_issuer_funding { + let real_amount = inst.get_free_foreign_asset_balance_for(asset, ISSUER_1); + assert_eq!(real_amount, expected_amount); + } } #[test] - fn all_bids_rejected() { + fn no_bids_made() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let issuer = ISSUER_1; let project_metadata = default_project_metadata(issuer); let evaluations = default_evaluations(); - let bids = default_bids(); let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); - let necessary_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &bids, - project_metadata.clone(), - None, - true, - ); - let necessary_usdt = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &bids, - project_metadata.clone(), - None, - ); - - inst.mint_plmc_to(necessary_plmc.clone()); - inst.mint_foreign_asset_to(necessary_usdt.clone()); - inst.advance_time( - ::AuctionOpeningDuration::get() + - ::AuctionClosingDuration::get() - - 1, - ) - .unwrap(); - - // We bid at the last block, which we assume will be after the random end - inst.bid_for_users(project_id, bids.clone()).unwrap(); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); - 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); + assert_eq!( + inst.get_project_details(project_id).weighted_average_price, + Some(project_metadata.minimum_price) + ); } #[test] @@ -507,7 +416,7 @@ mod round_flow { inst.bid_for_users(project_id, bids).unwrap(); - inst.start_community_funding(project_id).unwrap(); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); let token_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); let normalized_wap = @@ -779,7 +688,7 @@ mod start_auction_extrinsic { use super::*; #[test] - fn pallet_can_start_auction_automatically() { + fn anyone_can_start_auction_after_initialize_period() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); let evaluations = default_evaluations(); @@ -788,48 +697,28 @@ mod start_auction_extrinsic { inst.mint_plmc_to(required_plmc); inst.evaluate_for_users(project_id, evaluations).unwrap(); - let update_block = inst.get_update_block(project_id, &UpdateType::EvaluationEnd).unwrap(); - inst.execute(|| System::set_block_number(update_block - 1)); - inst.advance_time(1).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionInitializePeriod); + let end_block = inst.get_project_details(project_id).round_duration.end().unwrap(); + inst.jump_to_block(end_block); - let update_block = inst.get_update_block(project_id, &UpdateType::AuctionOpeningStart).unwrap(); - inst.execute(|| System::set_block_number(update_block - 1)); - inst.advance_time(1).unwrap(); - } + inst.execute(|| PolimecFunding::start_auction(RuntimeOrigin::signed(420u32), project_id)).unwrap(); - #[test] - fn issuer_can_start_auction_manually() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); - let evaluations = default_evaluations(); - let required_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); - inst.mint_plmc_to(required_plmc); - inst.evaluate_for_users(project_id, evaluations).unwrap(); - inst.advance_time(::EvaluationDuration::get() + 1).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionInitializePeriod); - inst.advance_time(1).unwrap(); - inst.execute(|| Pallet::::do_start_auction(ISSUER_1, project_id)).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Auction); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionRound); } #[test] - fn stranger_cannot_start_auction_manually() { + fn issuer_can_start_auction_before_initialize_period_end() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); let evaluations = default_evaluations(); let required_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); inst.mint_plmc_to(required_plmc); inst.evaluate_for_users(project_id, evaluations).unwrap(); - inst.advance_time(::EvaluationDuration::get() + 1).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionInitializePeriod); - inst.advance_time(1).unwrap(); - for account in 6000..6010 { - inst.execute(|| { - let response = Pallet::::do_start_auction(account, project_id); - assert_noop!(response, Error::::NotIssuer); - }); - } + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionInitializePeriod); + + inst.execute(|| Pallet::::start_auction(RuntimeOrigin::signed(ISSUER_1), project_id)).unwrap(); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionRound); } } @@ -838,53 +727,51 @@ mod start_auction_extrinsic { use super::*; #[test] - fn cannot_start_auction_manually_before_evaluation_finishes() { + fn anyone_cannot_start_auction_before_initialize_period() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); + let evaluations = default_evaluations(); + let required_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); + + inst.mint_plmc_to(required_plmc); + inst.evaluate_for_users(project_id, evaluations).unwrap(); + + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionInitializePeriod); + inst.execute(|| { assert_noop!( - PolimecFunding::do_start_auction(ISSUER_1, project_id), - Error::::TransitionPointNotSet + PolimecFunding::start_auction(RuntimeOrigin::signed(420u32), project_id), + Error::::TooEarlyForRound ); }); + + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionInitializePeriod); } #[test] - fn cannot_start_auction_manually_if_evaluation_fails() { + fn issuer_cannot_start_auction_manually_before_evaluation_finishes() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); - inst.advance_time(::EvaluationDuration::get() + 1).unwrap(); inst.execute(|| { assert_noop!( - PolimecFunding::do_start_auction(ISSUER_1, project_id), - Error::::TransitionPointNotSet + PolimecFunding::start_auction(RuntimeOrigin::signed(ISSUER_1), project_id), + Error::::IncorrectRound ); }); - assert_eq!( - inst.get_project_details(project_id).status, - ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed) - ); } #[test] - fn auction_doesnt_start_automatically_if_evaluation_fails() { - // Test our success assumption is ok - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); - let evaluations = default_evaluations(); - let required_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); - inst.mint_plmc_to(required_plmc); - inst.evaluate_for_users(project_id, evaluations).unwrap(); - inst.start_auction(project_id, ISSUER_1).unwrap(); - - // Main test with failed evaluation + fn cannot_start_auction_manually_if_evaluation_fails() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); - let evaluation_end_execution = inst.get_update_block(project_id, &UpdateType::EvaluationEnd).unwrap(); - inst.execute(|| System::set_block_number(evaluation_end_execution - 1)); - inst.advance_time(1).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); + inst.execute(|| { + assert_noop!( + PolimecFunding::start_auction(RuntimeOrigin::signed(ISSUER_1), project_id), + Error::::IncorrectRound + ); + }); } } } @@ -959,491 +846,491 @@ mod bid_extrinsic { ); } - #[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); - } + // #[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)] diff --git a/pallets/funding/src/tests/4_community.rs b/pallets/funding/src/tests/4_contribution.rs similarity index 91% rename from pallets/funding/src/tests/4_community.rs rename to pallets/funding/src/tests/4_contribution.rs index b3ded28f6..efbd81b91 100644 --- a/pallets/funding/src/tests/4_community.rs +++ b/pallets/funding/src/tests/4_contribution.rs @@ -12,15 +12,16 @@ mod round_flow { use std::collections::HashSet; #[test] - fn community_round_completed() { + fn contribution_round_completed() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let _ = inst.create_remainder_contributing_project( + let _ = inst.create_finished_project( default_project_metadata(ISSUER_1), ISSUER_1, None, default_evaluations(), default_bids(), default_community_buys(), + default_remainder_buys(), ); } @@ -63,7 +64,7 @@ mod round_flow { } #[test] - fn community_round_ends_on_all_ct_sold_exact() { + fn contribution_round_ends_on_all_ct_sold_exact() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let bids = vec![ BidParams::new_with_defaults(BIDDER_1, 40_000 * CT_UNIT), @@ -94,7 +95,7 @@ mod round_flow { // Buy remaining CTs inst.contribute_for_users(project_id, contributions) .expect("The Buyer should be able to buy the exact amount of remaining CTs"); - inst.advance_time(2u64).unwrap(); + // Check remaining CTs is 0 assert_eq!( inst.get_project_details(project_id).remaining_contribution_tokens, @@ -103,7 +104,11 @@ mod round_flow { ); // Check project is in FundingEnded state - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); + assert_eq!( + inst.go_to_next_state(project_id), + ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful) + ); inst.do_free_plmc_assertions(plmc_existential_deposits); inst.do_free_foreign_asset_assertions(vec![UserToForeignAssets::::new( @@ -115,7 +120,6 @@ mod round_flow { vec![plmc_fundings[0].clone()], HoldReason::Participation(project_id).into(), ); - inst.do_contribution_transferred_foreign_asset_assertions(foreign_asset_fundings, project_id); } #[test] @@ -298,7 +302,7 @@ mod round_flow { assert_eq!(inst.get_project_details(project_id).remaining_contribution_tokens, 0); // We can successfully finish the project - inst.finish_funding(project_id, None).unwrap(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); }; for decimals in 6..=18 { @@ -435,63 +439,79 @@ mod contribute_extrinsic { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let bob = 42069; let project_metadata = default_project_metadata(ISSUER_1); + // An evaluator that did a bid but it was not accepted at the end of the auction, can use that PLMC for contributing let mut evaluations = default_evaluations(); - let bob_evaluation = (bob, 1337 * USD_UNIT).into(); + let bob_evaluation = (bob, 10_000 * USD_UNIT).into(); evaluations.push(bob_evaluation); - let bids = default_bids(); - let bob_bid: BidParams = (bob, 1337 * CT_UNIT).into(); - let all_bids = bids.iter().chain(vec![bob_bid.clone()].iter()).cloned().collect_vec(); + let plmc_price = ::PriceProvider::get_decimals_aware_price( + PLMC_FOREIGN_ID, + USD_DECIMALS, + PLMC_DECIMALS, + ) + .unwrap(); let project_id = inst.create_auctioning_project(default_project_metadata(ISSUER_2), ISSUER_2, None, evaluations); + let bucket = inst.execute(|| Buckets::::get(project_id).unwrap()); + let first_bucket = bucket.amount_left; - let evaluation_plmc_bond = + // Failed bids can only happen on oversubscription. We want Bob's bid as the last one of the first bucket + let bob_plmc_bond = inst.execute(|| Balances::balance_on_hold(&HoldReason::Evaluation(project_id).into(), &bob)); - let slashable_plmc_bond = ::EvaluatorSlash::get() * evaluation_plmc_bond; - let usable_plmc_bond = evaluation_plmc_bond - slashable_plmc_bond; + let usable_bond = bob_plmc_bond - ::EvaluatorSlash::get() * bob_plmc_bond; + let usable_usd = plmc_price.saturating_mul_int(usable_bond); + let usable_bob_ct = bucket.current_price.reciprocal().unwrap().saturating_mul_int(usable_usd); - let bids_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &all_bids, + 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_foreign = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &all_bids, + let bids_funding_assets = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( + &bids, project_metadata.clone(), None, ); - inst.mint_foreign_asset_to(bids_foreign.clone()); - + inst.mint_foreign_asset_to(bids_funding_assets.clone()); inst.bid_for_users(project_id, bids).unwrap(); - let auction_end = ::AuctionOpeningDuration::get() + - ::AuctionClosingDuration::get(); - inst.advance_time(auction_end - 1).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Auction); - inst.bid_for_users(project_id, vec![bob_bid]).unwrap(); + assert_eq!(inst.execute(|| Balances::free_balance(&bob)), inst.get_ed()); - inst.start_community_funding(project_id).unwrap(); - assert!(matches!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound(..))); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); + let stored_bids = + inst.execute(|| Bids::::iter_prefix_values((project_id,)).collect::>()); + // Free up the plmc and usdt from the failed bid: + inst.execute(|| { + PolimecFunding::settle_failed_bid(RuntimeOrigin::signed(bob), project_id, bob, 1).unwrap(); + }); + let bob_plmc = inst.execute(|| Balances::free_balance(&bob)); + assert_close_enough!(bob_plmc, inst.get_ed() + usable_bond, Perquintill::from_float(0.9999)); - let plmc_price = ::PriceProvider::get_decimals_aware_price( - PLMC_FOREIGN_ID, - USD_DECIMALS, - PLMC_DECIMALS, - ) - .unwrap(); + // 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 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 bob_contribution = (bob, usable_bob_ct).into(); let contribution_usdt = inst.calculate_contributed_funding_asset_spent(vec![bob_contribution], wap); inst.mint_foreign_asset_to(contribution_usdt.clone()); + inst.execute(|| { assert_ok!(Pallet::::contribute( RuntimeOrigin::signed(bob), @@ -502,11 +522,18 @@ mod contribute_extrinsic { project_metadata.clone().policy_ipfs_cid.unwrap() ), project_id, - usable_ct, + usable_bob_ct, 1u8.try_into().unwrap(), AcceptedFundingAsset::USDT, )); }); + + // Check he had no free PLMC + assert_close_enough!( + inst.execute(|| Balances::free_balance(&bob)), + inst.get_ed(), + Perquintill::from_float(0.999) + ); } #[test] @@ -742,16 +769,14 @@ mod contribute_extrinsic { BidParams::new(BIDDER_1, 400_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), BidParams::new(BIDDER_2, 100_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), ]; - let failing_bids_after_random_end = - vec![(BIDDER_3, 25_000 * CT_UNIT).into(), (BIDDER_4, 25_000 * CT_UNIT).into()]; + // This bids should fill the first bucket. let failing_bids_sold_out = - vec![(BIDDER_5, 250_000 * CT_UNIT).into(), (BIDDER_6, 250_000 * CT_UNIT).into()]; + vec![(BIDDER_6, 250_000 * CT_UNIT).into()]; let all_bids = failing_bids_sold_out .iter() .chain(successful_bids.iter()) - .chain(failing_bids_after_random_end.iter()) .cloned() .collect_vec(); @@ -775,14 +800,7 @@ mod contribute_extrinsic { inst.bid_for_users(project_id, failing_bids_sold_out).unwrap(); inst.bid_for_users(project_id, successful_bids).unwrap(); - inst.advance_time( - ::AuctionOpeningDuration::get() + - ::AuctionClosingDuration::get() - - 1, - ) - .unwrap(); - inst.bid_for_users(project_id, failing_bids_after_random_end).unwrap(); - inst.start_community_funding(project_id).unwrap(); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); // Some low amount of plmc and usdt to cover a purchase of 10CTs. let plmc_mints = vec![ @@ -826,38 +844,14 @@ mod contribute_extrinsic { }); }; - // Bidder 3 has a losing bid due to bidding after the random end. His did should be able to contribute regardless of what investor type - // or account he uses to sign the transaction - bid_should_succeed(BIDDER_3, InvestorType::Institutional, BIDDER_3); - bid_should_succeed(BUYER_3, InvestorType::Institutional, BIDDER_3); - bid_should_succeed(BIDDER_3, InvestorType::Professional, BIDDER_3); - bid_should_succeed(BUYER_3, InvestorType::Professional, BIDDER_3); - bid_should_succeed(BIDDER_3, InvestorType::Retail, BIDDER_3); - bid_should_succeed(BUYER_3, InvestorType::Retail, BIDDER_3); - - // Bidder 4 has a losing bid due to bidding after the random end, and he was also an evaluator. Same conditions as before should apply. - bid_should_succeed(BIDDER_4, InvestorType::Institutional, BIDDER_4); - bid_should_succeed(BUYER_4, InvestorType::Institutional, BIDDER_4); - bid_should_succeed(BIDDER_4, InvestorType::Professional, BIDDER_4); - bid_should_succeed(BUYER_4, InvestorType::Professional, BIDDER_4); - bid_should_succeed(BIDDER_4, InvestorType::Retail, BIDDER_4); - bid_should_succeed(BUYER_4, InvestorType::Retail, BIDDER_4); - - // Bidder 5 has a losing bid due to CTs being sold out at his price point. Same conditions as before should apply. + // Bidder has a losing bid due to CTs being sold out at his price point. + // Their did should be able to contribute regardless of what investor type or account he uses to sign the transaction bid_should_succeed(BIDDER_5, InvestorType::Institutional, BIDDER_5); bid_should_succeed(BUYER_5, InvestorType::Institutional, BIDDER_5); bid_should_succeed(BIDDER_5, InvestorType::Professional, BIDDER_5); bid_should_succeed(BUYER_5, InvestorType::Professional, BIDDER_5); bid_should_succeed(BIDDER_5, InvestorType::Retail, BIDDER_5); bid_should_succeed(BUYER_5, InvestorType::Retail, BIDDER_5); - - // Bidder 6 has a losing bid due to CTs being sold out at his price point, and he was also an evaluator. Same conditions as before should apply. - bid_should_succeed(BIDDER_6, InvestorType::Institutional, BIDDER_6); - bid_should_succeed(BUYER_6, InvestorType::Institutional, BIDDER_6); - bid_should_succeed(BIDDER_6, InvestorType::Professional, BIDDER_6); - bid_should_succeed(BUYER_6, InvestorType::Professional, BIDDER_6); - bid_should_succeed(BIDDER_6, InvestorType::Retail, BIDDER_6); - bid_should_succeed(BUYER_6, InvestorType::Retail, BIDDER_6); } #[test] @@ -912,10 +906,7 @@ mod contribute_extrinsic { )); }); - inst.start_remainder_or_end_funding(project_id).unwrap(); - inst.finish_funding(project_id, None).unwrap(); - - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); let free_balance = inst.get_free_plmc_balance_for(BUYER_4); let bid_held_balance = @@ -926,8 +917,10 @@ mod contribute_extrinsic { assert_eq!(bid_held_balance, frozen_amount); assert_eq!(frozen_balance, frozen_amount); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); + assert_eq!( + inst.go_to_next_state(project_id), + ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed) + ); inst.execute(|| { PolimecFunding::settle_failed_contribution(RuntimeOrigin::signed(BUYER_4), project_id, BUYER_4, 0) @@ -996,11 +989,11 @@ mod contribute_extrinsic { )); }); - inst.start_remainder_or_end_funding(project_id).unwrap(); - inst.finish_funding(project_id, None).unwrap(); - - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); + assert_eq!( + inst.go_to_next_state(project_id), + ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful) + ); let free_balance = inst.get_free_plmc_balance_for(BUYER_4); let bid_held_balance = @@ -1045,6 +1038,36 @@ mod contribute_extrinsic { assert_eq!(bid_held_balance, Zero::zero()); assert_eq!(frozen_balance, frozen_amount); } + + #[test] + fn participant_was_evaluator_and_bidder() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let issuer = ISSUER_1; + let participant = 42069u32; + let project_metadata = default_project_metadata(issuer); + let mut evaluations = default_evaluations(); + evaluations.push((participant, 100 * USD_UNIT).into()); + let mut bids = default_bids(); + bids.push(BidParams::new(participant, 1000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT)); + let community_contributions = default_community_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)] @@ -1123,7 +1146,10 @@ mod contribute_extrinsic { }); assert_eq!(plmc_bond_stored, inst.sum_balance_mappings(vec![plmc_funding.clone()])); - assert_eq!(foreign_asset_contributions_stored, inst.sum_foreign_mappings(vec![foreign_funding.clone()])); + assert_eq!( + foreign_asset_contributions_stored, + inst.sum_foreign_asset_mappings(vec![foreign_funding.clone()])[0].1 + ); } #[test] @@ -1587,14 +1613,6 @@ mod contribute_extrinsic { None, default_evaluations(), ); - let remaining_project = inst.create_remainder_contributing_project( - default_project_metadata(ISSUER_4), - ISSUER_4, - None, - default_evaluations(), - default_bids(), - default_community_buys(), - ); let finished_project = inst.create_finished_project( default_project_metadata(ISSUER_5), ISSUER_5, @@ -1605,8 +1623,7 @@ mod contribute_extrinsic { default_remainder_buys(), ); - let projects = - vec![created_project, evaluating_project, auctioning_project, remaining_project, finished_project]; + let projects = vec![created_project, evaluating_project, auctioning_project, finished_project]; for project in projects { let project_policy = inst.get_project_metadata(project).policy_ipfs_cid.unwrap(); inst.execute(|| { @@ -1790,5 +1807,6 @@ mod contribute_extrinsic { ); }); } + } } diff --git a/pallets/funding/src/tests/6_funding_end.rs b/pallets/funding/src/tests/5_funding_end.rs similarity index 96% rename from pallets/funding/src/tests/6_funding_end.rs rename to pallets/funding/src/tests/5_funding_end.rs index 1754dd2e6..e4461e86b 100644 --- a/pallets/funding/src/tests/6_funding_end.rs +++ b/pallets/funding/src/tests/5_funding_end.rs @@ -8,7 +8,7 @@ mod round_flow { #[test] fn evaluator_slash_is_decided() { - let (mut inst, project_id) = create_project_with_funding_percentage(20, None, true); + let (mut inst, project_id) = create_project_with_funding_percentage(20, true); assert_eq!( inst.get_project_details(project_id).status, ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed) @@ -22,7 +22,7 @@ mod round_flow { #[test] fn evaluator_unchanged_is_decided() { let (mut inst, project_id) = - create_project_with_funding_percentage(80, Some(FundingOutcomeDecision::AcceptFunding), true); + create_project_with_funding_percentage(80, true); assert_eq!( inst.get_project_details(project_id).status, ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful) @@ -35,7 +35,7 @@ mod round_flow { #[test] fn evaluator_reward_is_decided() { - let (mut inst, project_id) = create_project_with_funding_percentage(95, None, true); + 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!( diff --git a/pallets/funding/src/tests/5_remainder.rs b/pallets/funding/src/tests/5_remainder.rs deleted file mode 100644 index 13f13f6e8..000000000 --- a/pallets/funding/src/tests/5_remainder.rs +++ /dev/null @@ -1,1888 +0,0 @@ -use super::*; -// use crate::instantiator::async_features::create_multiple_projects_at; -use frame_support::{dispatch::DispatchResultWithPostInfo, traits::fungibles::metadata::Inspect}; -use sp_runtime::bounded_vec; -use std::collections::HashSet; - -#[cfg(test)] -mod round_flow { - use super::*; - - #[cfg(test)] - mod success { - use super::*; - - #[test] - fn remainder_round_works() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let _ = inst.create_finished_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - default_community_buys(), - default_remainder_buys(), - ); - } - - #[test] - fn remainder_round_ends_on_all_ct_sold_exact() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_remainder_contributing_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - default_community_buys(), - ); - const BOB: AccountId = 808; - - let remaining_ct = inst.get_project_details(project_id).remaining_contribution_tokens; - let ct_price = inst.get_project_details(project_id).weighted_average_price.expect("CT Price should exist"); - - let contributions = vec![ContributionParams::new(BOB, remaining_ct, 1u8, AcceptedFundingAsset::USDT)]; - let plmc_fundings = inst.calculate_contributed_plmc_spent(contributions.clone(), ct_price, false); - let plmc_existential_deposits = contributions.accounts().existential_deposits(); - let foreign_asset_fundings = - inst.calculate_contributed_funding_asset_spent(contributions.clone(), ct_price); - - inst.mint_plmc_to(plmc_fundings.clone()); - inst.mint_plmc_to(plmc_existential_deposits.clone()); - inst.mint_foreign_asset_to(foreign_asset_fundings.clone()); - - // Buy remaining CTs - inst.contribute_for_users(project_id, contributions) - .expect("The Buyer should be able to buy the exact amount of remaining CTs"); - inst.advance_time(2u64).unwrap(); - - // Check remaining CTs is 0 - assert_eq!( - inst.get_project_details(project_id).remaining_contribution_tokens, - 0, - "There are still remaining CTs" - ); - - // Check project is in FundingEnded state - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); - - inst.do_free_plmc_assertions(plmc_existential_deposits); - inst.do_free_foreign_asset_assertions(vec![UserToForeignAssets::::new( - BOB, - 0_u128, - AcceptedFundingAsset::USDT.to_assethub_id(), - )]); - inst.do_reserved_plmc_assertions( - vec![plmc_fundings[0].clone()], - HoldReason::Participation(project_id).into(), - ); - inst.do_contribution_transferred_foreign_asset_assertions(foreign_asset_fundings, project_id); - } - - #[test] - fn round_has_total_ct_allocation_minus_auction_sold() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let evaluations = default_evaluations(); - let bids = default_bids(); - - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - evaluations.clone(), - bids.clone(), - vec![], - ); - let project_details = inst.get_project_details(project_id); - let bid_ct_sold: BalanceOf = inst.execute(|| { - Bids::::iter_prefix_values((project_id,)) - .fold(Zero::zero(), |acc, bid| acc + bid.final_ct_amount) - }); - assert_eq!( - project_details.remaining_contribution_tokens, - project_metadata.total_allocation_size - bid_ct_sold - ); - - let contributions = vec![(BUYER_1, project_details.remaining_contribution_tokens).into()]; - - let plmc_contribution_funding = inst.calculate_contributed_plmc_spent( - contributions.clone(), - project_details.weighted_average_price.unwrap(), - false, - ); - let plmc_existential_deposits = plmc_contribution_funding.accounts().existential_deposits(); - inst.mint_plmc_to(plmc_contribution_funding.clone()); - inst.mint_plmc_to(plmc_existential_deposits.clone()); - - let foreign_asset_contribution_funding = inst.calculate_contributed_funding_asset_spent( - contributions.clone(), - project_details.weighted_average_price.unwrap(), - ); - inst.mint_foreign_asset_to(foreign_asset_contribution_funding.clone()); - - inst.contribute_for_users(project_id, contributions).unwrap(); - - assert_eq!(inst.get_project_details(project_id).remaining_contribution_tokens, 0); - } - - #[test] - fn different_decimals_ct_works_as_expected() { - // Setup some base values to compare different decimals - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let ed = inst.get_ed(); - let default_project_metadata = default_project_metadata(ISSUER_1); - let original_decimal_aware_price = default_project_metadata.minimum_price; - let original_price = ::PriceProvider::convert_back_to_normal_price( - original_decimal_aware_price, - USD_DECIMALS, - default_project_metadata.token_information.decimals, - ) - .unwrap(); - let usable_plmc_price = inst.execute(|| { - ::PriceProvider::get_decimals_aware_price( - PLMC_FOREIGN_ID, - USD_DECIMALS, - PLMC_DECIMALS, - ) - .unwrap() - }); - let usdt_price = inst.execute(|| { - ::PriceProvider::get_decimals_aware_price( - AcceptedFundingAsset::USDT.to_assethub_id(), - USD_DECIMALS, - ForeignAssets::decimals(AcceptedFundingAsset::USDT.to_assethub_id()), - ) - .unwrap() - }); - let usdc_price = inst.execute(|| { - ::PriceProvider::get_decimals_aware_price( - AcceptedFundingAsset::USDC.to_assethub_id(), - USD_DECIMALS, - ForeignAssets::decimals(AcceptedFundingAsset::USDC.to_assethub_id()), - ) - .unwrap() - }); - let dot_price = inst.execute(|| { - ::PriceProvider::get_decimals_aware_price( - AcceptedFundingAsset::DOT.to_assethub_id(), - USD_DECIMALS, - ForeignAssets::decimals(AcceptedFundingAsset::DOT.to_assethub_id()), - ) - .unwrap() - }); - - let mut funding_assets_cycle = - vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT] - .into_iter() - .cycle(); - - let mut total_fundings_ct = Vec::new(); - let mut total_fundings_usd = Vec::new(); - let mut total_fundings_plmc = Vec::new(); - - let mut decimal_test = |decimals: u8| { - let funding_asset = funding_assets_cycle.next().unwrap(); - let funding_asset_usd_price = match funding_asset { - AcceptedFundingAsset::USDT => usdt_price, - AcceptedFundingAsset::USDC => usdc_price, - AcceptedFundingAsset::DOT => dot_price, - }; - - let mut project_metadata = default_project_metadata.clone(); - project_metadata.token_information.decimals = decimals; - project_metadata.minimum_price = - ::PriceProvider::calculate_decimals_aware_price( - original_price, - USD_DECIMALS, - decimals, - ) - .unwrap(); - - project_metadata.total_allocation_size = 1_000_000 * 10u128.pow(decimals as u32); - project_metadata.mainnet_token_max_supply = project_metadata.total_allocation_size; - project_metadata.participation_currencies = bounded_vec!(funding_asset); - - let issuer: AccountIdOf = (10_000 + inst.get_new_nonce()).try_into().unwrap(); - let evaluations = inst.generate_successful_evaluations( - project_metadata.clone(), - default_evaluators(), - default_weights(), - ); - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - issuer, - None, - evaluations, - vec![], - vec![], - ); - - let total_funding_ct = project_metadata.total_allocation_size; - let total_funding_usd = project_metadata.minimum_price.saturating_mul_int(total_funding_ct); - let total_funding_plmc = usable_plmc_price.reciprocal().unwrap().saturating_mul_int(total_funding_usd); - let total_funding_funding_asset = - funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(total_funding_usd); - - total_fundings_ct.push(total_funding_ct); - total_fundings_usd.push(total_funding_usd); - total_fundings_plmc.push(total_funding_plmc); - - // Every project should want to raise 10MM USD - assert_eq!(total_funding_usd, 10_000_000 * USD_UNIT); - - // Every project should produce the same PLMC bond when having the full funding at multiplier 1. - assert_close_enough!(total_funding_plmc, 1_190_476 * PLMC, Perquintill::from_float(0.999)); - - // Every project should have a different amount of CTs to raise, depending on their decimals - assert_eq!(total_funding_ct, 1_000_000 * 10u128.pow(decimals as u32)); - - // Buying all the remaining tokens. This is a fixed USD value, but the extrinsic amount depends on CT decimals. - inst.mint_plmc_to(vec![UserToPLMCBalance::new(BUYER_1, total_funding_plmc + ed)]); - inst.mint_foreign_asset_to(vec![UserToForeignAssets::new( - BUYER_1, - total_funding_funding_asset, - funding_asset.to_assethub_id(), - )]); - - assert_ok!(inst.execute(|| PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_1), - get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Retail, - generate_did_from_account(BUYER_1), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - total_funding_ct, - 1u8.try_into().unwrap(), - funding_asset, - ))); - - // the remaining tokens should be zero - assert_eq!(inst.get_project_details(project_id).remaining_contribution_tokens, 0); - - // We can successfully finish the project - inst.finish_funding(project_id, None).unwrap(); - }; - - for decimals in 6..=18 { - decimal_test(decimals); - } - - // Since we use the same original price and allocation size and adjust for decimals, - // the USD and PLMC amounts should be the same - assert!(total_fundings_usd.iter().all(|x| *x == total_fundings_usd[0])); - assert!(total_fundings_plmc.iter().all(|x| *x == total_fundings_plmc[0])); - - // CT amounts however should be different from each other - let mut hash_set_1 = HashSet::new(); - for amount in total_fundings_ct { - assert!(!hash_set_1.contains(&amount)); - hash_set_1.insert(amount); - } - } - } -} - -#[cfg(test)] -mod contribute_extrinsic { - use super::*; - - #[cfg(test)] - mod success { - use super::*; - use frame_support::traits::fungible::InspectFreeze; - use pallet_balances::AccountData; - - #[test] - fn evaluation_bond_counts_towards_contribution() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - - const BOB: AccountId = 42069; - const CARL: AccountId = 420691; - let mut evaluations = default_evaluations(); - let bob_evaluation: UserToUSDBalance = (BOB, 1337 * USD_UNIT).into(); - let carl_evaluation: UserToUSDBalance = (CARL, 1337 * USD_UNIT).into(); - evaluations.push(bob_evaluation.clone()); - evaluations.push(carl_evaluation.clone()); - - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - evaluations, - default_bids(), - vec![], - ); - let ct_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); - let plmc_price = ::PriceProvider::get_decimals_aware_price( - PLMC_FOREIGN_ID, - USD_DECIMALS, - PLMC_DECIMALS, - ) - .unwrap(); - - let evaluation_plmc_bond = - inst.execute(|| Balances::balance_on_hold(&HoldReason::Evaluation(project_id).into(), &BOB)); - let slashable_plmc = ::EvaluatorSlash::get() * evaluation_plmc_bond; - let usable_plmc = evaluation_plmc_bond - slashable_plmc; - - let usable_usd = plmc_price.checked_mul_int(usable_plmc).unwrap(); - let slashable_usd = plmc_price.checked_mul_int(slashable_plmc).unwrap(); - - let usable_ct = ct_price.reciprocal().unwrap().saturating_mul_int(usable_usd); - let slashable_ct = ct_price.reciprocal().unwrap().saturating_mul_int(slashable_usd); - - // Can't contribute with only the evaluation bond - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BOB), - get_mock_jwt_with_cid( - BOB, - InvestorType::Retail, - generate_did_from_account(BOB), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - usable_ct + slashable_ct, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - ), - Error::::ParticipantNotEnoughFunds - ); - }); - - // Can partially use the usable evaluation bond (half in this case) - let contribution_usdt = - inst.calculate_contributed_funding_asset_spent(vec![(BOB, usable_ct / 2).into()], ct_price); - inst.mint_foreign_asset_to(contribution_usdt.clone()); - inst.execute(|| { - assert_ok!(Pallet::::contribute( - RuntimeOrigin::signed(BOB), - get_mock_jwt_with_cid( - BOB, - InvestorType::Retail, - generate_did_from_account(BOB), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - usable_ct / 2, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - )); - }); - - // Can use the full evaluation bond - let contribution_usdt = - inst.calculate_contributed_funding_asset_spent(vec![(CARL, usable_ct).into()], ct_price); - inst.mint_foreign_asset_to(contribution_usdt.clone()); - inst.execute(|| { - assert_ok!(Pallet::::contribute( - RuntimeOrigin::signed(CARL), - get_mock_jwt_with_cid( - CARL, - InvestorType::Retail, - generate_did_from_account(CARL), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - usable_ct, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - )); - }); - } - - #[test] - fn evaluation_bond_used_on_failed_bid_can_be_reused_on_contribution() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let bob = 42069; - let project_metadata = default_project_metadata(ISSUER_1); - // An evaluator that did a bid but it was not accepted at the end of the auction, can use that PLMC for contributing - let mut evaluations = default_evaluations(); - let bob_evaluation = (bob, 1337 * USD_UNIT).into(); - evaluations.push(bob_evaluation); - - let bids = default_bids(); - let bob_bid: BidParams = (bob, 1337 * CT_UNIT).into(); - let all_bids = bids.iter().chain(vec![bob_bid.clone()].iter()).cloned().collect_vec(); - - let project_id = - inst.create_auctioning_project(default_project_metadata(ISSUER_2), ISSUER_2, None, evaluations); - - let evaluation_plmc_bond = - inst.execute(|| Balances::balance_on_hold(&HoldReason::Evaluation(project_id).into(), &bob)); - let slashable_plmc_bond = ::EvaluatorSlash::get() * evaluation_plmc_bond; - let usable_plmc_bond = evaluation_plmc_bond - slashable_plmc_bond; - - let bids_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &all_bids, - project_metadata.clone(), - None, - true, - ); - inst.mint_plmc_to(bids_plmc.clone()); - - let bids_foreign = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &all_bids, - project_metadata.clone(), - None, - ); - inst.mint_foreign_asset_to(bids_foreign.clone()); - - inst.bid_for_users(project_id, bids).unwrap(); - - let auction_end = ::AuctionOpeningDuration::get() + - ::AuctionClosingDuration::get(); - inst.advance_time(auction_end - 1).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Auction); - inst.bid_for_users(project_id, vec![bob_bid]).unwrap(); - - inst.start_community_funding(project_id).unwrap(); - assert!(matches!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound(..))); - inst.start_remainder_or_end_funding(project_id).unwrap(); - - let plmc_price = ::PriceProvider::get_decimals_aware_price( - PLMC_FOREIGN_ID, - USD_DECIMALS, - PLMC_DECIMALS, - ) - .unwrap(); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - let usable_usd = plmc_price.saturating_mul_int(usable_plmc_bond); - let usable_ct = wap.reciprocal().unwrap().saturating_mul_int(usable_usd); - - let bob_contribution = (bob, 1337 * CT_UNIT).into(); - let contribution_usdt = inst.calculate_contributed_funding_asset_spent(vec![bob_contribution], wap); - inst.mint_foreign_asset_to(contribution_usdt.clone()); - inst.execute(|| { - assert_ok!(Pallet::::contribute( - RuntimeOrigin::signed(bob), - get_mock_jwt_with_cid( - bob, - InvestorType::Retail, - generate_did_from_account(bob), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - usable_ct, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - )); - }); - } - - #[test] - fn contribute_with_multiple_currencies() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - - let mut project_metadata_all = default_project_metadata(ISSUER_1); - project_metadata_all.participation_currencies = - vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT] - .try_into() - .unwrap(); - - let mut project_metadata_usdt = default_project_metadata(ISSUER_2); - project_metadata_usdt.participation_currencies = vec![AcceptedFundingAsset::USDT].try_into().unwrap(); - - let mut project_metadata_usdc = default_project_metadata(ISSUER_3); - project_metadata_usdc.participation_currencies = vec![AcceptedFundingAsset::USDC].try_into().unwrap(); - - let mut project_metadata_dot = default_project_metadata(ISSUER_4); - project_metadata_dot.participation_currencies = vec![AcceptedFundingAsset::DOT].try_into().unwrap(); - - let evaluations = default_evaluations(); - - let usdt_bids = default_bids() - .into_iter() - .map(|mut b| { - b.asset = AcceptedFundingAsset::USDT; - b - }) - .collect::>(); - - let usdc_bids = default_bids() - .into_iter() - .map(|mut b| { - b.asset = AcceptedFundingAsset::USDC; - b - }) - .collect::>(); - - let dot_bids = default_bids() - .into_iter() - .map(|mut b| { - b.asset = AcceptedFundingAsset::DOT; - b - }) - .collect::>(); - - let usdt_contribution = ContributionParams::new(BUYER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let usdc_contribution = ContributionParams::new(BUYER_2, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDC); - let dot_contribution = ContributionParams::new(BUYER_3, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::DOT); - - let project_id_all = inst.create_remainder_contributing_project( - project_metadata_all.clone(), - ISSUER_1, - None, - evaluations.clone(), - default_bids(), - vec![], - ); - let wap = inst.get_project_details(project_id_all).weighted_average_price.unwrap(); - - let plmc_fundings = inst.calculate_contributed_plmc_spent( - vec![usdt_contribution.clone(), usdc_contribution.clone(), dot_contribution.clone()], - wap, - false, - ); - let plmc_existential_deposits = plmc_fundings.accounts().existential_deposits(); - let plmc_all_mints = - inst.generic_map_operation(vec![plmc_fundings, plmc_existential_deposits], MergeOperation::Add); - inst.mint_plmc_to(plmc_all_mints.clone()); - inst.mint_plmc_to(plmc_all_mints.clone()); - inst.mint_plmc_to(plmc_all_mints.clone()); - - let usdt_fundings = inst.calculate_contributed_funding_asset_spent( - vec![usdt_contribution.clone(), usdc_contribution.clone(), dot_contribution.clone()], - wap, - ); - inst.mint_foreign_asset_to(usdt_fundings.clone()); - inst.mint_foreign_asset_to(usdt_fundings.clone()); - inst.mint_foreign_asset_to(usdt_fundings.clone()); - - assert_ok!(inst.contribute_for_users( - project_id_all, - vec![usdt_contribution.clone(), usdc_contribution.clone(), dot_contribution.clone()] - )); - - let project_id_usdt = inst.create_remainder_contributing_project( - project_metadata_usdt.clone(), - ISSUER_2, - None, - evaluations.clone(), - usdt_bids, - vec![], - ); - - assert_ok!(inst.contribute_for_users(project_id_usdt, vec![usdt_contribution.clone()])); - assert_err!( - inst.contribute_for_users(project_id_usdt, vec![usdc_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - assert_err!( - inst.contribute_for_users(project_id_usdt, vec![dot_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - - let project_id_usdc = inst.create_remainder_contributing_project( - project_metadata_usdc.clone(), - ISSUER_3, - None, - evaluations.clone(), - usdc_bids, - vec![], - ); - - assert_err!( - inst.contribute_for_users(project_id_usdc, vec![usdt_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - assert_ok!(inst.contribute_for_users(project_id_usdc, vec![usdc_contribution.clone()])); - assert_err!( - inst.contribute_for_users(project_id_usdc, vec![dot_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - - let project_id_dot = inst.create_remainder_contributing_project( - project_metadata_dot.clone(), - ISSUER_4, - None, - evaluations.clone(), - dot_bids, - vec![], - ); - - assert_err!( - inst.contribute_for_users(project_id_dot, vec![usdt_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - assert_err!( - inst.contribute_for_users(project_id_dot, vec![usdc_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - assert_ok!(inst.contribute_for_users(project_id_dot, vec![dot_contribution.clone()])); - } - - fn test_contribution_setup( - inst: &mut MockInstantiator, - project_id: ProjectId, - contributor: AccountIdOf, - investor_type: InvestorType, - u8_multiplier: u8, - ) -> DispatchResultWithPostInfo { - let project_policy = inst.get_project_metadata(project_id).policy_ipfs_cid.unwrap(); - let jwt = get_mock_jwt_with_cid( - contributor.clone(), - investor_type, - generate_did_from_account(contributor), - project_policy, - ); - let amount = 1000 * CT_UNIT; - let multiplier = Multiplier::force_new(u8_multiplier); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - if u8_multiplier > 0 { - let contribution = ContributionParams:: { - contributor: contributor.clone(), - amount, - multiplier, - asset: AcceptedFundingAsset::USDT, - }; - - let necessary_plmc = inst.calculate_contributed_plmc_spent(vec![contribution.clone()], wap, false); - let plmc_existential_amounts = necessary_plmc.accounts().existential_deposits(); - let necessary_usdt = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); - - inst.mint_plmc_to(necessary_plmc.clone()); - inst.mint_plmc_to(plmc_existential_amounts.clone()); - inst.mint_foreign_asset_to(necessary_usdt.clone()); - } - inst.execute(|| { - Pallet::::contribute( - RuntimeOrigin::signed(contributor), - jwt, - project_id, - amount, - multiplier, - AcceptedFundingAsset::USDT, - ) - }) - } - - #[test] - fn non_retail_multiplier_limits() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let mut project_metadata = default_project_metadata(ISSUER_1); - project_metadata.mainnet_token_max_supply = 80_000_000 * CT_UNIT; - project_metadata.total_allocation_size = 10_000_000 * CT_UNIT; - project_metadata.bidding_ticket_sizes = BiddingTicketSizes { - professional: TicketSize::new(5000 * USD_UNIT, None), - institutional: TicketSize::new(5000 * USD_UNIT, None), - phantom: Default::default(), - }; - project_metadata.contributing_ticket_sizes = ContributingTicketSizes { - retail: TicketSize::new(USD_UNIT, None), - professional: TicketSize::new(USD_UNIT, None), - institutional: TicketSize::new(USD_UNIT, None), - phantom: Default::default(), - }; - let evaluations = - inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); - let bids = inst.generate_bids_from_total_ct_percent( - project_metadata.clone(), - 50, - default_weights(), - default_bidders(), - default_multipliers(), - ); - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - evaluations, - bids, - vec![], - ); - - // Professional contributions: 0x multiplier should fail - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Professional, 0), - Error::::ForbiddenMultiplier - ); - // Professional contributions: 1 - 10x multiplier should work - for multiplier in 1..=10u8 { - assert_ok!(test_contribution_setup( - &mut inst, - project_id, - BUYER_1, - InvestorType::Professional, - multiplier - )); - } - // Professional contributions: >=11x multiplier should fail - for multiplier in 11..=50u8 { - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Professional, multiplier), - Error::::ForbiddenMultiplier - ); - } - - // Institutional contributions: 0x multiplier should fail - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_2, InvestorType::Institutional, 0), - Error::::ForbiddenMultiplier - ); - // Institutional contributions: 1 - 25x multiplier should work - for multiplier in 1..=25u8 { - assert_ok!(test_contribution_setup( - &mut inst, - project_id, - BUYER_2, - InvestorType::Institutional, - multiplier - )); - } - // Institutional contributions: >=26x multiplier should fail - for multiplier in 26..=50u8 { - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_2, InvestorType::Institutional, multiplier), - Error::::ForbiddenMultiplier - ); - } - } - - #[test] - fn retail_multiplier_limits() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let mut issuer: AccountId = 6969420; - - let mut create_project = |inst: &mut MockInstantiator| { - issuer += 1; - inst.create_remainder_contributing_project( - default_project_metadata(issuer), - issuer, - None, - default_evaluations(), - default_bids(), - vec![], - ) - }; - - let max_allowed_multipliers_map = vec![(2, 1), (4, 2), (9, 4), (24, 7), (25, 10)]; - - let mut previous_projects_created = 0; - for (projects_participated_amount, max_allowed_multiplier) in max_allowed_multipliers_map { - (previous_projects_created..projects_participated_amount - 1).for_each(|_| { - let project_id = create_project(&mut inst); - assert_ok!(test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Retail, 1)); - }); - - let project_id = create_project(&mut inst); - previous_projects_created = projects_participated_amount; - - // 0x multiplier should fail - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Retail, 0), - Error::::ForbiddenMultiplier - ); - - // Multipliers that should work - for multiplier in 1..=max_allowed_multiplier { - assert_ok!(test_contribution_setup( - &mut inst, - project_id, - BUYER_1, - InvestorType::Retail, - multiplier - )); - } - - // Multipliers that should NOT work - for multiplier in max_allowed_multiplier + 1..=50 { - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Retail, multiplier), - Error::::ForbiddenMultiplier - ); - } - } - } - - #[test] - fn did_with_winning_bid_can_contribute() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let mut evaluations = default_evaluations(); - evaluations.push((BIDDER_4, 1337 * USD_UNIT).into()); - - let successful_bids = vec![ - BidParams::new(BIDDER_1, 400_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - BidParams::new(BIDDER_2, 100_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ]; - let failing_bids_after_random_end = - vec![(BIDDER_3, 25_000 * CT_UNIT).into(), (BIDDER_4, 25_000 * CT_UNIT).into()]; - // This bids should fill the first bucket. - let failing_bids_sold_out = - vec![(BIDDER_5, 250_000 * CT_UNIT).into(), (BIDDER_6, 250_000 * CT_UNIT).into()]; - - let all_bids = failing_bids_sold_out - .iter() - .chain(successful_bids.iter()) - .chain(failing_bids_after_random_end.iter()) - .cloned() - .collect_vec(); - - let project_id = - inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); - - let plmc_fundings = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &all_bids.clone(), - project_metadata.clone(), - None, - true, - ); - inst.mint_plmc_to(plmc_fundings.clone()); - - let foreign_funding = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &all_bids.clone(), - project_metadata.clone(), - None, - ); - inst.mint_foreign_asset_to(foreign_funding.clone()); - - inst.bid_for_users(project_id, failing_bids_sold_out).unwrap(); - inst.bid_for_users(project_id, successful_bids).unwrap(); - inst.advance_time( - ::AuctionOpeningDuration::get() + - ::AuctionClosingDuration::get(), - ) - .unwrap(); - inst.bid_for_users(project_id, failing_bids_after_random_end).unwrap(); - inst.advance_time(2).unwrap(); - assert!(matches!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound(..))); - inst.start_remainder_or_end_funding(project_id).unwrap(); - - // Some low amount of plmc and usdt to cover a purchase of 10CTs. - let plmc_mints = vec![ - (BIDDER_3, 42069 * PLMC).into(), - (BIDDER_4, 42069 * PLMC).into(), - (BIDDER_5, 42069 * PLMC).into(), - (BIDDER_6, 42069 * PLMC).into(), - (BUYER_3, 42069 * PLMC).into(), - (BUYER_4, 42069 * PLMC).into(), - (BUYER_5, 42069 * PLMC).into(), - (BUYER_6, 42069 * PLMC).into(), - ]; - inst.mint_plmc_to(plmc_mints); - let usdt_mints = vec![ - (BIDDER_3, 42069 * CT_UNIT).into(), - (BIDDER_4, 42069 * CT_UNIT).into(), - (BIDDER_5, 42069 * CT_UNIT).into(), - (BIDDER_6, 42069 * CT_UNIT).into(), - (BUYER_3, 42069 * CT_UNIT).into(), - (BUYER_4, 42069 * CT_UNIT).into(), - (BUYER_5, 42069 * CT_UNIT).into(), - (BUYER_6, 42069 * CT_UNIT).into(), - ]; - inst.mint_foreign_asset_to(usdt_mints); - - let mut bid_should_succeed = |account, investor_type, did_acc| { - inst.execute(|| { - assert_ok!(Pallet::::contribute( - RuntimeOrigin::signed(account), - get_mock_jwt_with_cid( - account, - investor_type, - generate_did_from_account(did_acc), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - 10 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - )); - }); - }; - - // Bidder 3 has a losing bid due to bidding after the random end. His did should be able to contribute regardless of what investor type - // or account he uses to sign the transaction - bid_should_succeed(BIDDER_3, InvestorType::Institutional, BIDDER_3); - bid_should_succeed(BUYER_3, InvestorType::Institutional, BIDDER_3); - bid_should_succeed(BIDDER_3, InvestorType::Professional, BIDDER_3); - bid_should_succeed(BUYER_3, InvestorType::Professional, BIDDER_3); - bid_should_succeed(BIDDER_3, InvestorType::Retail, BIDDER_3); - bid_should_succeed(BUYER_3, InvestorType::Retail, BIDDER_3); - - // Bidder 4 has a losing bid due to bidding after the random end, and he was also an evaluator. Same conditions as before should apply. - bid_should_succeed(BIDDER_4, InvestorType::Institutional, BIDDER_4); - bid_should_succeed(BUYER_4, InvestorType::Institutional, BIDDER_4); - bid_should_succeed(BIDDER_4, InvestorType::Professional, BIDDER_4); - bid_should_succeed(BUYER_4, InvestorType::Professional, BIDDER_4); - bid_should_succeed(BIDDER_4, InvestorType::Retail, BIDDER_4); - bid_should_succeed(BUYER_4, InvestorType::Retail, BIDDER_4); - - // Bidder 5 has a losing bid due to CTs being sold out at his price point. Same conditions as before should apply. - bid_should_succeed(BIDDER_5, InvestorType::Institutional, BIDDER_5); - bid_should_succeed(BUYER_5, InvestorType::Institutional, BIDDER_5); - bid_should_succeed(BIDDER_5, InvestorType::Professional, BIDDER_5); - bid_should_succeed(BUYER_5, InvestorType::Professional, BIDDER_5); - bid_should_succeed(BIDDER_5, InvestorType::Retail, BIDDER_5); - bid_should_succeed(BUYER_5, InvestorType::Retail, BIDDER_5); - - // Bidder 6 has a losing bid due to CTs being sold out at his price point, and he was also an evaluator. Same conditions as before should apply. - bid_should_succeed(BIDDER_6, InvestorType::Institutional, BIDDER_6); - bid_should_succeed(BUYER_6, InvestorType::Institutional, BIDDER_6); - bid_should_succeed(BIDDER_6, InvestorType::Professional, BIDDER_6); - bid_should_succeed(BUYER_6, InvestorType::Professional, BIDDER_6); - bid_should_succeed(BIDDER_6, InvestorType::Retail, BIDDER_6); - bid_should_succeed(BUYER_6, InvestorType::Retail, BIDDER_6); - } - - #[test] - fn can_contribute_with_frozen_tokens_funding_failed() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(issuer); - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - issuer, - None, - default_evaluations(), - vec![], - vec![], - ); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - let contribution = ContributionParams::new(BUYER_4, 500 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let plmc_required = inst.calculate_contributed_plmc_spent(vec![contribution.clone()], wap, false); - let frozen_amount = plmc_required[0].plmc_amount; - let plmc_existential_deposits = plmc_required.accounts().existential_deposits(); - - inst.mint_plmc_to(plmc_existential_deposits); - inst.mint_plmc_to(plmc_required.clone()); - - inst.execute(|| { - mock::Balances::set_freeze(&(), &BUYER_4, plmc_required[0].plmc_amount).unwrap(); - }); - - let usdt_required = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); - inst.mint_foreign_asset_to(usdt_required); - - inst.execute(|| { - assert_noop!( - Balances::transfer_allow_death(RuntimeOrigin::signed(BUYER_4), ISSUER_1, frozen_amount,), - TokenError::Frozen - ); - }); - - inst.execute(|| { - assert_ok!(PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_4), - get_mock_jwt_with_cid( - BUYER_4, - InvestorType::Institutional, - generate_did_from_account(BUYER_4), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - contribution.amount, - contribution.multiplier, - contribution.asset - )); - }); - - inst.finish_funding(project_id, None).unwrap(); - - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); - - let free_balance = inst.get_free_plmc_balance_for(BUYER_4); - let bid_held_balance = - inst.get_reserved_plmc_balance_for(BUYER_4, HoldReason::Participation(project_id).into()); - let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BUYER_4)); - let account_data = inst.execute(|| System::account(&BUYER_4)).data; - - assert_eq!(free_balance, inst.get_ed()); - assert_eq!(bid_held_balance, frozen_amount); - assert_eq!(frozen_balance, frozen_amount); - let expected_account_data = AccountData { - free: inst.get_ed(), - reserved: frozen_amount, - frozen: frozen_amount, - flags: Default::default(), - }; - assert_eq!(account_data, expected_account_data); - - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); - - inst.execute(|| { - PolimecFunding::settle_failed_contribution(RuntimeOrigin::signed(BUYER_4), project_id, BUYER_4, 0) - .unwrap(); - }); - - let free_balance = inst.get_free_plmc_balance_for(BUYER_4); - let bid_held_balance = - inst.get_reserved_plmc_balance_for(BUYER_4, HoldReason::Evaluation(project_id).into()); - let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BUYER_4)); - let account_data = inst.execute(|| System::account(&BUYER_4)).data; - - assert_eq!(free_balance, inst.get_ed() + frozen_amount); - assert_eq!(bid_held_balance, Zero::zero()); - assert_eq!(frozen_balance, frozen_amount); - let expected_account_data = AccountData { - free: inst.get_ed() + frozen_amount, - reserved: Zero::zero(), - frozen: frozen_amount, - flags: Default::default(), - }; - assert_eq!(account_data, expected_account_data); - assert_eq!(account_data.frozen, account_data.free - inst.get_ed()); - } - - #[test] - fn can_contribute_with_frozen_tokens_funding_success() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(issuer); - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - issuer, - None, - default_evaluations(), - default_bids(), - vec![], - ); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - let contribution = ContributionParams::new(BUYER_4, 500 * CT_UNIT, 5u8, AcceptedFundingAsset::USDT); - let plmc_required = inst.calculate_contributed_plmc_spent(vec![contribution.clone()], wap, false); - let frozen_amount = plmc_required[0].plmc_amount; - let plmc_existential_deposits = plmc_required.accounts().existential_deposits(); - - inst.mint_plmc_to(plmc_existential_deposits); - inst.mint_plmc_to(plmc_required.clone()); - - inst.execute(|| { - mock::Balances::set_freeze(&(), &BUYER_4, plmc_required[0].plmc_amount).unwrap(); - }); - - let usdt_required = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); - inst.mint_foreign_asset_to(usdt_required); - - inst.execute(|| { - assert_noop!( - Balances::transfer_allow_death(RuntimeOrigin::signed(BUYER_4), ISSUER_1, frozen_amount,), - TokenError::Frozen - ); - }); - - inst.execute(|| { - assert_ok!(PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_4), - get_mock_jwt_with_cid( - BUYER_4, - InvestorType::Institutional, - generate_did_from_account(BUYER_4), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - contribution.amount, - contribution.multiplier, - contribution.asset - )); - }); - - inst.finish_funding(project_id, None).unwrap(); - - let decision_block = inst - .get_update_block(project_id, &UpdateType::ProjectDecision(FundingOutcomeDecision::AcceptFunding)) - .unwrap(); - inst.jump_to_block(decision_block); - - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); - - let free_balance = inst.get_free_plmc_balance_for(BUYER_4); - let bid_held_balance = - inst.get_reserved_plmc_balance_for(BUYER_4, HoldReason::Participation(project_id).into()); - let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BUYER_4)); - let account_data = inst.execute(|| System::account(&BUYER_4)).data; - - assert_eq!(free_balance, inst.get_ed()); - assert_eq!(bid_held_balance, frozen_amount); - assert_eq!(frozen_balance, frozen_amount); - let expected_account_data = AccountData { - free: inst.get_ed(), - reserved: frozen_amount, - frozen: frozen_amount, - flags: Default::default(), - }; - assert_eq!(account_data, expected_account_data); - - inst.execute(|| { - PolimecFunding::settle_successful_contribution(RuntimeOrigin::signed(BUYER_4), project_id, BUYER_4, 0) - .unwrap(); - }); - - let free_balance = inst.get_free_plmc_balance_for(BUYER_4); - let bid_held_balance = - inst.get_reserved_plmc_balance_for(BUYER_4, HoldReason::Participation(project_id).into()); - let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BUYER_4)); - let account_data = inst.execute(|| System::account(&BUYER_4)).data; - - assert_eq!(free_balance, inst.get_ed()); - assert_eq!(bid_held_balance, frozen_amount); - assert_eq!(frozen_balance, frozen_amount); - let expected_account_data = AccountData { - free: inst.get_ed(), - reserved: frozen_amount, - frozen: frozen_amount, - flags: Default::default(), - }; - assert_eq!(account_data, expected_account_data); - - let vest_duration = - MultiplierOf::::new(5u8).unwrap().calculate_vesting_duration::(); - let now = inst.current_block(); - inst.jump_to_block(now + vest_duration + 1u64); - inst.execute(|| { - assert_ok!(mock::LinearRelease::vest( - RuntimeOrigin::signed(BUYER_4), - HoldReason::Participation(project_id).into() - )); - }); - - let free_balance = inst.get_free_plmc_balance_for(BUYER_4); - let bid_held_balance = - inst.get_reserved_plmc_balance_for(BUYER_4, HoldReason::Participation(project_id).into()); - let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BUYER_4)); - let account_data = inst.execute(|| System::account(&BUYER_4)).data; - - assert_eq!(free_balance, inst.get_ed() + frozen_amount); - assert_eq!(bid_held_balance, Zero::zero()); - assert_eq!(frozen_balance, frozen_amount); - let expected_account_data = AccountData { - free: inst.get_ed() + frozen_amount, - reserved: Zero::zero(), - frozen: frozen_amount, - flags: Default::default(), - }; - assert_eq!(account_data, expected_account_data); - } - - #[test] - fn participant_was_evaluator_and_bidder() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let participant = 42069u32; - let project_metadata = default_project_metadata(issuer); - let mut evaluations = default_evaluations(); - evaluations.push((participant, 100 * USD_UNIT).into()); - let mut bids = default_bids(); - bids.push(BidParams::new(participant, 1000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT)); - let community_contributions = default_community_buys(); - let mut remainder_contributions = default_remainder_buys(); - remainder_contributions.push(ContributionParams::new( - participant, - 10 * CT_UNIT, - 1u8, - AcceptedFundingAsset::USDT, - )); - - let _project_id = inst.create_finished_project( - project_metadata.clone(), - issuer, - None, - evaluations, - bids, - community_contributions, - remainder_contributions, - ); - } - } - - #[cfg(test)] - mod failure { - use super::*; - use frame_support::traits::{ - fungible::Mutate, - fungibles::Mutate as OtherMutate, - tokens::{Fortitude, Precision}, - }; - - #[test] - fn contribution_errors_if_user_limit_is_reached() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_remainder_contributing_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - vec![], - ); - const CONTRIBUTOR: AccountIdOf = 420; - - let project_details = inst.get_project_details(project_id); - let token_price = project_details.weighted_average_price.unwrap(); - - // Create a contribution vector that will reach the limit of contributions for a user-project - let token_amount: BalanceOf = CT_UNIT; - let range = 0..::MaxContributionsPerUser::get(); - let contributions: Vec> = range - .map(|_| ContributionParams::new(CONTRIBUTOR, token_amount, 1u8, AcceptedFundingAsset::USDT)) - .collect(); - - let plmc_funding = inst.calculate_contributed_plmc_spent(contributions.clone(), token_price, false); - let plmc_existential_deposits = plmc_funding.accounts().existential_deposits(); - - let foreign_funding = inst.calculate_contributed_funding_asset_spent(contributions.clone(), token_price); - - inst.mint_plmc_to(plmc_funding.clone()); - inst.mint_plmc_to(plmc_existential_deposits.clone()); - - inst.mint_foreign_asset_to(foreign_funding.clone()); - - // Reach up to the limit of contributions for a user-project - assert!(inst.contribute_for_users(project_id, contributions).is_ok()); - - // Try to contribute again, but it should fail because the limit of contributions for a user-project was reached. - let over_limit_contribution = - ContributionParams::new(CONTRIBUTOR, token_amount, 1u8, AcceptedFundingAsset::USDT); - assert!(inst.contribute_for_users(project_id, vec![over_limit_contribution]).is_err()); - - // Check that the right amount of PLMC is bonded, and funding currency is transferred - let contributor_post_buy_plmc_balance = - inst.execute(|| ::NativeCurrency::balance(&CONTRIBUTOR)); - let contributor_post_buy_foreign_asset_balance = inst.execute(|| { - ::FundingCurrency::balance( - AcceptedFundingAsset::USDT.to_assethub_id(), - CONTRIBUTOR, - ) - }); - - assert_eq!(contributor_post_buy_plmc_balance, inst.get_ed()); - assert_eq!(contributor_post_buy_foreign_asset_balance, 0); - - let plmc_bond_stored = inst.execute(|| { - ::NativeCurrency::balance_on_hold( - &HoldReason::Participation(project_id.into()).into(), - &CONTRIBUTOR, - ) - }); - let foreign_asset_contributions_stored = inst.execute(|| { - Contributions::::iter_prefix_values((project_id, CONTRIBUTOR)) - .map(|c| c.funding_asset_amount) - .sum::>() - }); - - assert_eq!(plmc_bond_stored, inst.sum_balance_mappings(vec![plmc_funding.clone()])); - assert_eq!(foreign_asset_contributions_stored, inst.sum_foreign_mappings(vec![foreign_funding.clone()])); - } - - #[test] - fn issuer_cannot_contribute_his_project() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - default_community_buys(), - ); - assert_err!( - inst.execute(|| crate::Pallet::::do_contribute( - &(&ISSUER_1 + 1), - project_id, - 500 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - generate_did_from_account(ISSUER_1), - InvestorType::Institutional, - project_metadata.clone().policy_ipfs_cid.unwrap(), - )), - Error::::ParticipationToOwnProject - ); - } - - #[test] - fn per_credential_type_ticket_size_minimums() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = ProjectMetadata { - token_information: default_token_information(), - mainnet_token_max_supply: 8_000_000 * CT_UNIT, - total_allocation_size: 1_000_000 * CT_UNIT, - auction_round_allocation_percentage: Percent::from_percent(50u8), - minimum_price: PriceProviderOf::::calculate_decimals_aware_price( - PriceOf::::from_float(10.0), - USD_DECIMALS, - CT_DECIMALS, - ) - .unwrap(), - bidding_ticket_sizes: BiddingTicketSizes { - professional: TicketSize::new(8000 * USD_UNIT, None), - institutional: TicketSize::new(20_000 * USD_UNIT, None), - phantom: Default::default(), - }, - contributing_ticket_sizes: ContributingTicketSizes { - retail: TicketSize::new(10 * USD_UNIT, None), - professional: TicketSize::new(100_000 * USD_UNIT, None), - institutional: TicketSize::new(200_000 * USD_UNIT, None), - phantom: Default::default(), - }, - participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), - funding_destination_account: ISSUER_1, - policy_ipfs_cid: Some(ipfs_hash()), - }; - - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - vec![], - ); - - inst.mint_plmc_to(vec![ - (BUYER_4, 50_000 * PLMC).into(), - (BUYER_5, 50_000 * PLMC).into(), - (BUYER_6, 50_000 * PLMC).into(), - ]); - - inst.mint_foreign_asset_to(vec![ - (BUYER_4, 50_000 * USDT_UNIT).into(), - (BUYER_5, 50_000 * USDT_UNIT).into(), - (BUYER_6, 50_000 * USDT_UNIT).into(), - ]); - - // contribution below 1 CT (10 USD) should fail for retail - let jwt = get_mock_jwt_with_cid( - BUYER_4, - InvestorType::Retail, - generate_did_from_account(BUYER_4), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BUYER_4), - jwt, - project_id, - CT_UNIT / 2, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - ), - Error::::TooLow - ); - }); - // contribution below 10_000 CT (100k USD) should fail for professionals - let jwt = get_mock_jwt_with_cid( - BUYER_5, - InvestorType::Professional, - generate_did_from_account(BUYER_5), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BUYER_5), - jwt, - project_id, - 9_999, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - ), - Error::::TooLow - ); - }); - - // contribution below 20_000 CT (200k USD) should fail for institutionals - let jwt = get_mock_jwt_with_cid( - BUYER_6, - InvestorType::Institutional, - generate_did_from_account(BUYER_6), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BUYER_6), - jwt, - project_id, - 19_999, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - ), - Error::::TooLow - ); - }); - } - - #[test] - fn per_credential_type_ticket_size_maximums() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = ProjectMetadata { - token_information: default_token_information(), - mainnet_token_max_supply: 8_000_000 * CT_UNIT, - total_allocation_size: 1_000_000 * CT_UNIT, - auction_round_allocation_percentage: Percent::from_percent(50u8), - minimum_price: PriceProviderOf::::calculate_decimals_aware_price( - PriceOf::::from_float(10.0), - USD_DECIMALS, - CT_DECIMALS, - ) - .unwrap(), - bidding_ticket_sizes: BiddingTicketSizes { - professional: TicketSize::new(5000 * USD_UNIT, None), - institutional: TicketSize::new(5000 * USD_UNIT, None), - phantom: Default::default(), - }, - contributing_ticket_sizes: ContributingTicketSizes { - retail: TicketSize::new(USD_UNIT, Some(300_000 * USD_UNIT)), - professional: TicketSize::new(USD_UNIT, Some(20_000 * USD_UNIT)), - institutional: TicketSize::new(USD_UNIT, Some(50_000 * USD_UNIT)), - phantom: Default::default(), - }, - participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), - funding_destination_account: ISSUER_1, - policy_ipfs_cid: Some(ipfs_hash()), - }; - - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - vec![], - ); - - inst.mint_plmc_to(vec![ - (BUYER_4, 500_000 * PLMC).into(), - (BUYER_5, 500_000 * PLMC).into(), - (BUYER_6, 500_000 * PLMC).into(), - (BUYER_7, 500_000 * PLMC).into(), - (BUYER_8, 500_000 * PLMC).into(), - (BUYER_9, 500_000 * PLMC).into(), - ]); - - inst.mint_foreign_asset_to(vec![ - (BUYER_4, 500_000 * USDT_UNIT).into(), - (BUYER_5, 500_000 * USDT_UNIT).into(), - (BUYER_6, 500_000 * USDT_UNIT).into(), - (BUYER_7, 500_000 * USDT_UNIT).into(), - (BUYER_8, 500_000 * USDT_UNIT).into(), - (BUYER_9, 500_000 * USDT_UNIT).into(), - ]); - - // total contributions with same DID above 30k CT (300k USD) should fail for retail - inst.execute(|| { - assert_ok!(Pallet::::do_contribute( - &BUYER_4, - project_id, - 28_000 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - generate_did_from_account(BUYER_4), - InvestorType::Retail, - project_metadata.clone().policy_ipfs_cid.unwrap(), - )); - }); - inst.execute(|| { - assert_noop!( - Pallet::::do_contribute( - &BUYER_5, - project_id, - 2001 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - // note we use the same did as bidder 1, on a different account - generate_did_from_account(BUYER_4), - InvestorType::Retail, - project_metadata.clone().policy_ipfs_cid.unwrap(), - ), - Error::::TooHigh - ); - }); - // bidding 2k total works - inst.execute(|| { - assert_ok!(Pallet::::do_contribute( - &BUYER_5, - project_id, - 2000 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - // note we use the same did as bidder 1, on a different account - generate_did_from_account(BUYER_4), - InvestorType::Retail, - project_metadata.clone().policy_ipfs_cid.unwrap(), - )); - }); - - // total contributions with same DID above 2k CT (20k USD) should fail for professionals - inst.execute(|| { - assert_ok!(Pallet::::do_contribute( - &BUYER_6, - project_id, - 1800 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - generate_did_from_account(BUYER_6), - InvestorType::Professional, - project_metadata.clone().policy_ipfs_cid.unwrap(), - )); - }); - inst.execute(|| { - assert_noop!( - Pallet::::do_contribute( - &BUYER_7, - project_id, - 201 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - // note we use the same did as bidder 1, on a different account - generate_did_from_account(BUYER_6), - InvestorType::Professional, - project_metadata.clone().policy_ipfs_cid.unwrap(), - ), - Error::::TooHigh - ); - }); - // bidding 2k total works - inst.execute(|| { - assert_ok!(Pallet::::do_contribute( - &BUYER_7, - project_id, - 200 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - // note we use the same did as bidder 1, on a different account - generate_did_from_account(BUYER_6), - InvestorType::Professional, - project_metadata.clone().policy_ipfs_cid.unwrap(), - )); - }); - - // total contributions with same DID above 5k CT (50 USD) should fail for institutionals - inst.execute(|| { - assert_ok!(Pallet::::do_contribute( - &BUYER_8, - project_id, - 4690 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - generate_did_from_account(BUYER_8), - InvestorType::Institutional, - project_metadata.clone().policy_ipfs_cid.unwrap(), - )); - }); - inst.execute(|| { - assert_noop!( - Pallet::::do_contribute( - &BUYER_9, - project_id, - 311 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - // note we use the same did as bidder 3, on a different account - generate_did_from_account(BUYER_8), - InvestorType::Institutional, - project_metadata.clone().policy_ipfs_cid.unwrap(), - ), - Error::::TooHigh - ); - }); - // bidding 5k total works - inst.execute(|| { - assert_ok!(Pallet::::do_contribute( - &BUYER_9, - project_id, - 310 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - // note we use the same did as bidder 3, on a different account - generate_did_from_account(BUYER_8), - InvestorType::Institutional, - project_metadata.clone().policy_ipfs_cid.unwrap(), - )); - }); - } - - #[test] - fn insufficient_funds() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - vec![], - ); - - let jwt = get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Retail, - generate_did_from_account(BUYER_1), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - let contribution = ContributionParams::new(BUYER_1, 1_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - // 1 unit less native asset than needed - let plmc_funding = inst.calculate_contributed_plmc_spent(vec![contribution.clone()], wap, false); - let plmc_existential_deposits = plmc_funding.accounts().existential_deposits(); - inst.mint_plmc_to(plmc_funding.clone()); - inst.mint_plmc_to(plmc_existential_deposits.clone()); - inst.execute(|| Balances::burn_from(&BUYER_1, 1, Precision::BestEffort, Fortitude::Force)).unwrap(); - - let foreign_funding = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); - inst.mint_foreign_asset_to(foreign_funding.clone()); - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BUYER_1), - jwt.clone(), - project_id, - 1_000 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - ), - Error::::ParticipantNotEnoughFunds - ); - }); - - // 1 unit less funding asset than needed - let plmc_funding = inst.calculate_contributed_plmc_spent(vec![contribution.clone()], wap, false); - let plmc_existential_deposits = plmc_funding.accounts().existential_deposits(); - inst.mint_plmc_to(plmc_funding.clone()); - inst.mint_plmc_to(plmc_existential_deposits.clone()); - let foreign_funding = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); - - inst.execute(|| ForeignAssets::set_balance(AcceptedFundingAsset::USDT.to_assethub_id(), &BUYER_1, 0)); - inst.mint_foreign_asset_to(foreign_funding.clone()); - - inst.execute(|| { - ForeignAssets::burn_from( - AcceptedFundingAsset::USDT.to_assethub_id(), - &BUYER_1, - 100, - Precision::BestEffort, - Fortitude::Force, - ) - }) - .unwrap(); - - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BUYER_1), - jwt, - project_id, - 1_000 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT, - ), - Error::::ParticipantNotEnoughFunds - ); - }); - } - - #[test] - fn called_outside_remainder_round() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - ISSUER_4, - None, - default_evaluations(), - default_bids(), - ); - - inst.execute(|| { - assert_noop!( - PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_1), - get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Retail, - generate_did_from_account(BUYER_1), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - 1000 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT - ), - Error::::IncorrectRound - ); - }); - } - - #[test] - fn contribute_with_unaccepted_currencies() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - - let mut project_metadata_usdt = default_project_metadata(ISSUER_2); - project_metadata_usdt.participation_currencies = vec![AcceptedFundingAsset::USDT].try_into().unwrap(); - - let mut project_metadata_usdc = default_project_metadata(ISSUER_3); - project_metadata_usdc.participation_currencies = vec![AcceptedFundingAsset::USDC].try_into().unwrap(); - - let evaluations = default_evaluations(); - - let usdt_bids = default_bids() - .into_iter() - .map(|mut b| { - b.asset = AcceptedFundingAsset::USDT; - b - }) - .collect::>(); - - let usdc_bids = default_bids() - .into_iter() - .map(|mut b| { - b.asset = AcceptedFundingAsset::USDC; - b - }) - .collect::>(); - - let usdt_contribution = ContributionParams::new(BUYER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let usdc_contribution = ContributionParams::new(BUYER_2, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDC); - let dot_contribution = ContributionParams::new(BUYER_3, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::DOT); - - let project_id_usdc = inst.create_remainder_contributing_project( - project_metadata_usdc, - ISSUER_3, - None, - evaluations.clone(), - usdc_bids, - vec![], - ); - assert_err!( - inst.contribute_for_users(project_id_usdc, vec![usdt_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - - let project_id_usdt = inst.create_remainder_contributing_project( - project_metadata_usdt, - ISSUER_2, - None, - evaluations.clone(), - usdt_bids, - vec![], - ); - assert_err!( - inst.contribute_for_users(project_id_usdt, vec![usdc_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - assert_err!( - inst.contribute_for_users(project_id_usdt, vec![dot_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - } - - #[test] - fn cannot_use_evaluation_bond_on_another_project_contribution() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata_1 = default_project_metadata(ISSUER_1); - let project_metadata_2 = default_project_metadata(ISSUER_2); - - let mut evaluations_1 = default_evaluations(); - let evaluations_2 = default_evaluations(); - - let evaluator_contributor = 69; - let evaluation_amount = 420 * USD_UNIT; - let evaluator_contribution = - ContributionParams::new(evaluator_contributor, 600 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - evaluations_1.push((evaluator_contributor, evaluation_amount).into()); - - let _project_id_1 = inst.create_remainder_contributing_project( - project_metadata_1.clone(), - ISSUER_1, - None, - evaluations_1, - default_bids(), - vec![], - ); - let project_id_2 = inst.create_remainder_contributing_project( - project_metadata_2.clone(), - ISSUER_2, - None, - evaluations_2, - default_bids(), - vec![], - ); - - let wap = inst.get_project_details(project_id_2).weighted_average_price.unwrap(); - - // Necessary Mints - let already_bonded_plmc = inst - .calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount).into()], false)[0] - .plmc_amount; - let usable_evaluation_plmc = - already_bonded_plmc - ::EvaluatorSlash::get() * already_bonded_plmc; - let necessary_plmc_for_contribution = - inst.calculate_contributed_plmc_spent(vec![evaluator_contribution.clone()], wap, false)[0].plmc_amount; - let necessary_usdt_for_contribution = - inst.calculate_contributed_funding_asset_spent(vec![evaluator_contribution.clone()], wap); - inst.mint_plmc_to(vec![UserToPLMCBalance::new( - evaluator_contributor, - necessary_plmc_for_contribution - usable_evaluation_plmc, - )]); - inst.mint_foreign_asset_to(necessary_usdt_for_contribution); - - inst.execute(|| { - assert_noop!( - PolimecFunding::contribute( - RuntimeOrigin::signed(evaluator_contributor), - get_mock_jwt_with_cid( - evaluator_contributor, - InvestorType::Retail, - generate_did_from_account(evaluator_contributor), - project_metadata_2.clone().policy_ipfs_cid.unwrap(), - ), - project_id_2, - evaluator_contribution.amount, - evaluator_contribution.multiplier, - evaluator_contribution.asset - ), - Error::::ParticipantNotEnoughFunds - ); - }); - } - - #[test] - fn wrong_policy_on_jwt() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - vec![], - ); - - inst.execute(|| { - assert_noop!( - PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_1), - get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Retail, - generate_did_from_account(BUYER_1), - "wrong_cid".as_bytes().to_vec().try_into().unwrap() - ), - project_id, - 5000 * CT_UNIT, - 1u8.try_into().unwrap(), - AcceptedFundingAsset::USDT - ), - Error::::PolicyMismatch - ); - }); - } - } -} diff --git a/pallets/funding/src/tests/7_settlement.rs b/pallets/funding/src/tests/6_settlement.rs similarity index 93% rename from pallets/funding/src/tests/7_settlement.rs rename to pallets/funding/src/tests/6_settlement.rs index f35835ea5..d4fa85c51 100644 --- a/pallets/funding/src/tests/7_settlement.rs +++ b/pallets/funding/src/tests/6_settlement.rs @@ -11,7 +11,7 @@ mod round_flow { #[test] fn can_fully_settle_accepted_project() { let percentage = 100u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, true); + 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); @@ -27,7 +27,7 @@ mod round_flow { #[test] fn can_fully_settle_failed_project() { let percentage = 33u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, true); + 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); @@ -54,7 +54,7 @@ mod settle_successful_evaluation_extrinsic { let percentage = 89u64; let (mut inst, project_id) = - create_project_with_funding_percentage(percentage, Some(FundingOutcomeDecision::AcceptFunding), true); + create_project_with_funding_percentage(percentage, true); let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); let evaluator = first_evaluation.evaluator; @@ -80,7 +80,7 @@ mod settle_successful_evaluation_extrinsic { fn evaluation_slashed() { let percentage = 50u64; let (mut inst, project_id) = - create_project_with_funding_percentage(percentage, Some(FundingOutcomeDecision::AcceptFunding), true); + create_project_with_funding_percentage(percentage, true); let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); let evaluator = first_evaluation.evaluator; @@ -136,8 +136,7 @@ mod settle_successful_evaluation_extrinsic { ), vec![], ); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful)); // The rewards are calculated as follows: // Data: @@ -228,7 +227,7 @@ mod settle_successful_evaluation_extrinsic { #[test] fn cannot_settle_twice() { let percentage = 100u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, true); + 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(|| { @@ -254,7 +253,7 @@ mod settle_successful_evaluation_extrinsic { #[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 (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; @@ -275,7 +274,7 @@ mod settle_successful_evaluation_extrinsic { #[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 (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; @@ -331,8 +330,7 @@ mod settle_successful_bid_extrinsic { vec![], ); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful)); // First bid assertions let stored_bid = inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); @@ -516,7 +514,7 @@ mod settle_successful_bid_extrinsic { 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(..))); // Mint the necessary community contribution balances let final_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); @@ -532,10 +530,9 @@ mod settle_successful_bid_extrinsic { 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(); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful)); + 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); @@ -572,7 +569,7 @@ mod settle_successful_bid_extrinsic { #[test] fn cannot_settle_twice() { let percentage = 100u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, true); + 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(|| { @@ -598,7 +595,7 @@ mod settle_successful_bid_extrinsic { #[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 (mut inst, project_id) = create_project_with_funding_percentage(percentage, true); let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); let bidder = first_bid.bidder; @@ -618,7 +615,7 @@ mod settle_successful_bid_extrinsic { #[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 (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; @@ -708,10 +705,8 @@ mod settle_successful_contribution_extrinsic { )); }); - inst.finish_funding(project_id, None).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); - let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_block); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful)); // First contribution assertions let stored_contribution = @@ -862,7 +857,7 @@ mod settle_successful_contribution_extrinsic { #[test] fn cannot_settle_twice() { let percentage = 100u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, true); + 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(|| { @@ -888,7 +883,7 @@ mod settle_successful_contribution_extrinsic { #[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 (mut inst, project_id) = create_project_with_funding_percentage(percentage, true); let first_contribution = inst.get_contributions(project_id).into_iter().next().unwrap(); let contributor = first_contribution.contributor; @@ -908,7 +903,7 @@ mod settle_successful_contribution_extrinsic { #[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 (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(|| { @@ -934,38 +929,11 @@ mod settle_failed_evaluation_extrinsic { 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 percentage = 20u64; let (mut inst, project_id) = - create_project_with_funding_percentage(percentage, Some(FundingOutcomeDecision::RejectFunding), true); + create_project_with_funding_percentage(percentage, true); let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); let evaluator = first_evaluation.evaluator; @@ -976,6 +944,7 @@ mod settle_failed_evaluation_extrinsic { EvaluatorsOutcomeOf::::Slashed ); + dbg!(inst.get_project_details(project_id).status); assert_ok!(inst.execute(|| PolimecFunding::settle_failed_evaluation( RuntimeOrigin::signed(evaluator), project_id, @@ -1000,7 +969,7 @@ mod settle_failed_evaluation_extrinsic { #[test] fn cannot_settle_twice() { let percentage = 33u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, true); + 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(|| { @@ -1026,7 +995,7 @@ mod settle_failed_evaluation_extrinsic { #[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 (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; @@ -1047,7 +1016,7 @@ mod settle_failed_evaluation_extrinsic { #[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 (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; @@ -1102,9 +1071,8 @@ mod settle_failed_bid_extrinsic { 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); + + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed)); // First bid assertions let stored_bid = inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); @@ -1206,7 +1174,7 @@ mod settle_failed_bid_extrinsic { #[test] fn cannot_settle_twice() { let percentage = 33u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, true); + 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(|| { @@ -1232,7 +1200,7 @@ mod settle_failed_bid_extrinsic { #[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 (mut inst, project_id) = create_project_with_funding_percentage(percentage, true); let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); let bidder = first_bid.bidder; inst.execute(|| { @@ -1251,7 +1219,7 @@ mod settle_failed_bid_extrinsic { #[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 (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; @@ -1341,10 +1309,8 @@ mod settle_failed_contribution_extrinsic { )); }); - 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); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed)); // First contribution assertions let stored_contribution = @@ -1473,7 +1439,7 @@ mod settle_failed_contribution_extrinsic { #[test] fn cannot_settle_twice() { let percentage = 33u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None, true); + 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(|| { @@ -1499,7 +1465,7 @@ mod settle_failed_contribution_extrinsic { #[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 (mut inst, project_id) = create_project_with_funding_percentage(percentage, true); let first_contribution = inst.get_contributions(project_id).into_iter().next().unwrap(); let contributor = first_contribution.contributor; @@ -1519,7 +1485,7 @@ mod settle_failed_contribution_extrinsic { #[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 (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; 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..ddeec6ff0 --- /dev/null +++ b/pallets/funding/src/tests/7_ct_migration.rs @@ -0,0 +1,289 @@ +// 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/8_ct_migration.rs b/pallets/funding/src/tests/8_ct_migration.rs deleted file mode 100644 index bf9349de8..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 602680a52..0e8f47c0c 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 43803e434..1d5b4b7cd 100644 --- a/pallets/funding/src/tests/mod.rs +++ b/pallets/funding/src/tests/mod.rs @@ -61,19 +61,17 @@ const BUYER_9: AccountId = 49; mod application; #[path = "3_auction.rs"] mod auction; -#[path = "4_community.rs"] +#[path = "4_contribution.rs"] mod community; -#[path = "8_ct_migration.rs"] +#[path = "7_ct_migration.rs"] mod ct_migration; #[path = "2_evaluation.rs"] mod evaluation; -#[path = "6_funding_end.rs"] +#[path = "5_funding_end.rs"] mod funding_end; mod misc; -#[path = "5_remainder.rs"] -mod remainder; mod runtime_api; -#[path = "7_settlement.rs"] +#[path = "6_settlement.rs"] mod settlement; pub mod defaults { @@ -371,7 +369,6 @@ pub mod defaults { pub fn create_project_with_funding_percentage( percentage: u64, - maybe_decision: Option, start_settlement: bool, ) -> (MockInstantiator, ProjectId) { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); @@ -397,40 +394,8 @@ pub fn create_project_with_funding_percentage( let project_id = inst.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids, contributions, vec![]); - match inst.get_project_details(project_id).status { - ProjectStatus::FundingSuccessful => { - assert!(percentage >= 33); - }, - ProjectStatus::FundingFailed => { - assert!(percentage <= 33); - }, - _ => panic!("unexpected project status"), - }; - if start_settlement { - let settlement_execution = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); - inst.jump_to_block(settlement_execution); - - let funding_sucessful = match percentage { - 0..=33 => false, - 34..=89 if matches!(maybe_decision, Some(FundingOutcomeDecision::RejectFunding)) => false, - 34..=89 if matches!(maybe_decision, Some(FundingOutcomeDecision::AcceptFunding)) => true, - 90..=100 => true, - _ => panic!("unexpected percentage"), - }; - if funding_sucessful { - assert_eq!( - inst.get_project_details(project_id).status, - ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful) - ); - inst.test_ct_created_for(project_id); - } else { - assert_eq!( - inst.get_project_details(project_id).status, - ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed) - ); - inst.test_ct_not_created_for(project_id); - } + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(_))); } (inst, project_id) @@ -447,7 +412,6 @@ pub fn create_finished_project_with_usd_raised( project_metadata.minimum_price.reciprocal().unwrap().saturating_mul_int(usd_target); project_metadata.auction_round_allocation_percentage = Percent::from_percent(50u8); - dbg!(project_metadata.minimum_price); let required_price = if usd_raised <= usd_target { project_metadata.minimum_price } else { @@ -462,7 +426,6 @@ pub fn create_finished_project_with_usd_raised( // selling all the CTs, we need the price to be double FixedU128::from_rational(2, 1) * required_price }; - dbg!(required_price); let evaluations = default_evaluations(); @@ -472,7 +435,6 @@ pub fn create_finished_project_with_usd_raised( let project_details = inst.get_project_details(project_id); let wap = project_details.weighted_average_price.unwrap(); - dbg!(wap); let usd_raised_so_far = project_details.funding_amount_reached_usd; let usd_remaining = usd_raised - usd_raised_so_far; @@ -489,14 +451,11 @@ pub fn create_finished_project_with_usd_raised( 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(); - } + + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); let project_details = inst.get_project_details(project_id); - dbg!(project_details.remaining_contribution_tokens); - assert_eq!(project_details.status, ProjectStatus::FundingSuccessful); + // We are happy if the amount raised is 99.999 of what we wanted assert_close_enough!(project_details.funding_amount_reached_usd, usd_raised, Perquintill::from_float(0.999)); assert_eq!(project_details.fundraising_target_usd, usd_target); diff --git a/pallets/funding/src/tests/runtime_api.rs b/pallets/funding/src/tests/runtime_api.rs index 462191954..a9a719adb 100644 --- a/pallets/funding/src/tests/runtime_api.rs +++ b/pallets/funding/src/tests/runtime_api.rs @@ -1,707 +1,707 @@ -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_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]); +// }); +// } diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index ddae6c917..eefaf8559 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -699,7 +699,7 @@ pub mod inner_types { Application, EvaluationRound, AuctionInitializePeriod, - Auction, + AuctionRound, CommunityRound(BlockNumber), FundingFailed, FundingSuccessful,