From e91f80a729959f8d1465ae268bfe8eef4fbc85ae Mon Sep 17 00:00:00 2001 From: Juan Ignacio RIos Date: Fri, 28 Jul 2023 15:02:23 +0200 Subject: [PATCH 1/4] feat: Evaluations converted from `StorageDoubleMap` to `StorageNMap` --- pallets/funding/src/functions.rs | 110 ++++++++++++------------------- pallets/funding/src/impls.rs | 82 ++++------------------- pallets/funding/src/lib.rs | 14 ++-- 3 files changed, 61 insertions(+), 145 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index 9c83fb6aa..ba7b6c659 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -30,7 +30,7 @@ use frame_support::{ fungible::{InspectHold, MutateHold as FungibleMutateHold}, fungibles::{metadata::Mutate as MetadataMutate, Create, Mutate as FungiblesMutate}, tokens::{Precision, Preservation}, - Get, + Get, Len, }, }; @@ -219,12 +219,8 @@ impl Pallet { // * Calculate new variables * let initial_balance: BalanceOf = 0u32.into(); - let total_amount_bonded = - Evaluations::::iter_prefix(project_id).fold(initial_balance, |total, (_evaluator, bonds)| { - let user_total_plmc_bond = - bonds.iter().fold(total, |acc, bond| acc.saturating_add(bond.original_plmc_bond)); - total.saturating_add(user_total_plmc_bond) - }); + let total_amount_bonded = Evaluations::::iter_prefix((project_id,)) + .fold(initial_balance, |total, (_evaluator, bond)| total.saturating_add(bond.original_plmc_bond)); let evaluation_target_usd = ::EvaluationSuccessThreshold::get() * fundraising_target_usd; let evaluation_target_plmc = current_plmc_price @@ -778,7 +774,7 @@ impl Pallet { let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectInfoNotFound)?; let now = >::block_number(); let evaluation_id = Self::next_evaluation_id(); - let mut caller_existing_evaluations = Evaluations::::get(project_id, evaluator.clone()); + let mut caller_existing_evaluations: Vec<(StorageItemIdOf, EvaluationInfoOf)> = Evaluations::::iter_prefix((project_id, evaluator.clone())).collect(); let plmc_usd_price = T::PriceProvider::get_price(PLMC_STATEMINT_ID).ok_or(Error::::PLMCPriceNotAvailable)?; let early_evaluation_reward_threshold_usd = T::EvaluationSuccessThreshold::get() * project_details.fundraising_target; @@ -821,38 +817,38 @@ impl Pallet { }; // * Update Storage * - // TODO: PLMC-144. Unlock the PLMC when it's the right time - - match caller_existing_evaluations.try_push(new_evaluation.clone()) { - Ok(_) => { - T::NativeCurrency::hold(&LockType::Evaluation(project_id), &evaluator, plmc_bond) - .map_err(|_| Error::::InsufficientBalance)?; - }, - Err(_) => { - // Evaluations are stored in descending order. If the evaluation vector for the user is full, we drop the lowest/last bond - let lowest_evaluation = caller_existing_evaluations.swap_remove(caller_existing_evaluations.len() - 1); - - ensure!(lowest_evaluation.original_plmc_bond < plmc_bond, Error::::EvaluationBondTooLow); - - T::NativeCurrency::release( - &LockType::Evaluation(project_id), - &lowest_evaluation.evaluator, - lowest_evaluation.original_plmc_bond, - Precision::Exact, - ) + if caller_existing_evaluations.len() < T::MaxEvaluationsPerUser::get() as usize { + T::NativeCurrency::hold(&LockType::Evaluation(project_id), &evaluator, plmc_bond) .map_err(|_| Error::::InsufficientBalance)?; + } else { + // If the evaluation vector for the user is full, we drop the lowest/last bond + let (low_id, lowest_evaluation) = caller_existing_evaluations + .iter() + .min_by_key(|(_, evaluation)| evaluation.original_plmc_bond) + .ok_or(Error::::ImpossibleState)? + .clone(); + + ensure!(lowest_evaluation.original_plmc_bond < plmc_bond, Error::::EvaluationBondTooLow); + ensure!( + lowest_evaluation.original_plmc_bond == lowest_evaluation.current_plmc_bond, + "Using evaluation funds for participating should not be possible in the evaluation round" + ); - T::NativeCurrency::hold(&LockType::Evaluation(project_id), &evaluator, plmc_bond) - .map_err(|_| Error::::InsufficientBalance)?; + T::NativeCurrency::release( + &LockType::Evaluation(project_id), + &lowest_evaluation.evaluator, + lowest_evaluation.original_plmc_bond, + Precision::Exact, + ) + .map_err(|_| Error::::InsufficientBalance)?; - // This should never fail since we just removed an element from the vector - caller_existing_evaluations.try_push(new_evaluation).map_err(|_| Error::::ImpossibleState)?; - }, - }; + T::NativeCurrency::hold(&LockType::Evaluation(project_id), &evaluator, plmc_bond) + .map_err(|_| Error::::InsufficientBalance)?; - caller_existing_evaluations.sort_by_key(|bond| Reverse(bond.original_plmc_bond)); + Evaluations::::remove((project_id, evaluator.clone(), low_id)); + } - Evaluations::::set(project_id, evaluator.clone(), caller_existing_evaluations); + Evaluations::::insert((project_id, evaluator.clone(), evaluation_id), new_evaluation); NextEvaluationId::::set(evaluation_id.saturating_add(One::one())); evaluation_round_info.total_bonded_usd += usd_amount; evaluation_round_info.total_bonded_plmc += plmc_bond; @@ -1443,12 +1439,7 @@ impl Pallet { ) -> Result<(), DispatchError> { // * Get variables * let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectInfoNotFound)?; - let mut user_evaluations = Evaluations::::get(project_id, evaluator.clone()); - let evaluation_position = user_evaluations - .iter() - .position(|evaluation| evaluation.id == evaluation_id) - .ok_or(Error::::EvaluationNotFound)?; - let released_evaluation = user_evaluations.swap_remove(evaluation_position); + let released_evaluation = Evaluations::::get((project_id, evaluator.clone(), evaluation_id)).ok_or(Error::::EvaluationNotFound)?; // * Validity checks * ensure!( @@ -1467,7 +1458,7 @@ impl Pallet { released_evaluation.current_plmc_bond, Precision::Exact, )?; - Evaluations::::set(project_id, evaluator.clone(), user_evaluations); + Evaluations::::remove((project_id, evaluator.clone(), evaluation_id)); // * Emit events * Self::deposit_event(Event::::BondReleased { @@ -1495,11 +1486,7 @@ impl Pallet { } else { return Err(Error::::NotAllowed.into()) }; - let mut user_evaluations = Evaluations::::get(project_id, evaluator.clone()); - let evaluation = user_evaluations - .iter_mut() - .find(|evaluation| evaluation.id == evaluation_id) - .ok_or(Error::::EvaluationNotFound)?; + let mut evaluation = Evaluations::::get((project_id, evaluator.clone(), evaluation_id)).ok_or(Error::::EvaluationNotFound)?; // * Validity checks * ensure!( @@ -1526,7 +1513,7 @@ impl Pallet { // * Update storage * T::ContributionTokenCurrency::mint_into(project_id, &evaluation.evaluator, reward_amount_ct)?; evaluation.rewarded_or_slashed = true; - Evaluations::::set(project_id, evaluator.clone(), user_evaluations); + Evaluations::::insert((project_id, evaluator.clone(), evaluation_id), evaluation); // * Emit events * Self::deposit_event(Event::::EvaluationRewarded { @@ -2083,22 +2070,8 @@ impl Pallet { project_id: ::ProjectIdentifier, ) -> Result, DispatchError> { let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectNotFound)?; - let evaluation_usd_amounts = Evaluations::::iter_prefix(project_id) - .map(|(evaluator, evaluations)| { - ( - evaluator, - evaluations.into_iter().fold( - (BalanceOf::::zero(), BalanceOf::::zero()), - |acc, evaluation| { - ( - acc.0.saturating_add(evaluation.early_usd_amount), - acc.1.saturating_add(evaluation.late_usd_amount), - ) - }, - ), - ) - }) - .collect::>(); + let evaluations = Evaluations::::iter_prefix((project_id,)).collect::>(); + let target_funding = project_details.fundraising_target; let funding_reached = project_details.funding_amount_reached; @@ -2111,11 +2084,12 @@ impl Pallet { let early_evaluator_reward_pot_usd = Perquintill::from_percent(20) * evaluator_fees; let normal_evaluator_reward_pot_usd = Perquintill::from_percent(80) * evaluator_fees; - let early_evaluator_total_bonded_usd = evaluation_usd_amounts + let early_evaluator_total_bonded_usd = evaluations + .iter() + .fold(BalanceOf::::zero(), |acc, ((_evaluator, _id), evaluation)| acc.saturating_add(evaluation.early_usd_amount)); + let late_evaluator_total_bonded_usd = evaluations .iter() - .fold(BalanceOf::::zero(), |acc, (_, (early, _))| acc.saturating_add(*early)); - let late_evaluator_total_bonded_usd = - evaluation_usd_amounts.iter().fold(BalanceOf::::zero(), |acc, (_, (_, late))| acc.saturating_add(*late)); + .fold(BalanceOf::::zero(), |acc, ((_evaluator, _id), evaluation)| acc.saturating_add(evaluation.late_usd_amount)); let normal_evaluator_total_bonded_usd = early_evaluator_total_bonded_usd.saturating_add(late_evaluator_total_bonded_usd); Ok(RewardInfo { diff --git a/pallets/funding/src/impls.rs b/pallets/funding/src/impls.rs index fd216590d..86ea7e7ee 100644 --- a/pallets/funding/src/impls.rs +++ b/pallets/funding/src/impls.rs @@ -228,14 +228,13 @@ enum OperationsLeft { } fn remaining_evaluators_to_reward_or_slash(project_id: T::ProjectIdentifier) -> u64 { - Evaluations::::iter_prefix_values(project_id) - .flatten() + Evaluations::::iter_prefix_values((project_id,)) .filter(|evaluation| !evaluation.rewarded_or_slashed) .count() as u64 } fn remaining_evaluations(project_id: T::ProjectIdentifier) -> u64 { - Evaluations::::iter_prefix_values(project_id).flatten().count() as u64 + Evaluations::::iter_prefix_values((project_id,)).count() as u64 } fn remaining_bids_to_release_funds(project_id: T::ProjectIdentifier) -> u64 { @@ -289,20 +288,11 @@ fn remaining_contributions_without_issuer_payout(project_id: T::Proje fn reward_or_slash_one_evaluation(project_id: T::ProjectIdentifier) -> Result<(Weight, u64), DispatchError> { let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectNotFound)?; - let project_evaluations: Vec<_> = Evaluations::::iter_prefix_values(project_id).collect(); - let remaining_evaluations = - project_evaluations.iter().flatten().filter(|evaluation| !evaluation.rewarded_or_slashed).count() as u64; - - let maybe_user_evaluations = project_evaluations - .into_iter() - .find(|evaluations| evaluations.iter().any(|evaluation| !evaluation.rewarded_or_slashed)); - - if let Some(mut user_evaluations) = maybe_user_evaluations { - let evaluation = user_evaluations - .iter_mut() - .find(|evaluation| !evaluation.rewarded_or_slashed) - .expect("user_evaluations can only exist if an item here is found; qed"); + let mut project_evaluations = Evaluations::::iter_prefix_values((project_id,)); + let mut remaining_evaluations = + project_evaluations.filter(|evaluation| !evaluation.rewarded_or_slashed); + if let Some(mut evaluation) = remaining_evaluations.next() { match project_details.evaluation_round_info.evaluators_outcome { EvaluatorsOutcome::Rewarded(_) => { match Pallet::::do_evaluation_reward( @@ -326,27 +316,19 @@ fn reward_or_slash_one_evaluation(project_id: T::ProjectIdentifier) - // if the evaluation outcome failed, we still want to flag it as rewarded or slashed. Otherwise the automatic // transition will get stuck. evaluation.rewarded_or_slashed = true; - Evaluations::::insert(project_id, evaluation.evaluator.clone(), user_evaluations); + Evaluations::::insert((project_id, evaluation.evaluator.clone(), evaluation.id), evaluation); - Ok((Weight::zero(), remaining_evaluations.saturating_sub(1u64))) + Ok((Weight::zero(), remaining_evaluations.count() as u64)) } else { Ok((Weight::zero(), 0u64)) } } -fn unbond_one_evaluation(project_id: T::ProjectIdentifier) -> (Weight, u64) { - let project_evaluations: Vec<_> = Evaluations::::iter_prefix_values(project_id).collect(); - let evaluation_count = project_evaluations.iter().flatten().count() as u64; - - let maybe_user_evaluations = - project_evaluations.into_iter().find(|evaluations| evaluations.iter().any(|e| e.rewarded_or_slashed)); - - if let Some(mut user_evaluations) = maybe_user_evaluations { - let evaluation = user_evaluations - .iter_mut() - .find(|evaluation| evaluation.rewarded_or_slashed) - .expect("user_evaluations can only exist if an item here is found; qed"); +fn unbond_one_evaluation(project_id: T::ProjectIdentifier) -> (Weight, u64) { + let mut project_evaluations = Evaluations::::iter_prefix_values((project_id,)).collect::>(); + let evaluation_count = project_evaluations.len() as u64; + if let Some(mut evaluation) = project_evaluations.iter().find(|evaluation| evaluation.rewarded_or_slashed) { match Pallet::::do_evaluation_unbond_for( T::PalletId::get().into_account_truncating(), evaluation.project_id, @@ -623,43 +605,3 @@ fn issuer_funding_payout_one_contribution(project_id: T::ProjectIdent (Weight::zero(), 0u64) } } - -// might come in handy later -#[allow(unused)] -fn unbond_evaluators(project_id: T::ProjectIdentifier, max_weight: Weight) -> (Weight, OperationsLeft) { - let evaluations = Evaluations::::iter_prefix_values(project_id).flatten().collect::>>(); - - let mut used_weight = Weight::zero(); - - let unbond_results = evaluations - .iter() - .take_while(|_evaluation| { - let new_used_weight = used_weight.saturating_add(T::WeightInfo::evaluation_unbond_for()); - if new_used_weight.any_gt(max_weight) { - false - } else { - used_weight = new_used_weight; - true - } - }) - .map(|evaluation| { - Pallet::::do_evaluation_unbond_for( - T::PalletId::get().into_account_truncating(), - evaluation.project_id, - evaluation.evaluator.clone(), - evaluation.id, - ) - }) - .collect::>(); - - let successful_results = - unbond_results.into_iter().filter(|result| if let Err(e) = result { false } else { true }).collect::>(); - - let operations_left = if successful_results.len() == evaluations.len() { - OperationsLeft::None - } else { - OperationsLeft::Some(evaluations.len().saturating_sub(successful_results.len()) as u64) - }; - - (used_weight, operations_left) -} diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 1e481518c..0a3aa40a0 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -445,14 +445,14 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn evaluations)] /// Keep track of the PLMC bonds made to each project by each evaluator - pub type Evaluations = StorageDoubleMap< + pub type Evaluations = StorageNMap< _, - Blake2_128Concat, - T::ProjectIdentifier, - Blake2_128Concat, - AccountIdOf, - BoundedVec, T::MaxEvaluationsPerUser>, - ValueQuery, + ( + NMapKey, + NMapKey>, + NMapKey>, + ), + EvaluationInfoOf, >; #[pallet::storage] From 380bf1a3421ecd2ed13c35c40a6c5e89cddc12c2 Mon Sep 17 00:00:00 2001 From: Juan Ignacio RIos Date: Fri, 28 Jul 2023 18:14:44 +0200 Subject: [PATCH 2/4] feat: Bids converted from `StorageDoubleMap` to `StorageNMap` --- pallets/funding/src/functions.rs | 209 ++++++++++++++----------------- pallets/funding/src/impls.rs | 70 +++-------- pallets/funding/src/lib.rs | 14 +-- pallets/funding/src/tests.rs | 23 ++-- 4 files changed, 130 insertions(+), 186 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index ba7b6c659..aa3eca08f 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -774,7 +774,8 @@ impl Pallet { let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectInfoNotFound)?; let now = >::block_number(); let evaluation_id = Self::next_evaluation_id(); - let mut caller_existing_evaluations: Vec<(StorageItemIdOf, EvaluationInfoOf)> = Evaluations::::iter_prefix((project_id, evaluator.clone())).collect(); + let mut caller_existing_evaluations: Vec<(StorageItemIdOf, EvaluationInfoOf)> = + Evaluations::::iter_prefix((project_id, evaluator.clone())).collect(); let plmc_usd_price = T::PriceProvider::get_price(PLMC_STATEMINT_ID).ok_or(Error::::PLMCPriceNotAvailable)?; let early_evaluation_reward_threshold_usd = T::EvaluationSuccessThreshold::get() * project_details.fundraising_target; @@ -821,7 +822,6 @@ impl Pallet { T::NativeCurrency::hold(&LockType::Evaluation(project_id), &evaluator, plmc_bond) .map_err(|_| Error::::InsufficientBalance)?; } else { - // If the evaluation vector for the user is full, we drop the lowest/last bond let (low_id, lowest_evaluation) = caller_existing_evaluations .iter() .min_by_key(|(_, evaluation)| evaluation.original_plmc_bond) @@ -887,7 +887,7 @@ impl Pallet { let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectInfoNotFound)?; let now = >::block_number(); let bid_id = Self::next_bid_id(); - let mut existing_bids = Bids::::get(project_id, bidder.clone()); + let mut existing_bids = Bids::::iter_prefix_values((project_id, bidder.clone())).collect::>(); let ticket_size = ct_usd_price.checked_mul_int(ct_amount).ok_or(Error::::BadMath)?; let funding_asset_usd_price = @@ -938,41 +938,35 @@ impl Pallet { }; // * Update storage * - match existing_bids.try_push(new_bid.clone()) { - Ok(_) => { - Self::try_plmc_participation_lock(&bidder, project_id, required_plmc_bond)?; - Self::try_funding_asset_hold(&bidder, project_id, required_funding_asset_transfer, asset_id)?; - - // TODO: PLMC-159. Send an XCM message to Statemint/e to transfer a `bid.market_cap` amount of USDC (or the Currency specified by the issuer) to the PalletId Account - // Alternative TODO: PLMC-159. The user should have the specified currency (e.g: USDC) already on Polimec - }, - Err(_) => { - // Since the bids are sorted by price, and in this branch the Vec is full, the last element is the lowest bid - let lowest_plmc_bond = existing_bids.iter().last().ok_or(Error::::ImpossibleState)?.plmc_bond; - - ensure!(new_bid.plmc_bond > lowest_plmc_bond, Error::::BidTooLow); - - Self::release_last_funding_item_in_vec( - &bidder, - project_id, - asset_id, - &mut existing_bids, - |x| x.plmc_bond, - |x| x.funding_asset_amount_locked, - )?; - - Self::try_plmc_participation_lock(&bidder, project_id, required_plmc_bond)?; + if existing_bids.len() < T::MaxBidsPerUser::get() as usize { + Self::try_plmc_participation_lock(&bidder, project_id, required_plmc_bond)?; + Self::try_funding_asset_hold(&bidder, project_id, required_funding_asset_transfer, asset_id)?; + } else { + let lowest_bid = + existing_bids.iter().min_by_key(|bid| bid.plmc_bond).ok_or(Error::::ImpossibleState)?.clone(); - Self::try_funding_asset_hold(&bidder, project_id, required_funding_asset_transfer, asset_id)?; + ensure!(new_bid.plmc_bond > lowest_bid.plmc_bond, Error::::BidTooLow); - // This should never fail, since we just removed an element from the Vec - existing_bids.try_push(new_bid).map_err(|_| Error::::ImpossibleState)?; - }, - }; + T::NativeCurrency::release( + &LockType::Participation(project_id), + &lowest_bid.bidder, + lowest_bid.plmc_bond, + Precision::Exact, + )?; + T::FundingCurrency::transfer( + asset_id, + &Self::fund_account_id(project_id), + &lowest_bid.bidder, + lowest_bid.funding_asset_amount_locked, + Preservation::Expendable, + )?; + Bids::::remove((project_id, lowest_bid.bidder, lowest_bid.id)); - existing_bids.sort_by(|a, b| b.cmp(a)); + Self::try_plmc_participation_lock(&bidder, project_id, required_plmc_bond)?; + Self::try_funding_asset_hold(&bidder, project_id, required_funding_asset_transfer, asset_id)?; + } - Bids::::set(project_id, bidder, existing_bids); + Bids::::insert((project_id, bidder, bid_id), new_bid); NextBidId::::set(bid_id.saturating_add(One::one())); Self::deposit_event(Event::::Bid { project_id, amount: ct_amount, price: ct_usd_price, multiplier }); @@ -1160,10 +1154,8 @@ impl Pallet { bidder: AccountIdOf, ) -> Result<(), DispatchError> { // * Get variables * - let bids = Bids::::get(project_id, &bidder); + let bids = Bids::::iter_prefix_values((project_id, bidder.clone())); let now = >::block_number(); - let mut new_bids = vec![]; - for mut bid in bids { let mut plmc_vesting = bid.plmc_vesting_period; @@ -1193,7 +1185,7 @@ impl Pallet { unbond_amount, Precision::Exact, )?; - new_bids.push(bid.clone()); + Bids::::insert((project_id, bidder.clone(), bid.id), bid.clone()); // * Emit events * Self::deposit_event(Event::::BondReleased { @@ -1204,13 +1196,6 @@ impl Pallet { }); } - // Should never return error since we are using the same amount of bids that were there before. - let new_bids: BoundedVec, T::MaxBidsPerUser> = - new_bids.try_into().map_err(|_| Error::::TooManyBids)?; - - // Update the AuctionInfo with the new bids vector - Bids::::insert(project_id, &bidder, new_bids); - Ok(()) } @@ -1230,8 +1215,7 @@ impl Pallet { bidder: AccountIdOf, ) -> Result<(), DispatchError> { // * Get variables * - let bids = Bids::::get(project_id, &bidder); - let mut new_bids = vec![]; + let bids = Bids::::iter_prefix_values((project_id, bidder.clone())); let now = >::block_number(); for mut bid in bids { let mut ct_vesting = bid.ct_vesting_period; @@ -1257,7 +1241,7 @@ impl Pallet { // TODO: Should we mint here, or should the full mint happen to the treasury and then do transfers from there? // Mint the funds for the user T::ContributionTokenCurrency::mint_into(bid.project_id, &bid.bidder, mint_amount)?; - new_bids.push(bid); + Bids::::insert((project_id, bidder.clone(), bid.id), bid.clone()); // * Emit events * Self::deposit_event(Event::::ContributionTokenMinted { @@ -1267,10 +1251,6 @@ impl Pallet { amount: mint_amount, }) } - // Update the bids with the new vesting period struct - let new_bids: BoundedVec, T::MaxBidsPerUser> = - new_bids.try_into().map_err(|_| Error::::TooManyBids)?; - Bids::::insert(project_id, &bidder, new_bids); Ok(()) } @@ -1439,7 +1419,8 @@ impl Pallet { ) -> Result<(), DispatchError> { // * Get variables * let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectInfoNotFound)?; - let released_evaluation = Evaluations::::get((project_id, evaluator.clone(), evaluation_id)).ok_or(Error::::EvaluationNotFound)?; + let released_evaluation = Evaluations::::get((project_id, evaluator.clone(), evaluation_id)) + .ok_or(Error::::EvaluationNotFound)?; // * Validity checks * ensure!( @@ -1486,7 +1467,8 @@ impl Pallet { } else { return Err(Error::::NotAllowed.into()) }; - let mut evaluation = Evaluations::::get((project_id, evaluator.clone(), evaluation_id)).ok_or(Error::::EvaluationNotFound)?; + let mut evaluation = Evaluations::::get((project_id, evaluator.clone(), evaluation_id)) + .ok_or(Error::::EvaluationNotFound)?; // * Validity checks * ensure!( @@ -1665,7 +1647,7 @@ impl Pallet { total_allocation_size: BalanceOf, ) -> Result<(), DispatchError> { // Get all the bids that were made before the end of the candle - let mut bids = Bids::::iter_prefix(project_id).flat_map(|(_bidder, bids)| bids).collect::>(); + let mut bids = Bids::::iter_prefix_values((project_id,)).collect::>(); // temp variable to store the sum of the bids let mut bid_token_amount_sum = BalanceOf::::zero(); // temp variable to store the total value of the bids (i.e price * amount) @@ -1870,68 +1852,57 @@ impl Pallet { let mut final_total_funding_reached_by_bids = BalanceOf::::zero(); // Update the bid in the storage - for bid in bids.into_iter() { - Bids::::mutate(project_id, bid.bidder.clone(), |bids| -> Result<(), DispatchError> { - let bid_index = - bids.clone().into_iter().position(|b| b.id == bid.id).ok_or(Error::::ImpossibleState)?; - let mut final_bid = bid; - - if final_bid.final_ct_usd_price > weighted_token_price { - final_bid.final_ct_usd_price = weighted_token_price; - let new_ticket_size = - weighted_token_price.checked_mul_int(final_bid.final_ct_amount).ok_or(Error::::BadMath)?; - - let funding_asset_price = T::PriceProvider::get_price(final_bid.funding_asset.to_statemint_id()) - .ok_or(Error::::PriceNotFound)?; - let funding_asset_amount_needed = funding_asset_price - .reciprocal() - .ok_or(Error::::BadMath)? - .checked_mul_int(new_ticket_size) - .ok_or(Error::::BadMath)?; - - let try_transfer = T::FundingCurrency::transfer( - final_bid.funding_asset.to_statemint_id(), - &project_account, - &final_bid.bidder, - final_bid.funding_asset_amount_locked.saturating_sub(funding_asset_amount_needed), - Preservation::Preserve, - ); - if let Err(e) = try_transfer { - Self::deposit_event(Event::::TransferError { error: e }); - } + for mut bid in bids.into_iter() { + if bid.final_ct_usd_price > weighted_token_price { + bid.final_ct_usd_price = weighted_token_price; + let new_ticket_size = + weighted_token_price.checked_mul_int(bid.final_ct_amount).ok_or(Error::::BadMath)?; + + let funding_asset_price = T::PriceProvider::get_price(bid.funding_asset.to_statemint_id()) + .ok_or(Error::::PriceNotFound)?; + let funding_asset_amount_needed = funding_asset_price + .reciprocal() + .ok_or(Error::::BadMath)? + .checked_mul_int(new_ticket_size) + .ok_or(Error::::BadMath)?; - final_bid.funding_asset_amount_locked = funding_asset_amount_needed; + let try_transfer = T::FundingCurrency::transfer( + bid.funding_asset.to_statemint_id(), + &project_account, + &bid.bidder, + bid.funding_asset_amount_locked.saturating_sub(funding_asset_amount_needed), + Preservation::Preserve, + ); + if let Err(e) = try_transfer { + Self::deposit_event(Event::::TransferError { error: e }); + } - let usd_bond_needed = final_bid - .multiplier - .calculate_bonding_requirement(new_ticket_size) - .map_err(|_| Error::::BadMath)?; - let plmc_bond_needed = plmc_price - .reciprocal() - .ok_or(Error::::BadMath)? - .checked_mul_int(usd_bond_needed) - .ok_or(Error::::BadMath)?; + bid.funding_asset_amount_locked = funding_asset_amount_needed; - let try_release = T::NativeCurrency::release( - &LockType::Participation(project_id), - &final_bid.bidder, - final_bid.plmc_bond.saturating_sub(plmc_bond_needed), - Precision::Exact, - ); - if let Err(e) = try_release { - Self::deposit_event(Event::::TransferError { error: e }); - } + let usd_bond_needed = + bid.multiplier.calculate_bonding_requirement(new_ticket_size).map_err(|_| Error::::BadMath)?; + let plmc_bond_needed = plmc_price + .reciprocal() + .ok_or(Error::::BadMath)? + .checked_mul_int(usd_bond_needed) + .ok_or(Error::::BadMath)?; - final_bid.plmc_bond = plmc_bond_needed; + let try_release = T::NativeCurrency::release( + &LockType::Participation(project_id), + &bid.bidder, + bid.plmc_bond.saturating_sub(plmc_bond_needed), + Precision::Exact, + ); + if let Err(e) = try_release { + Self::deposit_event(Event::::TransferError { error: e }); } - let final_ticket_size = final_bid - .final_ct_usd_price - .checked_mul_int(final_bid.final_ct_amount) - .ok_or(Error::::BadMath)?; - final_total_funding_reached_by_bids += final_ticket_size; - bids[bid_index] = final_bid; - Ok(()) - })?; + + bid.plmc_bond = plmc_bond_needed; + } + let final_ticket_size = + bid.final_ct_usd_price.checked_mul_int(bid.final_ct_amount).ok_or(Error::::BadMath)?; + final_total_funding_reached_by_bids += final_ticket_size; + Bids::::insert((project_id, bid.bidder.clone(), bid.id), bid); } // Update storage @@ -2084,12 +2055,14 @@ impl Pallet { let early_evaluator_reward_pot_usd = Perquintill::from_percent(20) * evaluator_fees; let normal_evaluator_reward_pot_usd = Perquintill::from_percent(80) * evaluator_fees; - let early_evaluator_total_bonded_usd = evaluations - .iter() - .fold(BalanceOf::::zero(), |acc, ((_evaluator, _id), evaluation)| acc.saturating_add(evaluation.early_usd_amount)); - let late_evaluator_total_bonded_usd = evaluations - .iter() - .fold(BalanceOf::::zero(), |acc, ((_evaluator, _id), evaluation)| acc.saturating_add(evaluation.late_usd_amount)); + let early_evaluator_total_bonded_usd = + evaluations.iter().fold(BalanceOf::::zero(), |acc, ((_evaluator, _id), evaluation)| { + acc.saturating_add(evaluation.early_usd_amount) + }); + let late_evaluator_total_bonded_usd = + evaluations.iter().fold(BalanceOf::::zero(), |acc, ((_evaluator, _id), evaluation)| { + acc.saturating_add(evaluation.late_usd_amount) + }); let normal_evaluator_total_bonded_usd = early_evaluator_total_bonded_usd.saturating_add(late_evaluator_total_bonded_usd); Ok(RewardInfo { diff --git a/pallets/funding/src/impls.rs b/pallets/funding/src/impls.rs index 86ea7e7ee..070481350 100644 --- a/pallets/funding/src/impls.rs +++ b/pallets/funding/src/impls.rs @@ -228,9 +228,8 @@ enum OperationsLeft { } fn remaining_evaluators_to_reward_or_slash(project_id: T::ProjectIdentifier) -> u64 { - Evaluations::::iter_prefix_values((project_id,)) - .filter(|evaluation| !evaluation.rewarded_or_slashed) - .count() as u64 + Evaluations::::iter_prefix_values((project_id,)).filter(|evaluation| !evaluation.rewarded_or_slashed).count() + as u64 } fn remaining_evaluations(project_id: T::ProjectIdentifier) -> u64 { @@ -238,11 +237,11 @@ fn remaining_evaluations(project_id: T::ProjectIdentifier) -> u64 { } fn remaining_bids_to_release_funds(project_id: T::ProjectIdentifier) -> u64 { - Bids::::iter_prefix_values(project_id).flatten().filter(|bid| !bid.funds_released).count() as u64 + Bids::::iter_prefix_values((project_id,)).filter(|bid| !bid.funds_released).count() as u64 } fn remaining_bids(project_id: T::ProjectIdentifier) -> u64 { - Bids::::iter_prefix_values(project_id).flatten().count() as u64 + Bids::::iter_prefix_values((project_id,)).count() as u64 } fn remaining_contributions_to_release_funds(project_id: T::ProjectIdentifier) -> u64 { @@ -279,7 +278,7 @@ fn remaining_contributions_without_ct_minted(_project_id: T::ProjectI } fn remaining_bids_without_issuer_payout(project_id: T::ProjectIdentifier) -> u64 { - Bids::::iter_prefix_values(project_id).flatten().filter(|bid| !bid.funds_released).count() as u64 + Bids::::iter_prefix_values((project_id,)).filter(|bid| !bid.funds_released).count() as u64 } fn remaining_contributions_without_issuer_payout(project_id: T::ProjectIdentifier) -> u64 { @@ -289,8 +288,7 @@ fn remaining_contributions_without_issuer_payout(project_id: T::Proje fn reward_or_slash_one_evaluation(project_id: T::ProjectIdentifier) -> Result<(Weight, u64), DispatchError> { let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectNotFound)?; let mut project_evaluations = Evaluations::::iter_prefix_values((project_id,)); - let mut remaining_evaluations = - project_evaluations.filter(|evaluation| !evaluation.rewarded_or_slashed); + let mut remaining_evaluations = project_evaluations.filter(|evaluation| !evaluation.rewarded_or_slashed); if let Some(mut evaluation) = remaining_evaluations.next() { match project_details.evaluation_round_info.evaluators_outcome { @@ -350,16 +348,10 @@ fn unbond_one_evaluation(project_id: T::ProjectIdentifier) -> (Weight } fn release_funds_one_bid(project_id: T::ProjectIdentifier) -> (Weight, u64) { - let project_bids: Vec<_> = Bids::::iter_prefix_values(project_id).collect(); - let remaining_bids = project_bids.iter().flatten().filter(|bid| !bid.funds_released).count() as u64; - let maybe_user_bids = project_bids.into_iter().find(|bids| bids.iter().any(|bid| !bid.funds_released)); - - if let Some(mut user_bids) = maybe_user_bids { - let bid = user_bids - .iter_mut() - .find(|bid| !bid.funds_released) - .expect("user_bids can only exist if an item here is found; qed"); + let project_bids = Bids::::iter_prefix_values((project_id,)); + let mut remaining_bids = project_bids.filter(|bid| !bid.funds_released); + if let Some(mut bid) = remaining_bids.next() { match Pallet::::do_release_bid_funds_for( T::PalletId::get().into_account_truncating(), bid.project_id, @@ -376,29 +368,19 @@ fn release_funds_one_bid(project_id: T::ProjectIdentifier) -> (Weight }; bid.funds_released = true; + Bids::::insert((project_id, bid.bidder.clone(), bid.id), bid); - Bids::::insert(project_id, bid.bidder.clone(), user_bids); - - (Weight::zero(), remaining_bids.saturating_sub(1u64)) + (Weight::zero(), remaining_bids.count() as u64) } else { (Weight::zero(), 0u64) } } fn unbond_one_bid(project_id: T::ProjectIdentifier) -> (Weight, u64) { - let project_bids: Vec<_> = Bids::::iter_prefix_values(project_id).collect(); - // let bids_count = project_bids.iter().flatten().count() as u64; - // remove when do_bid_unbond_for is correctly implemented - let bids_count = 0u64; - - let maybe_user_bids = project_bids.into_iter().find(|bids| bids.iter().any(|e| e.funds_released)); - - if let Some(mut user_bids) = maybe_user_bids { - let bid = user_bids - .iter_mut() - .find(|bid| bid.funds_released) - .expect("user_evaluations can only exist if an item here is found; qed"); + let project_bids = Bids::::iter_prefix_values((project_id,)); + let mut remaining_bids = project_bids.filter(|bid| bid.funds_released); + if let Some(mut bid) = remaining_bids.next() { match Pallet::::do_bid_unbond_for( T::PalletId::get().into_account_truncating(), bid.project_id, @@ -413,7 +395,7 @@ fn unbond_one_bid(project_id: T::ProjectIdentifier) -> (Weight, u64) error: e, }), }; - (Weight::zero(), bids_count.saturating_sub(1u64)) + (Weight::zero(), remaining_bids.count() as u64) } else { (Weight::zero(), 0u64) } @@ -519,23 +501,11 @@ fn mint_ct_for_one_contribution(_project_id: T::ProjectIdentifier) -> } fn issuer_funding_payout_one_bid(project_id: T::ProjectIdentifier) -> (Weight, u64) { - let project_bids: Vec<_> = Bids::::iter_prefix_values(project_id).collect(); + let project_bids = Bids::::iter_prefix_values((project_id,)); - // let remaining_bids = project_bids - // .iter() - // .flatten() - // .filter(|bid| !bid.funds_released) - // .count() as u64; - let remaining_bids = 0u64; - - let maybe_user_bids = project_bids.into_iter().find(|bids| bids.iter().any(|bid| !bid.funds_released)); - - if let Some(mut user_bids) = maybe_user_bids { - let bid = user_bids - .iter_mut() - .find(|bid| !bid.funds_released) - .expect("user_bids can only exist if an item here is found; qed"); + let mut remaining_bids = project_bids.filter(|bid| !bid.funds_released); + if let Some(mut bid) = remaining_bids.next() { match Pallet::::do_payout_bid_funds_for( T::PalletId::get().into_account_truncating(), bid.project_id, @@ -553,9 +523,9 @@ fn issuer_funding_payout_one_bid(project_id: T::ProjectIdentifier) -> bid.funds_released = true; - Bids::::insert(project_id, bid.bidder.clone(), user_bids); + Bids::::insert((project_id, bid.bidder.clone(), bid.id), bid); - (Weight::zero(), remaining_bids.saturating_sub(1u64)) + (Weight::zero(), remaining_bids.count() as u64) } else { (Weight::zero(), 0u64) } diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 0a3aa40a0..5a91abe69 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -458,14 +458,14 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn bids)] /// StorageMap containing the bids for each project and user - pub type Bids = StorageDoubleMap< + pub type Bids = StorageNMap< _, - Blake2_128Concat, - T::ProjectIdentifier, - Blake2_128Concat, - AccountIdOf, - BoundedVec, T::MaxBidsPerUser>, - ValueQuery, + ( + NMapKey, + NMapKey>, + NMapKey>, + ), + BidInfoOf, >; #[pallet::storage] diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index 80bc9c5c1..c80af0261 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -507,8 +507,7 @@ impl TestEnvironment { self.ext_env.borrow_mut().execute_with(|| { // total amount of contributions for this user for this project stored in the mapping let contribution_total: ::Balance = - Bids::::get(project_id, user.clone()) - .iter() + Bids::::iter_prefix_values((project_id, user.clone())) .map(|c| c.funding_asset_amount_locked) .sum(); assert_eq!( @@ -899,12 +898,11 @@ impl<'a> CommunityFundingProject<'a> { let project_metadata = self.get_project_metadata(); let project_details = self.get_project_details(); let project_id = self.get_project_id(); - let project_bids = self.in_ext(|| Bids::::iter_prefix(project_id).collect::>()); - let flattened_bids = project_bids.into_iter().map(|bid| bid.1).flatten().collect::>(); + let project_bids = self.in_ext(|| Bids::::iter_prefix_values((project_id,)).collect::>()); assert!(matches!(project_details.weighted_average_price, Some(_)), "Weighted average price should exist"); for filter in bid_expectations { - let _found_bid = flattened_bids.iter().find(|bid| filter.matches_bid(&bid)).unwrap(); + let _found_bid = project_bids.iter().find(|bid| filter.matches_bid(&bid)).unwrap(); } // Remaining CTs are updated @@ -2318,7 +2316,8 @@ mod auction_round_success { for bid in included_bids { let pid = auctioning_project.get_project_id(); - let stored_bids = auctioning_project.in_ext(|| FundingModule::bids(pid, bid.bidder)); + let mut stored_bids = + auctioning_project.in_ext(|| Bids::::iter_prefix_values((pid, bid.bidder.clone()))); let desired_bid = BidInfoFilter { project_id: Some(pid), bidder: Some(bid.bidder), @@ -2329,14 +2328,15 @@ mod auction_round_success { }; assert!( - stored_bids.iter().any(|bid| desired_bid.matches_bid(&bid)), + test_env.in_ext(|| stored_bids.any(|bid| desired_bid.matches_bid(&bid))), "Stored bid does not match the given filter" ) } for bid in excluded_bids { let pid = auctioning_project.get_project_id(); - let stored_bids = auctioning_project.in_ext(|| FundingModule::bids(pid, bid.bidder)); + let mut stored_bids = + auctioning_project.in_ext(|| Bids::::iter_prefix_values((pid, bid.bidder.clone()))); let desired_bid = BidInfoFilter { project_id: Some(pid), bidder: Some(bid.bidder), @@ -2346,7 +2346,7 @@ mod auction_round_success { ..Default::default() }; assert!( - stored_bids.iter().any(|bid| desired_bid.matches_bid(&bid)), + test_env.in_ext(|| stored_bids.any(|bid| desired_bid.matches_bid(&bid))), "Stored bid does not match the given filter" ); } @@ -2441,7 +2441,8 @@ mod auction_round_success { let community_funding_project = CommunityFundingProject::new_with(&test_env, project, issuer, evaluations, bids); let project_id = community_funding_project.project_id; - let bidder_2_bid = test_env.in_ext(|| Bids::::get(project_id, BIDDER_2))[0]; + let bidder_2_bid = + test_env.in_ext(|| Bids::::iter_prefix_values((project_id, BIDDER_2)).next().unwrap()); assert_eq!(bidder_2_bid.final_ct_usd_price.checked_mul_int(US_DOLLAR).unwrap(), 17_6_666_666_666); } } @@ -2532,7 +2533,7 @@ mod auction_round_failure { auctioning_project.bid_for_users(bids).expect("Bids should pass"); test_env.ext_env.borrow_mut().execute_with(|| { - let mut stored_bids = FundingModule::bids(project_id, DAVE); + let mut stored_bids = Bids::::iter_prefix_values((project_id, DAVE)).collect::>(); assert_eq!(stored_bids.len(), 4); stored_bids.sort(); assert_eq!(stored_bids[0].original_ct_usd_price, 5_u128.into()); From 4e1bf98b30e4da99b6b3a51afd1db6bcee53a165 Mon Sep 17 00:00:00 2001 From: Juan Ignacio RIos Date: Mon, 31 Jul 2023 09:52:20 +0200 Subject: [PATCH 3/4] chore: fix tests --- pallets/funding/src/impls.rs | 5 ++++- pallets/funding/src/tests.rs | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pallets/funding/src/impls.rs b/pallets/funding/src/impls.rs index 070481350..272e914c6 100644 --- a/pallets/funding/src/impls.rs +++ b/pallets/funding/src/impls.rs @@ -395,7 +395,10 @@ fn unbond_one_bid(project_id: T::ProjectIdentifier) -> (Weight, u64) error: e, }), }; - (Weight::zero(), remaining_bids.count() as u64) + // (Weight::zero(), remaining_bids.count() as u64) + // TODO: Remove this below when function is implemented + (Weight::zero(), 0u64) + } else { (Weight::zero(), 0u64) } diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index c80af0261..41363f624 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -2514,6 +2514,7 @@ mod auction_round_failure { TestBid::new(DAVE, 10_000 * USDT_UNIT, 2_u128.into(), None, AcceptedFundingAsset::USDT), // 20k TestBid::new(DAVE, 12_000 * USDT_UNIT, 8_u128.into(), None, AcceptedFundingAsset::USDT), // 96k TestBid::new(DAVE, 15_000 * USDT_UNIT, 5_u128.into(), None, AcceptedFundingAsset::USDT), // 75k + // Bid with lowest PLMC bonded gets dropped TestBid::new(DAVE, 1_000 * USDT_UNIT, 7_u128.into(), None, AcceptedFundingAsset::USDT), // 7k TestBid::new(DAVE, 20_000 * USDT_UNIT, 5_u128.into(), None, AcceptedFundingAsset::USDT), // 100k ]; @@ -2536,9 +2537,9 @@ mod auction_round_failure { let mut stored_bids = Bids::::iter_prefix_values((project_id, DAVE)).collect::>(); assert_eq!(stored_bids.len(), 4); stored_bids.sort(); - assert_eq!(stored_bids[0].original_ct_usd_price, 5_u128.into()); + assert_eq!(stored_bids[0].original_ct_usd_price, 2_u128.into()); assert_eq!(stored_bids[1].original_ct_usd_price, 5_u128.into()); - assert_eq!(stored_bids[2].original_ct_usd_price, 7_u128.into()); + assert_eq!(stored_bids[2].original_ct_usd_price, 5_u128.into()); assert_eq!(stored_bids[3].original_ct_usd_price, 8_u128.into()); }); } From 2de86b9ce205e5c41a3d147ed8c53c7d996f8853 Mon Sep 17 00:00:00 2001 From: Juan Ignacio RIos Date: Mon, 31 Jul 2023 13:19:13 +0200 Subject: [PATCH 4/4] feat: converted Contributions map --- pallets/funding/src/functions.rs | 127 ++++++++++--------------------- pallets/funding/src/impls.rs | 89 ++++++++-------------- pallets/funding/src/lib.rs | 14 ++-- pallets/funding/src/tests.rs | 19 ++--- pallets/sandbox/src/lib.rs | 3 +- 5 files changed, 87 insertions(+), 165 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index aa3eca08f..085dc93ce 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -997,10 +997,11 @@ impl Pallet { ) -> Result<(), DispatchError> { // * Get variables * let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectNotFound)?; - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectInfoNotFound)?; + let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectInfoNotFound)?; let now = >::block_number(); let contribution_id = Self::next_contribution_id(); - let mut existing_contributions = Contributions::::get(project_id, contributor.clone()); + let mut existing_contributions = + Contributions::::iter_prefix_values((project_id, contributor.clone())).collect::>(); let ct_usd_price = project_details.weighted_average_price.ok_or(Error::::AuctionNotStarted)?; let mut ticket_size = ct_usd_price.checked_mul_int(token_amount).ok_or(Error::::BadMath)?; @@ -1066,46 +1067,47 @@ impl Pallet { // * Update storage * // Try adding the new contribution to the system - match existing_contributions.try_push(new_contribution.clone()) { - Ok(_) => { - Self::try_plmc_participation_lock(&contributor, project_id, required_plmc_bond)?; - Self::try_funding_asset_hold(&contributor, project_id, required_funding_asset_transfer, asset_id)?; - }, - Err(_) => { - // The contributions are sorted by highest PLMC bond. If the contribution vector for the user is full, we drop the lowest/last item - let lowest_plmc_bond = - existing_contributions.iter().last().ok_or(Error::::ImpossibleState)?.plmc_bond; - - ensure!(new_contribution.plmc_bond > lowest_plmc_bond, Error::::ContributionTooLow); + if existing_contributions.len() < T::MaxContributionsPerUser::get() as usize { + Self::try_plmc_participation_lock(&contributor, project_id, required_plmc_bond)?; + Self::try_funding_asset_hold(&contributor, project_id, required_funding_asset_transfer, asset_id)?; + } else { + let lowest_contribution = existing_contributions + .iter() + .min_by_key(|contribution| contribution.plmc_bond) + .ok_or(Error::::ImpossibleState)?; - Self::release_last_funding_item_in_vec( - &contributor, - project_id, - asset_id, - &mut existing_contributions, - |x| x.plmc_bond, - |x| x.funding_asset_amount, - )?; + ensure!(new_contribution.plmc_bond > lowest_contribution.plmc_bond, Error::::ContributionTooLow); - Self::try_plmc_participation_lock(&contributor, project_id, required_plmc_bond)?; + T::NativeCurrency::release( + &LockType::Participation(project_id), + &lowest_contribution.contributor, + lowest_contribution.plmc_bond, + Precision::Exact, + )?; + T::FundingCurrency::transfer( + asset_id, + &Self::fund_account_id(project_id), + &lowest_contribution.contributor, + lowest_contribution.funding_asset_amount, + Preservation::Expendable, + )?; + Contributions::::remove((project_id, lowest_contribution.contributor.clone(), lowest_contribution.id)); - Self::try_funding_asset_hold(&contributor, project_id, required_funding_asset_transfer, asset_id)?; + Self::try_plmc_participation_lock(&contributor, project_id, required_plmc_bond)?; + Self::try_funding_asset_hold(&contributor, project_id, required_funding_asset_transfer, asset_id)?; - // This should never fail, since we just removed an item from the vector - existing_contributions.try_push(new_contribution).map_err(|_| Error::::ImpossibleState)?; - }, + project_details.remaining_contribution_tokens = + project_details.remaining_contribution_tokens.saturating_add(lowest_contribution.ct_amount); + project_details.funding_amount_reached = + project_details.funding_amount_reached.saturating_sub(lowest_contribution.usd_contribution_amount); } - existing_contributions.sort_by_key(|contribution| Reverse(contribution.plmc_bond)); - - Contributions::::set(project_id, contributor.clone(), existing_contributions); + Contributions::::insert((project_id, contributor.clone(), contribution_id), new_contribution.clone()); NextContributionId::::set(contribution_id.saturating_add(One::one())); - ProjectsDetails::::mutate(project_id, |maybe_project| { - if let Some(project) = maybe_project { - project.remaining_contribution_tokens = remaining_cts_after_purchase; - project.funding_amount_reached = project.funding_amount_reached.saturating_add(ticket_size); - } - }); + + project_details.remaining_contribution_tokens = project_details.remaining_contribution_tokens.saturating_sub(new_contribution.ct_amount); + project_details.funding_amount_reached = project_details.funding_amount_reached.saturating_add(new_contribution.usd_contribution_amount); + ProjectsDetails::::insert(project_id, project_details); // If no CTs remain, end the funding phase if remaining_cts_after_purchase == 0u32.into() { @@ -1271,9 +1273,8 @@ impl Pallet { ) -> Result<(), DispatchError> { // * Get variables * let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectNotFound)?; - let contributions = Contributions::::get(project_id, &claimer); + let contributions = Contributions::::iter_prefix_values((project_id, &claimer)); let now = >::block_number(); - let mut updated_contributions = vec![]; // * Validity checks * // TODO: PLMC-133. Check the right credential status @@ -1313,7 +1314,7 @@ impl Pallet { unbond_amount, Precision::Exact, )?; - updated_contributions.push(contribution); + Contributions::::insert((project_id, &claimer, contribution.id), contribution.clone()); // * Emit events * Self::deposit_event(Event::BondReleased { @@ -1324,14 +1325,6 @@ impl Pallet { }) } - // * Update storage * - // TODO: PLMC-147. For now only the participants of the Community Round can claim their tokens - // Obviously also the participants of the Auction Round should be able to claim their tokens - // In theory this should never fail, since we insert the same number of contributions as before - let updated_contributions: BoundedVec, T::MaxContributionsPerUser> = - updated_contributions.try_into().map_err(|_| Error::::TooManyContributions)?; - Contributions::::insert(project_id, &claimer, updated_contributions); - Ok(()) } @@ -1352,16 +1345,10 @@ impl Pallet { ) -> Result<(), DispatchError> { // * Get variables * let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectNotFound)?; - let contributions = Contributions::::get(project_id, &claimer); + let contributions = Contributions::::iter_prefix_values((project_id, &claimer)); let now = >::block_number(); - let mut updated_contributions = vec![]; // * Validity checks * - // TODO: PLMC-133. Check the right credential status - // ensure!( - // T::HandleMembers::is_in(&MemberRole::Issuer, &issuer), - // Error::::NotAuthorized - // ); ensure!(project_details.status == ProjectStatus::FundingSuccessful, Error::::CannotClaimYet); // TODO: PLMC-160. Check the flow of the final_price if the final price discovery during the Auction Round fails @@ -1385,11 +1372,7 @@ impl Pallet { } contribution.ct_vesting_period = ct_vesting; - // * Update storage * - // TODO: Should we mint here, or should the full mint happen to the treasury and then do transfers from there? - // Mint the funds for the user - T::ContributionTokenCurrency::mint_into(project_id, &claimer, mint_amount)?; - updated_contributions.push(contribution); + Contributions::::insert((project_id, contribution.contributor.clone(), contribution.id), contribution.clone()); // * Emit events * Self::deposit_event(Event::ContributionTokenMinted { @@ -1401,13 +1384,6 @@ impl Pallet { } // * Update storage * - // TODO: PLMC-147. For now only the participants of the Community Round can claim their tokens - // Obviously also the participants of the Auction Round should be able to claim their tokens - // In theory this should never fail, since we insert the same number of contributions as before - let updated_contributions: BoundedVec, T::MaxContributionsPerUser> = - updated_contributions.try_into().map_err(|_| Error::::TooManyContributions)?; - Contributions::::insert(project_id, &claimer, updated_contributions); - Ok(()) } @@ -1995,27 +1971,6 @@ impl Pallet { Ok(()) } - // TODO(216): use the hold interface of the fungibles::MutateHold once its implemented on pallet_assets. - pub fn release_last_funding_item_in_vec( - who: &T::AccountId, - project_id: T::ProjectIdentifier, - asset_id: AssetIdOf, - vec: &mut BoundedVec, - plmc_getter: impl Fn(&I) -> BalanceOf, - funding_asset_getter: impl Fn(&I) -> BalanceOf, - ) -> Result<(), DispatchError> { - let fund_account = Self::fund_account_id(project_id); - let last_item = vec.swap_remove(vec.len() - 1); - let plmc_amount = plmc_getter(&last_item); - let funding_asset_amount = funding_asset_getter(&last_item); - - T::NativeCurrency::release(&LockType::Participation(project_id), &who, plmc_amount, Precision::Exact)?; - - T::FundingCurrency::transfer(asset_id, &fund_account, &who, funding_asset_amount, Preservation::Expendable)?; - - Ok(()) - } - pub fn calculate_fees(project_id: T::ProjectIdentifier) -> Result, DispatchError> { let funding_reached = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectNotFound)?.funding_amount_reached; diff --git a/pallets/funding/src/impls.rs b/pallets/funding/src/impls.rs index 272e914c6..226662cd2 100644 --- a/pallets/funding/src/impls.rs +++ b/pallets/funding/src/impls.rs @@ -245,14 +245,13 @@ fn remaining_bids(project_id: T::ProjectIdentifier) -> u64 { } fn remaining_contributions_to_release_funds(project_id: T::ProjectIdentifier) -> u64 { - Contributions::::iter_prefix_values(project_id) - .flatten() + Contributions::::iter_prefix_values((project_id,)) .filter(|contribution| !contribution.funds_released) .count() as u64 } fn remaining_contributions(project_id: T::ProjectIdentifier) -> u64 { - Contributions::::iter_prefix_values(project_id).flatten().count() as u64 + Contributions::::iter_prefix_values((project_id,)).count() as u64 } fn remaining_bids_without_plmc_vesting(_project_id: T::ProjectIdentifier) -> u64 { @@ -282,7 +281,7 @@ fn remaining_bids_without_issuer_payout(project_id: T::ProjectIdentif } fn remaining_contributions_without_issuer_payout(project_id: T::ProjectIdentifier) -> u64 { - Contributions::::iter_prefix_values(project_id).flatten().filter(|bid| !bid.funds_released).count() as u64 + Contributions::::iter_prefix_values((project_id,)).filter(|bid| !bid.funds_released).count() as u64 } fn reward_or_slash_one_evaluation(project_id: T::ProjectIdentifier) -> Result<(Weight, u64), DispatchError> { @@ -370,7 +369,9 @@ fn release_funds_one_bid(project_id: T::ProjectIdentifier) -> (Weight bid.funds_released = true; Bids::::insert((project_id, bid.bidder.clone(), bid.id), bid); - (Weight::zero(), remaining_bids.count() as u64) + // (Weight::zero(), remaining_bids.count() as u64) + // TODO: delete this when function is implemented + (Weight::zero(), 0u64) } else { (Weight::zero(), 0u64) } @@ -398,31 +399,16 @@ fn unbond_one_bid(project_id: T::ProjectIdentifier) -> (Weight, u64) // (Weight::zero(), remaining_bids.count() as u64) // TODO: Remove this below when function is implemented (Weight::zero(), 0u64) - } else { (Weight::zero(), 0u64) } } fn release_funds_one_contribution(project_id: T::ProjectIdentifier) -> (Weight, u64) { - let project_contributions: Vec<_> = Contributions::::iter_prefix_values(project_id).collect(); - // let remaining_contributions = project_contributions - // .iter() - // .flatten() - // .filter(|contribution| !contribution.funds_released) - // .count() as u64; - // remove when do_release_contribution_funds_for is correctly implemented - let remaining_contributions = 0u64; - let maybe_user_contributions = project_contributions - .into_iter() - .find(|contributions| contributions.iter().any(|contribution| !contribution.funds_released)); - - if let Some(mut user_contributions) = maybe_user_contributions { - let contribution = user_contributions - .iter_mut() - .find(|contribution| !contribution.funds_released) - .expect("user_contributions can only exist if an item here is found; qed"); + let project_contributions = Contributions::::iter_prefix_values((project_id,)); + let mut remaining_contributions = project_contributions.filter(|contribution| !contribution.funds_released); + if let Some(mut contribution) = remaining_contributions.next() { match Pallet::::do_release_contribution_funds_for( T::PalletId::get().into_account_truncating(), contribution.project_id, @@ -440,29 +426,22 @@ fn release_funds_one_contribution(project_id: T::ProjectIdentifier) - contribution.funds_released = true; - Contributions::::insert(project_id, contribution.contributor.clone(), user_contributions); + Contributions::::insert((project_id, contribution.contributor.clone(), contribution.id), contribution); + // (Weight::zero(), remaining_contributions.count() as u64) + // TODO: Remove this when function is implemented + (Weight::zero(), 0u64) - (Weight::zero(), remaining_contributions.saturating_sub(1u64)) } else { (Weight::zero(), 0u64) } } fn unbond_one_contribution(project_id: T::ProjectIdentifier) -> (Weight, u64) { - let project_contributions: Vec<_> = Contributions::::iter_prefix_values(project_id).collect(); + let project_contributions = Contributions::::iter_prefix_values((project_id,)).collect::>(); - // let contributions_count = project_contributions.iter().flatten().count() as u64; - let contributions_count = 0u64; - - let maybe_user_contributions = - project_contributions.into_iter().find(|contributions| contributions.iter().any(|e| e.funds_released)); - - if let Some(mut user_contributions) = maybe_user_contributions { - let contribution = user_contributions - .iter_mut() - .find(|contribution| contribution.funds_released) - .expect("user_evaluations can only exist if an item here is found; qed"); + let mut remaining_contributions = project_contributions.clone().into_iter().filter(|contribution| contribution.funds_released); + if let Some(mut contribution) = remaining_contributions.next() { match Pallet::::do_contribution_unbond_for( T::PalletId::get().into_account_truncating(), contribution.project_id, @@ -477,7 +456,9 @@ fn unbond_one_contribution(project_id: T::ProjectIdentifier) -> (Weig error: e, }), }; - (Weight::zero(), contributions_count.saturating_sub(1u64)) + // (Weight::zero(), (project_contributions.len() as u64).saturating_sub(One::one())) + // TODO: Remove this when function is implemented + (Weight::zero(), 0u64) } else { (Weight::zero(), 0u64) } @@ -528,32 +509,21 @@ fn issuer_funding_payout_one_bid(project_id: T::ProjectIdentifier) -> Bids::::insert((project_id, bid.bidder.clone(), bid.id), bid); - (Weight::zero(), remaining_bids.count() as u64) + // (Weight::zero(), remaining_bids.count() as u64) + // TODO: Remove this when function is implemented + (Weight::zero(), 0u64) } else { (Weight::zero(), 0u64) } } fn issuer_funding_payout_one_contribution(project_id: T::ProjectIdentifier) -> (Weight, u64) { - let project_contributions: Vec<_> = Contributions::::iter_prefix_values(project_id).collect(); - - // let remaining_contributions = project_contributions - // .iter() - // .flatten() - // .filter(|contribution| !contribution.funds_released) - // .count() as u64; - let remaining_contributions = 0u64; + let project_contributions = Contributions::::iter_prefix_values((project_id,)); - let maybe_user_contributions = project_contributions - .into_iter() - .find(|contributions| contributions.iter().any(|contribution| !contribution.funds_released)); - - if let Some(mut user_contributions) = maybe_user_contributions { - let contribution = user_contributions - .iter_mut() - .find(|contribution| !contribution.funds_released) - .expect("user_contributions can only exist if an item here is found; qed"); + let mut remaining_contributions = project_contributions + .filter(|contribution| !contribution.funds_released); + if let Some(mut contribution) = remaining_contributions.next() { match Pallet::::do_payout_contribution_funds_for( T::PalletId::get().into_account_truncating(), contribution.project_id, @@ -571,9 +541,12 @@ fn issuer_funding_payout_one_contribution(project_id: T::ProjectIdent contribution.funds_released = true; - Contributions::::insert(project_id, contribution.contributor.clone(), user_contributions); + Contributions::::insert((project_id, contribution.contributor.clone(), contribution.id), contribution); + + // (Weight::zero(), remaining_contributions.count() as u64) + // TODO: remove this when function is implemented + (Weight::zero(), 0u64) - (Weight::zero(), remaining_contributions.saturating_sub(1u64)) } else { (Weight::zero(), 0u64) } diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 5a91abe69..cb2dba1a2 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -471,14 +471,14 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn contributions)] /// Contributions made during the Community and Remainder round. i.e token buys - pub type Contributions = StorageDoubleMap< + pub type Contributions = StorageNMap< _, - Blake2_128Concat, - T::ProjectIdentifier, - Blake2_128Concat, - AccountIdOf, - BoundedVec, T::MaxContributionsPerUser>, - ValueQuery, + ( + NMapKey, + NMapKey>, + NMapKey>, + ), + ContributionInfoOf, >; #[pallet::event] diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index 41363f624..4e3669618 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -527,8 +527,7 @@ impl TestEnvironment { ) { for (user, expected_amount, _token_id) in correct_funds { self.ext_env.borrow_mut().execute_with(|| { - Contributions::::get(project_id, user.clone()) - .iter() + Contributions::::iter_prefix_values((project_id, user.clone())) .find(|c| c.funding_asset_amount == expected_amount) .expect("Contribution not found in storage"); }); @@ -2515,7 +2514,7 @@ mod auction_round_failure { TestBid::new(DAVE, 12_000 * USDT_UNIT, 8_u128.into(), None, AcceptedFundingAsset::USDT), // 96k TestBid::new(DAVE, 15_000 * USDT_UNIT, 5_u128.into(), None, AcceptedFundingAsset::USDT), // 75k // Bid with lowest PLMC bonded gets dropped - TestBid::new(DAVE, 1_000 * USDT_UNIT, 7_u128.into(), None, AcceptedFundingAsset::USDT), // 7k + TestBid::new(DAVE, 1_000 * USDT_UNIT, 7_u128.into(), None, AcceptedFundingAsset::USDT), // 7k TestBid::new(DAVE, 20_000 * USDT_UNIT, 5_u128.into(), None, AcceptedFundingAsset::USDT), // 100k ]; @@ -2790,7 +2789,7 @@ mod community_round_success { let project_id = community_funding_project.get_project_id(); let bob_total_contributions: BalanceOf = community_funding_project - .in_ext(|| Contributions::::get(project_id, BOB).iter().map(|c| c.funding_asset_amount).sum()); + .in_ext(|| Contributions::::iter_prefix_values((project_id, BOB)).map(|c| c.funding_asset_amount).sum()); let total_contributed = calculate_contributed_funding_asset_spent(contributions.clone(), token_price) .iter() @@ -2967,8 +2966,7 @@ mod community_round_success { ::NativeCurrency::balance_on_hold(&LockType::Participation(project_id), &CONTRIBUTOR) }); let statemint_asset_contributions_stored = project.in_ext(|| { - Contributions::::get(project.project_id, CONTRIBUTOR) - .iter() + Contributions::::iter_prefix_values((project.project_id, CONTRIBUTOR)) .map(|c| c.funding_asset_amount) .sum::>() }); @@ -3004,8 +3002,7 @@ mod community_round_success { ::NativeCurrency::balance_on_hold(&LockType::Participation(project_id), &CONTRIBUTOR) }); let new_statemint_asset_contributions_stored = project.in_ext(|| { - Contributions::::get(project.project_id, CONTRIBUTOR) - .iter() + Contributions::::iter_prefix_values((project.project_id, CONTRIBUTOR)) .map(|c| c.funding_asset_amount) .sum::>() }); @@ -3070,8 +3067,7 @@ mod community_round_success { ::NativeCurrency::balance_on_hold(&LockType::Participation(project_id), &CONTRIBUTOR) }); let statemint_asset_contributions_stored = project.in_ext(|| { - Contributions::::get(project.project_id, CONTRIBUTOR) - .iter() + Contributions::::iter_prefix_values((project.project_id, CONTRIBUTOR)) .map(|c| c.funding_asset_amount) .sum::>() }); @@ -3107,8 +3103,7 @@ mod community_round_success { ::NativeCurrency::balance_on_hold(&LockType::Participation(project_id), &CONTRIBUTOR) }); let new_statemint_asset_contributions_stored = project.in_ext(|| { - Contributions::::get(project.project_id, CONTRIBUTOR) - .iter() + Contributions::::iter_prefix_values((project.project_id, CONTRIBUTOR)) .map(|c| c.funding_asset_amount) .sum::>() }); diff --git a/pallets/sandbox/src/lib.rs b/pallets/sandbox/src/lib.rs index 8c430fabf..6d12efb26 100644 --- a/pallets/sandbox/src/lib.rs +++ b/pallets/sandbox/src/lib.rs @@ -44,8 +44,7 @@ pub mod pallet { // Calculate how much funding was done already let project_contributions: ::Balance = - funding::Contributions::::iter_prefix_values(project_id) - .flatten() + funding::Contributions::::iter_prefix_values((project_id,)) .fold(0u64.into(), |total_tokens_bought, contribution| { total_tokens_bought + contribution.funding_asset_amount });