diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs index 220e9a87d..359e55e67 100644 --- a/integration-tests/src/lib.rs +++ b/integration-tests/src/lib.rs @@ -182,4 +182,4 @@ pub mod shortcuts { pub type PenpalSystem = ::System; pub type StatemintSystem = ::System; } -use shortcuts::*; \ No newline at end of file +use shortcuts::*; diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs index ae851b0bc..23580e04b 100644 --- a/pallets/funding/src/benchmarking.rs +++ b/pallets/funding/src/benchmarking.rs @@ -416,25 +416,20 @@ mod benchmarks { 1u8, AcceptedFundingAsset::USDT, ); + let bid_params = + inst.simulate_bids_with_bucket(vec![bid_params], project_id)[0].clone(); let necessary_plmc: Vec> = - BenchInstantiator::::calculate_auction_plmc_spent(vec![bid_params.clone()]); + BenchInstantiator::::calculate_auction_plmc_spent(&vec![bid_params.clone()], None); let existential_deposits: Vec> = necessary_plmc.accounts().existential_deposits(); let necessary_usdt: Vec> = - BenchInstantiator::::calculate_auction_funding_asset_spent(vec![bid_params.clone()]); + BenchInstantiator::::calculate_auction_funding_asset_spent(&vec![bid_params.clone()], None); inst.mint_plmc_to(necessary_plmc.clone()); inst.mint_plmc_to(existential_deposits.clone()); inst.mint_statemint_asset_to(necessary_usdt.clone()); #[extrinsic_call] - bid( - RawOrigin::Signed(bidder.clone()), - project_id, - bid_params.amount, - bid_params.price, - bid_params.multiplier, - bid_params.asset, - ); + bid(RawOrigin::Signed(bidder.clone()), project_id, bid_params.amount, bid_params.multiplier, bid_params.asset); // * validity checks * // Storage @@ -515,7 +510,7 @@ mod benchmarks { let project_metadata = default_project::(inst.get_new_nonce(), issuer.clone()); - let project_id = inst.create_community_contributing_project( + let (project_id, _) = inst.create_community_contributing_project( project_metadata.clone(), issuer, default_evaluations::(), diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index 4452b5c9d..f93077810 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -86,11 +86,6 @@ impl Pallet { // * Calculate new variables * let fundraising_target = initial_metadata.minimum_price.checked_mul_int(total_allocation_size).ok_or(Error::::BadMath)?; - let bucket_delta_amount = Percent::from_percent(10) * initial_metadata.total_allocation_size.0; - let ten_percent_in_price: ::Price = - PriceOf::::checked_from_rational(1, 10).ok_or(Error::::BadMath)?; - let bucket_delta_price: ::Price = - initial_metadata.minimum_price.saturating_mul(ten_percent_in_price); let now = >::block_number(); let project_details = ProjectDetails { issuer: issuer.clone(), @@ -109,13 +104,8 @@ impl Pallet { }, funding_end_block: None, }; - let bucket: BucketOf = Bucket::new( - initial_metadata.total_allocation_size.0, - initial_metadata.minimum_price, - bucket_delta_price, - bucket_delta_amount, - ); + let bucket: BucketOf = Self::create_bucket_from_metadata(&initial_metadata)?; // * Update storage * ProjectsMetadata::::insert(project_id, &initial_metadata); ProjectsDetails::::insert(project_id, project_details); @@ -856,13 +846,12 @@ impl Pallet { Ok(()) } - /// Bid for a project in the bidding stage + /// Bid for a project in the bidding stage. /// /// # Arguments /// * `bidder` - The account that is bidding /// * `project_id` - The project to bid for /// * `amount` - The amount of tokens that the bidder wants to buy - /// * `price` - The price in USD per token that the bidder is willing to pay for /// * `multiplier` - Used for calculating how much PLMC needs to be bonded to spend this much money (in USD) /// /// # Storage access @@ -874,7 +863,6 @@ impl Pallet { bidder: &AccountIdOf, project_id: T::ProjectIdentifier, ct_amount: BalanceOf, - _ct_usd_price: T::Price, multiplier: MultiplierOf, funding_asset: AcceptedFundingAsset, ) -> Result<(), DispatchError> { @@ -1819,6 +1807,22 @@ impl Pallet { Ok(()) } + pub fn create_bucket_from_metadata(metadata: &ProjectMetadataOf) -> Result, DispatchError> { + let bucket_delta_amount = Percent::from_percent(10) * metadata.total_allocation_size.0; + let ten_percent_in_price: ::Price = + PriceOf::::checked_from_rational(1, 10).ok_or(Error::::BadMath)?; + let bucket_delta_price: ::Price = metadata.minimum_price.saturating_mul(ten_percent_in_price); + + let bucket: BucketOf = Bucket::new( + metadata.total_allocation_size.0, + metadata.minimum_price, + bucket_delta_price, + bucket_delta_amount, + ); + + Ok(bucket) + } + pub fn calculate_plmc_bond( ticket_size: BalanceOf, multiplier: MultiplierOf, @@ -1861,57 +1865,20 @@ impl Pallet { let mut bid_usd_value_sum = BalanceOf::::zero(); let project_account = Self::fund_account_id(project_id); let plmc_price = T::PriceProvider::get_price(PLMC_STATEMINT_ID).ok_or(Error::::PLMCPriceNotAvailable)?; - // sort bids by price, and equal prices sorted by block number + // sort bids by price, and equal prices sorted by id bids.sort_by(|a, b| b.cmp(a)); // accept only bids that were made before `end_block` i.e end of candle auction let bids: Result, DispatchError> = bids .into_iter() .map(|mut bid| { if bid.when > end_block { - bid.status = BidStatus::Rejected(RejectionReason::AfterCandleEnd); - bid.final_ct_amount = Zero::zero(); - bid.final_ct_usd_price = Zero::zero(); - - T::FundingCurrency::transfer( - bid.funding_asset.to_statemint_id(), - &project_account, - &bid.bidder, - bid.funding_asset_amount_locked, - Preservation::Preserve, - )?; - T::NativeCurrency::release( - &LockType::Participation(project_id), - &bid.bidder, - bid.plmc_bond, - Precision::Exact, - )?; - bid.funding_asset_amount_locked = Zero::zero(); - bid.plmc_bond = Zero::zero(); - - return Ok(bid) + return Self::refund_bid(&mut bid, project_id, &project_account, RejectionReason::AfterCandleEnd) + .and(Ok(bid)) } let buyable_amount = total_allocation_size.saturating_sub(bid_token_amount_sum); if buyable_amount.is_zero() { - bid.status = BidStatus::Rejected(RejectionReason::NoTokensLeft); - bid.final_ct_amount = Zero::zero(); - bid.final_ct_usd_price = Zero::zero(); - - T::FundingCurrency::transfer( - bid.funding_asset.to_statemint_id(), - &project_account, - &bid.bidder, - bid.funding_asset_amount_locked, - Preservation::Preserve, - )?; - T::NativeCurrency::release( - &LockType::Participation(project_id), - &bid.bidder, - bid.plmc_bond, - Precision::Exact, - )?; - bid.funding_asset_amount_locked = Zero::zero(); - bid.plmc_bond = Zero::zero(); - return Ok(bid) + return Self::refund_bid(&mut bid, project_id, &project_account, RejectionReason::NoTokensLeft) + .and(Ok(bid)) } else if bid.original_ct_amount <= buyable_amount { let maybe_ticket_size = bid.original_ct_usd_price.checked_mul_int(bid.original_ct_amount); if let Some(ticket_size) = maybe_ticket_size { @@ -1919,27 +1886,8 @@ impl Pallet { bid_usd_value_sum.saturating_accrue(ticket_size); bid.status = BidStatus::Accepted; } else { - bid.status = BidStatus::Rejected(RejectionReason::BadMath); - - bid.final_ct_amount = Zero::zero(); - bid.final_ct_usd_price = Zero::zero(); - - T::FundingCurrency::transfer( - bid.funding_asset.to_statemint_id(), - &project_account, - &bid.bidder, - bid.funding_asset_amount_locked, - Preservation::Preserve, - )?; - T::NativeCurrency::release( - &LockType::Participation(project_id), - &bid.bidder, - bid.plmc_bond, - Precision::Exact, - )?; - bid.funding_asset_amount_locked = Zero::zero(); - bid.plmc_bond = Zero::zero(); - return Ok(bid) + return Self::refund_bid(&mut bid, project_id, &project_account, RejectionReason::BadMath) + .and(Ok(bid)) } } else { let maybe_ticket_size = bid.original_ct_usd_price.checked_mul_int(buyable_amount); @@ -1983,27 +1931,8 @@ impl Pallet { bid.funding_asset_amount_locked = funding_asset_amount_needed; bid.plmc_bond = plmc_bond_needed; } else { - bid.status = BidStatus::Rejected(RejectionReason::BadMath); - bid.final_ct_amount = Zero::zero(); - bid.final_ct_usd_price = Zero::zero(); - - T::FundingCurrency::transfer( - bid.funding_asset.to_statemint_id(), - &project_account, - &bid.bidder, - bid.funding_asset_amount_locked, - Preservation::Preserve, - )?; - T::NativeCurrency::release( - &LockType::Participation(project_id), - &bid.bidder, - bid.plmc_bond, - Precision::Exact, - )?; - bid.funding_asset_amount_locked = Zero::zero(); - bid.plmc_bond = Zero::zero(); - - return Ok(bid) + return Self::refund_bid(&mut bid, project_id, &project_account, RejectionReason::BadMath) + .and(Ok(bid)) } } @@ -2032,27 +1961,20 @@ impl Pallet { let current_bucket = Buckets::::get(project_id).ok_or(Error::::ProjectNotFound)?; let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectNotFound)?; let is_first_bucket = current_bucket.current_price == project_metadata.minimum_price; + + let calc_weighted_price_fn = |bid: &BidInfoOf, amount: BalanceOf| -> Option> { + let ticket_size = bid.original_ct_usd_price.saturating_mul_int(amount); + let bid_weight = ::saturating_from_rational(ticket_size, bid_usd_value_sum); + let weighted_price = bid.original_ct_usd_price.saturating_mul(bid_weight); + Some(weighted_price) + }; let weighted_token_price = match is_first_bucket && !bids.is_empty() { true => project_metadata.minimum_price, false => bids .iter() .filter_map(|bid| match bid.status { - BidStatus::Accepted => { - let ticket_size = bid.original_ct_usd_price.saturating_mul_int(bid.original_ct_amount); - let bid_weight = - ::saturating_from_rational(ticket_size, bid_usd_value_sum); - let weighted_price = bid.original_ct_usd_price.saturating_mul(bid_weight); - Some(weighted_price) - }, - - BidStatus::PartiallyAccepted(amount, _) => { - let ticket_size = bid.original_ct_usd_price.saturating_mul_int(amount); - let bid_weight = - ::saturating_from_rational(ticket_size, bid_usd_value_sum); - let weighted_price = bid.original_ct_usd_price.saturating_mul(bid_weight); - Some(weighted_price) - }, - + BidStatus::Accepted => calc_weighted_price_fn(bid, bid.original_ct_amount), + BidStatus::PartiallyAccepted(amount, _) => calc_weighted_price_fn(bid, amount), _ => None, }) .reduce(|a, b| a.saturating_add(b)) @@ -2125,6 +2047,31 @@ impl Pallet { Ok(()) } + /// Refund a bid because of `reason`. + fn refund_bid<'a>( + bid: &'a mut BidInfoOf, + project_id: T::ProjectIdentifier, + project_account: &'a AccountIdOf, + reason: RejectionReason, + ) -> Result<(), DispatchError> { + bid.status = BidStatus::Rejected(reason); + bid.final_ct_amount = Zero::zero(); + bid.final_ct_usd_price = Zero::zero(); + + T::FundingCurrency::transfer( + bid.funding_asset.to_statemint_id(), + project_account, + &bid.bidder, + bid.funding_asset_amount_locked, + Preservation::Preserve, + )?; + T::NativeCurrency::release(&LockType::Participation(project_id), &bid.bidder, bid.plmc_bond, Precision::Exact)?; + bid.funding_asset_amount_locked = Zero::zero(); + bid.plmc_bond = Zero::zero(); + + Ok(()) + } + pub fn select_random_block( candle_starting_block: T::BlockNumber, candle_ending_block: T::BlockNumber, diff --git a/pallets/funding/src/instantiator.rs b/pallets/funding/src/instantiator.rs index ce056b045..6a2487c2b 100644 --- a/pallets/funding/src/instantiator.rs +++ b/pallets/funding/src/instantiator.rs @@ -42,10 +42,10 @@ use sp_std::{ use crate::{ traits::{BondingRequirementCalculation, ProvideStatemintPrice}, AcceptedFundingAsset, AccountIdOf, AssetIdOf, AuctionPhase, BalanceOf, BidInfoOf, BidStatus, Bids, BlockNumberOf, - BlockNumberPair, Cleaner, Config, Contributions, Error, EvaluationInfoOf, EvaluationRoundInfoOf, EvaluatorsOutcome, - Event, LockType, MultiplierOf, PhaseTransitionPoints, PriceOf, ProjectDetailsOf, ProjectIdOf, ProjectMetadataOf, - ProjectStatus, ProjectsDetails, ProjectsMetadata, ProjectsToUpdate, RewardInfoOf, UpdateType, VestingInfoOf, - PLMC_STATEMINT_ID, + BlockNumberPair, BucketOf, Buckets, Cleaner, Config, Contributions, Error, EvaluationInfoOf, EvaluationRoundInfoOf, + EvaluatorsOutcome, Event, LockType, MultiplierOf, PhaseTransitionPoints, PriceOf, ProjectDetailsOf, ProjectIdOf, + ProjectMetadataOf, ProjectStatus, ProjectsDetails, ProjectsMetadata, ProjectsToUpdate, RewardInfoOf, UpdateType, + VestingInfoOf, PLMC_STATEMINT_ID, }; pub use testing_macros::*; @@ -199,10 +199,11 @@ where for UserToPLMCBalance { account, plmc_amount } in correct_funds { self.execute(|| { let reserved = ::NativeCurrency::balance_on_hold(&reserve_type, &account); - // TODO: Enable this - // assert_eq!(reserved, plmc_amount); + println!("Account: {:?}, reserved: {:?}, expected: {:?}", account, Into::into(reserved), plmc_amount); + assert_eq!(reserved, plmc_amount); }); } + println!("NEXT") } pub fn mint_plmc_to(&mut self, mapping: Vec>) { @@ -258,8 +259,7 @@ where for UserToPLMCBalance { account, plmc_amount } in correct_funds { self.execute(|| { let free = ::NativeCurrency::balance(&account); - // TODO: Enable this - // assert_eq!(free, plmc_amount); + assert_eq!(free, plmc_amount); }); } } @@ -285,11 +285,11 @@ where Bids::::iter_prefix_values((project_id, account.clone())) .map(|c| c.funding_asset_amount_locked) .fold(Zero::zero(), |a, b| a + b); - // assert_eq!( - // contribution_total, asset_amount, - // "Wrong statemint asset balance expected for stored auction info on user {:?}", - // account - // ); + assert_eq!( + contribution_total, asset_amount, + "Wrong statemint asset balance expected for stored auction info on user {:?}", + account + ); }); } } @@ -444,48 +444,104 @@ where output } - pub fn calculate_auction_plmc_spent(bids: Vec>) -> Vec> { + /// Calculate the amount of PLMC that would be locked if the given bids were to be accepted. + /// This is the amount of PLMC that would be locked if the bids were to be accepted, but not + /// considering the evaluation bonds. + /// + /// * `bids` - The bids to calculate the bonded PLMC amount for. + /// * `weighted_price` - Used to calculate the new PLMC bond after the weighted price has + /// been calculated (if the weighted price is lower than the bid price). + /// + pub fn calculate_auction_plmc_spent( + bids: &Vec>, + weighted_price: Option>, + ) -> Vec> { let plmc_price = T::PriceProvider::get_price(PLMC_STATEMINT_ID).unwrap().clone(); let mut output = Vec::new(); for bid in bids { - let usd_ticket_size = bid.price.saturating_mul_int(bid.amount); - let multiplier = bid.multiplier; - let usd_bond = multiplier.calculate_bonding_requirement::(usd_ticket_size).unwrap(); + let final_price = match weighted_price { + Some(p) if bid.price < p => bid.price, + Some(p) => p, + None => bid.price, + }; + + let usd_ticket_size = final_price.saturating_mul_int(bid.amount); + let usd_bond = bid.multiplier.calculate_bonding_requirement::(usd_ticket_size).unwrap(); let plmc_bond = plmc_price.reciprocal().unwrap().saturating_mul_int(usd_bond); - output.push(UserToPLMCBalance::new(bid.bidder, plmc_bond)); + output.push(UserToPLMCBalance::new(bid.bidder.clone(), plmc_bond)); } output } - // This differs from `calculate_auction_plmc_spent` in that it recalculates bids over the average price as using that price. - pub fn calculate_auction_plmc_spent_after_price_calculation( - bids: Vec>, - price: PriceOf, - ) -> Vec> { - let plmc_price = T::PriceProvider::get_price(PLMC_STATEMINT_ID).unwrap().clone(); + pub fn calculate_auction_funding_asset_spent( + bids: &Vec>, + weighted_price: Option>, + ) -> Vec> { let mut output = Vec::new(); for bid in bids { - let final_price = if bid.price < price { bid.price } else { price }; - + let final_price = match weighted_price { + Some(p) if bid.price < p => bid.price, + Some(p) => p, + None => bid.price, + }; + let asset_price = T::PriceProvider::get_price(bid.asset.to_statemint_id()).unwrap().clone(); let usd_ticket_size = final_price.saturating_mul_int(bid.amount); - let usd_bond = bid.multiplier.calculate_bonding_requirement::(usd_ticket_size).unwrap(); - let plmc_bond = plmc_price.reciprocal().unwrap().saturating_mul_int(usd_bond); - output.push(UserToPLMCBalance::new(bid.bidder, plmc_bond)); + let funding_asset_spent = asset_price.reciprocal().unwrap().saturating_mul_int(usd_ticket_size); + output.push(UserToStatemintAsset::new( + bid.bidder.clone(), + funding_asset_spent, + bid.asset.to_statemint_id(), + )); } output } - pub fn calculate_auction_funding_asset_spent(bids: Vec>) -> Vec> { + pub fn simulate_bids_with_bucket(&mut self, bids: Vec>, project_id: T::ProjectIdentifier) -> Vec> { let mut output = Vec::new(); + let mut bucket: BucketOf = self.execute(|| Buckets::::get(project_id).unwrap().clone()); for bid in bids { - let asset_price = T::PriceProvider::get_price(bid.asset.to_statemint_id()).unwrap().clone(); - let usd_ticket_size = bid.price.saturating_mul_int(bid.amount); - let funding_asset_spent = asset_price.reciprocal().unwrap().saturating_mul_int(usd_ticket_size); - output.push(UserToStatemintAsset::new(bid.bidder, funding_asset_spent, bid.asset.to_statemint_id())); + let mut amount_to_bid = bid.amount; + + 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 { + bidder: bid.bidder.clone(), + amount: bid_amount, + price: bucket.current_price, + multiplier: bid.multiplier, + asset: bid.asset, + }); + bucket.update(bid_amount); + amount_to_bid.saturating_reduce(bid_amount); + } } output } + /// Filters the bids that would be rejected after the auction ends. + pub fn filter_bids_after_auction(bids: Vec>, total_cts: BalanceOf) -> Vec> { + let mut filtered_bids: Vec> = Vec::new(); + let mut sorted_bids = bids; + sorted_bids.sort_by(|a, b| b.price.cmp(&a.price)); + 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 { + bidder: bid.bidder.clone(), + amount: total_cts_left, + price: bid.price, + multiplier: bid.multiplier, + asset: bid.asset, + }); + total_cts_left = Zero::zero(); + } + } + filtered_bids + } + pub fn calculate_contributed_plmc_spent( contributions: Vec>, token_usd_price: PriceOf, @@ -520,34 +576,27 @@ where plmc_amount: slash_percentage * plmc_amount.clone(), }) .collect::>(); - let available_evaluation_locked_plmc_for_lock_transfer = Self::merge_subtract_mappings_by_user( - evaluation_locked_plmc_amounts.clone(), - vec![slashable_min_deposits.clone()], + let available_evaluation_locked_plmc_for_lock_transfer = Self::generic_map_operation( + vec![evaluation_locked_plmc_amounts.clone(), slashable_min_deposits.clone()], + MergeOperation::Subtract, ); - // how much new plmc was actually locked, considering already evaluation bonds used first. - let actual_contribution_locked_plmc_amounts = Self::generic_map_merge( + // how much new plmc was actually locked, considering already evaluation bonds used + // first. + let actual_contribution_locked_plmc_amounts = Self::generic_map_operation( vec![ theoretical_contribution_locked_plmc_amounts.clone(), available_evaluation_locked_plmc_for_lock_transfer, ], - |user_to_plmc| user_to_plmc.account.clone(), - |UserToPLMCBalance { account: acc_1, plmc_amount: contribution_amount }, - UserToPLMCBalance { account: _acc_2, plmc_amount: evaluation_amount }| { - if contribution_amount > evaluation_amount { - UserToPLMCBalance::new(acc_1.clone(), *contribution_amount - *evaluation_amount) - } else { - UserToPLMCBalance::new(acc_1.clone(), Zero::zero()) - } - }, + MergeOperation::Subtract, ); - let mut result = Self::merge_add_mappings_by_user(vec![ + let mut result = Self::generic_map_operation(vec![ evaluation_locked_plmc_amounts, actual_contribution_locked_plmc_amounts, - ]); + ], MergeOperation::Add); if slashed { - result = Self::merge_subtract_mappings_by_user(result, vec![slashable_min_deposits]); + result = Self::generic_map_operation(vec![result, slashable_min_deposits], MergeOperation::Subtract); } result @@ -567,52 +616,6 @@ where output } - /// add all the user -> I maps together, and add the I's of the ones with the same user. - // Mappings should be sorted based on their account id, ascending. - pub fn merge_add_mappings_by_user(mut mappings: Vec>>) -> Vec> { - let mut output = mappings.swap_remove(0); - output.sort_by_key(|k| k.account.clone()); - for mut map in mappings { - map.sort_by_key(|k| k.account.clone()); - let old_output = output.clone(); - output = Vec::new(); - let mut i = 0; - let mut j = 0; - loop { - let old_tup = old_output.get(i); - let new_tup = map.get(j); - - match (old_tup, new_tup) { - (None, None) => break, - (Some(_), None) => { - output.extend_from_slice(&old_output[i..]); - break - }, - (None, Some(_)) => { - output.extend_from_slice(&map[j..]); - break - }, - ( - Some(UserToPLMCBalance { account: acc_i, plmc_amount: val_i }), - Some(UserToPLMCBalance { account: acc_j, plmc_amount: val_j }), - ) => - if acc_i == acc_j { - output.push(UserToPLMCBalance::new(acc_i.clone(), val_i.clone() + val_j.clone())); - i += 1; - j += 1; - } else if acc_i < acc_j { - output.push(old_output[i].clone()); - i += 1; - } else { - output.push(map[j].clone()); - j += 1; - }, - } - } - } - output - } - pub fn generic_map_merge_reduce( mappings: Vec>, key_extractor: impl Fn(&M) -> K, @@ -630,156 +633,34 @@ where output.into_iter().collect() } - pub fn generic_map_merge( - mut mappings: Vec>, - key_extractor: impl Fn(&M) -> K, - merger: impl Fn(&M, &M) -> M, - ) -> Vec { + /// Merge the given mappings into one mapping, where the values are merged using the given + /// merge operation. + /// + /// In case of the `Add` operation, all values are Unioned, and duplicate accounts are + /// added together. + /// In case of the `Subtract` operation, all values of the first mapping are subtracted by + /// the values of the other mappings. Accounts in the other mappings that are not present + /// in the first mapping are ignored. + /// + /// # Pseudocode Example + /// List1: [(A, 10), (B, 5), (C, 5)] + /// List2: [(A, 5), (B, 5), (D, 5)] + /// + /// Add: [(A, 15), (B, 10), (C, 5), (D, 5)] + /// Subtract: [(A, 5), (B, 0), (C, 5)] + pub fn generic_map_operation::Inner> + IntoIterator::Inner>> ( + mut mappings: Vec, + ops: MergeOperation, + ) -> N { let mut output = mappings.swap_remove(0); - output.sort_by_key(|k| key_extractor(k)); - for mut new_map in mappings { - new_map.sort_by_key(|k| key_extractor(k)); - let old_output = output.clone(); - output = Vec::new(); - let mut i = 0; - let mut j = 0; - loop { - let output_item = old_output.get(i); - let new_item = new_map.get(j); - - match (output_item, new_item) { - (None, None) => break, - (Some(_), None) => { - output.extend_from_slice(&old_output[i..]); - break - }, - (None, Some(_)) => { - output.extend_from_slice(&new_map[j..]); - break - }, - (Some(m_i), Some(m_j)) => { - let k_i = key_extractor(m_i); - let k_j = key_extractor(m_j); - if k_i == k_j { - output.push(merger(m_i, m_j)); - i += 1; - j += 1; - } else if k_i < k_j { - output.push(old_output[i].clone()); - i += 1; - } else { - output.push(new_map[j].clone()); - j += 1; - } - }, - } - } - } - output - } - - pub fn generic_map_subtract( - original_mapping: Vec, - mappings: Vec>, - key_extractor: impl Fn(&M) -> K, - subtractor: impl Fn(&M, &M) -> M, - ) -> Vec { - let mut output = original_mapping; - output.sort_by_key(|k| key_extractor(k)); - for mut new_map in mappings { - new_map.sort_by_key(|k| key_extractor(k)); - let old_output = output.clone(); - output = Vec::new(); - let mut i = 0; - let mut j = 0; - loop { - let output_item = old_output.get(i); - let new_item = new_map.get(j); - - match (output_item, new_item) { - (None, None) => break, - (Some(_), None) => { - output.extend_from_slice(&old_output[i..]); - break - }, - (None, Some(_)) => { - output.extend_from_slice(&new_map[j..]); - break - }, - (Some(m_i), Some(m_j)) => { - let k_i = key_extractor(m_i); - let k_j = key_extractor(m_j); - if k_i == k_j { - output.push(subtractor(m_i, m_j)); - i += 1; - j += 1; - } else if k_i < k_j { - output.push(old_output[i].clone()); - i += 1; - } else { - output.push(new_map[j].clone()); - j += 1; - } - }, - } - } - } - output - } - - // Accounts in base_mapping will be deducted balances from the matching accounts in substract_mappings. - // Mappings in substract_mappings without a match in base_mapping have no effect, nor will they get returned - pub fn merge_subtract_mappings_by_user( - base_mapping: Vec>, - subtract_mappings: Vec>>, - ) -> Vec> { - let mut output = base_mapping; - output.sort_by_key(|k| k.account.clone()); - for mut map in subtract_mappings { - map.sort_by_key(|k| k.account.clone()); - let old_output = output.clone(); - output = Vec::new(); - let mut i = 0; - let mut j = 0; - loop { - let old_tup = old_output.get(i); - let new_tup = map.get(j); - - match (old_tup, new_tup) { - (None, None) => break, - (Some(_), None) => { - output.extend_from_slice(&old_output[i..]); - break - }, - (None, Some(_)) => { - // uncomment this if we want to keep unmatched mappings on the substractor - // output.extend_from_slice(&map[j..]); - break - }, - ( - Some(UserToPLMCBalance { account: acc_i, plmc_amount: val_i }), - Some(UserToPLMCBalance { account: acc_j, plmc_amount: val_j }), - ) => { - if acc_i == acc_j { - output.push(UserToPLMCBalance::new( - acc_i.clone(), - val_i.clone().saturating_sub(val_j.clone()), - )); - i += 1; - j += 1; - } else if acc_i < acc_j { - output.push(old_output[i].clone()); - i += 1; - } else { - // uncomment to keep unmatched maps - // output.push(map[j]); - j += 1; - } - }, - } + output = output.merge_accounts(MergeOperation::Add); + for map in mappings { + match ops { + MergeOperation::Add => output.extend(map), + MergeOperation::Subtract => output = output.subtract_accounts(map), } } - output + output.merge_accounts(ops) } pub fn sum_balance_mappings(mut mappings: Vec>>) -> BalanceOf { @@ -1047,7 +928,7 @@ where let plmc_existential_deposits: Vec> = evaluators.existential_deposits(); let expected_remaining_plmc: Vec> = - Self::merge_add_mappings_by_user(vec![prev_plmc_balances.clone(), plmc_existential_deposits.clone()]); + Self::generic_map_operation(vec![prev_plmc_balances.clone(), plmc_existential_deposits.clone()], MergeOperation::Add); self.mint_plmc_to(plmc_eval_deposits.clone()); self.mint_plmc_to(plmc_existential_deposits.clone()); @@ -1068,7 +949,7 @@ where pub fn bid_for_users(&mut self, project_id: ProjectIdOf, bids: Vec>) -> Result<(), DispatchError> { for bid in bids { self.execute(|| { - crate::Pallet::::do_bid(&bid.bidder, project_id, bid.amount, bid.price, bid.multiplier, bid.asset) + crate::Pallet::::do_bid(&bid.bidder, project_id, bid.amount, bid.multiplier, bid.asset) })?; } Ok(()) @@ -1108,20 +989,21 @@ where issuer: AccountIdOf, evaluations: Vec>, bids: Vec>, - ) -> ProjectIdOf { - let project_id = self.create_auctioning_project(project_metadata, issuer, evaluations.clone()); - + ) -> (ProjectIdOf, Vec>) { if bids.is_empty() { panic!("Cannot start community funding without bids") } + + let project_id = self.create_auctioning_project(project_metadata.clone(), issuer, evaluations.clone()); + let bids = self.simulate_bids_with_bucket(bids, project_id); let bidders = bids.accounts(); let asset_id = bids[0].asset.to_statemint_id(); let prev_plmc_balances = self.get_free_plmc_balances_for(bidders.clone()); let prev_funding_asset_balances = self.get_free_statemint_asset_balances_for(asset_id, bidders.clone()); let plmc_evaluation_deposits: Vec> = Self::calculate_evaluation_plmc_spent(evaluations.clone()); - let plmc_bid_deposits: Vec> = Self::calculate_auction_plmc_spent(bids.clone()); + let plmc_bid_deposits: Vec> = Self::calculate_auction_plmc_spent(&bids, None); let participation_usable_evaluation_deposits = plmc_evaluation_deposits .clone() .into_iter() @@ -1130,58 +1012,58 @@ where x }) .collect::>>(); - let necessary_plmc_mint = Self::merge_subtract_mappings_by_user( - plmc_bid_deposits.clone(), - vec![participation_usable_evaluation_deposits], + let necessary_plmc_mint = Self::generic_map_operation( + + vec![plmc_bid_deposits.clone(), participation_usable_evaluation_deposits], + MergeOperation::Subtract, ); let total_plmc_participation_locked = plmc_bid_deposits; let plmc_existential_deposits: Vec> = bidders.existential_deposits(); - let funding_asset_deposits = Self::calculate_auction_funding_asset_spent(bids.clone()); + let funding_asset_deposits = Self::calculate_auction_funding_asset_spent(&bids, None); let bidder_balances = Self::sum_balance_mappings(vec![necessary_plmc_mint.clone(), plmc_existential_deposits.clone()]); let expected_free_plmc_balances = - Self::merge_add_mappings_by_user(vec![prev_plmc_balances.clone(), plmc_existential_deposits.clone()]); + Self::generic_map_operation(vec![prev_plmc_balances.clone(), plmc_existential_deposits.clone()], MergeOperation::Add); let prev_supply = self.get_plmc_total_supply(); let post_supply = prev_supply + bidder_balances; - let stored_bids = self.execute(|| Bids::::iter_prefix_values((project_id,)).collect_vec()); - let new_bids = stored_bids - .into_iter() - .filter(|bid| bid.final_ct_amount != 0u32.into()) - .map(|bid| BidParams::::from(bid.bidder, bid.final_ct_amount, bid.final_ct_usd_price)) - .collect_vec(); - - let bid_expectations = new_bids - .iter() - .map(|bid| BidInfoFilter:: { - final_ct_amount: Some(bid.amount), - final_ct_usd_price: Some(bid.price), - ..Default::default() - }) - .collect_vec(); - - let total_ct_sold = new_bids.iter().map(|bid| bid.amount).fold(Zero::zero(), |acc, item| item + acc); - self.mint_plmc_to(necessary_plmc_mint.clone()); self.mint_plmc_to(plmc_existential_deposits.clone()); self.mint_statemint_asset_to(funding_asset_deposits.clone()); - self.bid_for_users(project_id, bids).expect("Bidding should work"); + self.bid_for_users(project_id, bids.clone()).expect("Bidding should work"); - self.do_reserved_plmc_assertions(total_plmc_participation_locked, LockType::Participation(project_id)); - self.do_bid_transferred_statemint_asset_assertions(funding_asset_deposits, project_id); - self.do_free_plmc_assertions(expected_free_plmc_balances); - // self.do_free_statemint_asset_assertions(prev_funding_asset_balances); + self.do_reserved_plmc_assertions( + total_plmc_participation_locked.merge_accounts(MergeOperation::Add), + LockType::Participation(project_id), + ); + self.do_bid_transferred_statemint_asset_assertions(funding_asset_deposits.merge_accounts(MergeOperation::Add), project_id); + self.do_free_plmc_assertions(expected_free_plmc_balances.merge_accounts(MergeOperation::Add)); + self.do_free_statemint_asset_assertions(prev_funding_asset_balances.merge_accounts(MergeOperation::Add)); assert_eq!(self.get_plmc_total_supply(), post_supply); self.start_community_funding(project_id).unwrap(); - // self.finalized_bids_assertions(project_id, bid_expectations, total_ct_sold); + let weighted_price = self.get_project_details(project_id).weighted_average_price.unwrap(); + let accepted_bids = Self::filter_bids_after_auction(bids, project_metadata.total_allocation_size.0); + let bid_expectations = accepted_bids + .iter() + .map(|bid| BidInfoFilter:: { + bidder: Some(bid.bidder.clone()), + final_ct_amount: Some(bid.amount), + final_ct_usd_price: Some(if bid.price < weighted_price { bid.price } else { weighted_price }), + ..Default::default() + }) + .collect_vec(); - project_id + let total_ct_sold = accepted_bids.iter().map(|bid| bid.amount).fold(Zero::zero(), |acc, item| item + acc); + + self.finalized_bids_assertions(project_id, bid_expectations, total_ct_sold); + + (project_id, accepted_bids) } pub fn contribute_for_users( @@ -1249,14 +1131,19 @@ where evaluations: Vec>, bids: Vec>, contributions: Vec>, - ) -> ProjectIdOf { - let project_id = - self.create_community_contributing_project(project_metadata, issuer, evaluations.clone(), bids.clone()); + ) -> (ProjectIdOf, Vec>) { + let (project_id, accepted_bids) = self.create_community_contributing_project( + project_metadata.clone(), + issuer, + evaluations.clone(), + bids, + ); if contributions.is_empty() { self.start_remainder_or_end_funding(project_id).unwrap(); - return project_id + return (project_id, accepted_bids) } + let ct_price = self.get_project_details(project_id).weighted_average_price.unwrap(); let contributors = contributions.accounts(); let asset_id = contributions[0].asset.to_statemint_id(); @@ -1264,13 +1151,14 @@ where let prev_funding_asset_balances = self.get_free_statemint_asset_balances_for(asset_id, contributors.clone()); let plmc_evaluation_deposits = Self::calculate_evaluation_plmc_spent(evaluations.clone()); - let plmc_bid_deposits = Self::calculate_auction_plmc_spent_after_price_calculation(bids.clone(), ct_price); + let plmc_bid_deposits = Self::calculate_auction_plmc_spent(&accepted_bids.clone(), Some(ct_price)); + let plmc_contribution_deposits = Self::calculate_contributed_plmc_spent(contributions.clone(), ct_price); let necessary_plmc_mint = - Self::merge_subtract_mappings_by_user(plmc_contribution_deposits.clone(), vec![plmc_evaluation_deposits]); + Self::generic_map_operation(vec![plmc_contribution_deposits.clone(), plmc_evaluation_deposits], MergeOperation::Subtract); let total_plmc_participation_locked = - Self::merge_add_mappings_by_user(vec![plmc_bid_deposits, plmc_contribution_deposits.clone()]); + Self::generic_map_operation(vec![plmc_bid_deposits, plmc_contribution_deposits.clone()], MergeOperation::Add); let plmc_existential_deposits = contributors.existential_deposits(); let funding_asset_deposits = Self::calculate_contributed_funding_asset_spent(contributions.clone(), ct_price); @@ -1278,7 +1166,7 @@ where Self::sum_balance_mappings(vec![necessary_plmc_mint.clone(), plmc_existential_deposits.clone()]); let expected_free_plmc_balances = - Self::merge_add_mappings_by_user(vec![prev_plmc_balances.clone(), plmc_existential_deposits.clone()]); + Self::generic_map_operation(vec![prev_plmc_balances.clone(), plmc_existential_deposits.clone()], MergeOperation::Add); let prev_supply = self.get_plmc_total_supply(); let post_supply = prev_supply + contributor_balances; @@ -1289,14 +1177,20 @@ where self.contribute_for_users(project_id, contributions.clone()).expect("Contributing should work"); - self.do_reserved_plmc_assertions(total_plmc_participation_locked, LockType::Participation(project_id)); - // self.do_contribution_transferred_statemint_asset_assertions(funding_asset_deposits, project_id); - self.do_free_plmc_assertions(expected_free_plmc_balances); - self.do_free_statemint_asset_assertions(prev_funding_asset_balances); + self.do_reserved_plmc_assertions( + total_plmc_participation_locked.merge_accounts(MergeOperation::Add), + LockType::Participation(project_id), + ); + self.do_contribution_transferred_statemint_asset_assertions( + funding_asset_deposits.merge_accounts(MergeOperation::Add), + project_id, + ); + self.do_free_plmc_assertions(expected_free_plmc_balances.merge_accounts(MergeOperation::Add)); + self.do_free_statemint_asset_assertions(prev_funding_asset_balances.merge_accounts(MergeOperation::Add)); assert_eq!(self.get_plmc_total_supply(), post_supply); self.start_remainder_or_end_funding(project_id).unwrap(); - project_id + (project_id, accepted_bids) } pub fn create_finished_project( @@ -1308,7 +1202,7 @@ where community_contributions: Vec>, remainder_contributions: Vec>, ) -> ProjectIdOf { - let project_id = self.create_remainder_contributing_project( + let (project_id, accepted_bids) = self.create_remainder_contributing_project( project_metadata.clone(), issuer, evaluations.clone(), @@ -1332,21 +1226,21 @@ where let prev_funding_asset_balances = self.get_free_statemint_asset_balances_for(asset_id, contributors.clone()); let plmc_evaluation_deposits = Self::calculate_evaluation_plmc_spent(evaluations.clone()); - let plmc_bid_deposits = Self::calculate_auction_plmc_spent_after_price_calculation(bids.clone(), ct_price); + let plmc_bid_deposits = Self::calculate_auction_plmc_spent(&accepted_bids.clone(), Some(ct_price)); let plmc_community_contribution_deposits = Self::calculate_contributed_plmc_spent(community_contributions.clone(), ct_price); let plmc_remainder_contribution_deposits = Self::calculate_contributed_plmc_spent(remainder_contributions.clone(), ct_price); - let necessary_plmc_mint = Self::merge_subtract_mappings_by_user( - plmc_remainder_contribution_deposits.clone(), - vec![plmc_evaluation_deposits], + let necessary_plmc_mint = Self::generic_map_operation( + vec![plmc_remainder_contribution_deposits.clone(), plmc_evaluation_deposits], + MergeOperation::Subtract, ); - let total_plmc_participation_locked = Self::merge_add_mappings_by_user(vec![ + let total_plmc_participation_locked = Self::generic_map_operation(vec![ plmc_bid_deposits, plmc_community_contribution_deposits, plmc_remainder_contribution_deposits.clone(), - ]); + ], MergeOperation::Add); let plmc_existential_deposits = contributors.existential_deposits(); let funding_asset_deposits = Self::calculate_contributed_funding_asset_spent(remainder_contributions.clone(), ct_price); @@ -1355,7 +1249,7 @@ where Self::sum_balance_mappings(vec![necessary_plmc_mint.clone(), plmc_existential_deposits.clone()]); let expected_free_plmc_balances = - Self::merge_add_mappings_by_user(vec![prev_plmc_balances.clone(), plmc_existential_deposits.clone()]); + Self::generic_map_operation(vec![prev_plmc_balances.clone(), plmc_existential_deposits.clone()], MergeOperation::Add); let prev_supply = self.get_plmc_total_supply(); let post_supply = prev_supply + contributor_balances; @@ -1367,10 +1261,16 @@ where self.contribute_for_users(project_id, remainder_contributions.clone()) .expect("Remainder Contributing should work"); - self.do_reserved_plmc_assertions(total_plmc_participation_locked, LockType::Participation(project_id)); - // self.do_contribution_transferred_statemint_asset_assertions(funding_asset_deposits, project_id); - self.do_free_plmc_assertions(expected_free_plmc_balances); - // self.do_free_statemint_asset_assertions(prev_funding_asset_balances); + self.do_reserved_plmc_assertions( + total_plmc_participation_locked.merge_accounts(MergeOperation::Add), + LockType::Participation(project_id), + ); + self.do_contribution_transferred_statemint_asset_assertions( + funding_asset_deposits.merge_accounts(MergeOperation::Add), + project_id, + ); + self.do_free_plmc_assertions(expected_free_plmc_balances.merge_accounts(MergeOperation::Add)); + self.do_free_statemint_asset_assertions(prev_funding_asset_balances.merge_accounts(MergeOperation::Add)); assert_eq!(self.get_plmc_total_supply(), post_supply); self.finish_funding(project_id).unwrap(); @@ -1378,20 +1278,20 @@ where if self.get_project_details(project_id).status == ProjectStatus::FundingSuccessful { // Check that remaining CTs are updated let project_details = self.get_project_details(project_id); - let auction_bought_tokens = bids.iter().map(|bid| bid.amount).fold(Zero::zero(), |acc, item| item + acc); + let auction_bought_tokens = accepted_bids.iter().map(|bid| bid.amount).fold(Zero::zero(), |acc, item| item + acc); let community_bought_tokens = community_contributions.iter().map(|cont| cont.amount).fold(Zero::zero(), |acc, item| item + acc); let remainder_bought_tokens = remainder_contributions.iter().map(|cont| cont.amount).fold(Zero::zero(), |acc, item| item + acc); - // assert_eq!( - // project_details.remaining_contribution_tokens.0 + project_details.remaining_contribution_tokens.1, - // project_metadata.total_allocation_size.0 + project_metadata.total_allocation_size.1 - - // auction_bought_tokens - - // community_bought_tokens - - // remainder_bought_tokens, - // "Remaining CTs are incorrect" - // ); + assert_eq!( + project_details.remaining_contribution_tokens.0 + project_details.remaining_contribution_tokens.1, + project_metadata.total_allocation_size.0 + project_metadata.total_allocation_size.1 - + auction_bought_tokens - + community_bought_tokens - + remainder_bought_tokens, + "Remaining CTs are incorrect" + ); } project_id @@ -1403,6 +1303,21 @@ pub trait Accounts { fn accounts(&self) -> Vec; } + +pub enum MergeOperation { + Add, + Subtract, +} +pub trait AccountMerge { + /// The inner type of the Vec implementing this Trait. + type Inner; + /// Merge accounts in the list based on the operation. + fn merge_accounts(&self, ops: MergeOperation) -> Self; + /// Subtract amount of the matching accounts in the other list from the current list. + /// If the account is not present in the current list, it is ignored. + fn subtract_accounts(&self, other_list: Self) -> Self; +} + pub trait ExistentialDeposits { fn existential_deposits(&self) -> Vec>; } @@ -1440,6 +1355,31 @@ impl Accounts for Vec> { btree.into_iter().collect_vec() } } +impl AccountMerge for Vec> { + type Inner = UserToPLMCBalance; + fn merge_accounts(&self, ops: MergeOperation) -> Self { + let mut btree = BTreeMap::new(); + for UserToPLMCBalance { account, plmc_amount } in self.iter() { + btree.entry(account.clone()) + .and_modify(|e: &mut BalanceOf| *e = + match ops { + MergeOperation::Add => e.saturating_add(*plmc_amount), + MergeOperation::Subtract => e.saturating_sub(*plmc_amount), + }) + .or_insert(*plmc_amount); + + } + btree.into_iter().map(|(account, plmc_amount)| UserToPLMCBalance::new(account, plmc_amount)).collect() + } + + fn subtract_accounts(&self, other_list: Self) -> Self { + let current_accounts = self.accounts(); + let filtered_list = other_list.into_iter().filter(|x| current_accounts.contains(&x.account)).collect_vec(); + let mut new_list = self.clone(); + new_list.extend(filtered_list); + new_list.merge_accounts(MergeOperation::Subtract) + } +} #[derive(Clone, PartialEq, Debug)] pub struct UserToUSDBalance { @@ -1463,6 +1403,31 @@ impl Accounts for Vec> { } } +impl AccountMerge for Vec> { + type Inner = UserToUSDBalance; + fn merge_accounts(&self, ops: MergeOperation) -> Self { + let mut btree = BTreeMap::new(); + for UserToUSDBalance { account, usd_amount } in self.iter() { + btree.entry(account.clone()) + .and_modify(|e: &mut BalanceOf| *e = + match ops { + MergeOperation::Add => e.saturating_add(*usd_amount), + MergeOperation::Subtract => e.saturating_sub(*usd_amount), + }) + .or_insert(*usd_amount); + } + btree.into_iter().map(|(account, usd_amount)| UserToUSDBalance::new(account, usd_amount)).collect() + } + + fn subtract_accounts(&self, other_list: Self) -> Self { + let current_accounts = self.accounts(); + let filtered_list = other_list.into_iter().filter(|x| current_accounts.contains(&x.account)).collect_vec(); + let mut new_list = self.clone(); + new_list.extend(filtered_list); + new_list.merge_accounts(MergeOperation::Subtract) + } +} + #[derive(Clone, PartialEq, Debug)] pub struct UserToStatemintAsset { pub account: AccountIdOf, @@ -1485,6 +1450,32 @@ impl Accounts for Vec> { btree.into_iter().collect_vec() } } + +impl AccountMerge for Vec> { + type Inner = UserToStatemintAsset; + fn merge_accounts(&self, ops: MergeOperation) -> Self { + let mut btree = BTreeMap::new(); + for UserToStatemintAsset { account, asset_amount, asset_id } in self.iter() { + btree.entry(account.clone()) + .and_modify(|e: &mut (BalanceOf, u32)| e.0 = + match ops { + MergeOperation::Add => e.0.saturating_add(*asset_amount), + MergeOperation::Subtract => e.0.saturating_sub(*asset_amount), + }) + .or_insert((*asset_amount, asset_id.clone())); + + } + btree.into_iter().map(|(account, info)| UserToStatemintAsset::new(account, info.0, info.1)).collect() + } + + fn subtract_accounts(&self, other_list: Self) -> Self { + let current_accounts = self.accounts(); + let filtered_list = other_list.into_iter().filter(|x| current_accounts.contains(&x.account)).collect_vec(); + let mut new_list = self.clone(); + new_list.extend(filtered_list); + new_list.merge_accounts(MergeOperation::Subtract) + } +} #[derive(Clone, Debug)] pub struct BidParams { pub bidder: AccountIdOf, @@ -1514,6 +1505,7 @@ impl BidParams { } } } + impl Accounts for Vec> { type Account = AccountIdOf; diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index e90c5ceaa..1f8c8b5a5 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -887,12 +887,11 @@ pub mod pallet { origin: OriginFor, project_id: T::ProjectIdentifier, #[pallet::compact] amount: BalanceOf, - _price: PriceOf, multiplier: T::Multiplier, asset: AcceptedFundingAsset, ) -> DispatchResult { let bidder = ensure_signed(origin)?; - Self::do_bid(&bidder, project_id, amount, _price, multiplier, asset) + Self::do_bid(&bidder, project_id, amount, multiplier, asset) } /// Buy tokens in the Community or Remainder round at the price set in the Auction Round diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index 515035eca..251c672b5 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -399,7 +399,7 @@ mod evaluation_round_success { ContributionParams::new(BUYER_7, 2_000 * ASSET_UNIT, 1u8, AcceptedFundingAsset::USDT), ]; - let project_id = inst.create_community_contributing_project(project_metadata, ISSUER, evaluations, bids); + let (project_id, _) = inst.create_community_contributing_project(project_metadata, ISSUER, evaluations, bids); let details = inst.get_project_details(project_id); let ct_price = details.weighted_average_price.unwrap(); let plmc_deposits = MockInstantiator::calculate_contributed_plmc_spent(contributions.clone(), ct_price); @@ -439,7 +439,7 @@ mod evaluation_round_success { let evaluations = default_evaluations(); let evaluators = evaluations.accounts(); - let project_id = inst.create_remainder_contributing_project( + let (project_id, _) = inst.create_remainder_contributing_project( default_project(inst.get_new_nonce(), ISSUER), ISSUER, evaluations.clone(), @@ -466,7 +466,7 @@ mod evaluation_round_success { let post_free_plmc = inst.get_free_plmc_balances_for(evaluators.clone()); - let increased_amounts = MockInstantiator::merge_subtract_mappings_by_user(post_free_plmc, vec![prev_free_plmc]); + let increased_amounts = MockInstantiator::generic_map_operation(vec![post_free_plmc, prev_free_plmc], MergeOperation::Subtract); assert_eq!(increased_amounts, MockInstantiator::calculate_evaluation_plmc_spent(evaluations)) } @@ -477,7 +477,7 @@ mod evaluation_round_success { let evaluations = default_evaluations(); let evaluators = evaluations.accounts(); - let project_id = inst.create_remainder_contributing_project( + let (project_id, _) = inst.create_remainder_contributing_project( default_project(inst.get_new_nonce(), ISSUER), ISSUER, evaluations.clone(), @@ -503,7 +503,7 @@ mod evaluation_round_success { let post_free_plmc = inst.get_free_plmc_balances_for(evaluators.clone()); - let increased_amounts = MockInstantiator::merge_subtract_mappings_by_user(post_free_plmc, vec![prev_free_plmc]); + let increased_amounts = MockInstantiator::generic_map_operation( vec![post_free_plmc, prev_free_plmc], MergeOperation::Subtract); assert_eq!( increased_amounts, @@ -526,10 +526,10 @@ mod evaluation_round_failure { let plmc_eval_deposits: Vec> = MockInstantiator::calculate_evaluation_plmc_spent(evaluations.clone()); let plmc_existential_deposits = plmc_eval_deposits.accounts().existential_deposits(); - let expected_evaluator_balances = MockInstantiator::merge_add_mappings_by_user(vec![ + let expected_evaluator_balances = MockInstantiator::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()); @@ -632,9 +632,9 @@ mod auction_round_success { let usable_evaluation_plmc = already_bonded_plmc - ::EvaluatorSlash::get() * already_bonded_plmc; let necessary_plmc_for_bid = - MockInstantiator::calculate_auction_plmc_spent(vec![evaluator_bid.clone()])[0].plmc_amount; + MockInstantiator::calculate_auction_plmc_spent(&vec![evaluator_bid.clone()], None)[0].plmc_amount; let necessary_usdt_for_bid = - MockInstantiator::calculate_auction_funding_asset_spent(vec![evaluator_bid.clone()]); + MockInstantiator::calculate_auction_funding_asset_spent(&vec![evaluator_bid.clone()], None); inst.mint_plmc_to(vec![UserToPLMCBalance::new( evaluator_bidder, @@ -660,11 +660,12 @@ mod auction_round_success { bids.push(BidParams::new(evaluator_bidder, 100 * ASSET_UNIT, 1.into(), 1u8, AcceptedFundingAsset::USDT)); } - let fill_necessary_plmc_for_bids = MockInstantiator::calculate_auction_plmc_spent(bids.clone()); - let fill_necessary_usdt_for_bids = MockInstantiator::calculate_auction_funding_asset_spent(bids.clone()); + let fill_necessary_plmc_for_bids = MockInstantiator::calculate_auction_plmc_spent(&bids.clone(), None); + let fill_necessary_usdt_for_bids = MockInstantiator::calculate_auction_funding_asset_spent(&bids, None); - let bid_necessary_plmc = MockInstantiator::calculate_auction_plmc_spent(vec![evaluator_bid.clone()]); - let bid_necessary_usdt = MockInstantiator::calculate_auction_funding_asset_spent(vec![evaluator_bid.clone()]); + let bid_necessary_plmc = MockInstantiator::calculate_auction_plmc_spent(&vec![evaluator_bid.clone()], None); + let bid_necessary_usdt = + MockInstantiator::calculate_auction_funding_asset_spent(&vec![evaluator_bid.clone()], None); let evaluation_bond = MockInstantiator::sum_balance_mappings(vec![fill_necessary_plmc_for_bids, bid_necessary_plmc.clone()]); @@ -714,8 +715,8 @@ mod auction_round_success { 1u8, AcceptedFundingAsset::USDT, )]; - let statemint_funding = MockInstantiator::calculate_auction_funding_asset_spent(bids.clone()); - let plmc_funding = MockInstantiator::calculate_auction_plmc_spent(bids.clone()); + let statemint_funding = MockInstantiator::calculate_auction_funding_asset_spent(&bids, None); + let plmc_funding = MockInstantiator::calculate_auction_plmc_spent(&bids, None); let ed_funding = plmc_funding.accounts().existential_deposits(); inst.mint_plmc_to(ed_funding); @@ -745,8 +746,8 @@ mod auction_round_success { BidParams::new(BIDDER_3, 35_000 * ASSET_UNIT, 1.into(), 1u8, AcceptedFundingAsset::USDT), ]; - let statemint_funding = MockInstantiator::calculate_auction_funding_asset_spent(bids.clone()); - let plmc_funding = MockInstantiator::calculate_auction_plmc_spent(bids.clone()); + let statemint_funding = MockInstantiator::calculate_auction_funding_asset_spent(&bids, None); + let plmc_funding = MockInstantiator::calculate_auction_plmc_spent(&bids, None); let ed_funding = plmc_funding.accounts().existential_deposits(); inst.mint_plmc_to(ed_funding); @@ -791,9 +792,9 @@ mod auction_round_success { let bid_info = BidParams::new(0, 50u128, PriceOf::::from_float(1.0), 1u8, AcceptedFundingAsset::USDT); let plmc_necessary_funding = - MockInstantiator::calculate_auction_plmc_spent(vec![bid_info.clone()])[0].plmc_amount; + MockInstantiator::calculate_auction_plmc_spent(&vec![bid_info.clone()], None)[0].plmc_amount; let statemint_asset_necessary_funding = - MockInstantiator::calculate_auction_funding_asset_spent(vec![bid_info.clone()])[0].asset_amount; + MockInstantiator::calculate_auction_funding_asset_spent(&vec![bid_info.clone()], None)[0].asset_amount; let mut bids_made: Vec> = vec![]; let starting_bid_block = inst.current_block(); @@ -949,13 +950,13 @@ mod auction_round_success { let project = default_project(inst.get_new_nonce(), issuer); let evaluations = default_evaluations(); let bids: Vec> = vec![ - BidParams::new(BIDDER_1, 10_000 * ASSET_UNIT, 15.into(), 1u8, AcceptedFundingAsset::USDT), - BidParams::new(BIDDER_2, 20_000 * ASSET_UNIT, 20.into(), 1u8, AcceptedFundingAsset::USDT), - BidParams::new(BIDDER_4, 20_000 * ASSET_UNIT, 16.into(), 1u8, AcceptedFundingAsset::USDT), - BidParams::new(BIDDER_5, 5_000 * ASSET_UNIT, 16.into(), 1u8, AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_1, 10_000 * ASSET_UNIT, 0.into(), 1u8, AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_2, 20_000 * ASSET_UNIT, 0.into(), 1u8, AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_4, 20_000 * ASSET_UNIT, 0.into(), 1u8, AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_5, 5_000 * ASSET_UNIT, 0.into(), 1u8, AcceptedFundingAsset::USDT), ]; - let project_id = inst.create_community_contributing_project(project, issuer, evaluations, bids); + let (project_id, _) = inst.create_community_contributing_project(project, issuer, evaluations, bids); let bidder_5_bid = inst.execute(|| Bids::::iter_prefix_values((project_id, BIDDER_5)).next().unwrap()); let wabgp = inst.get_project_details(project_id).weighted_average_price.unwrap(); @@ -1216,8 +1217,7 @@ mod auction_round_success { assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); let final_price = details.weighted_average_price.unwrap(); - let plmc_locked_for_bids = - MockInstantiator::calculate_auction_plmc_spent_after_price_calculation(new_bids, final_price); + let plmc_locked_for_bids = MockInstantiator::calculate_auction_plmc_spent(&new_bids, Some(final_price)); for UserToPLMCBalance { account, plmc_amount } in plmc_locked_for_bids { let schedule = inst.execute(|| { @@ -1397,13 +1397,13 @@ mod auction_round_success { let project_id = inst.create_auctioning_project(project, issuer, evaluations); - let mut bidders_plmc = MockInstantiator::calculate_auction_plmc_spent(bids.clone()); + let mut bidders_plmc = MockInstantiator::calculate_auction_plmc_spent(&bids, None); bidders_plmc .iter_mut() .for_each(|UserToPLMCBalance { account: _, plmc_amount }| *plmc_amount += MockInstantiator::get_ed()); inst.mint_plmc_to(bidders_plmc.clone()); - let bidders_funding_assets = MockInstantiator::calculate_auction_funding_asset_spent(bids.clone()); + let bidders_funding_assets = MockInstantiator::calculate_auction_funding_asset_spent(&bids, None); inst.mint_statemint_asset_to(bidders_funding_assets.clone()); inst.bid_for_users(project_id, bids).unwrap(); @@ -1429,9 +1429,9 @@ mod auction_round_success { assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); let plmc_locked_for_accepted_bid = - MockInstantiator::calculate_auction_plmc_spent_after_price_calculation(accepted_bid, final_price); + MockInstantiator::calculate_auction_plmc_spent(&accepted_bid, Some(final_price)); let plmc_locked_for_rejected_bid = - MockInstantiator::calculate_auction_plmc_spent_after_price_calculation(rejected_bid, final_price); + MockInstantiator::calculate_auction_plmc_spent(&rejected_bid, Some(final_price)); let UserToPLMCBalance { account: accepted_user, plmc_amount: accepted_plmc_amount } = plmc_locked_for_accepted_bid[0]; @@ -1854,15 +1854,14 @@ mod auction_round_success { let post_bidders_plmc_balances = inst.get_free_plmc_balances_for(bids.accounts()); - let mut delta_bidders_plmc_balances = MockInstantiator::merge_subtract_mappings_by_user( - post_bidders_plmc_balances, - vec![prev_bidders_plmc_balances], + let mut delta_bidders_plmc_balances = MockInstantiator::generic_map_operation( + vec![post_bidders_plmc_balances, prev_bidders_plmc_balances], + MergeOperation::Subtract, ); delta_bidders_plmc_balances.sort_by_key(|item| item.account.clone()); let final_price = details.weighted_average_price.unwrap(); - let mut plmc_locked_for_bids = - MockInstantiator::calculate_auction_plmc_spent_after_price_calculation(bids, final_price); + let mut plmc_locked_for_bids = MockInstantiator::calculate_auction_plmc_spent(&bids, Some(final_price)); plmc_locked_for_bids.sort_by_key(|item| item.account.clone()); assert_eq!(delta_bidders_plmc_balances, plmc_locked_for_bids); @@ -1931,16 +1930,15 @@ mod auction_round_success { let post_bidders_plmc_balances = inst.get_free_plmc_balances_for(bids.accounts()); - let mut delta_bidders_plmc_balances = MockInstantiator::merge_subtract_mappings_by_user( - post_bidders_plmc_balances, - vec![prev_bidders_plmc_balances], + let mut delta_bidders_plmc_balances = MockInstantiator::generic_map_operation( + vec![post_bidders_plmc_balances, prev_bidders_plmc_balances], + MergeOperation::Subtract, ); delta_bidders_plmc_balances.sort_by_key(|item| item.account); let details = inst.get_project_details(project_id); let final_price = details.weighted_average_price.unwrap(); - let mut plmc_locked_for_bids = - MockInstantiator::calculate_auction_plmc_spent_after_price_calculation(bids, final_price); + let mut plmc_locked_for_bids = MockInstantiator::calculate_auction_plmc_spent(&bids, Some(final_price)); plmc_locked_for_bids.sort_by_key(|item| item.account); assert_eq!(delta_bidders_plmc_balances, plmc_locked_for_bids); @@ -1972,7 +1970,6 @@ mod auction_round_failure { RuntimeOrigin::signed(BIDDER_2), 0, 1, - 100_u128.into(), 1u8.try_into().unwrap(), AcceptedFundingAsset::USDT ), @@ -2013,11 +2010,11 @@ mod auction_round_failure { BidParams::new(DAVE, 20_000 * USDT_UNIT, 5_u128.into(), 1u8, AcceptedFundingAsset::USDT), // 100k ]; - let mut plmc_fundings = MockInstantiator::calculate_auction_plmc_spent(bids.clone()); + let mut plmc_fundings = MockInstantiator::calculate_auction_plmc_spent(&bids, None); // Existential deposit on DAVE plmc_fundings.push(UserToPLMCBalance::new(DAVE, MockInstantiator::get_ed())); - let statemint_asset_fundings = MockInstantiator::calculate_auction_funding_asset_spent(bids.clone()); + let statemint_asset_fundings = MockInstantiator::calculate_auction_funding_asset_spent(&bids, None); // Fund enough for all PLMC bonds for the bids (multiplier of 1) inst.mint_plmc_to(plmc_fundings); @@ -2090,19 +2087,14 @@ mod auction_round_failure { BidParams::new(BIDDER_2, 5_000 * ASSET_UNIT, FixedU128::from_float(1.0), 1u8, AcceptedFundingAsset::USDT); let glutton_bid_2 = BidParams::new(BIDDER_1, 5_000 * ASSET_UNIT, FixedU128::from_float(1.1), 1u8, AcceptedFundingAsset::USDT); - let mut plmc_fundings = MockInstantiator::calculate_auction_plmc_spent(vec![ - rejected_bid.clone(), - glutton_bid_1.clone(), - glutton_bid_2.clone(), - ]); + let bids = inst.simulate_bids_with_bucket( + vec![glutton_bid_1.clone(), rejected_bid.clone(), glutton_bid_2.clone()], + project_id, + ); + let mut plmc_fundings = MockInstantiator::calculate_auction_plmc_spent(&bids, None); plmc_fundings.push(UserToPLMCBalance::new(BIDDER_1, MockInstantiator::get_ed())); plmc_fundings.push(UserToPLMCBalance::new(BIDDER_2, MockInstantiator::get_ed())); - - let usdt_fundings = MockInstantiator::calculate_auction_funding_asset_spent(vec![ - rejected_bid.clone(), - glutton_bid_1.clone(), - glutton_bid_2.clone(), - ]); + let usdt_fundings = MockInstantiator::calculate_auction_funding_asset_spent(&bids, None); inst.mint_plmc_to(plmc_fundings.clone()); inst.mint_statemint_asset_to(usdt_fundings.clone()); @@ -2115,7 +2107,7 @@ mod auction_round_failure { ]); inst.do_reserved_plmc_assertions( vec![ - UserToPLMCBalance::new(BIDDER_1, plmc_fundings[0].plmc_amount), + UserToPLMCBalance::new(BIDDER_1, plmc_fundings[0].plmc_amount + plmc_fundings[2].plmc_amount), UserToPLMCBalance::new(BIDDER_2, plmc_fundings[1].plmc_amount), ], LockType::Participation(project_id), @@ -2138,13 +2130,27 @@ mod auction_round_failure { inst.start_community_funding(project_id).unwrap(); + let weighted_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); + let plmc_fundings_after_round = MockInstantiator::calculate_auction_plmc_spent(&bids, Some(weighted_price)); + let usdt_fundings_after_round = + MockInstantiator::calculate_auction_funding_asset_spent(&bids, Some(weighted_price)); + inst.do_free_plmc_assertions(vec![ - UserToPLMCBalance::new(BIDDER_1, MockInstantiator::get_ed()), + UserToPLMCBalance::new( + BIDDER_1, + MockInstantiator::get_ed() + (plmc_fundings[2].plmc_amount - plmc_fundings_after_round[2].plmc_amount), + ), UserToPLMCBalance::new(BIDDER_2, plmc_fundings[1].plmc_amount + MockInstantiator::get_ed()), ]); inst.do_reserved_plmc_assertions( - vec![UserToPLMCBalance::new(BIDDER_1, plmc_fundings[0].plmc_amount), UserToPLMCBalance::new(BIDDER_2, 0)], + vec![ + UserToPLMCBalance::new( + BIDDER_1, + plmc_fundings_after_round[0].plmc_amount + plmc_fundings_after_round[2].plmc_amount, + ), + UserToPLMCBalance::new(BIDDER_2, 0), + ], LockType::Participation(project_id), ); @@ -2152,7 +2158,7 @@ mod auction_round_failure { vec![ UserToStatemintAsset::new( BIDDER_1, - usdt_fundings[0].asset_amount, + usdt_fundings_after_round[0].asset_amount + usdt_fundings_after_round[2].asset_amount, AcceptedFundingAsset::USDT.to_statemint_id(), ), UserToStatemintAsset::new(BIDDER_2, 0, AcceptedFundingAsset::USDT.to_statemint_id()), @@ -2168,12 +2174,13 @@ mod auction_round_failure { let (bid_in, bid_out) = (default_bids()[0].clone(), default_bids()[1].clone()); - let mut plmc_fundings = MockInstantiator::calculate_auction_plmc_spent(vec![bid_in.clone(), bid_out.clone()]); + let mut plmc_fundings = + MockInstantiator::calculate_auction_plmc_spent(&vec![bid_in.clone(), bid_out.clone()], None); plmc_fundings.push(UserToPLMCBalance::new(BIDDER_1, MockInstantiator::get_ed())); plmc_fundings.push(UserToPLMCBalance::new(BIDDER_2, MockInstantiator::get_ed())); let usdt_fundings = - MockInstantiator::calculate_auction_funding_asset_spent(vec![bid_in.clone(), bid_out.clone()]); + MockInstantiator::calculate_auction_funding_asset_spent(&vec![bid_in.clone(), bid_out.clone()], None); inst.mint_plmc_to(plmc_fundings.clone()); inst.mint_statemint_asset_to(usdt_fundings.clone()); @@ -2299,7 +2306,7 @@ mod community_round_success { let issuer = ISSUER; let evaluations = default_evaluations(); let bids = default_bids(); - let project_id = inst.create_community_contributing_project(metadata, issuer, evaluations, bids); + let (project_id, _) = inst.create_community_contributing_project(metadata, issuer, evaluations, bids); const BOB: AccountId = 42; let token_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); @@ -2343,7 +2350,7 @@ mod community_round_success { BidParams::from(BIDDER_1, 40_000 * ASSET_UNIT, FixedU128::from_float(1.0)), BidParams::from(BIDDER_2, 10_000 * ASSET_UNIT, FixedU128::from_float(1.0)), ]; - let project_id = + let (project_id, _) = inst.create_community_contributing_project(default_project(0, ISSUER), ISSUER, default_evaluations(), bids); const BOB: AccountId = 808; @@ -2390,7 +2397,7 @@ mod community_round_success { BidParams::new(BIDDER_1, 40_000 * ASSET_UNIT, FixedU128::from_float(1.0), 1u8, AcceptedFundingAsset::USDT), BidParams::new(BIDDER_2, 10_000 * ASSET_UNIT, FixedU128::from_float(1.0), 1u8, AcceptedFundingAsset::USDT), ]; - let project_id = + let (project_id, _) = inst.create_community_contributing_project(default_project(0, ISSUER), ISSUER, default_evaluations(), bids); const BOB: AccountId = 808; @@ -2453,7 +2460,7 @@ mod community_round_success { #[test] fn contribution_is_returned_on_limit_reached_same_mult_diff_ct() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_community_contributing_project( + let (project_id, _) = inst.create_community_contributing_project( default_project(0, ISSUER), ISSUER, default_evaluations(), @@ -2561,7 +2568,7 @@ mod community_round_success { #[test] fn contribution_is_returned_on_limit_reached_diff_mult_same_ct() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_community_contributing_project( + let (project_id, _) = inst.create_community_contributing_project( default_project(0, ISSUER), ISSUER, default_evaluations(), @@ -2679,7 +2686,7 @@ mod community_round_success { evaluations.push(UserToUSDBalance::new(evaluator_contributor, evaluation_amount)); let bids = default_bids(); - let project_id = inst.create_community_contributing_project(project, issuer, evaluations, bids); + let (project_id, _) = inst.create_community_contributing_project(project, issuer, evaluations, bids); let ct_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); let already_bonded_plmc = MockInstantiator::calculate_evaluation_plmc_spent(vec![UserToUSDBalance::new( evaluator_contributor, @@ -2746,7 +2753,7 @@ mod community_round_success { .saturating_mul_int(evaluation_bond); evaluations.push(UserToUSDBalance::new(evaluator_contributor, evaluation_usd_amount)); - let project_id = inst.create_community_contributing_project(project, issuer, evaluations, bids); + let (project_id, _) = inst.create_community_contributing_project(project, issuer, evaluations, bids); inst.mint_plmc_to(vec![UserToPLMCBalance::new( evaluator_contributor, @@ -2780,7 +2787,7 @@ mod community_round_success { evaluations.push(UserToUSDBalance::new(evaluator_contributor, evaluation_amount)); let bids = default_bids(); - let project_id = inst.create_community_contributing_project(project, issuer, evaluations, bids); + let (project_id, _) = inst.create_community_contributing_project(project, issuer, evaluations, bids); let ct_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); let necessary_plmc_for_contribution = MockInstantiator::calculate_contributed_plmc_spent(vec![contribution.clone()], ct_price)[0].plmc_amount; @@ -2795,6 +2802,7 @@ mod community_round_success { necessary_plmc_for_contribution > plmc_available_for_participating && necessary_plmc_for_contribution < plmc_evaluation_amount ); + println!("Plmc contr: {:?}, plmc eval: {:?}, plmc avail: {:?}", necessary_plmc_for_contribution, plmc_evaluation_amount, plmc_available_for_participating); let necessary_usdt_for_contribution = MockInstantiator::calculate_contributed_funding_asset_spent(vec![contribution.clone()], ct_price); @@ -2816,7 +2824,7 @@ mod community_round_success { evaluations.push(UserToUSDBalance::new(evaluator_contributor, evaluation_amount)); let bids = default_bids(); - let project_id = inst.create_community_contributing_project(project, issuer, evaluations, bids); + let (project_id, _) = inst.create_community_contributing_project(project, issuer, evaluations, bids); let ct_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); let necessary_plmc_for_contribution = @@ -3598,15 +3606,9 @@ mod community_round_failure { )[0] .asset_amount; - let contributors_funding_delta = MockInstantiator::generic_map_subtract( - post_contributors_funding_balances, - vec![prev_contributors_funding_balances], - |item| item.account, - |minuend, subtrahend| { - let mut output = minuend.clone(); - output.asset_amount = minuend.asset_amount - subtrahend.asset_amount; - output - }, + let contributors_funding_delta = MockInstantiator::generic_map_operation( + vec![post_contributors_funding_balances, prev_contributors_funding_balances], + MergeOperation::Subtract, ); let issuer_funding_delta = post_issuer_funding_balance - prev_issuer_funding_balance; @@ -3744,15 +3746,9 @@ mod community_round_failure { )[0] .asset_amount; - let contributors_funding_delta = MockInstantiator::generic_map_subtract( - post_contributors_funding_balances, - vec![prev_contributors_funding_balances], - |item| item.account, - |minuend, subtrahend| { - let mut output = minuend.clone(); - output.asset_amount = minuend.asset_amount - subtrahend.asset_amount; - output - }, + let contributors_funding_delta = MockInstantiator::generic_map_operation( + vec![post_contributors_funding_balances, prev_contributors_funding_balances], + MergeOperation::Subtract, ); let issuer_funding_delta = post_issuer_funding_balance - prev_issuer_funding_balance; @@ -3823,33 +3819,20 @@ mod community_round_failure { community_contributions.iter().map(|contribution| contribution.contributor.clone()).collect::>(), ); - let mut delta_bidders_plmc_balances = MockInstantiator::generic_map_subtract( - post_bidders_plmc_balances, - vec![prev_bidders_plmc_balances], - |item| item.account, - |minuend, subtrahend| { - let mut output = minuend.clone(); - output.plmc_amount = minuend.plmc_amount - subtrahend.plmc_amount; - output - }, + let mut delta_bidders_plmc_balances = MockInstantiator::generic_map_operation( + vec![post_bidders_plmc_balances, prev_bidders_plmc_balances], + MergeOperation::Subtract, ); delta_bidders_plmc_balances.sort_by_key(|item| item.account); - let mut delta_contributors_plmc_balances = MockInstantiator::generic_map_subtract( - post_contributors_plmc_balances, - vec![prev_contributors_plmc_balances], - |item| item.account, - |minuend, subtrahend| { - let mut output = minuend.clone(); - output.plmc_amount = minuend.plmc_amount - subtrahend.plmc_amount; - output - }, + let mut delta_contributors_plmc_balances = MockInstantiator::generic_map_operation( + vec![post_contributors_plmc_balances, prev_contributors_plmc_balances], + MergeOperation::Subtract ); delta_contributors_plmc_balances.sort_by_key(|item| item.account); let final_price = details.weighted_average_price.unwrap(); - let mut plmc_locked_for_bids = - MockInstantiator::calculate_auction_plmc_spent_after_price_calculation(bids, final_price); + let mut plmc_locked_for_bids = MockInstantiator::calculate_auction_plmc_spent(&bids, Some(final_price)); plmc_locked_for_bids.sort_by_key(|item| item.account); let mut plmc_locked_for_contributions = MockInstantiator::calculate_contributed_plmc_spent(community_contributions, final_price); @@ -3950,21 +3933,20 @@ mod community_round_failure { community_contributions.iter().map(|contribution| contribution.contributor.clone()).collect::>(), ); - let mut delta_bidders_plmc_balances = MockInstantiator::merge_subtract_mappings_by_user( - post_bidders_plmc_balances, - vec![prev_bidders_plmc_balances], + let mut delta_bidders_plmc_balances = MockInstantiator::generic_map_operation( + vec![post_bidders_plmc_balances, prev_bidders_plmc_balances], + MergeOperation::Subtract, ); delta_bidders_plmc_balances.sort_by_key(|item| item.account); - let mut delta_contributors_plmc_balances = MockInstantiator::merge_subtract_mappings_by_user( - post_contributors_plmc_balances, - vec![prev_contributors_plmc_balances], + let mut delta_contributors_plmc_balances = MockInstantiator::generic_map_operation( + vec![post_contributors_plmc_balances, prev_contributors_plmc_balances], + MergeOperation::Subtract, ); delta_contributors_plmc_balances.sort_by_key(|item| item.account); let details = inst.get_project_details(project_id); let final_price = details.weighted_average_price.unwrap(); - let mut plmc_locked_for_bids = - MockInstantiator::calculate_auction_plmc_spent_after_price_calculation(bids, final_price); + let mut plmc_locked_for_bids = MockInstantiator::calculate_auction_plmc_spent(&bids, Some(final_price)); plmc_locked_for_bids.sort_by_key(|item| item.account); let mut plmc_locked_for_contributions = MockInstantiator::calculate_contributed_plmc_spent(community_contributions, final_price); @@ -4005,7 +3987,7 @@ mod remainder_round_success { evaluations.push(UserToUSDBalance::new(evaluator_contributor, evaluation_amount)); let bids = default_bids(); - let project_id = + let (project_id, _) = inst.create_remainder_contributing_project(project, issuer, evaluations, bids, community_contributions); let ct_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); let already_bonded_plmc = MockInstantiator::calculate_evaluation_plmc_spent(vec![UserToUSDBalance::new( @@ -4074,7 +4056,7 @@ mod remainder_round_success { .saturating_mul_int(evaluation_bond); evaluations.push(UserToUSDBalance::new(evaluator_contributor, evaluation_usd_amount)); - let project_id = + let (project_id, _) = inst.create_remainder_contributing_project(project, issuer, evaluations, bids, default_community_buys()); inst.mint_plmc_to(vec![UserToPLMCBalance::new( @@ -4099,7 +4081,7 @@ mod remainder_round_success { #[test] fn remainder_round_ends_on_all_ct_sold_exact() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_remainder_contributing_project( + let (project_id, _) = inst.create_remainder_contributing_project( default_project(0, ISSUER), ISSUER, default_evaluations(), @@ -4150,7 +4132,7 @@ mod remainder_round_success { #[test] fn remainder_round_ends_on_all_ct_sold_overbuy() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_remainder_contributing_project( + let (project_id, _) = inst.create_remainder_contributing_project( default_project(0, ISSUER), ISSUER, default_evaluations(), @@ -4548,14 +4530,14 @@ mod remainder_round_success { .into_iter() .map(|bid| BidParams::from(bid.bidder, bid.final_ct_amount, bid.final_ct_usd_price)) .collect(); - let auction_locked_plmc = MockInstantiator::calculate_auction_plmc_spent_after_price_calculation(bids, price); + let auction_locked_plmc = MockInstantiator::calculate_auction_plmc_spent(&bids, Some(price)); let community_locked_plmc = MockInstantiator::calculate_contributed_plmc_spent(community_contributions, price); let remainder_locked_plmc = MockInstantiator::calculate_contributed_plmc_spent(remainder_contributions, price); - let all_plmc_locks = MockInstantiator::merge_add_mappings_by_user(vec![ + let all_plmc_locks = MockInstantiator::generic_map_operation(vec![ auction_locked_plmc, community_locked_plmc, remainder_locked_plmc, - ]); + ], MergeOperation::Add); inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); inst.advance_time(10u64).unwrap(); @@ -5202,34 +5184,19 @@ mod remainder_round_failure { MockInstantiator::calculate_contributed_funding_asset_spent(community_contributions.clone(), final_price); let expected_remainder_contribution_payouts = MockInstantiator::calculate_contributed_funding_asset_spent(remainder_contributions.clone(), final_price); - let all_expected_payouts = MockInstantiator::generic_map_merge_reduce( + let all_expected_payouts = MockInstantiator::generic_map_operation( vec![ expected_bid_payouts.clone(), expected_community_contribution_payouts, expected_remainder_contribution_payouts, ], - |item| item.account.clone(), - BalanceOf::::zero(), - |item, s| item.asset_amount + s, + MergeOperation::Add, ); let prev_issuer_funding_balance = inst .get_free_statemint_asset_balances_for(expected_bid_payouts[0].asset_id, vec![issuer.clone()])[0] .asset_amount; - let bidders = bids.iter().map(|bid| bid.bidder.clone()).collect::>(); - let community_contributors = community_contributions - .iter() - .map(|test_contribution| test_contribution.contributor.clone()) - .collect::>(); - let remainder_contributors = remainder_contributions - .iter() - .map(|test_contribution| test_contribution.contributor.clone()) - .collect::>(); - let all_participants = MockInstantiator::generic_map_merge( - vec![bidders, community_contributors, remainder_contributors], - |account| account.clone(), - |acc_1, _acc_2| acc_1.clone(), - ); + let all_participants = all_expected_payouts.accounts(); let prev_participants_funding_balances = inst.get_free_statemint_asset_balances_for(expected_bid_payouts[0].asset_id, all_participants.clone()); @@ -5256,11 +5223,9 @@ mod remainder_round_failure { )[0] .asset_amount; - let all_participants_funding_delta = MockInstantiator::generic_map_merge_reduce( + let all_participants_funding_delta = MockInstantiator::generic_map_operation( vec![prev_participants_funding_balances, post_participants_funding_balances], - |item| item.account.clone(), - Zero::zero(), - |item, s| item.asset_amount + s, + MergeOperation::Add, ); let issuer_funding_delta = post_issuer_funding_balance - prev_issuer_funding_balance; @@ -5319,34 +5284,19 @@ mod remainder_round_failure { MockInstantiator::calculate_contributed_funding_asset_spent(community_contributions.clone(), final_price); let expected_remainder_contribution_payouts = MockInstantiator::calculate_contributed_funding_asset_spent(remainder_contributions.clone(), final_price); - let all_expected_payouts = MockInstantiator::generic_map_merge_reduce( + let all_expected_payouts = MockInstantiator::generic_map_operation( vec![ expected_bid_payouts.clone(), expected_community_contribution_payouts, expected_remainder_contribution_payouts, ], - |item| item.account.clone(), - BalanceOf::::zero(), - |item, s| item.asset_amount + s, + MergeOperation::Add, ); let prev_issuer_funding_balance = inst .get_free_statemint_asset_balances_for(expected_bid_payouts[0].asset_id, vec![issuer.clone()])[0] .asset_amount; - let bidders = bids.iter().map(|bid| bid.bidder.clone()).collect::>(); - let community_contributors = community_contributions - .iter() - .map(|test_contribution| test_contribution.contributor.clone()) - .collect::>(); - let remainder_contributors = remainder_contributions - .iter() - .map(|test_contribution| test_contribution.contributor.clone()) - .collect::>(); - let all_participants = MockInstantiator::generic_map_merge( - vec![bidders, community_contributors, remainder_contributors], - |account| account.clone(), - |acc_1, _acc_2| acc_1.clone(), - ); + let all_participants = all_expected_payouts.accounts(); let prev_participants_funding_balances = inst.get_free_statemint_asset_balances_for(expected_bid_payouts[0].asset_id, all_participants.clone()); @@ -5403,11 +5353,9 @@ mod remainder_round_failure { )[0] .asset_amount; - let all_participants_funding_delta = MockInstantiator::generic_map_merge_reduce( + let all_participants_funding_delta = MockInstantiator::generic_map_operation( vec![prev_participants_funding_balances, post_participants_funding_balances], - |item| item.account.clone(), - Zero::zero(), - |item, s| item.asset_amount + s, + MergeOperation::Add, ); let issuer_funding_delta = post_issuer_funding_balance - prev_issuer_funding_balance; @@ -5469,8 +5417,7 @@ mod remainder_round_failure { .filter(|bid| matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..))) .map(|bid| BidParams::from(bid.bidder, bid.final_ct_amount, bid.final_ct_usd_price)) .collect_vec(); - let expected_bid_payouts = - MockInstantiator::calculate_auction_plmc_spent_after_price_calculation(bids.clone(), final_price); + let expected_bid_payouts = MockInstantiator::calculate_auction_plmc_spent(&bids.clone(), Some(final_price)); let expected_community_contribution_payouts = MockInstantiator::calculate_contributed_plmc_spent(community_contributions.clone(), final_price); let expected_remainder_contribution_payouts = MockInstantiator::calculate_contributed_plmc_spent( @@ -5485,32 +5432,19 @@ mod remainder_round_failure { ], final_price, ); - let all_expected_payouts = MockInstantiator::generic_map_merge( + let all_expected_payouts = MockInstantiator::generic_map_operation( vec![ expected_evaluator_contributor_return, expected_bid_payouts.clone(), expected_community_contribution_payouts, expected_remainder_contribution_payouts, ], - |item| item.account.clone(), - |item1, item2| UserToPLMCBalance::new(item1.account, item1.plmc_amount + item2.plmc_amount), + MergeOperation::Add, ); let prev_issuer_funding_balance = inst.get_free_plmc_balances_for(vec![issuer.clone()])[0].plmc_amount; - let bidders = bids.iter().map(|bid| bid.bidder.clone()).collect::>(); - let community_contributors = community_contributions - .iter() - .map(|test_contribution| test_contribution.contributor.clone()) - .collect::>(); - let remainder_contributors = remainder_contributions - .iter() - .map(|test_contribution| test_contribution.contributor.clone()) - .collect::>(); - let all_participants = MockInstantiator::generic_map_merge( - vec![bidders, community_contributors, remainder_contributors], - |account| account.clone(), - |acc_1, _acc_2| acc_1.clone(), - ); + + let all_participants = all_expected_payouts.accounts(); let prev_participants_plmc_balances = inst.get_free_plmc_balances_for(all_participants.clone()); call_and_is_ok!( @@ -5528,9 +5462,9 @@ mod remainder_round_failure { let post_issuer_funding_balance = inst.get_free_plmc_balances_for(vec![issuer.clone()])[0].plmc_amount; let post_participants_plmc_balances = inst.get_free_plmc_balances_for(all_participants); - let all_participants_plmc_deltas = MockInstantiator::merge_subtract_mappings_by_user( - post_participants_plmc_balances, - vec![prev_participants_plmc_balances], + let all_participants_plmc_deltas = MockInstantiator::generic_map_operation( + vec![post_participants_plmc_balances,prev_participants_plmc_balances], + MergeOperation::Subtract, ); let issuer_funding_delta = post_issuer_funding_balance - prev_issuer_funding_balance; @@ -5588,8 +5522,7 @@ mod remainder_round_failure { final_price, true, ); - let expected_bid_payouts = - MockInstantiator::calculate_auction_plmc_spent_after_price_calculation(bids.clone(), final_price); + let expected_bid_payouts = MockInstantiator::calculate_auction_plmc_spent(&bids.clone(), Some(final_price)); let expected_community_contribution_payouts = MockInstantiator::calculate_contributed_plmc_spent(community_contributions.clone(), final_price); let expected_remainder_contribution_payouts = MockInstantiator::calculate_contributed_plmc_spent( @@ -5604,26 +5537,18 @@ mod remainder_round_failure { ], final_price, ); - let all_expected_payouts = MockInstantiator::generic_map_merge( + let all_expected_payouts = MockInstantiator::generic_map_operation( vec![ expected_evaluator_contributor_return, expected_bid_payouts.clone(), expected_community_contribution_payouts, expected_remainder_contribution_payouts, ], - |item| item.account.clone(), - |item1, item2| UserToPLMCBalance::new(item1.account, item1.plmc_amount + item2.plmc_amount), + MergeOperation::Add, ); let prev_issuer_funding_balance = inst.get_free_plmc_balances_for(vec![issuer.clone()])[0].plmc_amount; - let bidders = bids.accounts(); - let community_contributors = community_contributions.accounts(); - let remainder_contributors = remainder_contributions.accounts(); - let all_participants = MockInstantiator::generic_map_merge( - vec![bidders, community_contributors, remainder_contributors], - |account| account.clone(), - |acc_1, _acc_2| acc_1.clone(), - ); + let all_participants = all_expected_payouts.accounts(); let prev_participants_plmc_balances = inst.get_free_plmc_balances_for(all_participants.clone()); call_and_is_ok!( @@ -5707,9 +5632,10 @@ mod remainder_round_failure { let post_issuer_funding_balance = inst.get_free_plmc_balances_for(vec![issuer.clone()])[0].plmc_amount; let post_participants_plmc_balances = inst.get_free_plmc_balances_for(all_participants); - let all_participants_plmc_deltas = MockInstantiator::merge_subtract_mappings_by_user( - post_participants_plmc_balances, - vec![prev_participants_plmc_balances], + let all_participants_plmc_deltas = MockInstantiator::generic_map_operation( + + vec![post_participants_plmc_balances, prev_participants_plmc_balances], + MergeOperation::Subtract, ); let issuer_funding_delta = post_issuer_funding_balance - prev_issuer_funding_balance; @@ -5968,11 +5894,11 @@ mod funding_end { ); let slashed_evaluation_locked_plmc = MockInstantiator::slash_evaluator_balances(old_evaluation_locked_plmc); - let expected_evaluator_free_balances = MockInstantiator::merge_add_mappings_by_user(vec![ + let expected_evaluator_free_balances = MockInstantiator::generic_map_operation(vec![ slashed_evaluation_locked_plmc, old_participation_locked_plmc, old_free_plmc, - ]); + ], MergeOperation::Add); let actual_evaluator_free_balances = inst.get_free_plmc_balances_for(evaluators.clone()); @@ -6010,11 +5936,11 @@ mod funding_end { ); let slashed_evaluation_locked_plmc = MockInstantiator::slash_evaluator_balances(old_evaluation_locked_plmc); - let expected_evaluator_free_balances = MockInstantiator::merge_add_mappings_by_user(vec![ + let expected_evaluator_free_balances = MockInstantiator::generic_map_operation(vec![ slashed_evaluation_locked_plmc, old_participation_locked_plmc, old_free_plmc, - ]); + ], MergeOperation::Add); let actual_evaluator_free_balances = inst.get_free_plmc_balances_for(evaluators.clone()); @@ -6046,11 +5972,11 @@ mod funding_end { ); let slashed_evaluation_locked_plmc = MockInstantiator::slash_evaluator_balances(old_evaluation_locked_plmc); - let expected_evaluator_free_balances = MockInstantiator::merge_add_mappings_by_user(vec![ + let expected_evaluator_free_balances = MockInstantiator::generic_map_operation(vec![ slashed_evaluation_locked_plmc, old_participation_locked_plmc, old_free_plmc, - ]); + ], MergeOperation::Add); let actual_evaluator_free_balances = inst.get_free_plmc_balances_for(evaluators.clone()); @@ -6246,7 +6172,7 @@ mod test_helper_functions { UserToPLMCBalance::new(BIDDER_5, EXPECTED_PLMC_AMOUNT_5), ]; - let result = MockInstantiator::calculate_auction_plmc_spent(bids); + let result = MockInstantiator::calculate_auction_plmc_spent(&bids, None); assert_eq!(result, expected_plmc_spent); } @@ -6720,7 +6646,8 @@ mod e2e_testing { let project = excel_project(inst.get_new_nonce()); let evaluations = excel_evaluators(); let bids = excel_bidders(); - let project_id = inst.create_community_contributing_project(project, issuer, evaluations, bids); + //let filtered_bids = MockInstantiator::filter_bids_after_auction(bids.clone(), project.total_allocation_size.0); + let (project_id, _) = inst.create_community_contributing_project(project, issuer, evaluations, bids); let wavgp_from_excel = 10.202357561; // Convert the float to a FixedU128 let wavgp_to_substrate = FixedU128::from_float(wavgp_from_excel); @@ -6759,17 +6686,17 @@ mod e2e_testing { .collect_vec(); let total_contribution = contributions.clone().into_iter().fold(0, |acc, bid| acc + bid.funding_asset_amount); - let total_ct_amount = contributions.clone().into_iter().fold(0, |acc, bid| acc + bid.ct_amount); let total_contribution_as_fixed = FixedU128::from_rational(total_contribution, PLMC); dbg!(total_contribution_as_fixed); // In USD + // let total_ct_amount = contributions.clone().into_iter().fold(0, |acc, bid| acc + bid.ct_amount); // let total_contribution_from_excel = 46821.0; // dbg!(total_contribution_from_excel); - // let res = total_contribution_as_fixed.to_float() - total_contribution_from_excel + // let res = total_contribution_as_fixed - FixedU128::from_float(total_contribution_from_excel); // // We are more precise than Excel. From the 11th decimal onwards, the difference should be less than 0.001. // assert!(res < FixedU128::from_float(0.001)); - let total_ct_sold_from_excel = 46821; - assert_eq!(total_ct_amount / PLMC, total_ct_sold_from_excel) + // let total_ct_sold_from_excel = 46821; + // assert_eq!(total_ct_amount / PLMC, total_ct_sold_from_excel); }) }