From ea242c4499ab4bd6f2c73ae2050dbd578e9eccc1 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Fri, 7 Feb 2025 10:51:52 +0100 Subject: [PATCH] General Cleanup --- pallets/funding/src/benchmarking.rs | 21 +- .../funding/src/functions/1_application.rs | 4 + pallets/funding/src/functions/2_evaluation.rs | 8 +- pallets/funding/src/functions/3_auction.rs | 18 +- .../{5_funding_end.rs => 4_funding_end.rs} | 7 +- .../{6_settlement.rs => 5_settlement.rs} | 15 +- .../{7_ct_migration.rs => 6_ct_migration.rs} | 2 + pallets/funding/src/functions/mod.rs | 8 +- .../src/{ => functions}/runtime_api.rs | 0 .../funding/src/instantiator/calculations.rs | 106 +- .../src/instantiator/chain_interactions.rs | 66 +- pallets/funding/src/instantiator/macros.rs | 45 - pallets/funding/src/instantiator/mod.rs | 2 +- pallets/funding/src/instantiator/types.rs | 73 -- pallets/funding/src/lib.rs | 74 +- pallets/funding/src/mock.rs | 2 +- pallets/funding/src/tests/1_application.rs | 29 +- pallets/funding/src/tests/2_evaluation.rs | 394 +++--- pallets/funding/src/tests/3_auction.rs | 1097 ++++++++--------- .../{5_funding_end.rs => 4_funding_end.rs} | 1 - .../{6_settlement.rs => 5_settlement.rs} | 4 +- .../{7_ct_migration.rs => 6_ct_migration.rs} | 3 - pallets/funding/src/tests/mod.rs | 39 +- pallets/funding/src/tests/runtime_api.rs | 5 - pallets/funding/src/types.rs | 2 +- polimec-common/common/src/credentials/mod.rs | 5 +- runtimes/polimec/src/lib.rs | 8 +- 27 files changed, 857 insertions(+), 1181 deletions(-) rename pallets/funding/src/functions/{5_funding_end.rs => 4_funding_end.rs} (88%) rename pallets/funding/src/functions/{6_settlement.rs => 5_settlement.rs} (93%) rename pallets/funding/src/functions/{7_ct_migration.rs => 6_ct_migration.rs} (99%) rename pallets/funding/src/{ => functions}/runtime_api.rs (100%) rename pallets/funding/src/tests/{5_funding_end.rs => 4_funding_end.rs} (99%) rename pallets/funding/src/tests/{6_settlement.rs => 5_settlement.rs} (99%) rename pallets/funding/src/tests/{7_ct_migration.rs => 6_ct_migration.rs} (98%) diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs index e87bdf6da..9e7b79b27 100644 --- a/pallets/funding/src/benchmarking.rs +++ b/pallets/funding/src/benchmarking.rs @@ -542,21 +542,14 @@ mod benchmarks { // Storage for (bid_params, price) in extrinsic_bids_post_bucketing.clone() { - let bid_filter = BidInfoFilter:: { - id: None, - project_id: Some(project_id), - bidder: Some(bidder.clone()), - status: Some(BidStatus::YetUnknown), - original_ct_amount: Some(bid_params.amount), - original_ct_usd_price: Some(price), - funding_asset: Some(AcceptedFundingAsset::USDT), - funding_asset_amount_locked: None, - mode: Some(bid_params.mode), - plmc_bond: None, - when: None, - }; Bids::::iter_prefix_values(project_id) - .find(|stored_bid| bid_filter.matches_bid(stored_bid)) + .find(|stored_bid| { + stored_bid.bidder == bidder.clone() && + stored_bid.original_ct_amount == bid_params.amount && + stored_bid.original_ct_usd_price == price && + stored_bid.funding_asset == AcceptedFundingAsset::USDT && + stored_bid.mode == bid_params.mode + }) .expect("bid not found"); } } diff --git a/pallets/funding/src/functions/1_application.rs b/pallets/funding/src/functions/1_application.rs index ca75f6b38..c45244af5 100644 --- a/pallets/funding/src/functions/1_application.rs +++ b/pallets/funding/src/functions/1_application.rs @@ -4,6 +4,7 @@ use super::*; impl Pallet { + /// Make sure the data provided by the issuer on project creation makes sense. fn project_validation( project_metadata: &ProjectMetadataOf, issuer: AccountIdOf, @@ -53,6 +54,7 @@ impl Pallet { Ok((project_details, bucket)) } + /// Create a new project. #[transactional] pub fn do_create_project( issuer: &AccountIdOf, @@ -93,6 +95,7 @@ impl Pallet { Ok(PostDispatchInfo { actual_weight: None, pays_fee: Pays::No }) } + /// Edit the project information before starting the raise #[transactional] pub fn do_edit_project( issuer: AccountIdOf, @@ -121,6 +124,7 @@ impl Pallet { Ok(PostDispatchInfo { actual_weight: None, pays_fee: Pays::No }) } + /// Remove the project before the raise started. #[transactional] pub fn do_remove_project(issuer: AccountIdOf, project_id: ProjectId, did: Did) -> DispatchResultWithPostInfo { // * Get variables * diff --git a/pallets/funding/src/functions/2_evaluation.rs b/pallets/funding/src/functions/2_evaluation.rs index 018854ae5..386b5177f 100644 --- a/pallets/funding/src/functions/2_evaluation.rs +++ b/pallets/funding/src/functions/2_evaluation.rs @@ -2,6 +2,7 @@ use super::*; use polimec_common::ProvideAssetPrice; impl Pallet { + /// Start the evaluation round of a project. This is how the raise is started. #[transactional] pub fn do_start_evaluation(caller: AccountIdOf, project_id: ProjectId) -> DispatchResultWithPostInfo { // * Get variables * @@ -29,6 +30,7 @@ impl Pallet { Ok(PostDispatchInfo { actual_weight: None, pays_fee: Pays::No }) } + /// End the evaluation round of a project, and start the auction round. #[transactional] pub fn do_end_evaluation(project_id: ProjectId) -> DispatchResult { // * Get variables * @@ -45,10 +47,7 @@ impl Pallet { // * Branch in possible project paths * // Successful path return if is_funded { - let mut project_ids = ProjectsInAuctionRound::::get().to_vec(); - project_ids.push(project_id); - let project_ids = WeakBoundedVec::force_from(project_ids, None); - ProjectsInAuctionRound::::put(project_ids); + ProjectsInAuctionRound::::insert(project_id, ()); Self::transition_project( project_id, project_details, @@ -72,6 +71,7 @@ impl Pallet { } } + /// Place an evaluation on a project #[transactional] pub fn do_evaluate( evaluator: &AccountIdOf, diff --git a/pallets/funding/src/functions/3_auction.rs b/pallets/funding/src/functions/3_auction.rs index 278edd623..389ff9052 100644 --- a/pallets/funding/src/functions/3_auction.rs +++ b/pallets/funding/src/functions/3_auction.rs @@ -2,6 +2,7 @@ use super::*; impl Pallet { + /// Place a bid on a project in the auction round #[transactional] pub fn do_bid(params: DoBidParams) -> DispatchResult { // * Get variables * @@ -119,6 +120,8 @@ impl Pallet { Ok(()) } + /// Inner function to perform bids within a bucket. do_bid makes sure to split the bid into buckets and call this as + /// many times as necessary #[transactional] fn do_perform_bid(do_perform_bid_params: DoPerformBidParams) -> Result, DispatchError> { let DoPerformBidParams { @@ -192,11 +195,13 @@ impl Pallet { Ok(new_bid) } + /// Process a bid that was outbid by a new bid. This will set it to Rejected so the user can get their funds back with `settle_bid` and bid again. pub fn do_process_next_oversubscribed_bid(project_id: ProjectId) -> DispatchResult { // Load and validate initial state let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; let bucket = Buckets::::get(project_id).ok_or(Error::::BucketNotFound)?; let mut ct_amount_oversubscribed = CTAmountOversubscribed::::get(project_id); + ensure!(ct_amount_oversubscribed > Zero::zero(), Error::::NoBidsOversubscribed); // Determine the current cutoff @@ -206,21 +211,21 @@ impl Pallet { if matches!(bid.status, BidStatus::PartiallyAccepted(_)) { cutoff } else { - let (new_price, new_index) = Self::get_next_cutoff(project_id, bucket.delta_price, bid_price, bid_index)?; + let (new_price, new_index) = + Self::get_next_cutoff(project_id, bucket.delta_price, bid_price, bid_index)?; OutbidBidsCutoff { bid_price: new_price, bid_index: new_index } } }, None => { let first_price = project_metadata.minimum_price; - let first_bounds = BidsBucketBounds::::get(project_id, first_price) - .ok_or(Error::::ImpossibleState)?; + let first_bounds = + BidsBucketBounds::::get(project_id, first_price).ok_or(Error::::ImpossibleState)?; OutbidBidsCutoff { bid_price: first_price, bid_index: first_bounds.last_bid_index } - } + }, }; // Process the bid at the cutoff - let mut bid = Bids::::get(project_id, current_cutoff.bid_index) - .ok_or(Error::::ImpossibleState)?; + let mut bid = Bids::::get(project_id, current_cutoff.bid_index).ok_or(Error::::ImpossibleState)?; let bid_amount = match bid.status { BidStatus::PartiallyAccepted(amount) => amount, @@ -244,6 +249,7 @@ impl Pallet { Ok(()) } + /// Get the next bid that should be processed by do_process_next_oversubscribed_bid pub fn get_next_cutoff( project_id: ProjectId, delta_price: PriceOf, diff --git a/pallets/funding/src/functions/5_funding_end.rs b/pallets/funding/src/functions/4_funding_end.rs similarity index 88% rename from pallets/funding/src/functions/5_funding_end.rs rename to pallets/funding/src/functions/4_funding_end.rs index 8acc27e17..29fad9e70 100644 --- a/pallets/funding/src/functions/5_funding_end.rs +++ b/pallets/funding/src/functions/4_funding_end.rs @@ -1,8 +1,8 @@ #[allow(clippy::wildcard_imports)] use super::*; -use itertools::Itertools; impl Pallet { + /// End the auction round and the fundraise. Check if the raise was successful or not. #[transactional] pub fn do_end_funding(project_id: ProjectId) -> DispatchResult { // * Get variables * @@ -20,10 +20,7 @@ impl Pallet { ); ensure!(ct_amount_oversubscribed.is_zero(), Error::::OversubscribedBidsRemaining); - let mut project_ids = ProjectsInAuctionRound::::get().to_vec(); - let (pos, _) = project_ids.iter().find_position(|id| **id == project_id).ok_or(Error::::ImpossibleState)?; - project_ids.remove(pos); - ProjectsInAuctionRound::::put(WeakBoundedVec::force_from(project_ids, None)); + ProjectsInAuctionRound::::remove(project_id); let auction_allocation_size = project_metadata.total_allocation_size; diff --git a/pallets/funding/src/functions/6_settlement.rs b/pallets/funding/src/functions/5_settlement.rs similarity index 93% rename from pallets/funding/src/functions/6_settlement.rs rename to pallets/funding/src/functions/5_settlement.rs index a2dfd5e75..852434325 100644 --- a/pallets/funding/src/functions/6_settlement.rs +++ b/pallets/funding/src/functions/5_settlement.rs @@ -21,6 +21,8 @@ use polimec_common::{ use sp_runtime::{traits::Zero, Perquintill}; impl Pallet { + /// Start the settlement round. Now users can mint their contribution tokens or get their funds back, and the issuer + /// will get the funds in their funding account. #[transactional] pub fn do_start_settlement(project_id: ProjectId) -> DispatchResult { let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; @@ -104,6 +106,7 @@ impl Pallet { Ok(()) } + /// Settle an evaluation, by maybe minting CTs, and releasing the PLMC bond. pub fn do_settle_evaluation(evaluation: EvaluationInfoOf, project_id: ProjectId) -> DispatchResult { let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; @@ -154,6 +157,9 @@ impl Pallet { Ok(()) } + /// Settle a bid. If bid was successful mint the CTs and release the PLMC bond (if multiplier > 1 and mode is Classic). + /// If was unsuccessful, release the PLMC bond and refund the funds. + /// If the project was successful, the issuer will get the funds. pub fn do_settle_bid(project_id: ProjectId, bid_id: u32) -> DispatchResult { let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; @@ -261,6 +267,7 @@ impl Pallet { } } + /// Mark a project as fully settled. Only once this is done we can mark migrations as completed. pub fn do_mark_project_as_settled(project_id: ProjectId) -> DispatchResult { let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; let outcome = match project_details.status { @@ -288,6 +295,7 @@ impl Pallet { Ok(()) } + /// Helper function to Mint CTs and handle the payment of new storage with "touch" fn mint_contribution_tokens( project_id: ProjectId, participant: &AccountIdOf, @@ -300,6 +308,7 @@ impl Pallet { Ok(()) } + /// Helper function to release the funding asset to the participant fn release_funding_asset( project_id: ProjectId, participant: &AccountIdOf, @@ -313,7 +322,7 @@ impl Pallet { T::FundingCurrency::transfer(asset.id(), &project_pot, participant, amount, Preservation::Expendable)?; Ok(()) } - + /// Helper function to release the PLMC bond to the participant fn release_participation_bond_for(participant: &AccountIdOf, amount: Balance) -> DispatchResult { if amount.is_zero() { return Ok(()); @@ -323,6 +332,7 @@ impl Pallet { Ok(()) } + /// Set the PLMC release schedule if mode was `Classic`. Return the schedule either way. fn set_plmc_bond_release_with_mode( participant: AccountIdOf, plmc_amount: Balance, @@ -337,6 +347,7 @@ impl Pallet { } } + /// Calculate the vesting info and add the PLMC release schedule to the user, or fully release the funds if possible. fn set_release_schedule_for( participant: &AccountIdOf, plmc_amount: Balance, @@ -361,6 +372,7 @@ impl Pallet { Ok(vesting_info.duration) } + /// Slash an evaluator and transfer funds to the treasury. fn slash_evaluator(evaluation: &EvaluationInfoOf) -> Result { let slash_percentage = T::EvaluatorSlash::get(); let treasury_account = T::BlockchainOperationTreasury::get(); @@ -384,6 +396,7 @@ impl Pallet { Ok(evaluation.current_plmc_bond.saturating_sub(slashed_amount)) } + /// Reward an evaluator and mint CTs. fn reward_evaluator( project_id: ProjectId, evaluation: &EvaluationInfoOf, diff --git a/pallets/funding/src/functions/7_ct_migration.rs b/pallets/funding/src/functions/6_ct_migration.rs similarity index 99% rename from pallets/funding/src/functions/7_ct_migration.rs rename to pallets/funding/src/functions/6_ct_migration.rs index 3f4eb97b7..cc1f05879 100644 --- a/pallets/funding/src/functions/7_ct_migration.rs +++ b/pallets/funding/src/functions/6_ct_migration.rs @@ -4,6 +4,7 @@ use xcm::v4::MaxPalletNameLen; // Offchain migration functions impl Pallet { + /// Mark a project as ready for offchain migration confirmations. #[transactional] pub fn do_start_offchain_migration(project_id: ProjectId, caller: AccountIdOf) -> DispatchResultWithPostInfo { let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; @@ -24,6 +25,7 @@ impl Pallet { Ok(PostDispatchInfo { actual_weight: None, pays_fee: Pays::No }) } + /// Confirm a user's migrations as completed #[transactional] pub fn do_confirm_offchain_migration( project_id: ProjectId, diff --git a/pallets/funding/src/functions/mod.rs b/pallets/funding/src/functions/mod.rs index 9c033b096..abf58b613 100644 --- a/pallets/funding/src/functions/mod.rs +++ b/pallets/funding/src/functions/mod.rs @@ -31,12 +31,14 @@ const QUERY_RESPONSE_TIME_WINDOW_BLOCKS: u32 = 20u32; mod application; #[path = "3_auction.rs"] mod auction; -#[path = "7_ct_migration.rs"] +#[path = "6_ct_migration.rs"] mod ct_migration; #[path = "2_evaluation.rs"] mod evaluation; -#[path = "5_funding_end.rs"] +#[path = "4_funding_end.rs"] mod funding_end; pub mod misc; -#[path = "6_settlement.rs"] +#[path = "5_settlement.rs"] mod settlement; + +pub mod runtime_api; diff --git a/pallets/funding/src/runtime_api.rs b/pallets/funding/src/functions/runtime_api.rs similarity index 100% rename from pallets/funding/src/runtime_api.rs rename to pallets/funding/src/functions/runtime_api.rs diff --git a/pallets/funding/src/instantiator/calculations.rs b/pallets/funding/src/instantiator/calculations.rs index 4b793b04f..8ce9f8201 100644 --- a/pallets/funding/src/instantiator/calculations.rs +++ b/pallets/funding/src/instantiator/calculations.rs @@ -25,17 +25,6 @@ impl< self.execute(|| T::FundingCurrency::minimum_balance(asset_id)) } - pub fn get_funding_asset_unit(&mut self, asset_id: AssetIdOf) -> Balance { - self.execute(|| { - let decimals = T::FundingCurrency::decimals(asset_id); - 10u128.pow(decimals as u32) - }) - } - - pub fn get_ct_account_deposit(&self) -> Balance { - ::ContributionTokenCurrency::deposit_required(One::one()) - } - pub fn calculate_evaluation_plmc_spent( &mut self, evaluations: Vec>, @@ -72,7 +61,7 @@ impl< while !amount_to_bid.is_zero() { let bid_amount = if amount_to_bid <= bucket.amount_left { amount_to_bid } else { bucket.amount_left }; output.push(( - BidParams::from((bid.bidder.clone(), bid.investor_type.clone(), bid_amount, bid.mode, bid.asset)), + BidParams::from((bid.bidder.clone(), bid.investor_type, bid_amount, bid.mode, bid.asset)), bucket.current_price, )); bucket.update(bid_amount); @@ -170,18 +159,6 @@ impl< output.merge_accounts(MergeOperation::Add) } - pub fn calculate_auction_plmc_spent_post_wap( - &mut self, - bids: &Vec>, - project_metadata: ProjectMetadataOf, - ) -> Vec> { - let plmc_charged = - self.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket(bids, project_metadata.clone(), None); - let plmc_returned = self.calculate_auction_plmc_returned_from_all_bids_made(bids, project_metadata.clone()); - - plmc_charged.subtract_accounts(plmc_returned) - } - pub fn calculate_auction_funding_asset_charged_with_given_price( &mut self, bids: &Vec>, @@ -273,45 +250,6 @@ impl< output.merge_accounts(MergeOperation::Add) } - pub fn calculate_auction_funding_asset_spent_post_wap( - &mut self, - bids: &Vec>, - project_metadata: ProjectMetadataOf, - ) -> Vec> { - let funding_asset_charged = self.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - bids, - project_metadata.clone(), - None, - ); - let funding_asset_returned = - self.calculate_auction_funding_asset_returned_from_all_bids_made(bids, project_metadata.clone()); - - funding_asset_charged.subtract_accounts(funding_asset_returned) - } - - /// Filters the bids that would be rejected after the auction ends. - pub fn filter_bids_after_auction(&self, bids: Vec>, total_cts: Balance) -> Vec> { - let mut filtered_bids: Vec> = Vec::new(); - let sorted_bids = bids; - let mut total_cts_left = total_cts; - for bid in sorted_bids { - if total_cts_left >= bid.amount { - total_cts_left.saturating_reduce(bid.amount); - filtered_bids.push(bid); - } else if !total_cts_left.is_zero() { - filtered_bids.push(BidParams::from(( - bid.bidder.clone(), - bid.investor_type, - total_cts_left, - bid.mode, - bid.asset, - ))); - total_cts_left = Zero::zero(); - } - } - filtered_bids - } - pub fn add_otm_fee_to( &mut self, balance: &mut Balance, @@ -351,24 +289,6 @@ impl< *balance += funding_asset_bond; } - pub fn generic_map_merge_reduce( - &self, - mappings: Vec>, - key_extractor: impl Fn(&M) -> K, - initial_state: S, - merge_reduce: impl Fn(&M, S) -> S, - ) -> Vec<(K, S)> { - let mut output = BTreeMap::new(); - for mut map in mappings { - for item in map.drain(..) { - let key = key_extractor(&item); - let new_state = merge_reduce(&item, output.get(&key).cloned().unwrap_or(initial_state.clone())); - output.insert(key, new_state); - } - } - output.into_iter().collect() - } - /// Merge the given mappings into one mapping, where the values are merged using the given /// merge operation. /// @@ -604,23 +524,6 @@ impl< balances } - pub fn calculate_total_reward_for_evaluation( - &self, - evaluation: EvaluationInfoOf, - reward_info: RewardInfo, - ) -> Balance { - let early_reward_weight = - Perquintill::from_rational(evaluation.early_usd_amount, reward_info.early_evaluator_total_bonded_usd); - let normal_reward_weight = Perquintill::from_rational( - evaluation.late_usd_amount.saturating_add(evaluation.early_usd_amount), - reward_info.normal_evaluator_total_bonded_usd, - ); - let early_evaluators_rewards = early_reward_weight * reward_info.early_evaluator_reward_pot; - let normal_evaluators_rewards = normal_reward_weight * reward_info.normal_evaluator_reward_pot; - - early_evaluators_rewards.saturating_add(normal_evaluators_rewards) - } - // We assume a single bid can cover the whole first bucket. Make sure the ticket sizes allow this. pub fn generate_bids_from_bucket( &self, @@ -677,13 +580,6 @@ impl< bids } - pub fn remainder_round_block(&self) -> BlockNumberFor { - T::EvaluationRoundDuration::get() + - T::AuctionRoundDuration::get() + - T::CommunityRoundDuration::get() + - One::one() - } - #[cfg(feature = "std")] pub fn eth_key_and_sig_from( &mut self, diff --git a/pallets/funding/src/instantiator/chain_interactions.rs b/pallets/funding/src/instantiator/chain_interactions.rs index 522fca0fb..aff823e96 100644 --- a/pallets/funding/src/instantiator/chain_interactions.rs +++ b/pallets/funding/src/instantiator/chain_interactions.rs @@ -107,25 +107,6 @@ impl< self.execute(|| ::ContributionTokenCurrency::balance(project_id, &user)) } - pub fn get_all_free_plmc_balances(&mut self) -> Vec> { - let user_keys = self.execute(|| frame_system::Account::::iter_keys().collect()); - self.get_free_plmc_balances_for(user_keys) - } - - pub fn get_all_reserved_plmc_balances( - &mut self, - reserve_type: ::RuntimeHoldReason, - ) -> Vec> { - let user_keys = self.execute(|| frame_system::Account::::iter_keys().collect()); - self.get_reserved_plmc_balances_for(user_keys, reserve_type) - } - - pub fn get_all_free_funding_asset_balances(&mut self, asset_id: AssetIdOf) -> Vec> { - let user_keys = - self.execute(|| frame_system::Account::::iter_keys().map(|a| (a, asset_id.clone())).collect()); - self.get_free_funding_asset_balances_for(user_keys) - } - pub fn get_plmc_total_supply(&mut self) -> Balance { self.execute(::NativeCurrency::total_issuance) } @@ -324,43 +305,11 @@ impl< self.do_reserved_plmc_assertions(expected_reserved_plmc_balances, HoldReason::Evaluation.into()); } - pub fn finalized_bids_assertions( - &mut self, - project_id: ProjectId, - bid_expectations: Vec>, - expected_ct_sold: Balance, - ) { - let project_metadata = self.get_project_metadata(project_id); - let project_details = self.get_project_details(project_id); - let project_bids = self.execute(|| Bids::::iter_prefix_values(project_id).collect::>()); - - for filter in bid_expectations { - let _found_bid = project_bids.iter().find(|bid| filter.matches_bid(bid)).unwrap(); - } - - // Remaining CTs are updated - assert_eq!( - project_details.remaining_contribution_tokens, - project_metadata.total_allocation_size - expected_ct_sold, - "Remaining CTs are incorrect" - ); - } - pub fn assert_plmc_free_balance(&mut self, account_id: AccountIdOf, expected_balance: Balance) { let real_balance = self.get_free_plmc_balance_for(account_id.clone()); assert_eq!(real_balance, expected_balance, "Unexpected PLMC balance for user {:?}", account_id); } - pub fn assert_plmc_held_balance( - &mut self, - account_id: AccountIdOf, - expected_balance: Balance, - hold_reason: ::RuntimeHoldReason, - ) { - let real_balance = self.get_reserved_plmc_balance_for(account_id.clone(), hold_reason); - assert_eq!(real_balance, expected_balance, "Unexpected PLMC balance for user {:?}", account_id); - } - pub fn assert_funding_asset_free_balance( &mut self, account_id: AccountIdOf, @@ -465,6 +414,12 @@ impl< self.mint_funding_asset_to(necessary_funding_assets); } + pub fn mint_necessary_tokens_for_evaluations(&mut self, evaluations: Vec>) { + let plmc_required = self.calculate_evaluation_plmc_spent(evaluations.clone()); + self.mint_plmc_ed_if_required(plmc_required.accounts()); + self.mint_plmc_to(plmc_required); + } + pub fn bid_for_users(&mut self, project_id: ProjectId, bids: Vec>) -> DispatchResultWithPostInfo { let project_policy = self.get_project_metadata(project_id).policy_ipfs_cid.unwrap(); @@ -514,10 +469,6 @@ impl< self.execute(|| Bids::::iter_prefix_values(project_id).collect()) } - pub fn get_bid(&mut self, bid_id: u32) -> BidInfoOf { - self.execute(|| Bids::::iter_values().find(|bid| bid.id == bid_id).unwrap()) - } - // Used to check all the USDT/USDC/DOT was paid to the issuer funding account pub fn assert_total_funding_paid_out(&mut self, project_id: ProjectId, bids: Vec>) { let project_metadata = self.get_project_metadata(project_id); @@ -805,9 +756,7 @@ impl< let status = self.go_to_next_state(project_id); - if status == ProjectStatus::FundingSuccessful { - self.test_ct_not_created_for(project_id); - } else if status == ProjectStatus::FundingFailed { + if status == ProjectStatus::FundingSuccessful || status == ProjectStatus::FundingFailed { self.test_ct_not_created_for(project_id); } else { panic!("Project should be in FundingSuccessful or FundingFailed status"); @@ -834,6 +783,7 @@ impl< ); assert!(matches!(self.go_to_next_state(project_id), ProjectStatus::SettlementStarted(_))); + self.test_ct_created_for(project_id); self.settle_project(project_id, mark_as_settled); diff --git a/pallets/funding/src/instantiator/macros.rs b/pallets/funding/src/instantiator/macros.rs index 1862e962d..8c385c058 100644 --- a/pallets/funding/src/instantiator/macros.rs +++ b/pallets/funding/src/instantiator/macros.rs @@ -40,17 +40,6 @@ macro_rules! assert_close_enough { }; } -#[macro_export] -macro_rules! call_and_is_ok { - ($inst: expr, $( $call: expr ),* ) => { - $inst.execute(|| { - $( - let result = $call; - assert!(result.is_ok(), "Call failed: {:?}", result); - )* - }) - }; - } #[macro_export] macro_rules! find_event { ($runtime:ty, $pattern:pat, $($field_name:ident == $field_value:expr),+) => { @@ -77,37 +66,3 @@ macro_rules! find_event { } }; } - -#[macro_export] -macro_rules! extract_from_event { - ($env: expr, $pattern:pat, $field:ident) => { - $env.execute(|| { - let events = System::events(); - - events.iter().find_map(|event_record| { - if let frame_system::EventRecord { event: RuntimeEvent::PolimecFunding($pattern), .. } = event_record { - Some($field.clone()) - } else { - None - } - }) - }) - }; -} - -#[macro_export] -macro_rules! define_names { - ($($name:ident: $id:expr, $label:expr);* $(;)?) => { - $( - pub const $name: AccountId = $id; - )* - - pub fn names() -> std::collections::HashMap { - let mut names = std::collections::HashMap::new(); - $( - names.insert($name, $label); - )* - names - } - }; - } diff --git a/pallets/funding/src/instantiator/mod.rs b/pallets/funding/src/instantiator/mod.rs index 3c4e8f0e1..a61515174 100644 --- a/pallets/funding/src/instantiator/mod.rs +++ b/pallets/funding/src/instantiator/mod.rs @@ -23,7 +23,7 @@ use frame_support::{ metadata::Inspect as MetadataInspect, roles::Inspect as RolesInspect, Inspect as FungiblesInspect, Mutate as FungiblesMutate, }, - AccountTouch, Get, OnFinalize, OnIdle, OnInitialize, + Get, OnFinalize, OnIdle, OnInitialize, }, weights::Weight, Parameter, diff --git a/pallets/funding/src/instantiator/types.rs b/pallets/funding/src/instantiator/types.rs index bdd842ae9..9d7a862f7 100644 --- a/pallets/funding/src/instantiator/types.rs +++ b/pallets/funding/src/instantiator/types.rs @@ -582,76 +582,3 @@ impl Conversions for Vec> { btree.into_iter().collect_vec() } } - -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct BidInfoFilter { - pub id: Option, - pub project_id: Option, - pub bidder: Option>, - pub status: Option, - pub original_ct_amount: Option, - pub original_ct_usd_price: Option>, - pub funding_asset: Option, - pub funding_asset_amount_locked: Option, - pub mode: Option, - pub plmc_bond: Option, - pub when: Option>, -} -impl BidInfoFilter { - pub(crate) fn matches_bid(&self, bid: &BidInfoOf) -> bool { - if self.id.is_some() && self.id.unwrap() != bid.id { - return false; - } - if self.project_id.is_some() && self.project_id.unwrap() != bid.project_id { - return false; - } - if self.bidder.is_some() && self.bidder.clone().unwrap() != bid.bidder.clone() { - return false; - } - if self.status.is_some() && self.status.as_ref().unwrap() != &bid.status { - return false; - } - if self.original_ct_amount.is_some() && self.original_ct_amount.unwrap() != bid.original_ct_amount { - return false; - } - if self.original_ct_usd_price.is_some() && self.original_ct_usd_price.unwrap() != bid.original_ct_usd_price { - return false; - } - if self.funding_asset.is_some() && self.funding_asset.unwrap() != bid.funding_asset { - return false; - } - if self.funding_asset_amount_locked.is_some() && - self.funding_asset_amount_locked.unwrap() != bid.funding_asset_amount_locked - { - return false; - } - if self.mode.is_some() && self.mode.unwrap() != bid.mode { - return false; - } - if self.plmc_bond.is_some() && self.plmc_bond.unwrap() != bid.plmc_bond { - return false; - } - if self.when.is_some() && self.when.unwrap() != bid.when { - return false; - } - - true - } -} -impl Default for BidInfoFilter { - fn default() -> Self { - BidInfoFilter:: { - id: None, - project_id: None, - bidder: None, - status: None, - original_ct_amount: None, - original_ct_usd_price: None, - funding_asset: None, - funding_asset_amount_locked: None, - mode: None, - plmc_bond: None, - when: None, - } - } -} diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 8a3dfc709..7113719f2 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -31,7 +31,7 @@ //! - **Evaluators**: They are incentivized to assess projects accurately by locking their PLMC. If at least 10% of its //! target funding (in USD) is locked in PLMC, a project is given access to the funding round. Evaluators are either //! rewarded in contribution tokens if the project gets funded, or have their PLMC slashed otherwise. -//! - **Participants**: They contribute financially to projects by locking PLMC and paying out USDT/USDC/DOT, and are rewarded in contribution tokens. +//! - **Bidders**: They contribute financially to projects by locking PLMC and paying out USDT/USDC/DOT, and are rewarded in contribution tokens. //! //! Users need to go through a KYC/AML by a third party in order to use the protocol. This process classifies them //! into one of the following categories, based on their investment experience and financial status: @@ -45,26 +45,19 @@ //! 3) **Evaluate**: Evaluators bond PLMC to evaluate a project with the [`evaluate`](Pallet::evaluate) extrinsic. //! 4) **Evaluation End**: Anyone can end the evaluation round with the [`end_evaluation`](Pallet::end_evaluation) extrinsic after the defined end block. //! 5) **Auction Start**: If the project receives at least 10% of its target funding (in USD) in PLMC bonded, the auction starts immediately after `end_evaluation` is called. -//! 6) **Bid**: Professional and institutional investors can place bids on the project using the [`bid`](Pallet::bid) extrinsic. The price starts at the issuer-defined minimum, and increases by increments of 10% in price and bucket size. -//! 7) **Auction End**: Anyone can end the auction round with the [`end_auction`](Pallet::end_auction) extrinsic after the defined end block. -//! 8) **Community Round Start**: After `end_auction` is called, a weighted average price is calculated from the bids, and the community round starts. -//! 9) **Contribute**: Anyone without a winning bid can now contribute at the weighted average price with the [`contribute`](Pallet::contribute) extrinsic. -//! 10) **Remainder Round Start**: After a defined [period](::CommunityRoundDuration), the remainder round starts. -//! 11) **Contribute**: Participants with winning bids can also contribute at the weighted average price with the [`contribute`](Pallet::contribute) extrinsic. -//! 12) **Funding End**: Anyone can end the project with the [`end_project`](Pallet::end_project) extrinsic after the defined end block. +//! 6) **Bid**: Investors can place bids on the project using the [`bid`](Pallet::bid) extrinsic. The price starts at the issuer-defined minimum, and increases by increments of 10% in price and bucket size. +//! 7) **Funding End**: Anyone can end the project with the [`end_project`](Pallet::end_project) extrinsic after the defined end block. //! The project will now be considered Failed if it reached <=33% of its target funding in USD, and Successful otherwise. -//! 13) **Settlement Start**: Anyone can start the settlement process with the [`start_settlement`](Pallet::start_settlement) extrinsic after the defined end block. -//! 14) **Settle Evaluation**: Anyone can now settle an evaluation with the [`settle_evaluation`](Pallet::settle_evaluation) extrinsic. +//! 8) **Settlement Start**: Anyone can start the settlement process with the [`start_settlement`](Pallet::start_settlement) extrinsic after the defined end block. +//! 9) **Settle Evaluation**: Anyone can now settle an evaluation with the [`settle_evaluation`](Pallet::settle_evaluation) extrinsic. //! This will unlock the PLMC bonded, and either apply a slash to the PLMC, or reward CTs to the evaluator. -//! 15) **Settle Bid**: Anyone can now settle a bid with the [`settle_bid`](Pallet::settle_bid) extrinsic. +//! 10) **Settle Bid**: Anyone can now settle a bid with the [`settle_bid`](Pallet::settle_bid) extrinsic. //! This will set a vesting schedule on the PLMC bonded, and pay out the funding assets to the issuer. It will also issue refunds in case the bid failed, //! or the price paid was higher than the weighted average price. -//! 16) **Settle Contribution**: Anyone can now settle a contribution with the [`settle_contribution`](Pallet::settle_contribution) extrinsic. -//! This will set a vesting schedule on the PLMC bonded, and pay out the funding assets to the issuer. -//! 17) **Settlement End**: Anyone can now mark the project settlement as finished by calling the [`mark_project_as_settled`](Pallet::mark_project_as_settled) extrinsic. -//! 18) **Migration Start**: Once the issuer has tokens to distribute on mainnet, he can start the migration process with the [`start_offchain`](Pallet::start_offchain_migration) extrinsic. -//! 19) **Confirm Migration**: The issuer has to mark each participant's CTs as migrated with the [`confirm_offchain_migration`](Pallet::confirm_offchain_migration) extrinsic. -//! 20) **Migration End**: Once all participants have migrated their CTs, anyone can mark the migration as finished with the [`mark_project_ct_migration_as_finished`](Pallet::mark_project_ct_migration_as_finished) extrinsic. +//! 11) **Settlement End**: Anyone can now mark the project settlement as finished by calling the [`mark_project_as_settled`](Pallet::mark_project_as_settled) extrinsic. +//! 12) **Migration Start**: Once the issuer has tokens to distribute on mainnet, he can start the migration process with the [`start_offchain`](Pallet::start_offchain_migration) extrinsic. +//! 13) **Confirm Migration**: The issuer has to mark each participant's CTs as migrated with the [`confirm_offchain_migration`](Pallet::confirm_offchain_migration) extrinsic. +//! 14) **Migration End**: Once all participants have migrated their CTs, anyone can mark the migration as finished with the [`mark_project_ct_migration_as_finished`](Pallet::mark_project_ct_migration_as_finished) extrinsic. // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] @@ -78,6 +71,7 @@ extern crate alloc; pub use crate::weights::WeightInfo; +use alloc::string::String; use frame_support::{ traits::{ tokens::{fungible, fungibles}, @@ -99,12 +93,12 @@ use sp_runtime::{traits::AccountIdConversion, FixedPointNumber, FixedU128}; use sp_std::prelude::*; pub use types::*; use xcm::v4::{prelude::*, SendXcm}; -mod functions; + +pub mod functions; pub mod storage_migrations; pub mod traits; pub mod types; pub mod weights; -use alloc::string::String; #[cfg(test)] pub mod mock; @@ -120,7 +114,6 @@ use polimec_common::migration_types::ParticipationType; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; -pub mod runtime_api; pub type AccountIdOf = ::AccountId; pub type ProjectId = u32; @@ -360,19 +353,17 @@ pub mod pallet { } #[pallet::storage] - /// A global counter for indexing the projects - /// OnEmpty in this case is GetDefault, so 0. + /// An increasing counter to assign a unique id to projects pub type NextProjectId = StorageValue<_, ProjectId, ValueQuery>; #[pallet::storage] + /// An increasing counter to assign a unique id to evaluations pub type NextEvaluationId = StorageValue<_, u32, ValueQuery>; #[pallet::storage] + /// An increasing counter to assign a unique id to bids pub type NextBidId = StorageValue<_, u32, ValueQuery>; - #[pallet::storage] - pub type NextContributionId = StorageValue<_, u32, ValueQuery>; - #[pallet::storage] /// A StorageMap containing the primary project information of projects pub type ProjectsMetadata = StorageMap<_, Blake2_128Concat, ProjectId, ProjectMetadataOf>; @@ -385,8 +376,9 @@ pub mod pallet { /// StorageMap containing additional information for the projects, relevant for correctness of the protocol pub type ProjectsDetails = StorageMap<_, Blake2_128Concat, ProjectId, ProjectDetailsOf>; + /// Used to track which projects need the automatic processing of oversubscribed bids in on_idle #[pallet::storage] - pub type ProjectsInAuctionRound = StorageValue<_, WeakBoundedVec>, ValueQuery>; + pub type ProjectsInAuctionRound = StorageMap<_, Blake2_128Concat, ProjectId, ()>; #[pallet::storage] /// Keep track of the PLMC bonds made to each project by each evaluator @@ -421,13 +413,18 @@ pub mod pallet { pub type OutbidBidsCutoffs = StorageMap<_, Blake2_128Concat, ProjectId, OutbidBidsCutoff>, OptionQuery>; + /// Used to knwo when to call `do_process_next_oversubscribed_bid`. When a new bid comes in on an oversubscribed project, + /// we add their bid amount to this value. #[pallet::storage] pub type CTAmountOversubscribed = StorageMap<_, Blake2_128Concat, ProjectId, Balance, ValueQuery>; + /// Stores the total usd amount participated by a user on a project. Used to track the ticket sizes when its capped. #[pallet::storage] pub type AuctionBoughtUSD = StorageNMap<_, (NMapKey, NMapKey), Balance, ValueQuery>; + /// Stores the CT amounts and vesting schedules for users with successful bids. Will be used by issuers to mint + /// their tokens on TGE #[pallet::storage] pub type UserMigrations = StorageNMap< _, @@ -441,9 +438,6 @@ pub mod pallet { #[pallet::storage] pub type UnmigratedCounter = StorageMap<_, Blake2_128Concat, ProjectId, u32, ValueQuery>; - #[pallet::storage] - pub type ActiveMigrationQueue = StorageMap<_, Blake2_128Concat, QueryId, (ProjectId, T::AccountId)>; - /// A map to keep track of what issuer's did has an active project. It prevents one issuer having multiple active projects #[pallet::storage] pub type DidWithActiveProjects = StorageMap<_, Blake2_128Concat, Did, ProjectId, OptionQuery>; @@ -992,15 +986,25 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_idle(_n: BlockNumberFor, available_weight: Weight) -> Weight { - let projects = ProjectsInAuctionRound::::get(); - if projects.is_empty() { + // Early return if no projects in auction round + if ProjectsInAuctionRound::::iter_keys().next().is_none() { return ::DbWeight::get().reads(1); } let mut weight_consumed = ::DbWeight::get().reads(1); + let read_weight = ::DbWeight::get().reads(1); let process_weight = ::DbWeight::get().reads_writes(1, 1); - for project_id in projects { + // Iterate over all projects in auction round + for (project_id, _) in ProjectsInAuctionRound::::iter() { + // Add weight for reading the storage item + weight_consumed.saturating_accrue(read_weight); + + // Check if we have enough weight to continue + if weight_consumed.saturating_add(process_weight).all_gt(available_weight) { + return weight_consumed; + } + loop { // Check weight before processing each bid if weight_consumed.saturating_add(process_weight).all_gt(available_weight) { @@ -1011,10 +1015,8 @@ pub mod pallet { match Self::do_process_next_oversubscribed_bid(project_id) { // Returns Ok if a bid was processed successfully Ok(_) => continue, - // Returns Err if there are no more bids to process, so we go to next project in the auction round - Err(_) => { - break; - }, + // Returns Err if there are no more bids to process, so we go to next project + Err(_) => break, } } } diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs index 977988a94..61a52bf4b 100644 --- a/pallets/funding/src/mock.rs +++ b/pallets/funding/src/mock.rs @@ -20,7 +20,6 @@ use super::*; use crate as pallet_funding; -use crate::runtime_api::{ExtrinsicHelpers, Leaderboards, ProjectInformation, UserInformation}; use alloc::string::String; use core::ops::RangeInclusive; use frame_support::{ @@ -32,6 +31,7 @@ use frame_support::{ }; use frame_system as system; use frame_system::{EnsureRoot, RawOrigin as SystemRawOrigin}; +use functions::runtime_api::{ExtrinsicHelpers, Leaderboards, ProjectInformation, UserInformation}; use polimec_common::{assets::AcceptedFundingAsset, credentials::EnsureInvestor, ProvideAssetPrice, USD_UNIT}; use polimec_common_test_utils::DummyXcmSender; use polkadot_parachain_primitives::primitives::Sibling; diff --git a/pallets/funding/src/tests/1_application.rs b/pallets/funding/src/tests/1_application.rs index 3992d6e10..6a2c8c964 100644 --- a/pallets/funding/src/tests/1_application.rs +++ b/pallets/funding/src/tests/1_application.rs @@ -1,27 +1,17 @@ use super::*; -use polimec_common::{ - assets::AcceptedFundingAsset, - credentials::InvestorType::{self}, -}; -use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt_with_cid}; #[cfg(test)] mod round_flow { use super::*; - #[cfg(test)] - mod success { - use super::*; - - #[test] - fn application_round_completed() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + #[test] + fn application_round_completed() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(issuer); + let issuer = ISSUER_1; + let project_metadata = default_project_metadata(issuer); - inst.create_evaluating_project(project_metadata, issuer, None); - } + inst.create_evaluating_project(project_metadata, issuer, None); } } @@ -32,9 +22,6 @@ mod create_project_extrinsic { #[cfg(test)] mod success { use super::*; - use polimec_common::assets::AcceptedFundingAsset::{DOT, USDC, USDT, WETH}; - use polimec_common_test_utils::get_mock_jwt_with_cid; - use std::collections::BTreeSet; #[test] fn project_id_autoincrement_works() { @@ -409,7 +396,6 @@ mod create_project_extrinsic { }); } - // Invalid metadata tests: #[test] fn mainnet_supply_less_than_allocation() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); @@ -842,8 +828,7 @@ mod edit_project_extrinsic { #[cfg(test)] mod success { use super::*; - use polimec_common::USD_DECIMALS; - use polimec_common_test_utils::get_mock_jwt; + #[test] fn project_id_stays_the_same() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); diff --git a/pallets/funding/src/tests/2_evaluation.rs b/pallets/funding/src/tests/2_evaluation.rs index f814d8f0b..a92c1bfe3 100644 --- a/pallets/funding/src/tests/2_evaluation.rs +++ b/pallets/funding/src/tests/2_evaluation.rs @@ -4,237 +4,222 @@ use super::*; mod round_flow { use super::*; - #[cfg(test)] - mod success { - use super::*; - use std::collections::HashSet; - - #[test] - fn evaluation_round_completed() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(issuer); - let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - - inst.create_auctioning_project(project_metadata, issuer, None, evaluations); - } + #[test] + fn evaluation_round_completed() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let issuer = ISSUER_1; + let project_metadata = default_project_metadata(issuer); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + + inst.create_auctioning_project(project_metadata, issuer, None, evaluations); + } - #[test] - fn multiple_evaluating_projects() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project1 = default_project_metadata(ISSUER_1); - let project2 = default_project_metadata(ISSUER_2); - let project3 = default_project_metadata(ISSUER_3); - let project4 = default_project_metadata(ISSUER_4); - let evaluations = inst.generate_successful_evaluations(project1.clone(), 5); - - inst.create_auctioning_project(project1, ISSUER_1, None, evaluations.clone()); - inst.create_auctioning_project(project2, ISSUER_2, None, evaluations.clone()); - inst.create_auctioning_project(project3, ISSUER_3, None, evaluations.clone()); - inst.create_auctioning_project(project4, ISSUER_4, None, evaluations); - } + #[test] + fn multiple_evaluating_projects() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project1 = default_project_metadata(ISSUER_1); + let project2 = default_project_metadata(ISSUER_2); + let project3 = default_project_metadata(ISSUER_3); + let project4 = default_project_metadata(ISSUER_4); + let evaluations = inst.generate_successful_evaluations(project1.clone(), 5); + + inst.create_auctioning_project(project1, ISSUER_1, None, evaluations.clone()); + inst.create_auctioning_project(project2, ISSUER_2, None, evaluations.clone()); + inst.create_auctioning_project(project3, ISSUER_3, None, evaluations.clone()); + inst.create_auctioning_project(project4, ISSUER_4, None, evaluations); + } - #[test] - fn plmc_price_change_doesnt_affect_evaluation_end() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); + #[test] + fn plmc_price_change_doesnt_affect_evaluation_end() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); - // Decreasing the price before the end doesn't make a project over the threshold fail. - let target_funding = - project_metadata.minimum_price.saturating_mul_int(project_metadata.total_allocation_size); - let target_evaluation_usd = Percent::from_percent(10) * target_funding; + // Decreasing the price before the end doesn't make a project over the threshold fail. + let target_funding = project_metadata.minimum_price.saturating_mul_int(project_metadata.total_allocation_size); + let target_evaluation_usd = Percent::from_percent(10) * target_funding; - let evaluations = vec![(EVALUATOR_1, target_evaluation_usd).into()]; - let evaluation_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone()); + let evaluations = vec![(EVALUATOR_1, target_evaluation_usd).into()]; + let evaluation_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone()); - inst.mint_plmc_ed_if_required(evaluations.accounts()); - inst.mint_plmc_to(evaluation_plmc); + inst.mint_plmc_ed_if_required(evaluations.accounts()); + inst.mint_plmc_to(evaluation_plmc); - let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); - inst.evaluate_for_users(project_id, evaluations.clone()).unwrap(); + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); + inst.evaluate_for_users(project_id, evaluations.clone()).unwrap(); - let old_price = ::PriceProvider::get_price(Location::here()).unwrap(); - PRICE_MAP.with_borrow_mut(|map| map.insert(Location::here(), old_price / 2.into())); + let old_price = ::PriceProvider::get_price(Location::here()).unwrap(); + PRICE_MAP.with_borrow_mut(|map| map.insert(Location::here(), old_price / 2.into())); - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionRound); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionRound); - // Increasing the price before the end doesn't make a project under the threshold succeed. - let evaluations = vec![(EVALUATOR_1, target_evaluation_usd / 2).into()]; - let evaluation_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone()); - inst.mint_plmc_to(evaluation_plmc); + // Increasing the price before the end doesn't make a project under the threshold succeed. + let evaluations = vec![(EVALUATOR_1, target_evaluation_usd / 2).into()]; + let evaluation_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone()); + inst.mint_plmc_to(evaluation_plmc); - let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_2, None); - inst.evaluate_for_users(project_id, evaluations.clone()).unwrap(); + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_2, None); + inst.evaluate_for_users(project_id, evaluations.clone()).unwrap(); - let old_price = ::PriceProvider::get_price(Location::here()).unwrap(); - PRICE_MAP.with_borrow_mut(|map| map.insert(Location::here(), old_price * 2.into())); + let old_price = ::PriceProvider::get_price(Location::here()).unwrap(); + PRICE_MAP.with_borrow_mut(|map| map.insert(Location::here(), old_price * 2.into())); - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); - } + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); + } - #[test] - fn different_decimals_ct_works_as_expected() { - // Setup some base values to compare different decimals - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let ed = inst.get_ed(); - let default_project_metadata = default_project_metadata(ISSUER_1); - let original_decimal_aware_price = default_project_metadata.minimum_price; - let original_price = ::PriceProvider::convert_back_to_normal_price( - original_decimal_aware_price, + #[test] + fn different_decimals_ct_works_as_expected() { + // Setup some base values to compare different decimals + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let ed = inst.get_ed(); + let default_project_metadata = default_project_metadata(ISSUER_1); + let original_decimal_aware_price = default_project_metadata.minimum_price; + let original_price = ::PriceProvider::convert_back_to_normal_price( + original_decimal_aware_price, + USD_DECIMALS, + default_project_metadata.token_information.decimals, + ) + .unwrap(); + let min_evaluation_amount_usd = ::MinUsdPerEvaluation::get(); + let stored_plmc_price = + inst.execute(|| ::PriceProvider::get_price(Location::here()).unwrap()); + let usable_plmc_price = inst.execute(|| { + ::PriceProvider::get_decimals_aware_price( + Location::here(), USD_DECIMALS, - default_project_metadata.token_information.decimals, + PLMC_DECIMALS, + ) + .unwrap() + }); + let min_evaluation_amount_plmc = + usable_plmc_price.reciprocal().unwrap().checked_mul_int(min_evaluation_amount_usd).unwrap(); + + // Test independent of CT decimals - Right PLMC conversion is stored. + // We move comma 4 places to the left since PLMC has 4 more decimals than USD. + assert_eq!(stored_plmc_price, FixedU128::from_float(8.4)); + assert_eq!(usable_plmc_price, FixedU128::from_float(0.00084)); + + let mut evaluation_ct_thresholds = Vec::new(); + let mut evaluation_usd_thresholds = Vec::new(); + let mut evaluation_plmc_thresholds = Vec::new(); + + let mut decimal_test = |decimals: u8| { + let mut project_metadata = default_project_metadata.clone(); + project_metadata.token_information.decimals = decimals; + project_metadata.minimum_price = ::PriceProvider::calculate_decimals_aware_price( + original_price, + USD_DECIMALS, + decimals, ) .unwrap(); - let min_evaluation_amount_usd = ::MinUsdPerEvaluation::get(); - let stored_plmc_price = - inst.execute(|| ::PriceProvider::get_price(Location::here()).unwrap()); - let usable_plmc_price = inst.execute(|| { - ::PriceProvider::get_decimals_aware_price( - Location::here(), - USD_DECIMALS, - PLMC_DECIMALS, - ) - .unwrap() - }); - let min_evaluation_amount_plmc = - usable_plmc_price.reciprocal().unwrap().checked_mul_int(min_evaluation_amount_usd).unwrap(); - - // Test independent of CT decimals - Right PLMC conversion is stored. - // We move comma 4 places to the left since PLMC has 4 more decimals than USD. - assert_eq!(stored_plmc_price, FixedU128::from_float(8.4)); - assert_eq!(usable_plmc_price, FixedU128::from_float(0.00084)); - - let mut evaluation_ct_thresholds = Vec::new(); - let mut evaluation_usd_thresholds = Vec::new(); - let mut evaluation_plmc_thresholds = Vec::new(); - - let mut decimal_test = |decimals: u8| { - let mut project_metadata = default_project_metadata.clone(); - project_metadata.token_information.decimals = decimals; - project_metadata.minimum_price = - ::PriceProvider::calculate_decimals_aware_price( - original_price, - USD_DECIMALS, - decimals, - ) - .unwrap(); - project_metadata.total_allocation_size = 1_000_000 * 10u128.pow(decimals as u32); - project_metadata.mainnet_token_max_supply = project_metadata.total_allocation_size; - - let issuer: AccountIdOf = (10_000 + inst.get_new_nonce()).try_into().unwrap(); - let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer, None); - - let evaluation_threshold = inst.execute(::EvaluationSuccessThreshold::get); - let evaluation_threshold_ct = evaluation_threshold * project_metadata.total_allocation_size; - evaluation_ct_thresholds.push(evaluation_threshold_ct); - - let evaluation_threshold_usd = - project_metadata.minimum_price.saturating_mul_int(evaluation_threshold_ct); - evaluation_usd_thresholds.push(evaluation_threshold_usd); - - let evaluation_threshold_plmc = - usable_plmc_price.reciprocal().unwrap().checked_mul_int(evaluation_threshold_usd).unwrap(); - evaluation_plmc_thresholds.push(evaluation_threshold_plmc); - - // CT price should be multiplied or divided by the amount of decimal difference with USD. - let decimal_abs_diff = USD_DECIMALS.abs_diff(decimals); - let original_price_as_usd = original_price.saturating_mul_int(10u128.pow(USD_DECIMALS as u32)); - let min_price_as_usd = project_metadata.minimum_price.saturating_mul_int(USD_UNIT); - if decimals < USD_DECIMALS { - assert_eq!(min_price_as_usd, original_price_as_usd * 10u128.pow(decimal_abs_diff as u32)); - } else { - assert_eq!(min_price_as_usd, original_price_as_usd / 10u128.pow(decimal_abs_diff as u32)); - } - - // A minimum evaluation goes through. This is a fixed USD/PLMC value, so independent of CT decimals. - inst.mint_plmc_to(vec![UserToPLMCBalance::new(EVALUATOR_1, min_evaluation_amount_plmc + ed)]); - assert_ok!(inst.execute(|| PolimecFunding::evaluate( - RuntimeOrigin::signed(EVALUATOR_1), - get_mock_jwt_with_cid( - EVALUATOR_1, - InvestorType::Retail, - generate_did_from_account(EVALUATOR_1), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - min_evaluation_amount_usd - ))); + project_metadata.total_allocation_size = 1_000_000 * 10u128.pow(decimals as u32); + project_metadata.mainnet_token_max_supply = project_metadata.total_allocation_size; + + let issuer: AccountIdOf = (10_000 + inst.get_new_nonce()).try_into().unwrap(); + let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer, None); - // Try bonding up to the threshold with a second evaluation - inst.mint_plmc_to(vec![UserToPLMCBalance::new( + let evaluation_threshold = inst.execute(::EvaluationSuccessThreshold::get); + let evaluation_threshold_ct = evaluation_threshold * project_metadata.total_allocation_size; + evaluation_ct_thresholds.push(evaluation_threshold_ct); + + let evaluation_threshold_usd = project_metadata.minimum_price.saturating_mul_int(evaluation_threshold_ct); + evaluation_usd_thresholds.push(evaluation_threshold_usd); + + let evaluation_threshold_plmc = + usable_plmc_price.reciprocal().unwrap().checked_mul_int(evaluation_threshold_usd).unwrap(); + evaluation_plmc_thresholds.push(evaluation_threshold_plmc); + + // CT price should be multiplied or divided by the amount of decimal difference with USD. + let decimal_abs_diff = USD_DECIMALS.abs_diff(decimals); + let original_price_as_usd = original_price.saturating_mul_int(10u128.pow(USD_DECIMALS as u32)); + let min_price_as_usd = project_metadata.minimum_price.saturating_mul_int(USD_UNIT); + if decimals < USD_DECIMALS { + assert_eq!(min_price_as_usd, original_price_as_usd * 10u128.pow(decimal_abs_diff as u32)); + } else { + assert_eq!(min_price_as_usd, original_price_as_usd / 10u128.pow(decimal_abs_diff as u32)); + } + + // A minimum evaluation goes through. This is a fixed USD/PLMC value, so independent of CT decimals. + inst.mint_plmc_to(vec![UserToPLMCBalance::new(EVALUATOR_1, min_evaluation_amount_plmc + ed)]); + assert_ok!(inst.execute(|| PolimecFunding::evaluate( + RuntimeOrigin::signed(EVALUATOR_1), + get_mock_jwt_with_cid( + EVALUATOR_1, + InvestorType::Retail, + generate_did_from_account(EVALUATOR_1), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + min_evaluation_amount_usd + ))); + + // Try bonding up to the threshold with a second evaluation + inst.mint_plmc_to(vec![UserToPLMCBalance::new( + EVALUATOR_2, + evaluation_threshold_plmc + ed - min_evaluation_amount_plmc, + )]); + assert_ok!(inst.execute(|| PolimecFunding::evaluate( + RuntimeOrigin::signed(EVALUATOR_2), + get_mock_jwt_with_cid( EVALUATOR_2, - evaluation_threshold_plmc + ed - min_evaluation_amount_plmc, - )]); - assert_ok!(inst.execute(|| PolimecFunding::evaluate( - RuntimeOrigin::signed(EVALUATOR_2), - get_mock_jwt_with_cid( - EVALUATOR_2, - InvestorType::Retail, - generate_did_from_account(EVALUATOR_2), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - evaluation_threshold_usd - min_evaluation_amount_usd - ))); + InvestorType::Retail, + generate_did_from_account(EVALUATOR_2), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + evaluation_threshold_usd - min_evaluation_amount_usd + ))); - // The evaluation should succeed when we bond the threshold PLMC amount in total. - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionRound); - }; + // The evaluation should succeed when we bond the threshold PLMC amount in total. + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionRound); + }; - for decimals in 6..=18 { - decimal_test(decimals); - } + for decimals in 6..=18 { + decimal_test(decimals); + } - // Since we use the same original price and allocation size and adjust for decimals, - // the USD and PLMC amounts should be the same - assert!(evaluation_usd_thresholds.iter().all(|x| *x == evaluation_usd_thresholds[0])); - assert!(evaluation_plmc_thresholds.iter().all(|x| *x == evaluation_plmc_thresholds[0])); + // Since we use the same original price and allocation size and adjust for decimals, + // the USD and PLMC amounts should be the same + assert!(evaluation_usd_thresholds.iter().all(|x| *x == evaluation_usd_thresholds[0])); + assert!(evaluation_plmc_thresholds.iter().all(|x| *x == evaluation_plmc_thresholds[0])); - // CT amounts however should be different from each other - let mut hash_set = HashSet::new(); - for amount in evaluation_ct_thresholds { - assert!(!hash_set.contains(&amount)); - hash_set.insert(amount); - } + // CT amounts however should be different from each other + let mut hash_set = HashSet::new(); + for amount in evaluation_ct_thresholds { + assert!(!hash_set.contains(&amount)); + hash_set.insert(amount); } } - #[cfg(test)] - mod failure { - use super::*; + #[test] + fn round_fails_after_not_enough_bonds() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let issuer = ISSUER_1; + let project_metadata = default_project_metadata(issuer); + let evaluations = inst.generate_failing_evaluations(project_metadata.clone(), 5); + let plmc_eval_deposits: Vec> = inst.calculate_evaluation_plmc_spent(evaluations.clone()); + let plmc_existential_deposits = plmc_eval_deposits.accounts().existential_deposits(); - #[test] - fn round_fails_after_not_enough_bonds() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(issuer); - let evaluations = inst.generate_failing_evaluations(project_metadata.clone(), 5); - let plmc_eval_deposits: Vec> = - inst.calculate_evaluation_plmc_spent(evaluations.clone()); - let plmc_existential_deposits = plmc_eval_deposits.accounts().existential_deposits(); - - let expected_evaluator_balances = inst.generic_map_operation( - vec![plmc_eval_deposits.clone(), plmc_existential_deposits.clone()], - MergeOperation::Add, - ); + let expected_evaluator_balances = inst.generic_map_operation( + vec![plmc_eval_deposits.clone(), plmc_existential_deposits.clone()], + MergeOperation::Add, + ); - inst.mint_plmc_to(plmc_eval_deposits.clone()); - inst.mint_plmc_to(plmc_existential_deposits.clone()); + inst.mint_plmc_to(plmc_eval_deposits.clone()); + inst.mint_plmc_to(plmc_existential_deposits.clone()); - let project_id = inst.create_evaluating_project(project_metadata, issuer, None); + let project_id = inst.create_evaluating_project(project_metadata, issuer, None); - inst.evaluate_for_users(project_id, evaluations).expect("Bonding should work"); + inst.evaluate_for_users(project_id, evaluations).expect("Bonding should work"); - inst.do_free_plmc_assertions(plmc_existential_deposits); - inst.do_reserved_plmc_assertions(plmc_eval_deposits, HoldReason::Evaluation.into()); + inst.do_free_plmc_assertions(plmc_existential_deposits); + inst.do_reserved_plmc_assertions(plmc_eval_deposits, HoldReason::Evaluation.into()); - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); - inst.settle_project(project_id, true); - inst.do_free_plmc_assertions(expected_evaluator_balances); - } + inst.settle_project(project_id, true); + inst.do_free_plmc_assertions(expected_evaluator_balances); } } @@ -318,7 +303,6 @@ mod start_evaluation_extrinsic { #[cfg(test)] mod failure { use super::*; - use polimec_common_test_utils::get_mock_jwt; #[test] fn non_institutional_jwt() { @@ -455,8 +439,6 @@ mod evaluate_extrinsic { #[cfg(test)] mod success { use super::*; - use frame_support::traits::fungible::InspectFreeze; - use pallet_balances::AccountData; #[test] fn all_investor_types() { @@ -470,10 +452,8 @@ mod evaluate_extrinsic { (EVALUATOR_2, 1000 * USD_UNIT).into(), (EVALUATOR_3, 20_000 * USD_UNIT).into(), ]; - let necessary_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone()); - inst.mint_plmc_ed_if_required(evaluations.accounts()); - inst.mint_plmc_to(necessary_plmc); + inst.mint_necessary_tokens_for_evaluations(evaluations.clone()); assert_ok!(inst.execute(|| PolimecFunding::evaluate( RuntimeOrigin::signed(evaluations[0].account), @@ -632,9 +612,7 @@ mod evaluate_extrinsic { }); let new_evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let new_plmc_required = inst.calculate_evaluation_plmc_spent(new_evaluations.clone()); - inst.mint_plmc_ed_if_required(new_plmc_required.accounts()); - inst.mint_plmc_to(new_plmc_required.clone()); + inst.mint_necessary_tokens_for_evaluations(new_evaluations.clone()); inst.evaluate_for_users(project_id, new_evaluations).unwrap(); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionRound); diff --git a/pallets/funding/src/tests/3_auction.rs b/pallets/funding/src/tests/3_auction.rs index f1540d534..d5fc1d5ba 100644 --- a/pallets/funding/src/tests/3_auction.rs +++ b/pallets/funding/src/tests/3_auction.rs @@ -1,336 +1,315 @@ use super::*; -use crate::ParticipationMode::*; -use frame_support::traits::fungible::InspectFreeze; -use polimec_common::assets::{AcceptedFundingAsset, AcceptedFundingAsset::USDT}; -use sp_core::bounded_vec; -use sp_runtime::traits::Convert; -use std::collections::HashSet; -use InvestorType::{self}; #[cfg(test)] mod round_flow { use super::*; - #[cfg(test)] - mod success { - use super::*; + #[test] + fn auction_round_completed() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 60, 10); + let _project_id = inst.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids); + } - #[test] - fn auction_round_completed() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 60, 10); - let _project_id = inst.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids); - } + #[test] + fn multiple_auction_projects_completed() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project1 = default_project_metadata(ISSUER_1); + let project2 = default_project_metadata(ISSUER_2); + let project3 = default_project_metadata(ISSUER_3); + let project4 = default_project_metadata(ISSUER_4); + let evaluations = inst.generate_successful_evaluations(project1.clone(), 5); + let bids = inst.generate_bids_from_total_ct_percent(project1.clone(), 60, 10); + + inst.create_finished_project(project1, ISSUER_1, None, evaluations.clone(), bids.clone()); + inst.create_finished_project(project2, ISSUER_2, None, evaluations.clone(), bids.clone()); + inst.create_finished_project(project3, ISSUER_3, None, evaluations.clone(), bids.clone()); + inst.create_finished_project(project4, ISSUER_4, None, evaluations, bids); + } - #[test] - fn multiple_auction_projects_completed() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project1 = default_project_metadata(ISSUER_1); - let project2 = default_project_metadata(ISSUER_2); - let project3 = default_project_metadata(ISSUER_3); - let project4 = default_project_metadata(ISSUER_4); - let evaluations = inst.generate_successful_evaluations(project1.clone(), 5); - let bids = inst.generate_bids_from_total_ct_percent(project1.clone(), 60, 10); - - inst.create_finished_project(project1, ISSUER_1, None, evaluations.clone(), bids.clone()); - inst.create_finished_project(project2, ISSUER_2, None, evaluations.clone(), bids.clone()); - inst.create_finished_project(project3, ISSUER_3, None, evaluations.clone(), bids.clone()); - inst.create_finished_project(project4, ISSUER_4, None, evaluations, bids); - } + #[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 = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); - #[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 = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed)); + } - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed)); - } + #[test] + fn different_decimals_ct_works_as_expected() { + // Setup some base values to compare different decimals + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let ed = inst.get_ed(); + let default_project_metadata = default_project_metadata(ISSUER_1); + let original_decimal_aware_price = default_project_metadata.minimum_price; + let original_price = ::PriceProvider::convert_back_to_normal_price( + original_decimal_aware_price, + USD_DECIMALS, + default_project_metadata.token_information.decimals, + ) + .unwrap(); + let usable_plmc_price = inst.execute(|| { + ::PriceProvider::get_decimals_aware_price( + Location::here(), + USD_DECIMALS, + PLMC_DECIMALS, + ) + .unwrap() + }); + let usdt_price = inst + .execute(|| PolimecFunding::get_decimals_aware_funding_asset_price(&AcceptedFundingAsset::USDT).unwrap()); + let usdc_price = inst + .execute(|| PolimecFunding::get_decimals_aware_funding_asset_price(&AcceptedFundingAsset::USDC).unwrap()); + let dot_price = inst + .execute(|| PolimecFunding::get_decimals_aware_funding_asset_price(&AcceptedFundingAsset::DOT).unwrap()); + + let mut funding_assets_cycle = + vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT].into_iter().cycle(); + + let mut min_bid_amounts_ct = Vec::new(); + let mut min_bid_amounts_usd = Vec::new(); + let mut auction_allocations_ct = Vec::new(); + let mut auction_allocations_usd = Vec::new(); + + let mut decimal_test = |decimals: u8| { + let funding_asset = funding_assets_cycle.next().unwrap(); + let funding_asset_usd_price = match funding_asset { + AcceptedFundingAsset::USDT => usdt_price, + AcceptedFundingAsset::USDC => usdc_price, + AcceptedFundingAsset::DOT => dot_price, + AcceptedFundingAsset::WETH => todo!(), + }; - #[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, + 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, - default_project_metadata.token_information.decimals, + decimals, ) .unwrap(); - let usable_plmc_price = inst.execute(|| { - ::PriceProvider::get_decimals_aware_price( - Location::here(), - USD_DECIMALS, - PLMC_DECIMALS, - ) - .unwrap() - }); - let usdt_price = inst.execute(|| { - PolimecFunding::get_decimals_aware_funding_asset_price(&AcceptedFundingAsset::USDT).unwrap() - }); - let usdc_price = inst.execute(|| { - PolimecFunding::get_decimals_aware_funding_asset_price(&AcceptedFundingAsset::USDC).unwrap() - }); - let dot_price = inst.execute(|| { - PolimecFunding::get_decimals_aware_funding_asset_price(&AcceptedFundingAsset::DOT).unwrap() - }); - - let mut funding_assets_cycle = - vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT] - .into_iter() - .cycle(); - - let mut min_bid_amounts_ct = Vec::new(); - let mut min_bid_amounts_usd = Vec::new(); - let mut auction_allocations_ct = Vec::new(); - let mut auction_allocations_usd = Vec::new(); - - let mut decimal_test = |decimals: u8| { - let funding_asset = funding_assets_cycle.next().unwrap(); - let funding_asset_usd_price = match funding_asset { - AcceptedFundingAsset::USDT => usdt_price, - AcceptedFundingAsset::USDC => usdc_price, - AcceptedFundingAsset::DOT => dot_price, - AcceptedFundingAsset::WETH => todo!(), - }; - - 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(), 5); - let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); - - let auction_allocation_ct = project_metadata.total_allocation_size; - auction_allocations_ct.push(auction_allocation_ct); - let auction_allocation_usd = project_metadata.minimum_price.saturating_mul_int(auction_allocation_ct); - auction_allocations_usd.push(auction_allocation_usd); - - let min_professional_bid_usd = - project_metadata.bidding_ticket_sizes.professional.usd_minimum_per_participation; - min_bid_amounts_usd.push(min_professional_bid_usd); - let min_professional_bid_ct = - project_metadata.minimum_price.reciprocal().unwrap().saturating_mul_int(min_professional_bid_usd); - let min_professional_bid_plmc = - usable_plmc_price.reciprocal().unwrap().saturating_mul_int(min_professional_bid_usd); - min_bid_amounts_ct.push(min_professional_bid_ct); - let min_professional_bid_funding_asset = - funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(min_professional_bid_usd); - - // Every project should want to raise 10MM USD on the auction round regardless of CT decimals - assert_eq!(auction_allocation_usd, 10_000_000 * USD_UNIT); - - // A minimum bid goes through. This is a fixed USD value, but the extrinsic amount depends on CT decimals. - inst.mint_plmc_ed_if_required(vec![BIDDER_1]); - inst.mint_funding_asset_ed_if_required(vec![(BIDDER_1, funding_asset.id())]); - inst.mint_plmc_to(vec![UserToPLMCBalance::new(BIDDER_1, min_professional_bid_plmc + ed)]); - inst.mint_funding_asset_to(vec![UserToFundingAsset::new( - BIDDER_1, - min_professional_bid_funding_asset, - funding_asset.id(), - )]); + 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); - assert_ok!(inst.execute(|| PolimecFunding::bid( - RuntimeOrigin::signed(BIDDER_1), - get_mock_jwt_with_cid( - BIDDER_1, - InvestorType::Professional, - generate_did_from_account(BIDDER_1), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - min_professional_bid_ct, - ParticipationMode::Classic(1u8), - funding_asset, - ))); + let issuer: AccountIdOf = (10_000 + inst.get_new_nonce()).try_into().unwrap(); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); - // The bucket should have 1MM * 10^decimals CT minus what we just bid - let bucket = inst.execute(|| Buckets::::get(project_id).unwrap()); - assert_eq!(bucket.amount_left, 1_000_000u128 * 10u128.pow(decimals as u32) - min_professional_bid_ct); - }; + let auction_allocation_ct = project_metadata.total_allocation_size; + auction_allocations_ct.push(auction_allocation_ct); + let auction_allocation_usd = project_metadata.minimum_price.saturating_mul_int(auction_allocation_ct); + auction_allocations_usd.push(auction_allocation_usd); + + let min_professional_bid_usd = + project_metadata.bidding_ticket_sizes.professional.usd_minimum_per_participation; + min_bid_amounts_usd.push(min_professional_bid_usd); + let min_professional_bid_ct = + project_metadata.minimum_price.reciprocal().unwrap().saturating_mul_int(min_professional_bid_usd); + let min_professional_bid_plmc = + usable_plmc_price.reciprocal().unwrap().saturating_mul_int(min_professional_bid_usd); + min_bid_amounts_ct.push(min_professional_bid_ct); + let min_professional_bid_funding_asset = + funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(min_professional_bid_usd); + + // Every project should want to raise 10MM USD on the auction round regardless of CT decimals + assert_eq!(auction_allocation_usd, 10_000_000 * USD_UNIT); + + // A minimum bid goes through. This is a fixed USD value, but the extrinsic amount depends on CT decimals. + inst.mint_plmc_ed_if_required(vec![BIDDER_1]); + inst.mint_funding_asset_ed_if_required(vec![(BIDDER_1, funding_asset.id())]); + inst.mint_plmc_to(vec![UserToPLMCBalance::new(BIDDER_1, min_professional_bid_plmc + ed)]); + inst.mint_funding_asset_to(vec![UserToFundingAsset::new( + BIDDER_1, + min_professional_bid_funding_asset, + funding_asset.id(), + )]); - for decimals in 6..=18 { - decimal_test(decimals); - } + assert_ok!(inst.execute(|| PolimecFunding::bid( + RuntimeOrigin::signed(BIDDER_1), + get_mock_jwt_with_cid( + BIDDER_1, + InvestorType::Professional, + generate_did_from_account(BIDDER_1), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + min_professional_bid_ct, + ParticipationMode::Classic(1u8), + funding_asset, + ))); - // Since we use the same original price and allocation size and adjust for decimals, - // the USD amounts should be the same - assert!(min_bid_amounts_usd.iter().all(|x| *x == min_bid_amounts_usd[0])); - assert!(auction_allocations_usd.iter().all(|x| *x == auction_allocations_usd[0])); + // The bucket should have 1MM * 10^decimals CT minus what we just bid + let bucket = inst.execute(|| Buckets::::get(project_id).unwrap()); + assert_eq!(bucket.amount_left, 1_000_000u128 * 10u128.pow(decimals as u32) - min_professional_bid_ct); + }; - // CT amounts however should be different from each other - let mut hash_set_1 = HashSet::new(); - for amount in min_bid_amounts_ct { - assert!(!hash_set_1.contains(&amount)); - hash_set_1.insert(amount); - } - let mut hash_set_2 = HashSet::new(); - for amount in auction_allocations_ct { - assert!(!hash_set_2.contains(&amount)); - hash_set_2.insert(amount); - } + for decimals in 6..=18 { + decimal_test(decimals); } - #[test] - fn auction_oversubscription() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let mut bucket = Pallet::::create_bucket_from_metadata(&project_metadata).unwrap(); - bucket.current_price = - bucket.initial_price + (bucket.delta_price.mul(PriceOf::::from_float(10.0))); - bucket.amount_left = bucket.delta_amount; - let bids = inst.generate_bids_from_bucket(project_metadata.clone(), bucket, USDT); + // Since we use the same original price and allocation size and adjust for decimals, + // the USD amounts should be the same + assert!(min_bid_amounts_usd.iter().all(|x| *x == min_bid_amounts_usd[0])); + assert!(auction_allocations_usd.iter().all(|x| *x == auction_allocations_usd[0])); - let _project_id = inst.create_finished_project( - project_metadata.clone(), - ISSUER_1, - None, - inst.generate_successful_evaluations(project_metadata.clone(), 5), - bids, - ); + // CT amounts however should be different from each other + let mut hash_set_1 = HashSet::new(); + for amount in min_bid_amounts_ct { + assert!(!hash_set_1.contains(&amount)); + hash_set_1.insert(amount); } - - #[test] - fn on_idle_clears_oversubscribed_bids() { - 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(), 5); - let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 100, 10); - - let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); - inst.mint_necessary_tokens_for_bids(project_id, bids.clone()); - inst.bid_for_users(project_id, bids.clone()).unwrap(); - - // Check full rejection of one bid by another - let last_bid_amount = bids[9].amount; - let oversubscribed_bid = - BidParams::::from((BIDDER_1, Retail, last_bid_amount, Classic(1u8), USDT)); - inst.mint_necessary_tokens_for_bids(project_id, vec![oversubscribed_bid.clone()]); - inst.bid_for_users(project_id, vec![oversubscribed_bid.clone()]).unwrap(); - assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id)), last_bid_amount); - inst.advance_time(1); - let rejected_bid = inst.execute(|| Bids::::get(project_id, 9)).unwrap(); - assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id)), Zero::zero()); - assert_eq!(rejected_bid.status, BidStatus::Rejected); - let yet_unknown_bid = inst.execute(|| Bids::::get(project_id, 8)).unwrap(); - assert_eq!(yet_unknown_bid.status, BidStatus::YetUnknown); - - // Check multiple bid rejections by one bid - let multiple_bids_amount = bids[8].amount + bids[7].amount + bids[6].amount; - let multiple_bids = - BidParams::::from((BIDDER_1, Retail, multiple_bids_amount, Classic(1u8), USDT)); - inst.mint_necessary_tokens_for_bids(project_id, vec![multiple_bids.clone()]); - inst.bid_for_users(project_id, vec![multiple_bids.clone()]).unwrap(); - assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id)), multiple_bids_amount); - inst.advance_time(1); - let rejected_bid_1 = inst.execute(|| Bids::::get(project_id, 8)).unwrap(); - let rejected_bid_2 = inst.execute(|| Bids::::get(project_id, 7)).unwrap(); - let rejected_bid_3 = inst.execute(|| Bids::::get(project_id, 6)).unwrap(); - assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id)), Zero::zero()); - assert_eq!(rejected_bid_1.status, BidStatus::Rejected); - assert_eq!(rejected_bid_2.status, BidStatus::Rejected); - assert_eq!(rejected_bid_3.status, BidStatus::Rejected); - let yet_unknown_bid = inst.execute(|| Bids::::get(project_id, 5)).unwrap(); - assert_eq!(yet_unknown_bid.status, BidStatus::YetUnknown); - - // Check partial rejection of one bid by another - let partial_bid_amount = last_bid_amount / 2; - let partial_bid = - BidParams::::from((BIDDER_1, Retail, partial_bid_amount, Classic(1u8), USDT)); - inst.mint_necessary_tokens_for_bids(project_id, vec![partial_bid.clone()]); - inst.bid_for_users(project_id, vec![partial_bid.clone()]).unwrap(); - assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id)), partial_bid_amount); - inst.advance_time(1); - let rejected_bid = inst.execute(|| Bids::::get(project_id, 5)).unwrap(); - assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id)), Zero::zero()); - assert_eq!(rejected_bid.status, BidStatus::PartiallyAccepted(last_bid_amount - partial_bid_amount)); - let yet_unknown_bid = inst.execute(|| Bids::::get(project_id, 4)).unwrap(); - assert_eq!(yet_unknown_bid.status, BidStatus::YetUnknown); + let mut hash_set_2 = HashSet::new(); + for amount in auction_allocations_ct { + assert!(!hash_set_2.contains(&amount)); + hash_set_2.insert(amount); } + } - #[test] - fn on_idle_clears_multiple_oversubscribed_projects() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - - // Create two projects with different metadata - let project_metadata_1 = default_project_metadata(ISSUER_1); - let project_metadata_2 = default_project_metadata(ISSUER_2); - - // Generate evaluations and bids for both projects - let evaluations_1 = inst.generate_successful_evaluations(project_metadata_1.clone(), 5); - let evaluations_2 = inst.generate_successful_evaluations(project_metadata_2.clone(), 5); - let bids_1 = inst.generate_bids_from_total_ct_percent(project_metadata_1.clone(), 100, 5); - let bids_2 = inst.generate_bids_from_total_ct_percent(project_metadata_2.clone(), 100, 5); + #[test] + fn auction_oversubscription() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); + let mut bucket = Pallet::::create_bucket_from_metadata(&project_metadata).unwrap(); + bucket.current_price = + bucket.initial_price + (bucket.delta_price.mul(PriceOf::::from_float(10.0))); + bucket.amount_left = bucket.delta_amount; + let bids = inst.generate_bids_from_bucket(project_metadata.clone(), bucket, USDT); + + let _project_id = inst.create_finished_project( + project_metadata.clone(), + ISSUER_1, + None, + inst.generate_successful_evaluations(project_metadata.clone(), 5), + bids, + ); + } - // Create two projects in auctioning state - let project_id_1 = - inst.create_auctioning_project(project_metadata_1.clone(), ISSUER_1, None, evaluations_1); - let project_id_2 = - inst.create_auctioning_project(project_metadata_2.clone(), ISSUER_2, None, evaluations_2); + #[test] + fn on_idle_clears_oversubscribed_bids() { + 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(), 5); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 100, 10); + + let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); + inst.mint_necessary_tokens_for_bids(project_id, bids.clone()); + inst.bid_for_users(project_id, bids.clone()).unwrap(); + + // Check full rejection of one bid by another + let last_bid_amount = bids[9].amount; + let oversubscribed_bid = + BidParams::::from((BIDDER_1, Retail, last_bid_amount, Classic(1u8), USDT)); + inst.mint_necessary_tokens_for_bids(project_id, vec![oversubscribed_bid.clone()]); + inst.bid_for_users(project_id, vec![oversubscribed_bid.clone()]).unwrap(); + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id)), last_bid_amount); + inst.advance_time(1); + let rejected_bid = inst.execute(|| Bids::::get(project_id, 9)).unwrap(); + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id)), Zero::zero()); + assert_eq!(rejected_bid.status, BidStatus::Rejected); + let yet_unknown_bid = inst.execute(|| Bids::::get(project_id, 8)).unwrap(); + assert_eq!(yet_unknown_bid.status, BidStatus::YetUnknown); + + // Check multiple bid rejections by one bid + let multiple_bids_amount = bids[8].amount + bids[7].amount + bids[6].amount; + let multiple_bids = + BidParams::::from((BIDDER_1, Retail, multiple_bids_amount, Classic(1u8), USDT)); + inst.mint_necessary_tokens_for_bids(project_id, vec![multiple_bids.clone()]); + inst.bid_for_users(project_id, vec![multiple_bids.clone()]).unwrap(); + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id)), multiple_bids_amount); + inst.advance_time(1); + let rejected_bid_1 = inst.execute(|| Bids::::get(project_id, 8)).unwrap(); + let rejected_bid_2 = inst.execute(|| Bids::::get(project_id, 7)).unwrap(); + let rejected_bid_3 = inst.execute(|| Bids::::get(project_id, 6)).unwrap(); + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id)), Zero::zero()); + assert_eq!(rejected_bid_1.status, BidStatus::Rejected); + assert_eq!(rejected_bid_2.status, BidStatus::Rejected); + assert_eq!(rejected_bid_3.status, BidStatus::Rejected); + let yet_unknown_bid = inst.execute(|| Bids::::get(project_id, 5)).unwrap(); + assert_eq!(yet_unknown_bid.status, BidStatus::YetUnknown); + + // Check partial rejection of one bid by another + let partial_bid_amount = last_bid_amount / 2; + let partial_bid = BidParams::::from((BIDDER_1, Retail, partial_bid_amount, Classic(1u8), USDT)); + inst.mint_necessary_tokens_for_bids(project_id, vec![partial_bid.clone()]); + inst.bid_for_users(project_id, vec![partial_bid.clone()]).unwrap(); + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id)), partial_bid_amount); + inst.advance_time(1); + let rejected_bid = inst.execute(|| Bids::::get(project_id, 5)).unwrap(); + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id)), Zero::zero()); + assert_eq!(rejected_bid.status, BidStatus::PartiallyAccepted(last_bid_amount - partial_bid_amount)); + let yet_unknown_bid = inst.execute(|| Bids::::get(project_id, 4)).unwrap(); + assert_eq!(yet_unknown_bid.status, BidStatus::YetUnknown); + } - // Place initial bids for both projects - inst.mint_necessary_tokens_for_bids(project_id_1, bids_1.clone()); - inst.mint_necessary_tokens_for_bids(project_id_2, bids_2.clone()); - inst.bid_for_users(project_id_1, bids_1.clone()).unwrap(); - inst.bid_for_users(project_id_2, bids_2.clone()).unwrap(); - - // Create oversubscribed bids for both projects - let oversubscribed_bid_1 = - BidParams::::from((BIDDER_1, Retail, bids_1[4].amount, Classic(1u8), USDT)); - let oversubscribed_bid_2 = - BidParams::::from((BIDDER_2, Retail, bids_2[4].amount, Classic(1u8), USDT)); - - // Place oversubscribed bids - inst.mint_necessary_tokens_for_bids(project_id_1, vec![oversubscribed_bid_1.clone()]); - inst.mint_necessary_tokens_for_bids(project_id_2, vec![oversubscribed_bid_2.clone()]); - inst.bid_for_users(project_id_1, vec![oversubscribed_bid_1.clone()]).unwrap(); - inst.bid_for_users(project_id_2, vec![oversubscribed_bid_2.clone()]).unwrap(); - - // Verify both projects are oversubscribed - assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id_1)), bids_1[4].amount); - assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id_2)), bids_2[4].amount); - - // Advance time to trigger on_idle - inst.advance_time(1); - - // Verify oversubscribed amounts are cleared for both projects - assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id_1)), Zero::zero()); - assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id_2)), Zero::zero()); - - // Verify bid statuses for both projects - let rejected_bid_1 = inst.execute(|| Bids::::get(project_id_1, 4)).unwrap(); - let rejected_bid_2 = inst.execute(|| Bids::::get(project_id_2, 9)).unwrap(); - assert_eq!(rejected_bid_1.status, BidStatus::Rejected); - assert_eq!(rejected_bid_2.status, BidStatus::Rejected); - - let yet_unknown_bid_1 = inst.execute(|| Bids::::get(project_id_1, 3)).unwrap(); - let yet_unknown_bid_2 = inst.execute(|| Bids::::get(project_id_2, 8)).unwrap(); - assert_eq!(yet_unknown_bid_1.status, BidStatus::YetUnknown); - assert_eq!(yet_unknown_bid_2.status, BidStatus::YetUnknown); - - inst.go_to_next_state(project_id_1); - inst.go_to_next_state(project_id_2); - - assert!(inst.execute(|| ProjectsInAuctionRound::::get()).to_vec().is_empty()) - } + #[test] + fn on_idle_clears_multiple_oversubscribed_projects() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + + // Create two projects with different metadata + let project_metadata_1 = default_project_metadata(ISSUER_1); + let project_metadata_2 = default_project_metadata(ISSUER_2); + + // Generate evaluations and bids for both projects + let evaluations_1 = inst.generate_successful_evaluations(project_metadata_1.clone(), 5); + let evaluations_2 = inst.generate_successful_evaluations(project_metadata_2.clone(), 5); + let bids_1 = inst.generate_bids_from_total_ct_percent(project_metadata_1.clone(), 100, 5); + let bids_2 = inst.generate_bids_from_total_ct_percent(project_metadata_2.clone(), 100, 5); + + // Create two projects in auctioning state + let project_id_1 = inst.create_auctioning_project(project_metadata_1.clone(), ISSUER_1, None, evaluations_1); + let project_id_2 = inst.create_auctioning_project(project_metadata_2.clone(), ISSUER_2, None, evaluations_2); + + // Place initial bids for both projects + inst.mint_necessary_tokens_for_bids(project_id_1, bids_1.clone()); + inst.mint_necessary_tokens_for_bids(project_id_2, bids_2.clone()); + inst.bid_for_users(project_id_1, bids_1.clone()).unwrap(); + inst.bid_for_users(project_id_2, bids_2.clone()).unwrap(); + + // Create oversubscribed bids for both projects + let oversubscribed_bid_1 = + BidParams::::from((BIDDER_1, Retail, bids_1[4].amount, Classic(1u8), USDT)); + let oversubscribed_bid_2 = + BidParams::::from((BIDDER_2, Retail, bids_2[4].amount, Classic(1u8), USDT)); + + // Place oversubscribed bids + inst.mint_necessary_tokens_for_bids(project_id_1, vec![oversubscribed_bid_1.clone()]); + inst.mint_necessary_tokens_for_bids(project_id_2, vec![oversubscribed_bid_2.clone()]); + inst.bid_for_users(project_id_1, vec![oversubscribed_bid_1.clone()]).unwrap(); + inst.bid_for_users(project_id_2, vec![oversubscribed_bid_2.clone()]).unwrap(); + + // Verify both projects are oversubscribed + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id_1)), bids_1[4].amount); + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id_2)), bids_2[4].amount); + + // Advance time to trigger on_idle + inst.advance_time(1); + + // Verify oversubscribed amounts are cleared for both projects + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id_1)), Zero::zero()); + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id_2)), Zero::zero()); + + // Verify bid statuses for both projects + let rejected_bid_1 = inst.execute(|| Bids::::get(project_id_1, 4)).unwrap(); + let rejected_bid_2 = inst.execute(|| Bids::::get(project_id_2, 9)).unwrap(); + assert_eq!(rejected_bid_1.status, BidStatus::Rejected); + assert_eq!(rejected_bid_2.status, BidStatus::Rejected); + + let yet_unknown_bid_1 = inst.execute(|| Bids::::get(project_id_1, 3)).unwrap(); + let yet_unknown_bid_2 = inst.execute(|| Bids::::get(project_id_2, 8)).unwrap(); + assert_eq!(yet_unknown_bid_1.status, BidStatus::YetUnknown); + assert_eq!(yet_unknown_bid_2.status, BidStatus::YetUnknown); + + inst.go_to_next_state(project_id_1); + inst.go_to_next_state(project_id_2); + + assert!(inst.execute(|| ProjectsInAuctionRound::::iter_keys().next().is_none())) } } @@ -341,7 +320,6 @@ mod bid_extrinsic { #[cfg(test)] mod success { use super::*; - use frame_support::dispatch::DispatchResult; #[test] fn evaluation_bond_counts_towards_bid() { @@ -1817,277 +1795,256 @@ mod bid_extrinsic { mod end_auction_extrinsic { use super::*; - #[cfg(test)] - mod success { - use super::*; - - // Partial acceptance at price <= wap (refund due to less CT bought) - // Rejection due to no more tokens left (full refund) - #[test] - fn bids_get_rejected_and_refunded() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let mut project_metadata = default_project_metadata(issuer); - project_metadata.total_allocation_size = 50_000 * CT_UNIT; - project_metadata.mainnet_token_max_supply = project_metadata.total_allocation_size; - 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 = inst.generate_successful_evaluations(project_metadata.clone(), 5); - - // 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::from(( - BIDDER_1, - Retail, - 5000 * CT_UNIT, - ParticipationMode::Classic(5u8), - AcceptedFundingAsset::USDT, - )); - let bid_2 = BidParams::from(( - BIDDER_2, - Institutional, - 40_000 * CT_UNIT, - ParticipationMode::Classic(5u8), - AcceptedFundingAsset::USDC, - )); - let bid_3 = BidParams::from(( - BIDDER_5, - Professional, - 10_000 * CT_UNIT, - ParticipationMode::Classic(5u8), - AcceptedFundingAsset::DOT, - )); - let bid_4 = BidParams::from(( - BIDDER_3, - Retail, - 6000 * CT_UNIT, - ParticipationMode::Classic(5u8), - AcceptedFundingAsset::USDT, - )); - let bid_5 = BidParams::from(( - BIDDER_4, - Retail, - 2000 * CT_UNIT, - ParticipationMode::Classic(5u8), - AcceptedFundingAsset::DOT, - )); - // post bucketing, the bids look like this: - // (BIDDER_1, 5k) - (BIDDER_2, 40k) - (BIDDER_5, 5k) - (BIDDER_5, 5k) - (BIDDER_3 - 5k) - (BIDDER_3 - 1k) - (BIDDER_4 - 2k) - // | -------------------- 10 USD ----------------------|---- 11 USD ---|---- 12 USD ----|----------- 13 USD -------------| - // (Accepted, 5k) - (Partially, 32k) - (Rejected, 5k) - (Accepted, 5k) - (Accepted - 5k) - (Accepted - 1k) - (Accepted - 2k) - - let bids = vec![bid_1, bid_2, bid_3, bid_4, bid_5]; - - let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); - - let plmc_amounts = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &bids, - project_metadata.clone(), - None, - ); - let funding_asset_amounts = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &bids, - project_metadata.clone(), - None, - ); - - inst.mint_plmc_ed_if_required(bids.accounts()); - inst.mint_funding_asset_ed_if_required(bids.to_account_asset_map()); - - let prev_plmc_balances = inst.get_free_plmc_balances_for(bids.accounts()); - let prev_funding_asset_balances = inst.get_free_funding_asset_balances_for(bids.to_account_asset_map()); - - inst.mint_plmc_to(plmc_amounts.clone()); - inst.mint_funding_asset_to(funding_asset_amounts.clone()); - - inst.bid_for_users(project_id, bids.clone()).unwrap(); - - inst.do_free_plmc_assertions(vec![ - UserToPLMCBalance::new(BIDDER_1, inst.get_ed()), - UserToPLMCBalance::new(BIDDER_2, inst.get_ed()), - ]); - inst.do_reserved_plmc_assertions(plmc_amounts.clone(), HoldReason::Participation.into()); - - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful)); - - let bidder_5_rejected_bid = inst.execute(|| Bids::::get(project_id, 2)).unwrap(); - let _bidder_5_accepted_bid = inst.execute(|| Bids::::get(project_id, 3)).unwrap(); - let bidder_5_plmc_pre_balance = inst.get_free_plmc_balance_for(bidder_5_rejected_bid.bidder); - let bidder_5_funding_asset_pre_balance = inst.get_free_funding_asset_balance_for( - bidder_5_rejected_bid.funding_asset.id(), - bidder_5_rejected_bid.bidder, - ); - - assert!(matches!( - inst.go_to_next_state(project_id), - ProjectStatus::SettlementStarted(FundingOutcome::Success) - )); - inst.settle_project(project_id, true); - - let returned_auction_plmc = - inst.calculate_auction_plmc_returned_from_all_bids_made(&bids, project_metadata.clone()); - let returned_funding_assets = - inst.calculate_auction_funding_asset_returned_from_all_bids_made(&bids, project_metadata.clone()); - - let expected_free_plmc = inst - .generic_map_operation(vec![returned_auction_plmc.clone(), prev_plmc_balances], MergeOperation::Add); - let expected_free_funding_assets = inst.generic_map_operation( - vec![returned_funding_assets.clone(), prev_funding_asset_balances], - MergeOperation::Add, - ); - let expected_reserved_plmc = inst.generic_map_operation( - vec![plmc_amounts.clone(), returned_auction_plmc.clone()], - MergeOperation::Subtract, - ); - let expected_final_funding_spent = inst.generic_map_operation( - vec![funding_asset_amounts.clone(), returned_funding_assets.clone()], - MergeOperation::Subtract, - ); - let expected_issuer_funding = inst.sum_funding_asset_mappings(vec![expected_final_funding_spent]); - - // Assertions about rejected bid - let bidder_5_plmc_post_balance = inst.get_free_plmc_balance_for(bidder_5_rejected_bid.bidder); - let bidder_5_funding_asset_post_balance = inst.get_free_funding_asset_balance_for( - bidder_5_rejected_bid.funding_asset.id(), - bidder_5_rejected_bid.bidder, - ); - - // Bidder 5's accepted bid should have some refunds due to paying the wap in the end instead of the bucket price. - // Bidder 5's rejected bid should have a full refund - let bidder_5_returned_plmc = - returned_auction_plmc.iter().find(|x| x.account == BIDDER_5).unwrap().plmc_amount; - let bidder_5_returned_funding_asset = - returned_funding_assets.iter().find(|x| x.account == BIDDER_5).unwrap().asset_amount; - - assert!(inst.execute(|| Bids::::get(project_id, 2)).is_none()); - assert_eq!(bidder_5_plmc_post_balance, bidder_5_plmc_pre_balance + bidder_5_returned_plmc); - assert_eq!( - bidder_5_funding_asset_post_balance, - bidder_5_funding_asset_pre_balance + bidder_5_returned_funding_asset - ); - - inst.do_free_plmc_assertions(expected_free_plmc); - inst.do_reserved_plmc_assertions(expected_reserved_plmc, HoldReason::Participation.into()); - inst.do_free_funding_asset_assertions(expected_free_funding_assets); - - for (asset, expected_amount) in expected_issuer_funding { - let real_amount = inst.get_free_funding_asset_balance_for(asset, ISSUER_1); - assert_eq!(real_amount, expected_amount); - } + // Partial acceptance at price <= wap (refund due to less CT bought) + // Rejection due to no more tokens left (full refund) + #[test] + fn bids_get_rejected_and_refunded() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let issuer = ISSUER_1; + let mut project_metadata = default_project_metadata(issuer); + project_metadata.total_allocation_size = 50_000 * CT_UNIT; + project_metadata.mainnet_token_max_supply = project_metadata.total_allocation_size; + 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 = inst.generate_successful_evaluations(project_metadata.clone(), 5); + + // 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::from(( + BIDDER_1, + Retail, + 5000 * CT_UNIT, + ParticipationMode::Classic(5u8), + AcceptedFundingAsset::USDT, + )); + let bid_2 = BidParams::from(( + BIDDER_2, + Institutional, + 40_000 * CT_UNIT, + ParticipationMode::Classic(5u8), + AcceptedFundingAsset::USDC, + )); + let bid_3 = BidParams::from(( + BIDDER_5, + Professional, + 10_000 * CT_UNIT, + ParticipationMode::Classic(5u8), + AcceptedFundingAsset::DOT, + )); + let bid_4 = BidParams::from(( + BIDDER_3, + Retail, + 6000 * CT_UNIT, + ParticipationMode::Classic(5u8), + AcceptedFundingAsset::USDT, + )); + let bid_5 = BidParams::from(( + BIDDER_4, + Retail, + 2000 * CT_UNIT, + ParticipationMode::Classic(5u8), + AcceptedFundingAsset::DOT, + )); + // post bucketing, the bids look like this: + // (BIDDER_1, 5k) - (BIDDER_2, 40k) - (BIDDER_5, 5k) - (BIDDER_5, 5k) - (BIDDER_3 - 5k) - (BIDDER_3 - 1k) - (BIDDER_4 - 2k) + // | -------------------- 10 USD ----------------------|---- 11 USD ---|---- 12 USD ----|----------- 13 USD -------------| + // (Accepted, 5k) - (Partially, 32k) - (Rejected, 5k) - (Accepted, 5k) - (Accepted - 5k) - (Accepted - 1k) - (Accepted - 2k) + + let bids = vec![bid_1, bid_2, bid_3, bid_4, bid_5]; + + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); + + let plmc_amounts = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + &bids, + project_metadata.clone(), + None, + ); + let funding_asset_amounts = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( + &bids, + project_metadata.clone(), + None, + ); + + inst.mint_plmc_ed_if_required(bids.accounts()); + inst.mint_funding_asset_ed_if_required(bids.to_account_asset_map()); + + let prev_plmc_balances = inst.get_free_plmc_balances_for(bids.accounts()); + let prev_funding_asset_balances = inst.get_free_funding_asset_balances_for(bids.to_account_asset_map()); + + inst.mint_plmc_to(plmc_amounts.clone()); + inst.mint_funding_asset_to(funding_asset_amounts.clone()); + + inst.bid_for_users(project_id, bids.clone()).unwrap(); + + inst.do_free_plmc_assertions(vec![ + UserToPLMCBalance::new(BIDDER_1, inst.get_ed()), + UserToPLMCBalance::new(BIDDER_2, inst.get_ed()), + ]); + inst.do_reserved_plmc_assertions(plmc_amounts.clone(), HoldReason::Participation.into()); + + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful)); + + let bidder_5_rejected_bid = inst.execute(|| Bids::::get(project_id, 2)).unwrap(); + let _bidder_5_accepted_bid = inst.execute(|| Bids::::get(project_id, 3)).unwrap(); + let bidder_5_plmc_pre_balance = inst.get_free_plmc_balance_for(bidder_5_rejected_bid.bidder); + let bidder_5_funding_asset_pre_balance = inst + .get_free_funding_asset_balance_for(bidder_5_rejected_bid.funding_asset.id(), bidder_5_rejected_bid.bidder); + + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success))); + inst.settle_project(project_id, true); + + let returned_auction_plmc = + inst.calculate_auction_plmc_returned_from_all_bids_made(&bids, project_metadata.clone()); + let returned_funding_assets = + inst.calculate_auction_funding_asset_returned_from_all_bids_made(&bids, project_metadata.clone()); + + let expected_free_plmc = + inst.generic_map_operation(vec![returned_auction_plmc.clone(), prev_plmc_balances], MergeOperation::Add); + let expected_free_funding_assets = inst.generic_map_operation( + vec![returned_funding_assets.clone(), prev_funding_asset_balances], + MergeOperation::Add, + ); + let expected_reserved_plmc = inst + .generic_map_operation(vec![plmc_amounts.clone(), returned_auction_plmc.clone()], MergeOperation::Subtract); + let expected_final_funding_spent = inst.generic_map_operation( + vec![funding_asset_amounts.clone(), returned_funding_assets.clone()], + MergeOperation::Subtract, + ); + let expected_issuer_funding = inst.sum_funding_asset_mappings(vec![expected_final_funding_spent]); + + // Assertions about rejected bid + let bidder_5_plmc_post_balance = inst.get_free_plmc_balance_for(bidder_5_rejected_bid.bidder); + let bidder_5_funding_asset_post_balance = inst + .get_free_funding_asset_balance_for(bidder_5_rejected_bid.funding_asset.id(), bidder_5_rejected_bid.bidder); + + // Bidder 5's accepted bid should have some refunds due to paying the wap in the end instead of the bucket price. + // Bidder 5's rejected bid should have a full refund + let bidder_5_returned_plmc = returned_auction_plmc.iter().find(|x| x.account == BIDDER_5).unwrap().plmc_amount; + let bidder_5_returned_funding_asset = + returned_funding_assets.iter().find(|x| x.account == BIDDER_5).unwrap().asset_amount; + + assert!(inst.execute(|| Bids::::get(project_id, 2)).is_none()); + assert_eq!(bidder_5_plmc_post_balance, bidder_5_plmc_pre_balance + bidder_5_returned_plmc); + assert_eq!( + bidder_5_funding_asset_post_balance, + bidder_5_funding_asset_pre_balance + bidder_5_returned_funding_asset + ); + + inst.do_free_plmc_assertions(expected_free_plmc); + inst.do_reserved_plmc_assertions(expected_reserved_plmc, HoldReason::Participation.into()); + inst.do_free_funding_asset_assertions(expected_free_funding_assets); + + for (asset, expected_amount) in expected_issuer_funding { + let real_amount = inst.get_free_funding_asset_balance_for(asset, ISSUER_1); + assert_eq!(real_amount, expected_amount); } + } - #[test] - fn oversubscribed_bid_can_get_refund_and_bid_again_in_auction_round() { - 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(), 5); - let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 100, 10); - - let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); - - inst.mint_necessary_tokens_for_bids(project_id, bids.clone()); - inst.bid_for_users(project_id, bids.clone()).unwrap(); - - // First bid is OTM - let first_bid_to_refund = bids[9].clone(); - // Second is classic - let second_bid_to_refund = bids[8].clone(); - - let oversubscribing_bids = vec![ - ( - BIDDER_1, - Retail, - first_bid_to_refund.amount + 200 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - ) - .into(), - ( - BIDDER_2, - Retail, - second_bid_to_refund.amount, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - ) - .into(), - ]; - - inst.mint_necessary_tokens_for_bids(project_id, oversubscribing_bids.clone()); - inst.bid_for_users(project_id, vec![oversubscribing_bids[0].clone()]).unwrap(); - - inst.process_oversubscribed_bids(project_id); - - let pre_first_refund_bidder_plmc_balance = inst.get_free_plmc_balance_for(first_bid_to_refund.bidder); - let pre_first_refund_bidder_funding_asset_balance = - inst.get_free_funding_asset_balance_for(first_bid_to_refund.asset.id(), first_bid_to_refund.bidder); - - let first_bid = inst.execute(|| Bids::::get(project_id, 9)).unwrap(); - assert!(matches!(first_bid.status, BidStatus::Rejected)); - let second_bid = inst.execute(|| Bids::::get(project_id, 8)).unwrap(); - assert!(matches!(second_bid.status, BidStatus::PartiallyAccepted(_))); - - inst.execute(|| { - assert_ok!(Pallet::::do_settle_bid(project_id, 9)); - assert_noop!( - Pallet::::do_settle_bid(project_id, 8), - Error::::SettlementNotStarted - ); - }); - - let post_first_refund_bidder_plmc_balance = inst.get_free_plmc_balance_for(first_bid_to_refund.bidder); - let post_first_refund_bidder_funding_asset_balance = - inst.get_free_funding_asset_balance_for(first_bid_to_refund.asset.id(), first_bid_to_refund.bidder); - - // OTM bid doesnt give PLMC refund to bidder - assert_eq!(post_first_refund_bidder_plmc_balance, pre_first_refund_bidder_plmc_balance); - let mut funding_asset_refund = first_bid.funding_asset_amount_locked; - let usd_ticket = first_bid.original_ct_usd_price.saturating_mul_int(first_bid.original_ct_amount); - inst.add_otm_fee_to(&mut funding_asset_refund, usd_ticket, first_bid_to_refund.asset); - assert_eq!( - post_first_refund_bidder_funding_asset_balance, - pre_first_refund_bidder_funding_asset_balance + funding_asset_refund - ); - - inst.bid_for_users(project_id, vec![oversubscribing_bids[1].clone()]).unwrap(); - - inst.process_oversubscribed_bids(project_id); - let pre_second_refund_bidder_plmc_balance = inst.get_free_plmc_balance_for(second_bid_to_refund.bidder); - let pre_second_refund_bidder_funding_asset_balance = - inst.get_free_funding_asset_balance_for(second_bid_to_refund.asset.id(), second_bid_to_refund.bidder); + #[test] + fn oversubscribed_bid_can_get_refund_and_bid_again_in_auction_round() { + 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(), 5); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 100, 10); - let second_bid = inst.execute(|| Bids::::get(project_id, 8)).unwrap(); - assert!(matches!(second_bid.status, BidStatus::Rejected)); - let third_bid = inst.execute(|| Bids::::get(project_id, 7)).unwrap(); - assert!(matches!(third_bid.status, BidStatus::PartiallyAccepted(_))); + let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); - inst.execute(|| { - assert_ok!(Pallet::::do_settle_bid(project_id, 8)); - assert_noop!( - Pallet::::do_settle_bid(project_id, 7), - Error::::SettlementNotStarted - ); - }); + inst.mint_necessary_tokens_for_bids(project_id, bids.clone()); + inst.bid_for_users(project_id, bids.clone()).unwrap(); - let post_second_refund_bidder_plmc_balance = inst.get_free_plmc_balance_for(second_bid_to_refund.bidder); - let post_second_refund_bidder_funding_asset_balance = - inst.get_free_funding_asset_balance_for(second_bid_to_refund.asset.id(), second_bid_to_refund.bidder); + // First bid is OTM + let first_bid_to_refund = bids[9].clone(); + // Second is classic + let second_bid_to_refund = bids[8].clone(); - // Classic bid - assert_eq!( - post_second_refund_bidder_plmc_balance, - pre_second_refund_bidder_plmc_balance + second_bid.plmc_bond + let oversubscribing_bids = vec![ + ( + BIDDER_1, + Retail, + first_bid_to_refund.amount + 200 * CT_UNIT, + ParticipationMode::Classic(1), + AcceptedFundingAsset::USDT, + ) + .into(), + (BIDDER_2, Retail, second_bid_to_refund.amount, ParticipationMode::Classic(1), AcceptedFundingAsset::USDT) + .into(), + ]; + + inst.mint_necessary_tokens_for_bids(project_id, oversubscribing_bids.clone()); + inst.bid_for_users(project_id, vec![oversubscribing_bids[0].clone()]).unwrap(); + + inst.process_oversubscribed_bids(project_id); + + let pre_first_refund_bidder_plmc_balance = inst.get_free_plmc_balance_for(first_bid_to_refund.bidder); + let pre_first_refund_bidder_funding_asset_balance = + inst.get_free_funding_asset_balance_for(first_bid_to_refund.asset.id(), first_bid_to_refund.bidder); + + let first_bid = inst.execute(|| Bids::::get(project_id, 9)).unwrap(); + assert!(matches!(first_bid.status, BidStatus::Rejected)); + let second_bid = inst.execute(|| Bids::::get(project_id, 8)).unwrap(); + assert!(matches!(second_bid.status, BidStatus::PartiallyAccepted(_))); + + inst.execute(|| { + assert_ok!(Pallet::::do_settle_bid(project_id, 9)); + assert_noop!( + Pallet::::do_settle_bid(project_id, 8), + Error::::SettlementNotStarted ); - assert_eq!( - post_second_refund_bidder_funding_asset_balance, - pre_second_refund_bidder_funding_asset_balance + second_bid.funding_asset_amount_locked + }); + + let post_first_refund_bidder_plmc_balance = inst.get_free_plmc_balance_for(first_bid_to_refund.bidder); + let post_first_refund_bidder_funding_asset_balance = + inst.get_free_funding_asset_balance_for(first_bid_to_refund.asset.id(), first_bid_to_refund.bidder); + + // OTM bid doesnt give PLMC refund to bidder + assert_eq!(post_first_refund_bidder_plmc_balance, pre_first_refund_bidder_plmc_balance); + let mut funding_asset_refund = first_bid.funding_asset_amount_locked; + let usd_ticket = first_bid.original_ct_usd_price.saturating_mul_int(first_bid.original_ct_amount); + inst.add_otm_fee_to(&mut funding_asset_refund, usd_ticket, first_bid_to_refund.asset); + assert_eq!( + post_first_refund_bidder_funding_asset_balance, + pre_first_refund_bidder_funding_asset_balance + funding_asset_refund + ); + + inst.bid_for_users(project_id, vec![oversubscribing_bids[1].clone()]).unwrap(); + + inst.process_oversubscribed_bids(project_id); + let pre_second_refund_bidder_plmc_balance = inst.get_free_plmc_balance_for(second_bid_to_refund.bidder); + let pre_second_refund_bidder_funding_asset_balance = + inst.get_free_funding_asset_balance_for(second_bid_to_refund.asset.id(), second_bid_to_refund.bidder); + + let second_bid = inst.execute(|| Bids::::get(project_id, 8)).unwrap(); + assert!(matches!(second_bid.status, BidStatus::Rejected)); + let third_bid = inst.execute(|| Bids::::get(project_id, 7)).unwrap(); + assert!(matches!(third_bid.status, BidStatus::PartiallyAccepted(_))); + + inst.execute(|| { + assert_ok!(Pallet::::do_settle_bid(project_id, 8)); + assert_noop!( + Pallet::::do_settle_bid(project_id, 7), + Error::::SettlementNotStarted ); - } + }); + + let post_second_refund_bidder_plmc_balance = inst.get_free_plmc_balance_for(second_bid_to_refund.bidder); + let post_second_refund_bidder_funding_asset_balance = + inst.get_free_funding_asset_balance_for(second_bid_to_refund.asset.id(), second_bid_to_refund.bidder); + + // Classic bid + assert_eq!( + post_second_refund_bidder_plmc_balance, + pre_second_refund_bidder_plmc_balance + second_bid.plmc_bond + ); + assert_eq!( + post_second_refund_bidder_funding_asset_balance, + pre_second_refund_bidder_funding_asset_balance + second_bid.funding_asset_amount_locked + ); } } diff --git a/pallets/funding/src/tests/5_funding_end.rs b/pallets/funding/src/tests/4_funding_end.rs similarity index 99% rename from pallets/funding/src/tests/5_funding_end.rs rename to pallets/funding/src/tests/4_funding_end.rs index 4547f37c7..7beb47211 100644 --- a/pallets/funding/src/tests/5_funding_end.rs +++ b/pallets/funding/src/tests/4_funding_end.rs @@ -1,5 +1,4 @@ use super::*; -use sp_runtime::PerThing; #[cfg(test)] mod end_funding_extrinsic { diff --git a/pallets/funding/src/tests/6_settlement.rs b/pallets/funding/src/tests/5_settlement.rs similarity index 99% rename from pallets/funding/src/tests/6_settlement.rs rename to pallets/funding/src/tests/5_settlement.rs index 018bbd495..fefe8ec0a 100644 --- a/pallets/funding/src/tests/6_settlement.rs +++ b/pallets/funding/src/tests/5_settlement.rs @@ -1,7 +1,5 @@ use super::*; -use frame_support::traits::fungibles::Inspect; -use polimec_common::assets::AcceptedFundingAsset; -use sp_runtime::bounded_vec; + #[cfg(test)] mod round_flow { use super::*; diff --git a/pallets/funding/src/tests/7_ct_migration.rs b/pallets/funding/src/tests/6_ct_migration.rs similarity index 98% rename from pallets/funding/src/tests/7_ct_migration.rs rename to pallets/funding/src/tests/6_ct_migration.rs index ea39766b6..3cfd73038 100644 --- a/pallets/funding/src/tests/7_ct_migration.rs +++ b/pallets/funding/src/tests/6_ct_migration.rs @@ -1,7 +1,4 @@ use super::*; -use frame_support::{assert_err, traits::fungibles::Inspect}; -use sp_runtime::bounded_vec; -use xcm::v4::MaxPalletNameLen; mod pallet_migration { use super::*; diff --git a/pallets/funding/src/tests/mod.rs b/pallets/funding/src/tests/mod.rs index a85fe23e7..ee939a3f1 100644 --- a/pallets/funding/src/tests/mod.rs +++ b/pallets/funding/src/tests/mod.rs @@ -1,35 +1,56 @@ use super::*; use crate::{ - instantiator::*, mock::*, traits::VestingDurationCalculation, CurrencyMetadata, Error, ProjectMetadata, TicketSize, + functions::runtime_api::{ExtrinsicHelpers, Leaderboards, ProjectInformation, UserInformation}, + instantiator::*, + mock::*, + traits::VestingDurationCalculation, + CurrencyMetadata, Error, + ParticipationMode::*, + ProjectMetadata, TicketSize, }; use defaults::*; use frame_support::{ assert_err, assert_noop, assert_ok, - traits::fungible::{MutateFreeze, MutateHold}, + dispatch::DispatchResult, + traits::{ + fungible::{InspectFreeze, MutateFreeze, MutateHold}, + fungibles::{metadata::Inspect as MetadataInspect, Inspect, Mutate}, + }, }; use itertools::Itertools; +use pallet_balances::AccountData; use parachains_common::DAYS; -use polimec_common::{ProvideAssetPrice, USD_DECIMALS, USD_UNIT}; -use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt_with_cid}; +use polimec_common::{ + assets::{ + AcceptedFundingAsset, + AcceptedFundingAsset::{DOT, USDC, USDT, WETH}, + }, + ProvideAssetPrice, USD_DECIMALS, USD_UNIT, +}; +use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt, get_mock_jwt_with_cid}; use sp_arithmetic::{traits::Zero, Percent, Perquintill}; -use sp_runtime::{traits::Convert, TokenError}; +use sp_runtime::{bounded_vec, traits::Convert, PerThing, TokenError}; use sp_std::cell::RefCell; -use std::iter::zip; +use std::{ + collections::{BTreeSet, HashSet}, + iter::zip, +}; +use xcm::v4::MaxPalletNameLen; use InvestorType::{self, *}; #[path = "1_application.rs"] mod application; #[path = "3_auction.rs"] mod auction; -#[path = "7_ct_migration.rs"] +#[path = "6_ct_migration.rs"] mod ct_migration; #[path = "2_evaluation.rs"] mod evaluation; -#[path = "5_funding_end.rs"] +#[path = "4_funding_end.rs"] mod funding_end; mod misc; mod runtime_api; -#[path = "6_settlement.rs"] +#[path = "5_settlement.rs"] mod settlement; pub type MockInstantiator = diff --git a/pallets/funding/src/tests/runtime_api.rs b/pallets/funding/src/tests/runtime_api.rs index 7bc867afa..855261c63 100644 --- a/pallets/funding/src/tests/runtime_api.rs +++ b/pallets/funding/src/tests/runtime_api.rs @@ -1,9 +1,4 @@ use super::*; -use crate::runtime_api::{ExtrinsicHelpers, Leaderboards, ProjectInformation, UserInformation}; -use frame_support::traits::fungibles::{metadata::Inspect, Mutate}; -use polimec_common::assets::AcceptedFundingAsset; -use sp_runtime::bounded_vec; -use InvestorType::{self}; #[test] fn top_evaluations() { diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index d3a705b91..523b26978 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -442,7 +442,7 @@ pub mod storage { pub fn calculate_usd_raised(self, allocation_size: Balance) -> Balance { let mut usd_raised = Balance::zero(); let mut cts_left = allocation_size; - let mut calculation_bucket = self.clone(); + let mut calculation_bucket = self; // If current bucket is the first bucket, then its not oversubscribed and we just return the price * tokens bought if calculation_bucket.current_price == calculation_bucket.initial_price { diff --git a/polimec-common/common/src/credentials/mod.rs b/polimec-common/common/src/credentials/mod.rs index 179fcc123..cc6e7cf5b 100644 --- a/polimec-common/common/src/credentials/mod.rs +++ b/polimec-common/common/src/credentials/mod.rs @@ -166,9 +166,8 @@ where state.serialize_field("iss", &self.issuer)?; state.serialize_field("investor_type", &self.investor_type)?; // Serialize the `ipfs_cid` and `did` fields as strings. - state - .serialize_field("aud", core::str::from_utf8(&self.ipfs_cid).map_err(|e| serde::ser::Error::custom(e))?)?; - state.serialize_field("did", core::str::from_utf8(&self.did).map_err(|e| serde::ser::Error::custom(e))?)?; + state.serialize_field("aud", core::str::from_utf8(&self.ipfs_cid).map_err(serde::ser::Error::custom)?)?; + state.serialize_field("did", core::str::from_utf8(&self.did).map_err(serde::ser::Error::custom)?)?; state.end() } } diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index d446ff16c..189302a7a 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -1493,7 +1493,7 @@ impl_runtime_apis! { } } - impl pallet_funding::runtime_api::Leaderboards for Runtime { + impl pallet_funding::functions::runtime_api::Leaderboards for Runtime { fn top_evaluations(project_id: ProjectId, amount: u32) -> Vec> { Funding::top_evaluations(project_id, amount) } @@ -1511,13 +1511,13 @@ impl_runtime_apis! { } } - impl pallet_funding::runtime_api::UserInformation for Runtime { + impl pallet_funding::functions::runtime_api::UserInformation for Runtime { fn contribution_tokens(account: AccountId) -> Vec<(ProjectId, Balance)> { Funding::contribution_tokens(account) } } - impl pallet_funding::runtime_api::ProjectInformation for Runtime { + impl pallet_funding::functions::runtime_api::ProjectInformation for Runtime { fn usd_target_percent_reached(project_id: ProjectId) -> FixedU128 { Funding::usd_target_percent_reached(project_id) } @@ -1527,7 +1527,7 @@ impl_runtime_apis! { } } - impl pallet_funding::runtime_api::ExtrinsicHelpers for Runtime { + impl pallet_funding::functions::runtime_api::ExtrinsicHelpers for Runtime { fn funding_asset_to_ct_amount_classic(project_id: ProjectId, asset: AcceptedFundingAsset, asset_amount: Balance) -> Balance { Funding::funding_asset_to_ct_amount_classic(project_id, asset, asset_amount) }