From abdead66c1de7509e817bed3c70884d75304c9e0 Mon Sep 17 00:00:00 2001 From: Juan Ignacio RIos Date: Thu, 27 Jul 2023 17:12:46 +0200 Subject: [PATCH 01/13] wip --- pallets/funding/src/functions.rs | 65 ++++++++++++++++++-- pallets/funding/src/impls.rs | 22 ++++++- pallets/funding/src/lib.rs | 58 ++++++++++++----- pallets/funding/src/mock.rs | 5 ++ pallets/funding/src/tests.rs | 7 ++- pallets/funding/src/types.rs | 8 +-- pallets/parachain-staking/src/tests.rs | 10 +-- runtimes/shared-configuration/src/funding.rs | 3 +- runtimes/shared-configuration/src/lib.rs | 9 +++ runtimes/standalone/src/lib.rs | 3 + runtimes/testnet/src/lib.rs | 15 ++--- 11 files changed, 161 insertions(+), 44 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index 085dc93ce..49ccd6f5e 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -29,8 +29,8 @@ use frame_support::{ traits::{ fungible::{InspectHold, MutateHold as FungibleMutateHold}, fungibles::{metadata::Mutate as MetadataMutate, Create, Mutate as FungiblesMutate}, - tokens::{Precision, Preservation}, - Get, Len, + tokens::{Fortitude, Precision, Preservation, Restriction}, + Get, Len }, }; @@ -596,10 +596,10 @@ impl Pallet { // * Update Storage * if funding_ratio <= Perquintill::from_percent(33u64) { - project_details.evaluation_round_info.evaluators_outcome = EvaluatorsOutcome::Slashed(vec![]); + project_details.evaluation_round_info.evaluators_outcome = EvaluatorsOutcome::Slashed; Self::make_project_funding_fail(project_id, project_details, FailureReason::TargetNotReached, 1u32.into()) } else if funding_ratio <= Perquintill::from_percent(75u64) { - project_details.evaluation_round_info.evaluators_outcome = EvaluatorsOutcome::Slashed(vec![]); + project_details.evaluation_round_info.evaluators_outcome = EvaluatorsOutcome::Slashed; project_details.status = ProjectStatus::AwaitingProjectDecision; Self::add_to_update_store( now + T::ManualAcceptanceDuration::get() + 1u32.into(), @@ -1428,7 +1428,7 @@ impl Pallet { Ok(()) } - pub fn do_evaluation_reward( + pub fn do_evaluation_reward_payout_for( caller: AccountIdOf, project_id: T::ProjectIdentifier, evaluator: AccountIdOf, @@ -1485,6 +1485,61 @@ impl Pallet { Ok(()) } + pub fn do_evaluation_slash_for( + caller: AccountIdOf, + project_id: T::ProjectIdentifier, + evaluator: AccountIdOf, + evaluation_id: StorageItemIdOf, + ) -> Result<(), DispatchError> { + // * Get variables * + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectInfoNotFound)?; + let slash_percentage = T::EvaluatorSlash::get(); + let treasury_account = T::TreasuryAccount::get(); + + 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)?; + + // * Validity checks * + ensure!( + evaluation.rewarded_or_slashed == false && + matches!(project_details.evaluation_round_info.evaluators_outcome, EvaluatorsOutcome::Slashed), + Error::::NotAllowed + ); + + // * Calculate variables * + let slashed_amount = slash_percentage * evaluation.current_plmc_bond; + + // * Update storage * + evaluation.rewarded_or_slashed = true; + + T::NativeCurrency::transfer_on_hold( + &LockType::Evaluation(project_id), + &evaluator, + &treasury_account, + slashed_amount, + Precision::Exact, + Restriction::Free, + Fortitude::Force, + )?; + + evaluation.current_plmc_bond = evaluation.current_plmc_bond.saturating_sub(slashed_amount); + Evaluations::::set(project_id, evaluator.clone(), user_evaluations); + + // * Emit events * + Self::deposit_event(Event::::EvaluationSlashed { + project_id, + evaluator: evaluator.clone(), + id: evaluation_id, + amount: slashed_amount, + caller, + }); + + Ok(()) + } + pub fn do_release_bid_funds_for( _caller: AccountIdOf, _project_id: T::ProjectIdentifier, diff --git a/pallets/funding/src/impls.rs b/pallets/funding/src/impls.rs index 226662cd2..095bf0892 100644 --- a/pallets/funding/src/impls.rs +++ b/pallets/funding/src/impls.rs @@ -292,14 +292,14 @@ fn reward_or_slash_one_evaluation(project_id: T::ProjectIdentifier) - if let Some(mut evaluation) = remaining_evaluations.next() { match project_details.evaluation_round_info.evaluators_outcome { EvaluatorsOutcome::Rewarded(_) => { - match Pallet::::do_evaluation_reward( + match Pallet::::do_evaluation_reward_payout_for( T::PalletId::get().into_account_truncating(), evaluation.project_id, evaluation.evaluator.clone(), evaluation.id, ) { Ok(_) => (), - Err(e) => Pallet::::deposit_event(Event::EvaluationRewardOrSlashFailed { + Err(e) => Pallet::::deposit_event(Event::EvaluationRewardFailed { project_id: evaluation.project_id, evaluator: evaluation.evaluator.clone(), id: evaluation.id, @@ -307,7 +307,23 @@ fn reward_or_slash_one_evaluation(project_id: T::ProjectIdentifier) - }), }; }, - _ => (), + EvaluatorsOutcome::Slashed => { + match Pallet::::do_evaluation_slash_for( + T::PalletId::get().into_account_truncating(), + evaluation.project_id, + evaluation.evaluator.clone(), + evaluation.id, + ) { + Ok(_) => (), + Err(e) => Pallet::::deposit_event(Event::EvaluationSlashFailed { + project_id: evaluation.project_id, + evaluator: evaluation.evaluator.clone(), + id: evaluation.id, + error: e, + }), + }; + }, + _ => {}, } // if the evaluation outcome failed, we still want to flag it as rewarded or slashed. Otherwise the automatic diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index cb2dba1a2..d2090858a 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -225,13 +225,13 @@ pub type AssetIdOf = <::FundingCurrency as fungibles::Inspect<::AccountId>>::AssetId; pub type RewardInfoOf = RewardInfo>; -pub type EvaluatorsOutcomeOf = EvaluatorsOutcome, BalanceOf>; +pub type EvaluatorsOutcomeOf = EvaluatorsOutcome>; pub type ProjectMetadataOf = ProjectMetadata>, BalanceOf, PriceOf, AccountIdOf, HashOf>; pub type ProjectDetailsOf = ProjectDetails, BlockNumberOf, PriceOf, BalanceOf, EvaluationRoundInfoOf>; -pub type EvaluationRoundInfoOf = EvaluationRoundInfo, BalanceOf>; +pub type EvaluationRoundInfoOf = EvaluationRoundInfo>; pub type VestingOf = Vesting, BalanceOf>; pub type EvaluationInfoOf = EvaluationInfo, ProjectIdOf, AccountIdOf, BalanceOf, BlockNumberOf>; @@ -383,6 +383,10 @@ pub mod pallet { type ManualAcceptanceDuration: Get; /// For now we expect 4 days from acceptance to settlement due to MiCA regulations. type SuccessToSettlementTime: Get; + + type EvaluatorSlash: Get; + + type TreasuryAccount: Get>; } #[pallet::storage] @@ -546,7 +550,13 @@ pub mod pallet { }, /// A transfer of tokens failed, but because it was done inside on_initialize it cannot be solved. TransferError { error: DispatchError }, - EvaluationRewardOrSlashFailed { + EvaluationRewardFailed { + project_id: ProjectIdOf, + evaluator: AccountIdOf, + id: StorageItemIdOf, + error: DispatchError, + }, + EvaluationSlashFailed { project_id: ProjectIdOf, evaluator: AccountIdOf, id: StorageItemIdOf, @@ -595,6 +605,13 @@ pub mod pallet { amount: BalanceOf, caller: AccountIdOf, }, + EvaluationSlashed { + project_id: ProjectIdOf, + evaluator: AccountIdOf, + id: StorageItemIdOf, + amount: BalanceOf, + caller: AccountIdOf, + }, } #[pallet::error] @@ -787,18 +804,6 @@ pub mod pallet { Self::do_evaluate(evaluator, project_id, usd_amount) } - /// Release evaluation-bonded PLMC when a project finishes its funding round. - #[pallet::weight(T::WeightInfo::evaluation_unbond_for())] - pub fn evaluation_unbond_for( - origin: OriginFor, - bond_id: T::StorageItemId, - project_id: T::ProjectIdentifier, - evaluator: AccountIdOf, - ) -> DispatchResult { - let releaser = ensure_signed(origin)?; - Self::do_evaluation_unbond_for(releaser, project_id, evaluator, bond_id) - } - /// Bid for a project in the Auction round #[pallet::weight(T::WeightInfo::bid())] pub fn bid( @@ -833,6 +838,29 @@ pub mod pallet { Self::do_contribute(contributor, project_id, amount, multiplier, asset) } + /// Release evaluation-bonded PLMC when a project finishes its funding round. + #[pallet::weight(T::WeightInfo::evaluation_unbond_for())] + pub fn evaluation_unbond_for( + origin: OriginFor, + bond_id: T::StorageItemId, + project_id: T::ProjectIdentifier, + evaluator: AccountIdOf, + ) -> DispatchResult { + let releaser = ensure_signed(origin)?; + Self::do_evaluation_unbond_for(releaser, project_id, evaluator, bond_id) + } + + #[pallet::weight(Weight::from_parts(0, 0))] + pub fn evaluation_reward_payout_for( + origin: OriginFor, + bond_id: T::StorageItemId, + project_id: T::ProjectIdentifier, + evaluator: AccountIdOf, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + Self::do_evaluation_reward_payout_for(caller, project_id, evaluator, bond_id) + } + /// Unbond some plmc from a contribution, after a step in the vesting period has passed. pub fn vested_plmc_bid_unbond_for( origin: OriginFor, diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs index 21bc8defe..211bcef1d 100644 --- a/pallets/funding/src/mock.rs +++ b/pallets/funding/src/mock.rs @@ -214,6 +214,9 @@ parameter_types! { (Percent::from_percent(6), u128::MAX), // Making it max signifies the last bracket ]; pub EarlyEvaluationThreshold: Percent = Percent::from_percent(10); + pub EvaluatorSlash: Percent = Percent::from_percent(20); + pub TreasuryAccount: AccountId = AccountId::from(69u64); + } use frame_support::traits::WithdrawReasons; @@ -248,6 +251,7 @@ impl pallet_funding::Config for TestRuntime { type EnglishAuctionDuration = EnglishAuctionDuration; type EvaluationDuration = EvaluationDuration; type EvaluationSuccessThreshold = EarlyEvaluationThreshold; + type EvaluatorSlash = EvaluatorSlash; type FeeBrackets = FeeBrackets; type FundingCurrency = StatemintAssets; type ManualAcceptanceDuration = ManualAcceptanceDuration; @@ -270,6 +274,7 @@ impl pallet_funding::Config for TestRuntime { type StringLimit = ConstU32<64>; type SuccessToSettlementTime = SuccessToSettlementTime; type Vesting = Vesting; + type TreasuryAccount = TreasuryAccount; type WeightInfo = (); } diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index 4e3669618..6a30bbf5d 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -1912,7 +1912,6 @@ mod evaluation_round_success { remainder_funding_project.end_funding(); test_env.advance_time(10).unwrap(); - let deets = remainder_funding_project.get_project_details(); let post_unbond_amounts: UserToPLMCBalance = prev_reserved_plmc.iter().map(|(evaluator, _amount)| (*evaluator, Zero::zero())).collect(); @@ -4116,6 +4115,7 @@ mod test_helper_functions { mod misc_features { use super::*; use crate::UpdateType::{CommunityFundingStart, RemainderFundingStart}; + use sp_arithmetic::Percent; #[test] fn remove_from_update_store_works() { @@ -4138,8 +4138,11 @@ mod misc_features { }); } - #[allow(dead_code)] + #[test] fn sandbox() { + let percent = Percent::from_percent(120); + let _x = percent * 100u64; + assert!(true) } } diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index 95fb2a0e6..36f60d337 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -540,17 +540,17 @@ pub mod inner_types { } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] - pub struct EvaluationRoundInfo { + pub struct EvaluationRoundInfo { pub total_bonded_usd: Balance, pub total_bonded_plmc: Balance, - pub evaluators_outcome: EvaluatorsOutcome, + pub evaluators_outcome: EvaluatorsOutcome, } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] - pub enum EvaluatorsOutcome { + pub enum EvaluatorsOutcome { Unchanged, Rewarded(RewardInfo), - Slashed(Vec<(AccountId, Balance)>), + Slashed, } #[derive(Default, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] diff --git a/pallets/parachain-staking/src/tests.rs b/pallets/parachain-staking/src/tests.rs index c6f8bc686..113b28859 100644 --- a/pallets/parachain-staking/src/tests.rs +++ b/pallets/parachain-staking/src/tests.rs @@ -4631,7 +4631,7 @@ fn test_delegator_with_deprecated_status_leaving_can_schedule_leave_delegators_a .build() .execute_with(|| { >::mutate(2, |value| { - value.as_mut().map(|mut state| { + value.as_mut().map(|state| { state.status = DelegatorStatus::Leaving(2); }) }); @@ -4659,7 +4659,7 @@ fn test_delegator_with_deprecated_status_leaving_can_cancel_leave_delegators_as_ .build() .execute_with(|| { >::mutate(2, |value| { - value.as_mut().map(|mut state| { + value.as_mut().map(|state| { state.status = DelegatorStatus::Leaving(2); }) }); @@ -4684,7 +4684,7 @@ fn test_delegator_with_deprecated_status_leaving_can_execute_leave_delegators_as .build() .execute_with(|| { >::mutate(2, |value| { - value.as_mut().map(|mut state| { + value.as_mut().map(|state| { state.status = DelegatorStatus::Leaving(2); }) }); @@ -4710,7 +4710,7 @@ fn test_delegator_with_deprecated_status_leaving_cannot_execute_leave_delegators .build() .execute_with(|| { >::mutate(2, |value| { - value.as_mut().map(|mut state| { + value.as_mut().map(|state| { state.status = DelegatorStatus::Leaving(2); }) }); @@ -4959,7 +4959,7 @@ fn test_execute_leave_delegators_with_deprecated_status_leaving_removes_auto_com )); >::mutate(2, |value| { - value.as_mut().map(|mut state| { + value.as_mut().map(|state| { state.status = DelegatorStatus::Leaving(2); }) }); diff --git a/runtimes/shared-configuration/src/funding.rs b/runtimes/shared-configuration/src/funding.rs index e0752f8d3..ac64720df 100644 --- a/runtimes/shared-configuration/src/funding.rs +++ b/runtimes/shared-configuration/src/funding.rs @@ -61,7 +61,7 @@ pub const MANUAL_ACCEPTANCE_DURATION: BlockNumber = 3; pub const MANUAL_ACCEPTANCE_DURATION: BlockNumber = 3 * DAYS; #[cfg(feature = "fast-gov")] -pub const MANUAL_ACCEPTANCE_DURATION: BlockNumber = 4; +pub const SUCCESS_TO_SETTLEMENT_TIME: BlockNumber = 4; #[cfg(not(feature = "fast-gov"))] pub const SUCCESS_TO_SETTLEMENT_TIME: BlockNumber = 4 * DAYS; @@ -88,4 +88,5 @@ parameter_types! { (Percent::from_percent(6), u128::MAX), // Making it max signifies the last bracket ]; pub EarlyEvaluationThreshold: Percent = Percent::from_percent(10); + pub EvaluatorSlash: Percent = Percent::from_percent(20); } diff --git a/runtimes/shared-configuration/src/lib.rs b/runtimes/shared-configuration/src/lib.rs index 01fd24175..9f5d27f4c 100644 --- a/runtimes/shared-configuration/src/lib.rs +++ b/runtimes/shared-configuration/src/lib.rs @@ -26,3 +26,12 @@ pub mod weights; /// Common types pub use parachains_common::{Balance, BlockNumber, DAYS}; + +pub use assets::*; +pub use currency::*; +pub use fee::*; +pub use funding::*; +pub use governance::*; +pub use staking::*; +pub use weights::*; + diff --git a/runtimes/standalone/src/lib.rs b/runtimes/standalone/src/lib.rs index fad1b29ec..18d4825da 100644 --- a/runtimes/standalone/src/lib.rs +++ b/runtimes/standalone/src/lib.rs @@ -352,6 +352,7 @@ parameter_types! { (Percent::from_percent(6), u128::MAX), // Making it max signifies the last bracket ]; pub EarlyEvaluationThreshold: Percent = Percent::from_percent(10); + pub TreasuryAccount: AccountId = AccountId::from([69u8; 32]); } impl pallet_funding::Config for Runtime { @@ -366,6 +367,7 @@ impl pallet_funding::Config for Runtime { type EnglishAuctionDuration = EnglishAuctionDuration; type EvaluationDuration = EvaluationDuration; type EvaluationSuccessThreshold = EarlyEvaluationThreshold; + type EvaluatorSlash = EvaluatorSlash; type FeeBrackets = FeeBrackets; type FundingCurrency = Assets; type ManualAcceptanceDuration = ManualAcceptanceDuration; @@ -388,6 +390,7 @@ impl pallet_funding::Config for Runtime { type SuccessToSettlementTime = SuccessToSettlementTime; type Vesting = Release; type WeightInfo = (); + type TreasuryAccount = TreasuryAccount; } impl pallet_linear_release::Config for Runtime { diff --git a/runtimes/testnet/src/lib.rs b/runtimes/testnet/src/lib.rs index 3a7d9a335..59bb2c944 100644 --- a/runtimes/testnet/src/lib.rs +++ b/runtimes/testnet/src/lib.rs @@ -68,15 +68,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); // Polimec Shared Imports use pallet_funding::BondTypeOf; pub use pallet_parachain_staking; -pub use shared_configuration::{ - assets::*, - currency::{vesting::*, *}, - fee::*, - funding::*, - governance::*, - staking::*, - weights::*, -}; +pub use shared_configuration::*; pub type NegativeImbalanceOf = as Currency<::AccountId>>::NegativeImbalance; @@ -487,6 +479,9 @@ impl pallet_assets::Config for Runtime { type WeightInfo = (); } +parameter_types! { + pub TreasuryAccount: AccountId = [69u8; 32].into(); +} impl pallet_funding::Config for Runtime { type AuctionInitializePeriodDuration = AuctionInitializePeriodDuration; type Balance = Balance; @@ -499,6 +494,7 @@ impl pallet_funding::Config for Runtime { type EnglishAuctionDuration = EnglishAuctionDuration; type EvaluationDuration = EvaluationDuration; type EvaluationSuccessThreshold = EarlyEvaluationThreshold; + type EvaluatorSlash = EvaluatorSlash; type FeeBrackets = FeeBrackets; type FundingCurrency = StatemintAssets; type ManualAcceptanceDuration = ManualAcceptanceDuration; @@ -521,6 +517,7 @@ impl pallet_funding::Config for Runtime { type SuccessToSettlementTime = SuccessToSettlementTime; type Vesting = Vesting; type WeightInfo = (); + type TreasuryAccount = TreasuryAccount; } parameter_types! { From c7115b18f1b3b670a41ddf2c5f1bfe06455f93a2 Mon Sep 17 00:00:00 2001 From: Juan Ignacio RIos Date: Mon, 31 Jul 2023 15:38:15 +0200 Subject: [PATCH 02/13] wip --- pallets/funding/src/functions.rs | 32 +++++----- pallets/funding/src/impls.rs | 69 +++++++++++---------- pallets/funding/src/mock.rs | 2 +- pallets/funding/src/tests.rs | 79 ++++++++++++++++++++++-- runtimes/shared-configuration/src/lib.rs | 1 - runtimes/standalone/src/lib.rs | 2 +- runtimes/testnet/src/lib.rs | 2 +- 7 files changed, 134 insertions(+), 53 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index 49ccd6f5e..fa405e989 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::{Fortitude, Precision, Preservation, Restriction}, - Get, Len + Get, }, }; @@ -774,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: Vec<(StorageItemIdOf, EvaluationInfoOf)> = + let 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 = @@ -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::::iter_prefix_values((project_id, bidder.clone())).collect::>(); + let 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 = @@ -1000,7 +1000,7 @@ impl Pallet { 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 = + let existing_contributions = Contributions::::iter_prefix_values((project_id, contributor.clone())).collect::>(); let ct_usd_price = project_details.weighted_average_price.ok_or(Error::::AuctionNotStarted)?; @@ -1105,8 +1105,10 @@ impl Pallet { Contributions::::insert((project_id, contributor.clone(), contribution_id), new_contribution.clone()); NextContributionId::::set(contribution_id.saturating_add(One::one())); - 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); + 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 @@ -1372,7 +1374,10 @@ impl Pallet { } contribution.ct_vesting_period = ct_vesting; - Contributions::::insert((project_id, contribution.contributor.clone(), contribution.id), contribution.clone()); + Contributions::::insert( + (project_id, contribution.contributor.clone(), contribution.id), + contribution.clone(), + ); // * Emit events * Self::deposit_event(Event::ContributionTokenMinted { @@ -1400,7 +1405,8 @@ impl Pallet { // * Validity checks * ensure!( - released_evaluation.rewarded_or_slashed == true && + (project_details.evaluation_round_info.evaluators_outcome == EvaluatorsOutcomeOf::::Unchanged || + released_evaluation.rewarded_or_slashed == true) && matches!( project_details.status, ProjectStatus::EvaluationFailed | ProjectStatus::FundingFailed | ProjectStatus::FundingSuccessful @@ -1496,11 +1502,9 @@ impl Pallet { let slash_percentage = T::EvaluatorSlash::get(); let treasury_account = T::TreasuryAccount::get(); - 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 user_evaluations = Evaluations::::iter_prefix_values((project_id, evaluator.clone())); + let mut evaluation = + user_evaluations.find(|evaluation| evaluation.id == evaluation_id).ok_or(Error::::EvaluationNotFound)?; // * Validity checks * ensure!( @@ -1526,7 +1530,7 @@ impl Pallet { )?; evaluation.current_plmc_bond = evaluation.current_plmc_bond.saturating_sub(slashed_amount); - Evaluations::::set(project_id, evaluator.clone(), user_evaluations); + Evaluations::::insert((project_id, evaluator.clone(), evaluation.id), evaluation); // * Emit events * Self::deposit_event(Event::::EvaluationSlashed { diff --git a/pallets/funding/src/impls.rs b/pallets/funding/src/impls.rs index 095bf0892..3488a5dab 100644 --- a/pallets/funding/src/impls.rs +++ b/pallets/funding/src/impls.rs @@ -27,10 +27,14 @@ impl DoRemainingOperation for CleanerState { } fn do_one_operation(&mut self, project_id: T::ProjectIdentifier) -> Result { + let evaluators_outcome = ProjectsDetails::::get(project_id) + .ok_or(Error::::ImpossibleState)? + .evaluation_round_info + .evaluators_outcome; match self { CleanerState::Initialized(PhantomData) => { *self = Self::EvaluationRewardOrSlash( - remaining_evaluators_to_reward_or_slash::(project_id), + remaining_evaluators_to_reward_or_slash::(project_id, evaluators_outcome), PhantomData, ); Ok(Weight::zero()) @@ -134,10 +138,15 @@ impl DoRemainingOperation for CleanerState { } fn do_one_operation(&mut self, project_id: T::ProjectIdentifier) -> Result { + let evaluators_outcome = ProjectsDetails::::get(project_id) + .ok_or(Error::::ImpossibleState)? + .evaluation_round_info + .evaluators_outcome; + match self { CleanerState::Initialized(PhantomData::) => { *self = CleanerState::EvaluationRewardOrSlash( - remaining_evaluators_to_reward_or_slash::(project_id), + remaining_evaluators_to_reward_or_slash::(project_id, evaluators_outcome), PhantomData::, ); Ok(Weight::zero()) @@ -222,14 +231,16 @@ impl DoRemainingOperation for CleanerState { } } -enum OperationsLeft { - Some(u64), - None, -} - -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 +fn remaining_evaluators_to_reward_or_slash( + project_id: T::ProjectIdentifier, + outcome: EvaluatorsOutcomeOf, +) -> u64 { + if outcome == EvaluatorsOutcomeOf::::Unchanged { + 0u64 + } else { + Evaluations::::iter_prefix_values((project_id,)).filter(|evaluation| !evaluation.rewarded_or_slashed).count() + as u64 + } } fn remaining_evaluations(project_id: T::ProjectIdentifier) -> u64 { @@ -245,9 +256,8 @@ 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,)) - .filter(|contribution| !contribution.funds_released) - .count() as u64 + Contributions::::iter_prefix_values((project_id,)).filter(|contribution| !contribution.funds_released).count() + as u64 } fn remaining_contributions(project_id: T::ProjectIdentifier) -> u64 { @@ -286,10 +296,10 @@ 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 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() { + if let Some(evaluation) = remaining_evaluations.next() { match project_details.evaluation_round_info.evaluators_outcome { EvaluatorsOutcome::Rewarded(_) => { match Pallet::::do_evaluation_reward_payout_for( @@ -323,25 +333,24 @@ fn reward_or_slash_one_evaluation(project_id: T::ProjectIdentifier) - }), }; }, - _ => {}, + _ => { + #[cfg(debug_assertions)] + unreachable!("EvaluatorsOutcome should be either Slashed or Rewarded if this function is called") + }, } - // 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(), evaluation.id), evaluation); - - Ok((Weight::zero(), remaining_evaluations.count() as u64)) + let remaining = remaining_evaluations.count() as u64; + Ok((Weight::zero(), remaining)) } else { Ok((Weight::zero(), 0u64)) } } fn unbond_one_evaluation(project_id: T::ProjectIdentifier) -> (Weight, u64) { - let mut project_evaluations = Evaluations::::iter_prefix_values((project_id,)).collect::>(); + let 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) { + if let Some(evaluation) = project_evaluations.iter().next() { match Pallet::::do_evaluation_unbond_for( T::PalletId::get().into_account_truncating(), evaluation.project_id, @@ -397,7 +406,7 @@ fn unbond_one_bid(project_id: T::ProjectIdentifier) -> (Weight, u64) 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() { + if let Some(bid) = remaining_bids.next() { match Pallet::::do_bid_unbond_for( T::PalletId::get().into_account_truncating(), bid.project_id, @@ -446,7 +455,6 @@ fn release_funds_one_contribution(project_id: T::ProjectIdentifier) - // (Weight::zero(), remaining_contributions.count() as u64) // TODO: Remove this when function is implemented (Weight::zero(), 0u64) - } else { (Weight::zero(), 0u64) } @@ -455,9 +463,10 @@ fn release_funds_one_contribution(project_id: T::ProjectIdentifier) - fn unbond_one_contribution(project_id: T::ProjectIdentifier) -> (Weight, u64) { let project_contributions = Contributions::::iter_prefix_values((project_id,)).collect::>(); - let mut remaining_contributions = project_contributions.clone().into_iter().filter(|contribution| contribution.funds_released); + let mut remaining_contributions = + project_contributions.clone().into_iter().filter(|contribution| contribution.funds_released); - if let Some(mut contribution) = remaining_contributions.next() { + if let Some(contribution) = remaining_contributions.next() { match Pallet::::do_contribution_unbond_for( T::PalletId::get().into_account_truncating(), contribution.project_id, @@ -536,8 +545,7 @@ fn issuer_funding_payout_one_bid(project_id: T::ProjectIdentifier) -> fn issuer_funding_payout_one_contribution(project_id: T::ProjectIdentifier) -> (Weight, u64) { let project_contributions = Contributions::::iter_prefix_values((project_id,)); - let mut remaining_contributions = project_contributions - .filter(|contribution| !contribution.funds_released); + 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( @@ -562,7 +570,6 @@ fn issuer_funding_payout_one_contribution(project_id: T::ProjectIdent // (Weight::zero(), remaining_contributions.count() as u64) // TODO: remove this when function is implemented (Weight::zero(), 0u64) - } else { (Weight::zero(), 0u64) } diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs index 211bcef1d..fdb476b3b 100644 --- a/pallets/funding/src/mock.rs +++ b/pallets/funding/src/mock.rs @@ -273,8 +273,8 @@ impl pallet_funding::Config for TestRuntime { type StorageItemId = u128; type StringLimit = ConstU32<64>; type SuccessToSettlementTime = SuccessToSettlementTime; - type Vesting = Vesting; type TreasuryAccount = TreasuryAccount; + type Vesting = Vesting; type WeightInfo = (); } diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index 6a30bbf5d..c15e1d3f8 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -38,7 +38,7 @@ use frame_support::{ use helper_functions::*; use crate::traits::BondingRequirementCalculation; -use sp_arithmetic::traits::Zero; +use sp_arithmetic::{traits::Zero, Percent, Perquintill}; use sp_runtime::{DispatchError, Either}; use sp_std::marker::PhantomData; use std::{cell::RefCell, iter::zip}; @@ -1179,6 +1179,18 @@ impl<'a> FinishedProject<'a> { finished_project } + + fn from_funding_reached(test_env: &'a TestEnvironment, percent: u64) -> Self { + let project_metadata = default_project(test_env.get_new_nonce()); + let min_price = project_metadata.minimum_price; + let usd_to_reach = Perquintill::from_percent(percent) * + (project_metadata.minimum_price.checked_mul_int(project_metadata.total_allocation_size).unwrap()); + let evaluations = default_evaluations(); + let bids = generate_bids_from_total_usd(Percent::from_percent(50u8) * usd_to_reach, min_price); + let contributions = + generate_contributions_from_total_usd(Percent::from_percent(50u8) * usd_to_reach, min_price); + FinishedProject::new_with(test_env, project_metadata, ISSUER, evaluations, bids, contributions, vec![]) + } } mod defaults { @@ -1634,6 +1646,14 @@ pub mod helper_functions { ); }); } + + pub fn slash_evaluator_balances(mut balances: UserToPLMCBalance) -> UserToPLMCBalance { + let slash_percentage = ::EvaluatorSlash::get(); + for (acc, balance) in balances.iter_mut() { + *balance -= slash_percentage * *balance; + } + balances + } } #[cfg(test)] @@ -1960,7 +1980,7 @@ mod evaluation_round_success { let increased_amounts = merge_subtract_mappings_by_user(post_free_plmc, vec![prev_free_plmc]); - assert_eq!(increased_amounts, calculate_evaluation_plmc_spent(evaluations)) + assert_eq!(increased_amounts, slash_evaluator_balances(calculate_evaluation_plmc_spent(evaluations))) } } @@ -2787,8 +2807,9 @@ mod community_round_success { .expect("The Buyer should be able to buy multiple times"); let project_id = community_funding_project.get_project_id(); - let bob_total_contributions: BalanceOf = community_funding_project - .in_ext(|| Contributions::::iter_prefix_values((project_id, BOB)).map(|c| c.funding_asset_amount).sum()); + let bob_total_contributions: BalanceOf = community_funding_project.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() @@ -3470,6 +3491,7 @@ mod funding_end { use super::*; use sp_arithmetic::{Percent, Perquintill}; use std::assert_matches::assert_matches; + use testing_macros::call_and_is_ok; #[test] fn automatic_fail_less_eq_33_percent() { @@ -3666,6 +3688,45 @@ mod funding_end { Cleaner::Success(CleanerState::Finished(PhantomData)) ); } + + #[test] + fn evaluators_get_slashed_funding_accepted() { + let test_env = TestEnvironment::new(); + let finished_project = FinishedProject::from_funding_reached(&test_env, 43u64); + let project_id = finished_project.get_project_id(); + assert_eq!(finished_project.get_project_details().status, ProjectStatus::AwaitingProjectDecision); + + let old_evaluation_locked_plmc = test_env.get_all_reserved_plmc_balances(LockType::Evaluation(project_id)); + let old_rest_plmc = merge_subtract_mappings_by_user(test_env.get_all_free_plmc_balances(), vec![old_evaluation_locked_plmc.clone()]); + + call_and_is_ok!( + test_env, + FundingModule::do_decide_project_outcome( + ISSUER, + finished_project.project_id, + FundingOutcomeDecision::AcceptFunding + ) + ); + test_env.advance_time(1u64).unwrap(); + assert_eq!(finished_project.get_project_details().status, ProjectStatus::FundingSuccessful); + test_env.advance_time(::SuccessToSettlementTime::get() + 10u64).unwrap(); + assert_matches!(finished_project.get_project_details().cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + + + + // assert_eq!(slashed_evaluations, slash_evaluator_balances(old_evaluations)) + + + + } + + fn evaluators_get_slashed_funding_funding_rejected() { + assert!(false) + } + + fn evaluators_get_slashed_funding_failed() { + assert!(false) + } } #[cfg(test)] @@ -4158,4 +4219,14 @@ mod testing_macros { }; } pub(crate) use assert_close_enough; + + macro_rules! call_and_is_ok { + ($env: expr, $call: expr) => { + $env.ext_env.borrow_mut().execute_with(|| { + let result = $call; + assert!(result.is_ok(), "Call failed: {:?}", result); + }); + }; + } + pub(crate) use call_and_is_ok; } diff --git a/runtimes/shared-configuration/src/lib.rs b/runtimes/shared-configuration/src/lib.rs index 9f5d27f4c..f1017f6a2 100644 --- a/runtimes/shared-configuration/src/lib.rs +++ b/runtimes/shared-configuration/src/lib.rs @@ -34,4 +34,3 @@ pub use funding::*; pub use governance::*; pub use staking::*; pub use weights::*; - diff --git a/runtimes/standalone/src/lib.rs b/runtimes/standalone/src/lib.rs index 18d4825da..3687db8d3 100644 --- a/runtimes/standalone/src/lib.rs +++ b/runtimes/standalone/src/lib.rs @@ -388,9 +388,9 @@ impl pallet_funding::Config for Runtime { type StorageItemId = u128; type StringLimit = ConstU32<64>; type SuccessToSettlementTime = SuccessToSettlementTime; + type TreasuryAccount = TreasuryAccount; type Vesting = Release; type WeightInfo = (); - type TreasuryAccount = TreasuryAccount; } impl pallet_linear_release::Config for Runtime { diff --git a/runtimes/testnet/src/lib.rs b/runtimes/testnet/src/lib.rs index 59bb2c944..6bc6ffb3e 100644 --- a/runtimes/testnet/src/lib.rs +++ b/runtimes/testnet/src/lib.rs @@ -515,9 +515,9 @@ impl pallet_funding::Config for Runtime { type StorageItemId = u128; type StringLimit = ConstU32<64>; type SuccessToSettlementTime = SuccessToSettlementTime; + type TreasuryAccount = TreasuryAccount; type Vesting = Vesting; type WeightInfo = (); - type TreasuryAccount = TreasuryAccount; } parameter_types! { From 556fd7fbe80f01f8b34ec3b5a724f3b926039ed3 Mon Sep 17 00:00:00 2001 From: Juan Ignacio RIos Date: Mon, 31 Jul 2023 16:15:44 +0200 Subject: [PATCH 03/13] feat: first slash test passing --- pallets/funding/src/tests.rs | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index c15e1d3f8..d377a1808 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -3696,8 +3696,17 @@ mod funding_end { let project_id = finished_project.get_project_id(); assert_eq!(finished_project.get_project_details().status, ProjectStatus::AwaitingProjectDecision); - let old_evaluation_locked_plmc = test_env.get_all_reserved_plmc_balances(LockType::Evaluation(project_id)); - let old_rest_plmc = merge_subtract_mappings_by_user(test_env.get_all_free_plmc_balances(), vec![old_evaluation_locked_plmc.clone()]); + let old_evaluation_locked_plmc: UserToPLMCBalance = test_env + .get_all_reserved_plmc_balances(LockType::Evaluation(project_id)) + .into_iter() + .filter(|(_acc, amount)| amount > &Zero::zero()) + .collect::>(); + + let evaluators = old_evaluation_locked_plmc.iter().map(|(acc, _)| acc.clone()).collect::>(); + + let old_participation_locked_plmc = + test_env.get_reserved_plmc_balances_for(evaluators.clone(), LockType::Participation(project_id)); + let old_free_plmc: UserToPLMCBalance = test_env.get_free_plmc_balances_for(evaluators.clone()); call_and_is_ok!( test_env, @@ -3710,20 +3719,29 @@ mod funding_end { test_env.advance_time(1u64).unwrap(); assert_eq!(finished_project.get_project_details().status, ProjectStatus::FundingSuccessful); test_env.advance_time(::SuccessToSettlementTime::get() + 10u64).unwrap(); - assert_matches!(finished_project.get_project_details().cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); - - - - // assert_eq!(slashed_evaluations, slash_evaluator_balances(old_evaluations)) + assert_matches!( + finished_project.get_project_details().cleanup, + Cleaner::Success(CleanerState::Finished(PhantomData)) + ); + let slashed_evaluation_locked_plmc = slash_evaluator_balances(old_evaluation_locked_plmc); + let expected_evaluator_free_balances = merge_add_mappings_by_user(vec![ + slashed_evaluation_locked_plmc, + old_participation_locked_plmc, + old_free_plmc, + ]); + let actual_evaluator_free_balances = test_env.get_free_plmc_balances_for(evaluators.clone()); + assert_eq!(actual_evaluator_free_balances, expected_evaluator_free_balances); } + #[test] fn evaluators_get_slashed_funding_funding_rejected() { assert!(false) } + #[test] fn evaluators_get_slashed_funding_failed() { assert!(false) } From a64732bb53c569bc632d709971624e474c94ff78 Mon Sep 17 00:00:00 2001 From: Juan Ignacio RIos Date: Mon, 31 Jul 2023 16:30:32 +0200 Subject: [PATCH 04/13] feat: all slashing tests passing --- pallets/funding/src/tests.rs | 80 ++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index d377a1808..e8ea607f6 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -1649,7 +1649,7 @@ pub mod helper_functions { pub fn slash_evaluator_balances(mut balances: UserToPLMCBalance) -> UserToPLMCBalance { let slash_percentage = ::EvaluatorSlash::get(); - for (acc, balance) in balances.iter_mut() { + for (_acc, balance) in balances.iter_mut() { *balance -= slash_percentage * *balance; } balances @@ -3738,12 +3738,86 @@ mod funding_end { #[test] fn evaluators_get_slashed_funding_funding_rejected() { - assert!(false) + let test_env = TestEnvironment::new(); + let finished_project = FinishedProject::from_funding_reached(&test_env, 56u64); + let project_id = finished_project.get_project_id(); + assert_eq!(finished_project.get_project_details().status, ProjectStatus::AwaitingProjectDecision); + + let old_evaluation_locked_plmc: UserToPLMCBalance = test_env + .get_all_reserved_plmc_balances(LockType::Evaluation(project_id)) + .into_iter() + .filter(|(_acc, amount)| amount > &Zero::zero()) + .collect::>(); + + let evaluators = old_evaluation_locked_plmc.iter().map(|(acc, _)| acc.clone()).collect::>(); + + let old_participation_locked_plmc = + test_env.get_reserved_plmc_balances_for(evaluators.clone(), LockType::Participation(project_id)); + let old_free_plmc: UserToPLMCBalance = test_env.get_free_plmc_balances_for(evaluators.clone()); + + call_and_is_ok!( + test_env, + FundingModule::do_decide_project_outcome( + ISSUER, + finished_project.project_id, + FundingOutcomeDecision::RejectFunding + ) + ); + test_env.advance_time(1u64).unwrap(); + assert_eq!(finished_project.get_project_details().status, ProjectStatus::FundingFailed); + test_env.advance_time(::SuccessToSettlementTime::get() + 10u64).unwrap(); + assert_matches!( + finished_project.get_project_details().cleanup, + Cleaner::Failure(CleanerState::Finished(PhantomData)) + ); + + let slashed_evaluation_locked_plmc = slash_evaluator_balances(old_evaluation_locked_plmc); + let expected_evaluator_free_balances = merge_add_mappings_by_user(vec![ + slashed_evaluation_locked_plmc, + old_participation_locked_plmc, + old_free_plmc, + ]); + + let actual_evaluator_free_balances = test_env.get_free_plmc_balances_for(evaluators.clone()); + + assert_eq!(actual_evaluator_free_balances, expected_evaluator_free_balances); } #[test] fn evaluators_get_slashed_funding_failed() { - assert!(false) + let test_env = TestEnvironment::new(); + let finished_project = FinishedProject::from_funding_reached(&test_env, 24u64); + let project_id = finished_project.get_project_id(); + assert_eq!(finished_project.get_project_details().status, ProjectStatus::FundingFailed); + + let old_evaluation_locked_plmc: UserToPLMCBalance = test_env + .get_all_reserved_plmc_balances(LockType::Evaluation(project_id)) + .into_iter() + .filter(|(_acc, amount)| amount > &Zero::zero()) + .collect::>(); + + let evaluators = old_evaluation_locked_plmc.iter().map(|(acc, _)| acc.clone()).collect::>(); + + let old_participation_locked_plmc = + test_env.get_reserved_plmc_balances_for(evaluators.clone(), LockType::Participation(project_id)); + let old_free_plmc: UserToPLMCBalance = test_env.get_free_plmc_balances_for(evaluators.clone()); + + test_env.advance_time(::SuccessToSettlementTime::get() + 10u64).unwrap(); + assert_matches!( + finished_project.get_project_details().cleanup, + Cleaner::Failure(CleanerState::Finished(PhantomData)) + ); + + let slashed_evaluation_locked_plmc = slash_evaluator_balances(old_evaluation_locked_plmc); + let expected_evaluator_free_balances = merge_add_mappings_by_user(vec![ + slashed_evaluation_locked_plmc, + old_participation_locked_plmc, + old_free_plmc, + ]); + + let actual_evaluator_free_balances = test_env.get_free_plmc_balances_for(evaluators.clone()); + + assert_eq!(actual_evaluator_free_balances, expected_evaluator_free_balances); } } From 83cb8d603561e5ba28bb1d3be4a351a3e17a514b Mon Sep 17 00:00:00 2001 From: Juan Ignacio RIos Date: Mon, 31 Jul 2023 17:15:29 +0200 Subject: [PATCH 05/13] feat(232): TDD approach. Test written, impl missing --- pallets/funding/src/functions.rs | 3 ++- pallets/funding/src/tests.rs | 39 ++++++++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index fa405e989..e34d85fb3 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -1514,7 +1514,8 @@ impl Pallet { ); // * Calculate variables * - let slashed_amount = slash_percentage * evaluation.current_plmc_bond; + // We need to make sure that the current plmc bond is always >= than the slash amount. + let slashed_amount = slash_percentage * evaluation.original_plmc_bond; // * Update storage * evaluation.rewarded_or_slashed = true; diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index e8ea607f6..f93a017dd 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -3157,11 +3157,11 @@ mod community_round_success { let ct_price = contributing_project.get_project_details().weighted_average_price.unwrap(); let already_bonded_plmc = calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])[0].1; - let necessary_plmc_for_bid = calculate_contributed_plmc_spent(vec![contribution], ct_price)[0].1; - let necessary_usdt_for_bid = calculate_contributed_funding_asset_spent(vec![contribution], ct_price); + let necessary_plmc_for_contribution = calculate_contributed_plmc_spent(vec![contribution], ct_price)[0].1; + let necessary_usdt_for_contribution = calculate_contributed_funding_asset_spent(vec![contribution], ct_price); - test_env.mint_plmc_to(vec![(evaluator_contributor, necessary_plmc_for_bid - already_bonded_plmc)]); - test_env.mint_statemint_asset_to(necessary_usdt_for_bid); + test_env.mint_plmc_to(vec![(evaluator_contributor, necessary_plmc_for_contribution - already_bonded_plmc)]); + test_env.mint_statemint_asset_to(necessary_usdt_for_contribution); contributing_project.buy_for_retail_users(vec![contribution]).unwrap(); } @@ -3235,6 +3235,37 @@ mod community_round_success { }); assert_eq!(evaluation_bond, 0); } + + #[test] + fn evaluator_cannot_use_slash_reserve_for_contributing() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let mut evaluations = default_evaluations(); + let evaluator_contributor = 69; + let evaluation_amount = 420 * US_DOLLAR; + let contribution = + TestContribution::new(evaluator_contributor, 600 * ASSET_UNIT, None, AcceptedFundingAsset::USDT); + evaluations.push((evaluator_contributor, evaluation_amount)); + let bids = default_bids(); + + let contributing_project = CommunityFundingProject::new_with(&test_env, project, issuer, evaluations, bids); + let project_id = contributing_project.get_project_id(); + let ct_price = contributing_project.get_project_details().weighted_average_price.unwrap(); + let necessary_plmc_for_contribution = calculate_contributed_plmc_spent(vec![contribution], ct_price)[0].1; + assert!(necessary_plmc_for_contribution > calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])[0].1); + let necessary_usdt_for_contribution = calculate_contributed_funding_asset_spent(vec![contribution], ct_price); + + test_env.mint_plmc_to(vec![(evaluator_contributor, necessary_plmc_for_contribution)]); + test_env.mint_statemint_asset_to(necessary_usdt_for_contribution); + + let slash_reserve = slash_evaluator_balances(calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])); + contributing_project.buy_for_retail_users(vec![contribution]).unwrap(); + + test_env.do_reserved_plmc_assertions(slash_reserve, LockType::Evaluation(project_id)); + + + } } #[cfg(test)] From 84b7da76a16b376e912fc10838f31923ec6b2357 Mon Sep 17 00:00:00 2001 From: Juan Ignacio RIos Date: Mon, 31 Jul 2023 18:08:16 +0200 Subject: [PATCH 06/13] wip --- pallets/funding/src/functions.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index e34d85fb3..e41970f15 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -1998,9 +1998,26 @@ impl Pallet { amount: BalanceOf, ) -> Result<(), DispatchError> { // Check if the user has already locked tokens in the evaluation period - let evaluation_bonded = ::NativeCurrency::balance_on_hold(&LockType::Evaluation(project_id), who); + let user_evaluations = Evaluations::::iter_prefix_values((project_id, who.clone())); + + let mut to_convert = amount; + for mut evaluation in user_evaluations { + if to_convert == Zero::zero() {break} + let slash_deposit = ::EvaluatorSlash::get() * evaluation.original_plmc_bond; + let available_to_convert = evaluation.current_plmc_bond.saturating_sub(slash_deposit); + let converted = to_convert.min(available_to_convert); + evaluation.current_plmc_bond = evaluation.current_plmc_bond.saturating_sub(converted); + Evaluations::::insert((project_id, who.clone(), evaluation.id), evaluation); + T::NativeCurrency::transfer_on_hold( + &LockType::Evaluation(project_id), + who, + converted, + Precision::Exact, + ) + + } - let new_amount_to_lock = amount.saturating_sub(evaluation_bonded); + let new_amount_to_lock = amount.saturating_sub(evaluation_lock_transfer); let evaluation_bonded_to_change_lock = amount.saturating_sub(new_amount_to_lock); T::NativeCurrency::release( From ad55dda3c5479431272ceff54eb20de37c77da14 Mon Sep 17 00:00:00 2001 From: Juan Ignacio RIos Date: Wed, 2 Aug 2023 13:49:03 +0200 Subject: [PATCH 07/13] wip --- pallets/funding/src/functions.rs | 29 +++++++++-------------------- pallets/funding/src/tests.rs | 26 ++++++++++++++++++-------- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index e41970f15..b0fe2992f 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -2002,33 +2002,22 @@ impl Pallet { let mut to_convert = amount; for mut evaluation in user_evaluations { - if to_convert == Zero::zero() {break} + if to_convert == Zero::zero() { + break + } let slash_deposit = ::EvaluatorSlash::get() * evaluation.original_plmc_bond; let available_to_convert = evaluation.current_plmc_bond.saturating_sub(slash_deposit); let converted = to_convert.min(available_to_convert); evaluation.current_plmc_bond = evaluation.current_plmc_bond.saturating_sub(converted); Evaluations::::insert((project_id, who.clone(), evaluation.id), evaluation); - T::NativeCurrency::transfer_on_hold( - &LockType::Evaluation(project_id), - who, - converted, - Precision::Exact, - ) - + T::NativeCurrency::release(&LockType::Evaluation(project_id), who, converted, Precision::Exact) + .map_err(|_| Error::::ImpossibleState)?; + T::NativeCurrency::hold(&LockType::Participation(project_id), who, converted) + .map_err(|_| Error::::ImpossibleState)?; + to_convert = to_convert.saturating_sub(converted) } - let new_amount_to_lock = amount.saturating_sub(evaluation_lock_transfer); - let evaluation_bonded_to_change_lock = amount.saturating_sub(new_amount_to_lock); - - T::NativeCurrency::release( - &LockType::Evaluation(project_id), - who, - evaluation_bonded_to_change_lock, - Precision::Exact, - ) - .map_err(|_| Error::::ImpossibleState)?; - - T::NativeCurrency::hold(&LockType::Participation(project_id), who, amount) + T::NativeCurrency::hold(&LockType::Participation(project_id), who, to_convert) .map_err(|_| Error::::InsufficientBalance)?; Ok(()) diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index f93a017dd..19a6b9cb9 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -829,8 +829,13 @@ impl<'a> CommunityFundingProject<'a> { let prev_funding_asset_balances = test_env.get_free_statemint_asset_balances_for(asset_id, bidders); let plmc_evaluation_deposits: UserToPLMCBalance = calculate_evaluation_plmc_spent(evaluations.clone()); let plmc_bid_deposits: UserToPLMCBalance = calculate_auction_plmc_spent(bids.clone()); + let participation_usable_evaluation_deposits = plmc_evaluation_deposits + .clone() + .into_iter() + .map(|(acc, amount)| (acc, amount.saturating_sub(::EvaluatorSlash::get() * amount))) + .collect::(); let necessary_plmc_mint = - merge_subtract_mappings_by_user(plmc_bid_deposits.clone(), vec![plmc_evaluation_deposits]); + merge_subtract_mappings_by_user(plmc_bid_deposits.clone(), vec![participation_usable_evaluation_deposits]); let total_plmc_participation_locked = plmc_bid_deposits; let plmc_existential_deposits: UserToPLMCBalance = bids.iter().map(|bid| (bid.bidder, get_ed())).collect::<_>(); let funding_asset_deposits = calculate_auction_funding_asset_spent(bids.clone()); @@ -1469,7 +1474,8 @@ pub mod helper_functions { output } - // Mappings should be sorted based on their account id, ascending. + // 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<(AccountIdOf, I)>, subtract_mappings: Vec, I)>>, @@ -1493,7 +1499,8 @@ pub mod helper_functions { break }, (None, Some(_)) => { - output.extend_from_slice(&map[j..]); + // uncomment this if we want to keep unmatched mappings on the substractor + // output.extend_from_slice(&map[j..]); break }, (Some((acc_i, val_i)), Some((acc_j, val_j))) => @@ -1505,7 +1512,8 @@ pub mod helper_functions { output.push(old_output[i]); i += 1; } else { - output.push(map[j]); + // uncomment to keep unmatched maps + // output.push(map[j]); j += 1; }, } @@ -3253,18 +3261,20 @@ mod community_round_success { let project_id = contributing_project.get_project_id(); let ct_price = contributing_project.get_project_details().weighted_average_price.unwrap(); let necessary_plmc_for_contribution = calculate_contributed_plmc_spent(vec![contribution], ct_price)[0].1; - assert!(necessary_plmc_for_contribution > calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])[0].1); + assert!( + necessary_plmc_for_contribution > + calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])[0].1 + ); let necessary_usdt_for_contribution = calculate_contributed_funding_asset_spent(vec![contribution], ct_price); test_env.mint_plmc_to(vec![(evaluator_contributor, necessary_plmc_for_contribution)]); test_env.mint_statemint_asset_to(necessary_usdt_for_contribution); - let slash_reserve = slash_evaluator_balances(calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])); + let slash_reserve = + slash_evaluator_balances(calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])); contributing_project.buy_for_retail_users(vec![contribution]).unwrap(); test_env.do_reserved_plmc_assertions(slash_reserve, LockType::Evaluation(project_id)); - - } } From ad21486f54c68ac16e47f92c2a67a1fe687df05a Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Wed, 2 Aug 2023 15:10:51 +0200 Subject: [PATCH 08/13] feat: evaluation locks can never go below slashable amount. Tested --- pallets/funding/src/functions.rs | 2 +- pallets/funding/src/tests.rs | 145 ++++++++++++++----------------- 2 files changed, 68 insertions(+), 79 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index b0fe2992f..fb9d6b3fb 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -27,7 +27,7 @@ use frame_support::{ ensure, pallet_prelude::DispatchError, traits::{ - fungible::{InspectHold, MutateHold as FungibleMutateHold}, + fungible::{MutateHold as FungibleMutateHold}, fungibles::{metadata::Mutate as MetadataMutate, Create, Mutate as FungiblesMutate}, tokens::{Fortitude, Precision, Preservation, Restriction}, Get, diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index 19a6b9cb9..15d035085 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -1502,6 +1502,7 @@ pub mod helper_functions { // uncomment this if we want to keep unmatched mappings on the substractor // output.extend_from_slice(&map[j..]); break + }, (Some((acc_i, val_i)), Some((acc_j, val_j))) => if acc_i == acc_j { @@ -1664,7 +1665,6 @@ pub mod helper_functions { } } -#[cfg(test)] mod creation_round_success { use super::*; @@ -1720,7 +1720,6 @@ mod creation_round_success { } } -#[cfg(test)] mod creation_round_failure { use super::*; @@ -1802,7 +1801,6 @@ mod creation_round_failure { } } -#[cfg(test)] mod evaluation_round_success { use super::*; use sp_arithmetic::Perquintill; @@ -1992,7 +1990,6 @@ mod evaluation_round_success { } } -#[cfg(test)] mod evaluation_round_failure { use super::*; @@ -2061,7 +2058,6 @@ mod evaluation_round_failure { } } -#[cfg(test)] mod auction_round_success { use super::*; @@ -2108,10 +2104,11 @@ mod auction_round_success { let bidding_project = AuctioningProject::new_with(&test_env, project, issuer, evaluations); let already_bonded_plmc = calculate_evaluation_plmc_spent(vec![(evaluator_bidder, evaluation_amount)])[0].1; + let usable_evaluation_plmc = already_bonded_plmc - ::EvaluatorSlash::get() * already_bonded_plmc; let necessary_plmc_for_bid = calculate_auction_plmc_spent(vec![evaluator_bid])[0].1; let necessary_usdt_for_bid = calculate_auction_funding_asset_spent(vec![evaluator_bid]); - test_env.mint_plmc_to(vec![(evaluator_bidder, necessary_plmc_for_bid - already_bonded_plmc)]); + test_env.mint_plmc_to(vec![(evaluator_bidder, necessary_plmc_for_bid - usable_evaluation_plmc)]); test_env.mint_statemint_asset_to(necessary_usdt_for_bid); bidding_project.bid_for_users(vec![evaluator_bid]).unwrap(); @@ -2138,9 +2135,8 @@ mod auction_round_success { let bid_necessary_plmc = calculate_auction_plmc_spent(vec![evaluator_bid]); let bid_necessary_usdt = calculate_auction_funding_asset_spent(vec![evaluator_bid]); - let mut evaluation_bond = sum_balance_mappings(vec![fill_necessary_plmc_for_bids, bid_necessary_plmc.clone()]); - const FUNDED_DELTA_PLMC: u128 = 69 * PLMC; - evaluation_bond -= FUNDED_DELTA_PLMC; + let evaluation_bond = sum_balance_mappings(vec![fill_necessary_plmc_for_bids, bid_necessary_plmc.clone()]); + let plmc_available_for_participation = evaluation_bond - ::EvaluatorSlash::get() * evaluation_bond; let evaluation_usd_amount = ::PriceProvider::get_price(PLMC_STATEMINT_ID) .unwrap() @@ -2150,32 +2146,20 @@ mod auction_round_success { let bidding_project = AuctioningProject::new_with(&test_env, project, issuer, evaluations); let project_id = bidding_project.get_project_id(); - test_env.mint_plmc_to(vec![(evaluator_bidder, FUNDED_DELTA_PLMC)]); + test_env.mint_plmc_to(vec![(evaluator_bidder, evaluation_bond - plmc_available_for_participation)]); test_env.mint_statemint_asset_to(fill_necessary_usdt_for_bids); test_env.mint_statemint_asset_to(bid_necessary_usdt); bidding_project.bid_for_users(bids).unwrap(); - - let evaluation_bond = test_env.in_ext(|| { - ::NativeCurrency::balance_on_hold( - &LockType::Evaluation(project_id), - &evaluator_bidder, - ) - }); - let post_fill_evaluation_bond = bid_necessary_plmc[0].1 - FUNDED_DELTA_PLMC; - assert!( - evaluation_bond < post_fill_evaluation_bond + 10_u128 || - evaluation_bond > post_fill_evaluation_bond - 10_u128 - ); - bidding_project.bid_for_users(vec![evaluator_bid]).unwrap(); - let evaluation_bond = test_env.in_ext(|| { + + let evaluation_bonded = test_env.in_ext(|| { ::NativeCurrency::balance_on_hold( &LockType::Evaluation(project_id), &evaluator_bidder, ) }); - assert_eq!(evaluation_bond, 0); + assert_eq!(evaluation_bonded, ::EvaluatorSlash::get() * evaluation_bond); } #[test] @@ -2473,7 +2457,6 @@ mod auction_round_success { } } -#[cfg(test)] mod auction_round_failure { use super::*; @@ -2720,8 +2703,8 @@ mod auction_round_failure { } } -#[cfg(test)] mod community_round_success { + use std::assert_matches::assert_matches; use super::*; use frame_support::traits::fungible::Inspect; @@ -3165,10 +3148,11 @@ mod community_round_success { let ct_price = contributing_project.get_project_details().weighted_average_price.unwrap(); let already_bonded_plmc = calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])[0].1; + let plmc_available_for_participating = already_bonded_plmc - ::EvaluatorSlash::get() * already_bonded_plmc; let necessary_plmc_for_contribution = calculate_contributed_plmc_spent(vec![contribution], ct_price)[0].1; let necessary_usdt_for_contribution = calculate_contributed_funding_asset_spent(vec![contribution], ct_price); - test_env.mint_plmc_to(vec![(evaluator_contributor, necessary_plmc_for_contribution - already_bonded_plmc)]); + test_env.mint_plmc_to(vec![(evaluator_contributor, necessary_plmc_for_contribution - plmc_available_for_participating)]); test_env.mint_statemint_asset_to(necessary_usdt_for_contribution); contributing_project.buy_for_retail_users(vec![contribution]).unwrap(); @@ -3203,9 +3187,8 @@ mod community_round_success { let overflow_necessary_usdt = calculate_contributed_funding_asset_spent(vec![overflow_contribution], expected_price); - let mut evaluation_bond = sum_balance_mappings(vec![fill_necessary_plmc, overflow_necessary_plmc.clone()]); - const FUNDED_DELTA_PLMC: u128 = 69 * PLMC; - evaluation_bond -= FUNDED_DELTA_PLMC; + let evaluation_bond = sum_balance_mappings(vec![fill_necessary_plmc, overflow_necessary_plmc.clone()]); + let plmc_available_for_participating = evaluation_bond - ::EvaluatorSlash::get() * evaluation_bond; let evaluation_usd_amount = ::PriceProvider::get_price(PLMC_STATEMINT_ID) .unwrap() @@ -3216,36 +3199,54 @@ mod community_round_success { CommunityFundingProject::new_with(&test_env, project, issuer, evaluations, bids); let project_id = community_funding_project.get_project_id(); - test_env.mint_plmc_to(vec![(evaluator_contributor, FUNDED_DELTA_PLMC)]); + test_env.mint_plmc_to(vec![(evaluator_contributor, evaluation_bond - plmc_available_for_participating)]); test_env.mint_statemint_asset_to(fill_necessary_usdt); test_env.mint_statemint_asset_to(overflow_necessary_usdt); community_funding_project.buy_for_retail_users(fill_contributions).unwrap(); + community_funding_project.buy_for_retail_users(vec![overflow_contribution]).unwrap(); - let evaluation_bond = test_env.in_ext(|| { + let evaluation_bonded = test_env.in_ext(|| { ::NativeCurrency::balance_on_hold( &LockType::Evaluation(project_id), &evaluator_contributor, ) }); - let post_fill_evaluation_bond = overflow_necessary_plmc[0].1 - FUNDED_DELTA_PLMC; + assert_eq!(evaluation_bonded, ::EvaluatorSlash::get() * evaluation_bond); + } + + #[test] + fn evaluator_cannot_use_slash_reserve_for_contributing_call_fail() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let mut evaluations = default_evaluations(); + let evaluator_contributor = 69; + let evaluation_amount = 420 * US_DOLLAR; + let contribution = + TestContribution::new(evaluator_contributor, 22 * ASSET_UNIT, None, AcceptedFundingAsset::USDT); + evaluations.push((evaluator_contributor, evaluation_amount)); + let bids = default_bids(); + + let contributing_project = CommunityFundingProject::new_with(&test_env, project, issuer, evaluations, bids); + let ct_price = contributing_project.get_project_details().weighted_average_price.unwrap(); + let necessary_plmc_for_contribution = calculate_contributed_plmc_spent(vec![contribution], ct_price)[0].1; + let plmc_evaluation_amount = calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])[0].1; + let plmc_available_for_participating = plmc_evaluation_amount - ::EvaluatorSlash::get() * plmc_evaluation_amount; assert!( - evaluation_bond < post_fill_evaluation_bond + 10_u128 || - evaluation_bond > post_fill_evaluation_bond - 10_u128 + necessary_plmc_for_contribution > plmc_available_for_participating && necessary_plmc_for_contribution < plmc_evaluation_amount ); + // 1199_9_999_999_999 + // 49_9_999_999_999 + let necessary_usdt_for_contribution = calculate_contributed_funding_asset_spent(vec![contribution], ct_price); - community_funding_project.buy_for_retail_users(vec![overflow_contribution]).unwrap(); - let evaluation_bond = test_env.in_ext(|| { - ::NativeCurrency::balance_on_hold( - &LockType::Evaluation(project_id), - &evaluator_contributor, - ) - }); - assert_eq!(evaluation_bond, 0); + test_env.mint_statemint_asset_to(necessary_usdt_for_contribution); + + assert_matches!(contributing_project.buy_for_retail_users(vec![contribution]), Err(_)); } #[test] - fn evaluator_cannot_use_slash_reserve_for_contributing() { + fn evaluator_cannot_use_slash_reserve_for_contributing_call_success() { let test_env = TestEnvironment::new(); let issuer = ISSUER; let project = default_project(test_env.get_new_nonce()); @@ -3253,37 +3254,40 @@ mod community_round_success { let evaluator_contributor = 69; let evaluation_amount = 420 * US_DOLLAR; let contribution = - TestContribution::new(evaluator_contributor, 600 * ASSET_UNIT, None, AcceptedFundingAsset::USDT); + TestContribution::new(evaluator_contributor, 22 * ASSET_UNIT, None, AcceptedFundingAsset::USDT); evaluations.push((evaluator_contributor, evaluation_amount)); let bids = default_bids(); let contributing_project = CommunityFundingProject::new_with(&test_env, project, issuer, evaluations, bids); let project_id = contributing_project.get_project_id(); + let ct_price = contributing_project.get_project_details().weighted_average_price.unwrap(); let necessary_plmc_for_contribution = calculate_contributed_plmc_spent(vec![contribution], ct_price)[0].1; + let plmc_evaluation_amount = calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])[0].1; + let plmc_available_for_participating = plmc_evaluation_amount - ::EvaluatorSlash::get() * plmc_evaluation_amount; assert!( - necessary_plmc_for_contribution > - calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])[0].1 + necessary_plmc_for_contribution > plmc_available_for_participating && necessary_plmc_for_contribution < plmc_evaluation_amount ); let necessary_usdt_for_contribution = calculate_contributed_funding_asset_spent(vec![contribution], ct_price); - test_env.mint_plmc_to(vec![(evaluator_contributor, necessary_plmc_for_contribution)]); + test_env.mint_plmc_to(vec![(evaluator_contributor, necessary_plmc_for_contribution - plmc_available_for_participating)]); test_env.mint_statemint_asset_to(necessary_usdt_for_contribution); - let slash_reserve = - slash_evaluator_balances(calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])); contributing_project.buy_for_retail_users(vec![contribution]).unwrap(); + let evaluation_locked = test_env.get_reserved_plmc_balances_for(vec![evaluator_contributor], LockType::Evaluation(project_id))[0].1; + let participation_locked = test_env.get_reserved_plmc_balances_for(vec![evaluator_contributor], LockType::Participation(project_id))[0].1; + + assert_eq!(evaluation_locked, ::EvaluatorSlash::get() * plmc_evaluation_amount); + assert_eq!(participation_locked, necessary_plmc_for_contribution); + - test_env.do_reserved_plmc_assertions(slash_reserve, LockType::Evaluation(project_id)); } } -#[cfg(test)] mod community_round_failure { // TODO: Maybe here we can test what happens if we sell all the CTs in the community round } -#[cfg(test)] mod remainder_round_success { use super::*; @@ -3321,10 +3325,11 @@ mod remainder_round_success { let ct_price = remainder_funding_project.get_project_details().weighted_average_price.unwrap(); let already_bonded_plmc = calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])[0].1; + let plmc_available_for_contribution = already_bonded_plmc - ::EvaluatorSlash::get() * already_bonded_plmc; let necessary_plmc_for_buy = calculate_contributed_plmc_spent(vec![remainder_contribution], ct_price)[0].1; let necessary_usdt_for_buy = calculate_contributed_funding_asset_spent(vec![remainder_contribution], ct_price); - test_env.mint_plmc_to(vec![(evaluator_contributor, necessary_plmc_for_buy - already_bonded_plmc)]); + test_env.mint_plmc_to(vec![(evaluator_contributor, necessary_plmc_for_buy - plmc_available_for_contribution)]); test_env.mint_statemint_asset_to(necessary_usdt_for_buy); remainder_funding_project.buy_for_any_user(vec![remainder_contribution]).unwrap(); @@ -3360,9 +3365,8 @@ mod remainder_round_success { let overflow_necessary_usdt = calculate_contributed_funding_asset_spent(vec![overflow_contribution], expected_price); - let mut evaluation_bond = sum_balance_mappings(vec![fill_necessary_plmc, overflow_necessary_plmc.clone()]); - const FUNDED_DELTA_PLMC: u128 = 69 * PLMC; - evaluation_bond -= FUNDED_DELTA_PLMC; + let evaluation_bond = sum_balance_mappings(vec![fill_necessary_plmc, overflow_necessary_plmc.clone()]); + let plmc_available_for_participating = evaluation_bond - ::EvaluatorSlash::get() * evaluation_bond; let evaluation_usd_amount = ::PriceProvider::get_price(PLMC_STATEMINT_ID) .unwrap() @@ -3374,32 +3378,22 @@ mod remainder_round_success { .unwrap_left(); let project_id = remainder_funding_project.get_project_id(); - test_env.mint_plmc_to(vec![(evaluator_contributor, FUNDED_DELTA_PLMC)]); + test_env.mint_plmc_to(vec![(evaluator_contributor, evaluation_bond - plmc_available_for_participating)]); test_env.mint_statemint_asset_to(fill_necessary_usdt_for_bids); test_env.mint_statemint_asset_to(overflow_necessary_usdt); remainder_funding_project.buy_for_any_user(fill_contributions).unwrap(); + remainder_funding_project.buy_for_any_user(vec![overflow_contribution]).unwrap(); - let evaluation_bond = test_env.in_ext(|| { - ::NativeCurrency::balance_on_hold( - &LockType::Evaluation(project_id), - &evaluator_contributor, - ) - }); - let post_fill_evaluation_bond = overflow_necessary_plmc[0].1 - FUNDED_DELTA_PLMC; - assert!( - evaluation_bond < post_fill_evaluation_bond + 10_u128 || - evaluation_bond > post_fill_evaluation_bond - 10_u128 - ); - remainder_funding_project.buy_for_any_user(vec![overflow_contribution]).unwrap(); - let evaluation_bond = test_env.in_ext(|| { + let evaluation_bonded = test_env.in_ext(|| { ::NativeCurrency::balance_on_hold( &LockType::Evaluation(project_id), &evaluator_contributor, ) }); - assert_eq!(evaluation_bond, 0); + assert_eq!(evaluation_bonded, ::EvaluatorSlash::get() * evaluation_bond); + } #[test] @@ -3527,7 +3521,6 @@ mod remainder_round_success { } } -#[cfg(test)] mod funding_end { use super::*; use sp_arithmetic::{Percent, Perquintill}; @@ -3862,7 +3855,6 @@ mod funding_end { } } -#[cfg(test)] mod purchased_vesting { use super::*; @@ -3964,7 +3956,6 @@ mod purchased_vesting { } } -#[cfg(test)] mod bids_vesting { use super::*; @@ -4054,7 +4045,6 @@ mod bids_vesting { } } -#[cfg(test)] mod test_helper_functions { use super::*; @@ -4305,7 +4295,6 @@ mod test_helper_functions { } } -#[cfg(test)] mod misc_features { use super::*; use crate::UpdateType::{CommunityFundingStart, RemainderFundingStart}; From da26dd69a11cf7ca496b32cfccc2be7215a0f5b5 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Wed, 2 Aug 2023 18:25:33 +0200 Subject: [PATCH 09/13] chore: fmt --- pallets/funding/src/functions.rs | 2 +- pallets/funding/src/tests.rs | 60 ++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index fb9d6b3fb..1a6626878 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -27,7 +27,7 @@ use frame_support::{ ensure, pallet_prelude::DispatchError, traits::{ - fungible::{MutateHold as FungibleMutateHold}, + fungible::MutateHold as FungibleMutateHold, fungibles::{metadata::Mutate as MetadataMutate, Create, Mutate as FungiblesMutate}, tokens::{Fortitude, Precision, Preservation, Restriction}, Get, diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index 15d035085..b4c2dc353 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -1502,7 +1502,6 @@ pub mod helper_functions { // uncomment this if we want to keep unmatched mappings on the substractor // output.extend_from_slice(&map[j..]); break - }, (Some((acc_i, val_i)), Some((acc_j, val_j))) => if acc_i == acc_j { @@ -2104,7 +2103,8 @@ mod auction_round_success { let bidding_project = AuctioningProject::new_with(&test_env, project, issuer, evaluations); let already_bonded_plmc = calculate_evaluation_plmc_spent(vec![(evaluator_bidder, evaluation_amount)])[0].1; - let usable_evaluation_plmc = already_bonded_plmc - ::EvaluatorSlash::get() * already_bonded_plmc; + let usable_evaluation_plmc = + already_bonded_plmc - ::EvaluatorSlash::get() * already_bonded_plmc; let necessary_plmc_for_bid = calculate_auction_plmc_spent(vec![evaluator_bid])[0].1; let necessary_usdt_for_bid = calculate_auction_funding_asset_spent(vec![evaluator_bid]); @@ -2136,7 +2136,8 @@ mod auction_round_success { let bid_necessary_usdt = calculate_auction_funding_asset_spent(vec![evaluator_bid]); let evaluation_bond = sum_balance_mappings(vec![fill_necessary_plmc_for_bids, bid_necessary_plmc.clone()]); - let plmc_available_for_participation = evaluation_bond - ::EvaluatorSlash::get() * evaluation_bond; + let plmc_available_for_participation = + evaluation_bond - ::EvaluatorSlash::get() * evaluation_bond; let evaluation_usd_amount = ::PriceProvider::get_price(PLMC_STATEMINT_ID) .unwrap() @@ -2704,9 +2705,9 @@ mod auction_round_failure { } mod community_round_success { - use std::assert_matches::assert_matches; use super::*; use frame_support::traits::fungible::Inspect; + use std::assert_matches::assert_matches; pub const HOURS: BlockNumber = 300u64; @@ -3148,11 +3149,15 @@ mod community_round_success { let ct_price = contributing_project.get_project_details().weighted_average_price.unwrap(); let already_bonded_plmc = calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])[0].1; - let plmc_available_for_participating = already_bonded_plmc - ::EvaluatorSlash::get() * already_bonded_plmc; + let plmc_available_for_participating = + already_bonded_plmc - ::EvaluatorSlash::get() * already_bonded_plmc; let necessary_plmc_for_contribution = calculate_contributed_plmc_spent(vec![contribution], ct_price)[0].1; let necessary_usdt_for_contribution = calculate_contributed_funding_asset_spent(vec![contribution], ct_price); - test_env.mint_plmc_to(vec![(evaluator_contributor, necessary_plmc_for_contribution - plmc_available_for_participating)]); + test_env.mint_plmc_to(vec![( + evaluator_contributor, + necessary_plmc_for_contribution - plmc_available_for_participating, + )]); test_env.mint_statemint_asset_to(necessary_usdt_for_contribution); contributing_project.buy_for_retail_users(vec![contribution]).unwrap(); @@ -3188,7 +3193,8 @@ mod community_round_success { calculate_contributed_funding_asset_spent(vec![overflow_contribution], expected_price); let evaluation_bond = sum_balance_mappings(vec![fill_necessary_plmc, overflow_necessary_plmc.clone()]); - let plmc_available_for_participating = evaluation_bond - ::EvaluatorSlash::get() * evaluation_bond; + let plmc_available_for_participating = + evaluation_bond - ::EvaluatorSlash::get() * evaluation_bond; let evaluation_usd_amount = ::PriceProvider::get_price(PLMC_STATEMINT_ID) .unwrap() @@ -3231,10 +3237,13 @@ mod community_round_success { let contributing_project = CommunityFundingProject::new_with(&test_env, project, issuer, evaluations, bids); let ct_price = contributing_project.get_project_details().weighted_average_price.unwrap(); let necessary_plmc_for_contribution = calculate_contributed_plmc_spent(vec![contribution], ct_price)[0].1; - let plmc_evaluation_amount = calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])[0].1; - let plmc_available_for_participating = plmc_evaluation_amount - ::EvaluatorSlash::get() * plmc_evaluation_amount; + let plmc_evaluation_amount = + calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])[0].1; + let plmc_available_for_participating = + plmc_evaluation_amount - ::EvaluatorSlash::get() * plmc_evaluation_amount; assert!( - necessary_plmc_for_contribution > plmc_available_for_participating && necessary_plmc_for_contribution < plmc_evaluation_amount + necessary_plmc_for_contribution > plmc_available_for_participating && + necessary_plmc_for_contribution < plmc_evaluation_amount ); // 1199_9_999_999_999 // 49_9_999_999_999 @@ -3263,24 +3272,31 @@ mod community_round_success { let ct_price = contributing_project.get_project_details().weighted_average_price.unwrap(); let necessary_plmc_for_contribution = calculate_contributed_plmc_spent(vec![contribution], ct_price)[0].1; - let plmc_evaluation_amount = calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])[0].1; - let plmc_available_for_participating = plmc_evaluation_amount - ::EvaluatorSlash::get() * plmc_evaluation_amount; + let plmc_evaluation_amount = + calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])[0].1; + let plmc_available_for_participating = + plmc_evaluation_amount - ::EvaluatorSlash::get() * plmc_evaluation_amount; assert!( - necessary_plmc_for_contribution > plmc_available_for_participating && necessary_plmc_for_contribution < plmc_evaluation_amount + necessary_plmc_for_contribution > plmc_available_for_participating && + necessary_plmc_for_contribution < plmc_evaluation_amount ); let necessary_usdt_for_contribution = calculate_contributed_funding_asset_spent(vec![contribution], ct_price); - test_env.mint_plmc_to(vec![(evaluator_contributor, necessary_plmc_for_contribution - plmc_available_for_participating)]); + test_env.mint_plmc_to(vec![( + evaluator_contributor, + necessary_plmc_for_contribution - plmc_available_for_participating, + )]); test_env.mint_statemint_asset_to(necessary_usdt_for_contribution); contributing_project.buy_for_retail_users(vec![contribution]).unwrap(); - let evaluation_locked = test_env.get_reserved_plmc_balances_for(vec![evaluator_contributor], LockType::Evaluation(project_id))[0].1; - let participation_locked = test_env.get_reserved_plmc_balances_for(vec![evaluator_contributor], LockType::Participation(project_id))[0].1; + let evaluation_locked = + test_env.get_reserved_plmc_balances_for(vec![evaluator_contributor], LockType::Evaluation(project_id))[0].1; + let participation_locked = test_env + .get_reserved_plmc_balances_for(vec![evaluator_contributor], LockType::Participation(project_id))[0] + .1; assert_eq!(evaluation_locked, ::EvaluatorSlash::get() * plmc_evaluation_amount); assert_eq!(participation_locked, necessary_plmc_for_contribution); - - } } @@ -3325,7 +3341,8 @@ mod remainder_round_success { let ct_price = remainder_funding_project.get_project_details().weighted_average_price.unwrap(); let already_bonded_plmc = calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount)])[0].1; - let plmc_available_for_contribution = already_bonded_plmc - ::EvaluatorSlash::get() * already_bonded_plmc; + let plmc_available_for_contribution = + already_bonded_plmc - ::EvaluatorSlash::get() * already_bonded_plmc; let necessary_plmc_for_buy = calculate_contributed_plmc_spent(vec![remainder_contribution], ct_price)[0].1; let necessary_usdt_for_buy = calculate_contributed_funding_asset_spent(vec![remainder_contribution], ct_price); @@ -3366,7 +3383,8 @@ mod remainder_round_success { calculate_contributed_funding_asset_spent(vec![overflow_contribution], expected_price); let evaluation_bond = sum_balance_mappings(vec![fill_necessary_plmc, overflow_necessary_plmc.clone()]); - let plmc_available_for_participating = evaluation_bond - ::EvaluatorSlash::get() * evaluation_bond; + let plmc_available_for_participating = + evaluation_bond - ::EvaluatorSlash::get() * evaluation_bond; let evaluation_usd_amount = ::PriceProvider::get_price(PLMC_STATEMINT_ID) .unwrap() @@ -3385,7 +3403,6 @@ mod remainder_round_success { remainder_funding_project.buy_for_any_user(fill_contributions).unwrap(); remainder_funding_project.buy_for_any_user(vec![overflow_contribution]).unwrap(); - let evaluation_bonded = test_env.in_ext(|| { ::NativeCurrency::balance_on_hold( &LockType::Evaluation(project_id), @@ -3393,7 +3410,6 @@ mod remainder_round_success { ) }); assert_eq!(evaluation_bonded, ::EvaluatorSlash::get() * evaluation_bond); - } #[test] From c2299978919d101e72893a5750d3914a54413201 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Thu, 3 Aug 2023 10:54:49 +0200 Subject: [PATCH 10/13] feat: ct minting for bids --- pallets/funding/src/functions.rs | 131 +++---------- pallets/funding/src/impls.rs | 32 +++- pallets/funding/src/lib.rs | 45 ++--- pallets/funding/src/tests.rs | 307 ++++++++++++------------------- pallets/funding/src/types.rs | 1 + 5 files changed, 187 insertions(+), 329 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index 1a6626878..747d8202a 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -28,7 +28,7 @@ use frame_support::{ pallet_prelude::DispatchError, traits::{ fungible::MutateHold as FungibleMutateHold, - fungibles::{metadata::Mutate as MetadataMutate, Create, Mutate as FungiblesMutate}, + fungibles::{metadata::Mutate as MetadataMutate, Create, Inspect, Mutate as FungiblesMutate}, tokens::{Fortitude, Precision, Preservation, Restriction}, Get, }, @@ -935,6 +935,7 @@ impl Pallet { ct_vesting_period, when: now, funds_released: false, + ct_minted: false, }; // * Update storage * @@ -1203,58 +1204,36 @@ impl Pallet { Ok(()) } - /// Mint contribution tokens after a step in the vesting period for a successful bid. - /// - /// # Arguments - /// * bidder: The account who made bids - /// * project_id: The project the bids where made for - /// - /// # Storage access - /// - /// * `AuctionsInfo` - Check if its time to mint some tokens based on the bid vesting period, and update the bid after minting. - /// * `T::ContributionTokenCurrency` - Mint the tokens to the bidder - pub fn do_vested_contribution_token_bid_mint_for( + pub fn do_bid_ct_mint_for( releaser: AccountIdOf, project_id: T::ProjectIdentifier, bidder: AccountIdOf, - ) -> Result<(), DispatchError> { + bid_id: T::StorageItemId, + ) -> DispatchResult { // * Get variables * - 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; - let mut mint_amount: BalanceOf = 0u32.into(); + let mut bid = Bids::::get((project_id, bidder.clone(), bid_id)).ok_or(Error::::BidNotFound)?; + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectNotFound)?; + let ct_amount = bid.final_ct_amount; - // * Validity checks * - // check that it is not too early to withdraw the next amount - if ct_vesting.next_withdrawal > now { - continue - } + // * Validity checks * + ensure!(project_details.status == ProjectStatus::FundingSuccessful, Error::::NotAllowed); + ensure!(matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..)), Error::::NotAllowed); + ensure!(T::ContributionTokenCurrency::asset_exists(project_id), Error::::CannotClaimYet); - // * Calculate variables * - // Update vesting period until the next withdrawal is in the future - while let Ok(amount) = ct_vesting.calculate_next_withdrawal() { - mint_amount = mint_amount.saturating_add(amount); - if ct_vesting.next_withdrawal > now { - break - } - } - bid.ct_vesting_period = ct_vesting; + // * Calculate variables * + bid.ct_minted = true; - // * 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(bid.project_id, &bid.bidder, mint_amount)?; - Bids::::insert((project_id, bidder.clone(), bid.id), bid.clone()); + // * Update storage * + T::ContributionTokenCurrency::mint_into(project_id, &bid.bidder, ct_amount)?; + Bids::::insert((project_id, bidder.clone(), bid_id), bid.clone()); - // * Emit events * - Self::deposit_event(Event::::ContributionTokenMinted { - caller: releaser.clone(), - project_id, - contributor: bidder.clone(), - amount: mint_amount, - }) - } + // * Emit events * + Self::deposit_event(Event::::ContributionTokenMinted { + releaser, + project_id: bid.project_id, + claimer: bidder, + amount: ct_amount, + }); Ok(()) } @@ -1330,68 +1309,6 @@ impl Pallet { Ok(()) } - /// Mint contribution tokens after a step in the vesting period for a contribution. - /// - /// # Arguments - /// * claimer: The account who made the contribution - /// * project_id: The project the contribution was made for - /// - /// # Storage access - /// * [`ProjectsDetails`] - Check that the funding period ended - /// * [`Contributions`] - Check if its time to mint some tokens based on the contributions vesting periods, and update the contribution after minting. - /// * [`T::ContributionTokenCurrency`] - Mint the tokens to the claimer - pub fn do_vested_contribution_token_purchase_mint_for( - releaser: AccountIdOf, - project_id: T::ProjectIdentifier, - claimer: AccountIdOf, - ) -> Result<(), DispatchError> { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectNotFound)?; - let contributions = Contributions::::iter_prefix_values((project_id, &claimer)); - let now = >::block_number(); - - // * Validity checks * - 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 - - for mut contribution in contributions { - let mut ct_vesting = contribution.ct_vesting_period; - let mut mint_amount: BalanceOf = 0u32.into(); - - // * Validity checks * - // check that it is not too early to withdraw the next amount - if ct_vesting.next_withdrawal > now { - continue - } - - // * Calculate variables * - // Update vesting period until the next withdrawal is in the future - while let Ok(amount) = ct_vesting.calculate_next_withdrawal() { - mint_amount = mint_amount.saturating_add(amount); - if ct_vesting.next_withdrawal > now { - break - } - } - contribution.ct_vesting_period = ct_vesting; - - Contributions::::insert( - (project_id, contribution.contributor.clone(), contribution.id), - contribution.clone(), - ); - - // * Emit events * - Self::deposit_event(Event::ContributionTokenMinted { - caller: releaser.clone(), - project_id, - contributor: claimer.clone(), - amount: mint_amount, - }) - } - - // * Update storage * - Ok(()) - } - pub fn do_evaluation_unbond_for( releaser: AccountIdOf, project_id: T::ProjectIdentifier, diff --git a/pallets/funding/src/impls.rs b/pallets/funding/src/impls.rs index 3488a5dab..4bad30556 100644 --- a/pallets/funding/src/impls.rs +++ b/pallets/funding/src/impls.rs @@ -270,9 +270,9 @@ fn remaining_bids_without_plmc_vesting(_project_id: T::ProjectIdentif 0u64 } -fn remaining_bids_without_ct_minted(_project_id: T::ProjectIdentifier) -> u64 { - // TODO: currently we vest the contribution tokens. We should change this to a direct mint. - 0u64 +fn remaining_bids_without_ct_minted(project_id: T::ProjectIdentifier) -> u64 { + let project_bids = Bids::::iter_prefix_values((project_id,)); + project_bids.filter(|bid| !bid.ct_minted).count() as u64 } fn remaining_contributions_without_plmc_vesting(_project_id: T::ProjectIdentifier) -> u64 { @@ -499,9 +499,29 @@ fn start_contribution_plmc_vesting_schedule(_project_id: T::ProjectId (Weight::zero(), 0u64) } -fn mint_ct_for_one_bid(_project_id: T::ProjectIdentifier) -> (Weight, u64) { - // TODO: Change when new vesting schedule is implemented - (Weight::zero(), 0u64) +fn mint_ct_for_one_bid(project_id: T::ProjectIdentifier) -> (Weight, u64) { + let project_bids = Bids::::iter_prefix_values((project_id,)); + let mut remaining_bids = project_bids.filter(|bid| !bid.ct_minted); + + if let Some(bid) = remaining_bids.next() { + match Pallet::::do_bid_ct_mint_for( + T::PalletId::get().into_account_truncating(), + bid.project_id, + bid.bidder.clone(), + bid.id, + ) { + Ok(_) => (), + Err(e) => Pallet::::deposit_event(Event::BidCtMintFailed { + project_id: bid.project_id, + bidder: bid.bidder.clone(), + id: bid.id, + error: e, + }), + }; + (Weight::zero(), remaining_bids.count() as u64) + } else { + (Weight::zero(), 0u64) + } } fn mint_ct_for_one_contribution(_project_id: T::ProjectIdentifier) -> (Weight, u64) { diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index d2090858a..379a3c837 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -543,9 +543,9 @@ pub mod pallet { }, /// Contribution tokens were minted to a user ContributionTokenMinted { - caller: AccountIdOf, + releaser: AccountIdOf, project_id: T::ProjectIdentifier, - contributor: AccountIdOf, + claimer: AccountIdOf, amount: BalanceOf, }, /// A transfer of tokens failed, but because it was done inside on_initialize it cannot be solved. @@ -612,6 +612,12 @@ pub mod pallet { amount: BalanceOf, caller: AccountIdOf, }, + BidCtMintFailed { + project_id: ProjectIdOf, + bidder: AccountIdOf, + id: StorageItemIdOf, + error: DispatchError, + }, } #[pallet::error] @@ -842,9 +848,9 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::evaluation_unbond_for())] pub fn evaluation_unbond_for( origin: OriginFor, - bond_id: T::StorageItemId, project_id: T::ProjectIdentifier, evaluator: AccountIdOf, + bond_id: T::StorageItemId, ) -> DispatchResult { let releaser = ensure_signed(origin)?; Self::do_evaluation_unbond_for(releaser, project_id, evaluator, bond_id) @@ -853,36 +859,35 @@ pub mod pallet { #[pallet::weight(Weight::from_parts(0, 0))] pub fn evaluation_reward_payout_for( origin: OriginFor, - bond_id: T::StorageItemId, project_id: T::ProjectIdentifier, evaluator: AccountIdOf, + bond_id: T::StorageItemId, ) -> DispatchResult { let caller = ensure_signed(origin)?; Self::do_evaluation_reward_payout_for(caller, project_id, evaluator, bond_id) } - /// Unbond some plmc from a contribution, after a step in the vesting period has passed. - pub fn vested_plmc_bid_unbond_for( + #[pallet::weight(Weight::from_parts(0, 0))] + pub fn bid_ct_mint_for( origin: OriginFor, project_id: T::ProjectIdentifier, bidder: AccountIdOf, + bid_id: T::StorageItemId, ) -> DispatchResult { - // TODO: PLMC-157. Manage the fact that the CTs may not be claimed by those entitled - let releaser = ensure_signed(origin)?; - - Self::do_vested_plmc_bid_unbond_for(releaser, project_id, bidder) + let caller = ensure_signed(origin)?; + Self::do_bid_ct_mint_for(caller, project_id, bidder, bid_id) } - // TODO: PLMC-157. Manage the fact that the CTs may not be claimed by those entitled - /// Mint contribution tokens after a step in the vesting period for a successful bid. - pub fn vested_contribution_token_bid_mint_for( + /// Unbond some plmc from a contribution, after a step in the vesting period has passed. + pub fn vested_plmc_bid_unbond_for( origin: OriginFor, project_id: T::ProjectIdentifier, bidder: AccountIdOf, ) -> DispatchResult { + // TODO: PLMC-157. Manage the fact that the CTs may not be claimed by those entitled let releaser = ensure_signed(origin)?; - Self::do_vested_contribution_token_bid_mint_for(releaser, project_id, bidder) + Self::do_vested_plmc_bid_unbond_for(releaser, project_id, bidder) } // TODO: PLMC-157. Manage the fact that the CTs may not be claimed by those entitled @@ -896,18 +901,6 @@ pub mod pallet { Self::do_vested_plmc_purchase_unbond_for(releaser, project_id, purchaser) } - - // TODO: PLMC-157. Manage the fact that the CTs may not be claimed by those entitled - /// Mint contribution tokens after a step in the vesting period for a contribution. - pub fn vested_contribution_token_purchase_mint_for( - origin: OriginFor, - project_id: T::ProjectIdentifier, - purchaser: AccountIdOf, - ) -> DispatchResult { - let releaser = ensure_signed(origin)?; - - Self::do_vested_contribution_token_purchase_mint_for(releaser, project_id, purchaser) - } } #[pallet::hooks] diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index b4c2dc353..90e3c7e33 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -2456,6 +2456,123 @@ mod auction_round_success { 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); } + + #[test] + fn ct_minted_for_bids_automatically() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = vec![]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids.clone(), + community_contributions, + remainder_contributions, + ); + let project_id = finished_project.get_project_id(); + let details = finished_project.get_project_details(); + assert_eq!(details.status, ProjectStatus::FundingSuccessful); + assert_eq!(details.cleanup, Cleaner::NotReady); + + test_env.advance_time(10u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + + let stored_bids = + test_env.in_ext(|| Bids::::iter_prefix_values((project_id,)).collect::>()); + assert_eq!(stored_bids.len(), bids.len()); + let user_ct_amounts = generic_map_merge_reduce( + vec![stored_bids], + |bid| bid.bidder, + BalanceOf::::zero(), + |bid, acc| acc + bid.final_ct_amount, + ); + assert_eq!(user_ct_amounts.len(), bids.len()); + + for (bidder, amount) in user_ct_amounts { + let minted = + test_env.in_ext(|| ::ContributionTokenCurrency::balance(project_id, bidder)); + assert_eq!(minted, amount); + } + } + + #[test] + fn ct_minted_for_bids_manually() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = vec![]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids.clone(), + community_contributions, + remainder_contributions, + ); + let project_id = finished_project.get_project_id(); + let details = finished_project.get_project_details(); + assert_eq!(details.status, ProjectStatus::FundingSuccessful); + assert_eq!(details.cleanup, Cleaner::NotReady); + let stored_bids = + test_env.in_ext(|| Bids::::iter_prefix_values((project_id,)).collect::>()); + + for bid in stored_bids.clone() { + test_env.in_ext(|| { + assert_noop!( + Pallet::::bid_ct_mint_for( + RuntimeOrigin::signed(bid.bidder), + project_id, + bid.bidder, + bid.id, + ), + Error::::CannotClaimYet + ); + }) + } + test_env.advance_time(1u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Initialized(PhantomData))); + + for bid in stored_bids.clone() { + test_env.in_ext(|| { + Pallet::::bid_ct_mint_for( + RuntimeOrigin::signed(bid.bidder), + project_id, + bid.bidder, + bid.id, + ) + .unwrap() + }); + } + + assert_eq!(stored_bids.len(), bids.len()); + let user_ct_amounts = generic_map_merge_reduce( + vec![stored_bids], + |bid| bid.bidder, + BalanceOf::::zero(), + |bid, acc| acc + bid.final_ct_amount, + ); + assert_eq!(user_ct_amounts.len(), bids.len()); + + for (bidder, amount) in user_ct_amounts { + let minted = + test_env.in_ext(|| ::ContributionTokenCurrency::balance(project_id, bidder)); + assert_eq!(minted, amount); + } + } } mod auction_round_failure { @@ -3871,196 +3988,6 @@ mod funding_end { } } -mod purchased_vesting { - use super::*; - - #[test] - fn individual_contribution_token_mints() { - // TODO: currently the vesting is limited to the whole payment at once. We should test it with several payments over a vesting period. - let test_env = TestEnvironment::new(); - let community_contributions = default_community_buys(); - let remainder_contributions = default_remainder_buys(); - let finished_project = FinishedProject::new_with( - &test_env, - default_project(test_env.get_new_nonce()), - ISSUER, - default_evaluations(), - default_bids(), - community_contributions.clone(), - remainder_contributions.clone(), - ); - let project_id = finished_project.project_id; - let user_buys = generic_map_merge( - vec![community_contributions.clone(), default_remainder_buys()], - |m| m.contributor.clone(), - |m1, m2| { - let total_amount = m1.amount.clone() + m2.amount.clone(); - let mut mx = m1.clone(); - mx.amount = total_amount; - mx - }, - ); - - for merged_contribution in user_buys { - let result = test_env.in_ext(|| { - FundingModule::vested_contribution_token_purchase_mint_for( - RuntimeOrigin::signed(merged_contribution.contributor), - project_id, - merged_contribution.contributor, - ) - }); - assert_ok!(result); - let minted_balance = test_env.in_ext(|| { - ::ContributionTokenCurrency::balance(project_id, merged_contribution.contributor) - }); - let desired_balance = merged_contribution.amount; - assert_eq!(minted_balance, desired_balance); - } - } - - #[test] - fn plmc_unbonded() { - let test_env = TestEnvironment::new(); - let evaluations = default_evaluations(); - let bids = default_bids(); - let community_contributions = default_community_buys(); - let remainder_contributions = default_remainder_buys(); - let finished_project = FinishedProject::new_with( - &test_env, - default_project(test_env.get_new_nonce()), - ISSUER, - evaluations.clone(), - bids.clone(), - community_contributions.clone(), - remainder_contributions.clone(), - ); - let project_id = finished_project.project_id; - let token_price = finished_project.get_project_details().weighted_average_price.unwrap(); - - let bidders_plmc_bond = calculate_auction_plmc_spent(bids.clone()); - let contributors_plmc_spent: UserToPLMCBalance = generic_map_merge_reduce( - vec![community_contributions.clone(), remainder_contributions.clone()], - |m| m.contributor.clone(), - 0_u128, - |contribution, total_plmc_spent| { - let new_plmc = calculate_contributed_plmc_spent(vec![contribution.clone()], token_price)[0].1; - total_plmc_spent.checked_add(new_plmc).unwrap() - }, - ); - - let participation_locked_plmc = - merge_add_mappings_by_user(vec![bidders_plmc_bond.clone(), contributors_plmc_spent.clone()]); - let purchase_unbonds = - merge_subtract_mappings_by_user(participation_locked_plmc.clone(), vec![bidders_plmc_bond.clone()]); - - for ((user, pre_locked), (_, post_released)) in zip(participation_locked_plmc, purchase_unbonds) { - let actual_bonded_plmc = test_env.in_ext(|| { - ::NativeCurrency::balance_on_hold(&LockType::Participation(project_id), &user) - }); - - assert_eq!(actual_bonded_plmc, pre_locked); - - let result = test_env.in_ext(|| { - FundingModule::vested_plmc_purchase_unbond_for(RuntimeOrigin::signed(user), project_id, user) - }); - let actual_bonded_plmc = test_env.in_ext(|| { - ::NativeCurrency::balance_on_hold(&LockType::Participation(project_id), &user) - }); - assert_ok!(result); - assert_eq!(actual_bonded_plmc, pre_locked - post_released); - } - } -} - -mod bids_vesting { - use super::*; - - #[test] - fn contribution_token_mints() { - let test_env = TestEnvironment::new(); - let bids = default_bids(); - let finished_project = FinishedProject::new_with( - &test_env, - default_project(test_env.get_new_nonce()), - ISSUER, - default_evaluations(), - bids.clone(), - default_community_buys(), - default_remainder_buys(), - ); - let project_id = finished_project.project_id; - - for bid in bids { - let actual_ct_balance = - test_env.in_ext(|| ::ContributionTokenCurrency::balance(project_id, bid.bidder)); - assert_eq!(actual_ct_balance, 0u32.into()); - - let result = test_env.in_ext(|| { - FundingModule::vested_contribution_token_bid_mint_for( - RuntimeOrigin::signed(bid.bidder), - project_id, - bid.bidder, - ) - }); - assert_ok!(result); - let minted_balance = - test_env.in_ext(|| ::ContributionTokenCurrency::balance(project_id, bid.bidder)); - assert_eq!(minted_balance, bid.amount); - } - } - - #[test] - fn plmc_unbonded() { - let test_env = TestEnvironment::new(); - let bids = default_bids(); - let community_contributions = default_community_buys(); - let remainder_contributions = default_remainder_buys(); - let finished_project = FinishedProject::new_with( - &test_env, - default_project(test_env.get_new_nonce()), - ISSUER, - default_evaluations(), - bids.clone(), - default_community_buys(), - default_remainder_buys(), - ); - let project_id = finished_project.project_id; - let ct_price = finished_project.get_project_details().weighted_average_price.unwrap(); - - let plmc_bid_deposits = calculate_auction_plmc_spent_after_price_calculation(bids.clone(), ct_price); - let plmc_community_contribution_deposits = - calculate_contributed_plmc_spent(community_contributions.clone(), ct_price); - let plmc_remainder_contribution_deposits = - calculate_contributed_plmc_spent(remainder_contributions.clone(), ct_price); - let total_plmc_participation_locked = merge_add_mappings_by_user(vec![ - plmc_bid_deposits.clone(), - plmc_community_contribution_deposits, - plmc_remainder_contribution_deposits.clone(), - ]); - - test_env - .do_reserved_plmc_assertions(total_plmc_participation_locked.clone(), LockType::Participation(project_id)); - - for (bidder, deposit) in plmc_bid_deposits { - let bidder_participation_locked = total_plmc_participation_locked - .clone() - .into_iter() - .find(|(acc, _)| acc.clone() == bidder.clone()) - .unwrap() - .1; - let result = test_env.in_ext(|| { - FundingModule::vested_plmc_bid_unbond_for(RuntimeOrigin::signed(bidder.clone()), project_id, bidder) - }); - assert_ok!(result); - - test_env.do_reserved_plmc_assertions( - vec![(bidder, bidder_participation_locked - deposit)], - LockType::Participation(project_id), - ); - } - } -} - mod test_helper_functions { use super::*; diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index 36f60d337..be2c0df62 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -198,6 +198,7 @@ pub mod storage_types { pub ct_vesting_period: CTVesting, pub when: BlockNumber, pub funds_released: bool, + pub ct_minted: bool, } impl< From 576385659ca060b0bbc3d6fa5cf547c20907af52 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Thu, 3 Aug 2023 11:35:11 +0200 Subject: [PATCH 11/13] feat: ct minting for community buys --- pallets/funding/src/functions.rs | 36 ++++ pallets/funding/src/impls.rs | 36 +++- pallets/funding/src/lib.rs | 18 +- pallets/funding/src/tests.rs | 343 +++++++++++++++++++++++++++++++ pallets/funding/src/types.rs | 1 + 5 files changed, 422 insertions(+), 12 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index 747d8202a..ceca9c95d 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -1064,6 +1064,7 @@ impl Pallet { plmc_vesting_period, ct_vesting_period, funds_released: false, + ct_minted: false, }; // * Update storage * @@ -1217,6 +1218,7 @@ impl Pallet { // * Validity checks * ensure!(project_details.status == ProjectStatus::FundingSuccessful, Error::::NotAllowed); + ensure!(bid.ct_minted == false, Error::::NotAllowed); ensure!(matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..)), Error::::NotAllowed); ensure!(T::ContributionTokenCurrency::asset_exists(project_id), Error::::CannotClaimYet); @@ -1238,6 +1240,40 @@ impl Pallet { Ok(()) } + pub fn do_contribution_ct_mint_for( + releaser: AccountIdOf, + project_id: T::ProjectIdentifier, + contributor: AccountIdOf, + contribution_id: T::StorageItemId, + ) -> DispatchResult { + // * Get variables * + let mut contribution = Contributions::::get((project_id, contributor.clone(), contribution_id)).ok_or(Error::::BidNotFound)?; + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectNotFound)?; + let ct_amount = contribution.ct_amount; + + // * Validity checks * + ensure!(project_details.status == ProjectStatus::FundingSuccessful, Error::::NotAllowed); + ensure!(contribution.ct_minted == false, Error::::NotAllowed); + ensure!(T::ContributionTokenCurrency::asset_exists(project_id), Error::::CannotClaimYet); + + // * Calculate variables * + contribution.ct_minted = true; + + // * Update storage * + T::ContributionTokenCurrency::mint_into(project_id, &contribution.contributor, ct_amount)?; + Contributions::::insert((project_id, contributor.clone(), contribution_id), contribution.clone()); + + // * Emit events * + Self::deposit_event(Event::::ContributionTokenMinted { + releaser, + project_id: contribution.project_id, + claimer: contributor, + amount: ct_amount, + }); + + Ok(()) + } + /// Unbond some plmc from a contribution, after a step in the vesting period has passed. /// /// # Arguments diff --git a/pallets/funding/src/impls.rs b/pallets/funding/src/impls.rs index 4bad30556..ea1a8ba25 100644 --- a/pallets/funding/src/impls.rs +++ b/pallets/funding/src/impls.rs @@ -281,9 +281,9 @@ fn remaining_contributions_without_plmc_vesting(_project_id: T::Proje 0u64 } -fn remaining_contributions_without_ct_minted(_project_id: T::ProjectIdentifier) -> u64 { - // TODO: currently we vest the contribution tokens. We should change this to a direct mint. - 0u64 +fn remaining_contributions_without_ct_minted(project_id: T::ProjectIdentifier) -> u64 { + let project_contributions = Contributions::::iter_prefix_values((project_id,)); + project_contributions.filter(|contribution| !contribution.ct_minted).count() as u64 } fn remaining_bids_without_issuer_payout(project_id: T::ProjectIdentifier) -> u64 { @@ -511,9 +511,9 @@ fn mint_ct_for_one_bid(project_id: T::ProjectIdentifier) -> (Weight, bid.id, ) { Ok(_) => (), - Err(e) => Pallet::::deposit_event(Event::BidCtMintFailed { + Err(e) => Pallet::::deposit_event(Event::CTMintFailed { project_id: bid.project_id, - bidder: bid.bidder.clone(), + claimer: bid.bidder.clone(), id: bid.id, error: e, }), @@ -524,9 +524,29 @@ fn mint_ct_for_one_bid(project_id: T::ProjectIdentifier) -> (Weight, } } -fn mint_ct_for_one_contribution(_project_id: T::ProjectIdentifier) -> (Weight, u64) { - // TODO: Change when new vesting schedule is implemented - (Weight::zero(), 0u64) +fn mint_ct_for_one_contribution(project_id: T::ProjectIdentifier) -> (Weight, u64) { + let project_contributions = Contributions::::iter_prefix_values((project_id,)); + let mut remaining_contributions = project_contributions.filter(|contribution| !contribution.ct_minted); + + if let Some(contribution) = remaining_contributions.next() { + match Pallet::::do_contribution_ct_mint_for( + T::PalletId::get().into_account_truncating(), + contribution.project_id, + contribution.contributor.clone(), + contribution.id, + ) { + Ok(_) => (), + Err(e) => Pallet::::deposit_event(Event::CTMintFailed { + project_id: contribution.project_id, + claimer: contribution.contributor.clone(), + id: contribution.id, + error: e, + }), + }; + (Weight::zero(), remaining_contributions.count() as u64) + } else { + (Weight::zero(), 0u64) + } } fn issuer_funding_payout_one_bid(project_id: T::ProjectIdentifier) -> (Weight, u64) { diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 379a3c837..3d52828a0 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -124,8 +124,7 @@ //! ensure!(project_details.status == pallet_funding::ProjectStatus::CommunityRound, "Project is not in the community round"); //! //! // Calculate how much funding was done already -//! let project_contributions: ::Balance = pallet_funding::Contributions::::iter_prefix_values(project_id) -//! .flatten() +//! let project_contributions: ::Balance = pallet_funding::Contributions::::iter_prefix_values((project_id,)) //! .fold( //! 0u64.into(), //! |total_tokens_bought, contribution| { @@ -612,9 +611,9 @@ pub mod pallet { amount: BalanceOf, caller: AccountIdOf, }, - BidCtMintFailed { + CTMintFailed { project_id: ProjectIdOf, - bidder: AccountIdOf, + claimer: AccountIdOf, id: StorageItemIdOf, error: DispatchError, }, @@ -878,6 +877,17 @@ pub mod pallet { Self::do_bid_ct_mint_for(caller, project_id, bidder, bid_id) } + #[pallet::weight(Weight::from_parts(0, 0))] + pub fn contribution_ct_mint_for( + origin: OriginFor, + project_id: T::ProjectIdentifier, + contributor: AccountIdOf, + contribution_id: T::StorageItemId, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + Self::do_contribution_ct_mint_for(caller, project_id, contributor, contribution_id) + } + /// Unbond some plmc from a contribution, after a step in the vesting period has passed. pub fn vested_plmc_bid_unbond_for( origin: OriginFor, diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index 90e3c7e33..5823931f6 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -2573,6 +2573,119 @@ mod auction_round_success { assert_eq!(minted, amount); } } + + #[test] + pub fn cannot_mint_ct_twice_manually() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = vec![]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids.clone(), + community_contributions, + remainder_contributions, + ); + let project_id = finished_project.get_project_id(); + let details = finished_project.get_project_details(); + assert_eq!(details.status, ProjectStatus::FundingSuccessful); + assert_eq!(details.cleanup, Cleaner::NotReady); + let stored_bids = + test_env.in_ext(|| Bids::::iter_prefix_values((project_id,)).collect::>()); + + test_env.advance_time(1u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Initialized(PhantomData))); + + for bid in stored_bids.clone() { + test_env.in_ext(|| { + Pallet::::bid_ct_mint_for( + RuntimeOrigin::signed(bid.bidder), + project_id, + bid.bidder, + bid.id, + ) + .unwrap(); + + assert_noop!( + Pallet::::bid_ct_mint_for( + RuntimeOrigin::signed(bid.bidder), + project_id, + bid.bidder, + bid.id, + ), + Error::::NotAllowed + ); + }); + } + } + + #[test] + pub fn cannot_mint_ct_manually_after_automatic_mint() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = vec![]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids.clone(), + community_contributions, + remainder_contributions, + ); + let project_id = finished_project.get_project_id(); + let details = finished_project.get_project_details(); + assert_eq!(details.status, ProjectStatus::FundingSuccessful); + assert_eq!(details.cleanup, Cleaner::NotReady); + + test_env.advance_time(10u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + + let stored_bids = + test_env.in_ext(|| Bids::::iter_prefix_values((project_id,)).collect::>()); + assert_eq!(stored_bids.len(), bids.len()); + let user_ct_amounts = generic_map_merge_reduce( + vec![stored_bids.clone()], + |bid| bid.bidder, + BalanceOf::::zero(), + |bid, acc| acc + bid.final_ct_amount, + ); + assert_eq!(user_ct_amounts.len(), bids.len()); + + for (bidder, amount) in user_ct_amounts { + let minted = + test_env.in_ext(|| ::ContributionTokenCurrency::balance(project_id, bidder)); + assert_eq!(minted, amount); + } + + for bid in stored_bids.clone() { + test_env.in_ext(|| { + assert_noop!( + Pallet::::bid_ct_mint_for( + RuntimeOrigin::signed(bid.bidder), + project_id, + bid.bidder, + bid.id, + ), + Error::::NotAllowed + ); + }) + } + } } mod auction_round_failure { @@ -3415,6 +3528,236 @@ mod community_round_success { assert_eq!(evaluation_locked, ::EvaluatorSlash::get() * plmc_evaluation_amount); assert_eq!(participation_locked, necessary_plmc_for_contribution); } + + #[test] + fn ct_minted_for_community_buys_automatically() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = vec![]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids, + community_contributions.clone(), + remainder_contributions, + ); + let project_id = finished_project.get_project_id(); + let details = finished_project.get_project_details(); + assert_eq!(details.status, ProjectStatus::FundingSuccessful); + assert_eq!(details.cleanup, Cleaner::NotReady); + + test_env.advance_time(10u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + + let stored_community_buys = + test_env.in_ext(|| Contributions::::iter_prefix_values((project_id,)).collect::>()); + assert_eq!(stored_community_buys.len(), community_contributions.len()); + let user_ct_amounts = generic_map_merge_reduce( + vec![stored_community_buys], + |contribution| contribution.contributor, + BalanceOf::::zero(), + |contribution, acc| acc + contribution.ct_amount, + ); + assert_eq!(user_ct_amounts.len(), community_contributions.len()); + + for (contributor, amount) in user_ct_amounts { + let minted = + test_env.in_ext(|| ::ContributionTokenCurrency::balance(project_id, contributor)); + assert_eq!(minted, amount); + } + } + + #[test] + fn ct_minted_for_community_buys_manually() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = vec![]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids, + community_contributions.clone(), + remainder_contributions, + ); + let project_id = finished_project.get_project_id(); + let details = finished_project.get_project_details(); + assert_eq!(details.status, ProjectStatus::FundingSuccessful); + assert_eq!(details.cleanup, Cleaner::NotReady); + let stored_contributions = + test_env.in_ext(|| Contributions::::iter_prefix_values((project_id,)).collect::>()); + + for contribution in stored_contributions.clone() { + test_env.in_ext(|| { + assert_noop!( + Pallet::::contribution_ct_mint_for( + RuntimeOrigin::signed(contribution.contributor), + project_id, + contribution.contributor, + contribution.id, + ), + Error::::CannotClaimYet + ); + }) + } + test_env.advance_time(1u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Initialized(PhantomData))); + + for contribution in stored_contributions.clone() { + test_env.in_ext(|| { + Pallet::::contribution_ct_mint_for( + RuntimeOrigin::signed(contribution.contributor), + project_id, + contribution.contributor, + contribution.id, + ) + .unwrap() + }); + } + + assert_eq!(stored_contributions.len(), community_contributions.len()); + let user_ct_amounts = generic_map_merge_reduce( + vec![stored_contributions], + |contribution| contribution.contributor, + BalanceOf::::zero(), + |contribution, acc| acc + contribution.ct_amount + ); + assert_eq!(user_ct_amounts.len(), community_contributions.len()); + + for (contributor, amount) in user_ct_amounts { + let minted = + test_env.in_ext(|| ::ContributionTokenCurrency::balance(project_id, contributor)); + assert_eq!(minted, amount); + } + } + + #[test] + pub fn cannot_mint_ct_twice_manually() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = vec![]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids, + community_contributions.clone(), + remainder_contributions, + ); + let project_id = finished_project.get_project_id(); + let details = finished_project.get_project_details(); + assert_eq!(details.status, ProjectStatus::FundingSuccessful); + assert_eq!(details.cleanup, Cleaner::NotReady); + let stored_contributions = + test_env.in_ext(|| Contributions::::iter_prefix_values((project_id,)).collect::>()); + + test_env.advance_time(1u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Initialized(PhantomData))); + + for contribution in stored_contributions.clone() { + test_env.in_ext(|| { + Pallet::::contribution_ct_mint_for( + RuntimeOrigin::signed(contribution.contributor), + project_id, + contribution.contributor, + contribution.id, + ) + .unwrap(); + + assert_noop!( + Pallet::::contribution_ct_mint_for( + RuntimeOrigin::signed(contribution.contributor), + project_id, + contribution.contributor, + contribution.id, + ), + Error::::NotAllowed + ); + }); + } + } + + #[test] + pub fn cannot_mint_ct_manually_after_automatic_mint() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = vec![]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids, + community_contributions.clone(), + remainder_contributions, + ); + let project_id = finished_project.get_project_id(); + let details = finished_project.get_project_details(); + assert_eq!(details.status, ProjectStatus::FundingSuccessful); + assert_eq!(details.cleanup, Cleaner::NotReady); + + test_env.advance_time(10u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + + let stored_contributions = + test_env.in_ext(|| Contributions::::iter_prefix_values((project_id,)).collect::>()); + assert_eq!(stored_contributions.len(), community_contributions.len()); + let user_ct_amounts = generic_map_merge_reduce( + vec![stored_contributions.clone()], + |contribution| contribution.contributor, + BalanceOf::::zero(), + |contribution, acc| acc + contribution.ct_amount, + ); + assert_eq!(user_ct_amounts.len(), community_contributions.len()); + + for (contributor, amount) in user_ct_amounts { + let minted = + test_env.in_ext(|| ::ContributionTokenCurrency::balance(project_id, contributor)); + assert_eq!(minted, amount); + } + + for contribution in stored_contributions.clone() { + test_env.in_ext(|| { + assert_noop!( + Pallet::::contribution_ct_mint_for( + RuntimeOrigin::signed(contribution.contributor), + project_id, + contribution.contributor, + contribution.id, + ), + Error::::NotAllowed + ); + }) + } + } } mod community_round_failure { diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index be2c0df62..6764d9c91 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -251,6 +251,7 @@ pub mod storage_types { pub plmc_vesting_period: PLMCVesting, pub ct_vesting_period: CTVesting, pub funds_released: bool, + pub ct_minted: bool, } } From 3013eb3be72dd4544f40334a7220da4a7888c49a Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Thu, 3 Aug 2023 14:05:25 +0200 Subject: [PATCH 12/13] feat: ct minting for remainder buys --- pallets/funding/src/functions.rs | 3 +- pallets/funding/src/tests.rs | 442 ++++++++++++++++++++++++++++++- 2 files changed, 430 insertions(+), 15 deletions(-) diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index ceca9c95d..9aaf7d708 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -1247,7 +1247,8 @@ impl Pallet { contribution_id: T::StorageItemId, ) -> DispatchResult { // * Get variables * - let mut contribution = Contributions::::get((project_id, contributor.clone(), contribution_id)).ok_or(Error::::BidNotFound)?; + let mut contribution = Contributions::::get((project_id, contributor.clone(), contribution_id)) + .ok_or(Error::::BidNotFound)?; let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectNotFound)?; let ct_amount = contribution.ct_amount; diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index 5823931f6..6cf0d3ddf 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -1007,6 +1007,9 @@ impl<'a> RemainderFundingProject<'a> { let community_funding_project = CommunityFundingProject::new_with(test_env, project_metadata, issuer, evaluations.clone(), bids.clone()); + if contributions.is_empty() { + return community_funding_project.start_remainder_or_end_funding() + } let project_id = community_funding_project.get_project_id(); let ct_price = community_funding_project.get_project_details().weighted_average_price.unwrap(); let contributors = contributions.iter().map(|cont| cont.contributor).collect::>(); @@ -2612,7 +2615,7 @@ mod auction_round_success { bid.bidder, bid.id, ) - .unwrap(); + .unwrap(); assert_noop!( Pallet::::bid_ct_mint_for( @@ -3569,8 +3572,8 @@ mod community_round_success { assert_eq!(user_ct_amounts.len(), community_contributions.len()); for (contributor, amount) in user_ct_amounts { - let minted = - test_env.in_ext(|| ::ContributionTokenCurrency::balance(project_id, contributor)); + let minted = test_env + .in_ext(|| ::ContributionTokenCurrency::balance(project_id, contributor)); assert_eq!(minted, amount); } } @@ -3626,7 +3629,7 @@ mod community_round_success { contribution.contributor, contribution.id, ) - .unwrap() + .unwrap() }); } @@ -3635,13 +3638,13 @@ mod community_round_success { vec![stored_contributions], |contribution| contribution.contributor, BalanceOf::::zero(), - |contribution, acc| acc + contribution.ct_amount + |contribution, acc| acc + contribution.ct_amount, ); assert_eq!(user_ct_amounts.len(), community_contributions.len()); for (contributor, amount) in user_ct_amounts { - let minted = - test_env.in_ext(|| ::ContributionTokenCurrency::balance(project_id, contributor)); + let minted = test_env + .in_ext(|| ::ContributionTokenCurrency::balance(project_id, contributor)); assert_eq!(minted, amount); } } @@ -3684,7 +3687,7 @@ mod community_round_success { contribution.contributor, contribution.id, ) - .unwrap(); + .unwrap(); assert_noop!( Pallet::::contribution_ct_mint_for( @@ -3739,8 +3742,8 @@ mod community_round_success { assert_eq!(user_ct_amounts.len(), community_contributions.len()); for (contributor, amount) in user_ct_amounts { - let minted = - test_env.in_ext(|| ::ContributionTokenCurrency::balance(project_id, contributor)); + let minted = test_env + .in_ext(|| ::ContributionTokenCurrency::balance(project_id, contributor)); assert_eq!(minted, amount); } @@ -3766,6 +3769,7 @@ mod community_round_failure { mod remainder_round_success { use super::*; + use crate::tests::testing_macros::{extract_from_event, find_event}; #[test] fn remainder_round_works() { @@ -3995,6 +3999,352 @@ mod remainder_round_success { remainder_funding_project.get_project_id(), ); } + + #[test] + fn ct_minted_for_remainder_buys_automatically() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = + vec![(EVALUATOR_1, 50_000 * PLMC), (EVALUATOR_2, 25_000 * PLMC), (EVALUATOR_3, 32_000 * PLMC)]; + let bids = vec![ + TestBid::new(BIDDER_1, 50000 * ASSET_UNIT, 18_u128.into(), None, AcceptedFundingAsset::USDT), + TestBid::new(BIDDER_2, 40000 * ASSET_UNIT, 15_u128.into(), None, AcceptedFundingAsset::USDT), + ]; + let community_contributions = vec![ + TestContribution::new(BUYER_1, 100 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + TestContribution::new(BUYER_2, 200 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + TestContribution::new(BUYER_3, 2000 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + ]; + let remainder_contributions = vec![ + TestContribution::new(EVALUATOR_2, 300 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + TestContribution::new(BUYER_2, 600 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + TestContribution::new(BIDDER_1, 4000 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + ]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids, + community_contributions, + remainder_contributions.clone(), + ); + let project_id = finished_project.get_project_id(); + let details = finished_project.get_project_details(); + assert_eq!(details.status, ProjectStatus::FundingSuccessful); + assert_eq!(details.cleanup, Cleaner::NotReady); + + test_env.advance_time(10u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + + let evaluator_2_reward = extract_from_event!( + &test_env, + Event::::EvaluationRewarded { evaluator: EVALUATOR_2, amount, .. }, + amount + ) + .unwrap(); + + let total_remainder_participant_ct_amounts = vec![ + (EVALUATOR_2, 300 * ASSET_UNIT + evaluator_2_reward), + (BUYER_2, 600 * ASSET_UNIT + 200 * ASSET_UNIT), + (BIDDER_1, 50000 * ASSET_UNIT + 4000 * ASSET_UNIT), + ]; + for (contributor, amount) in total_remainder_participant_ct_amounts { + let minted = test_env + .in_ext(|| ::ContributionTokenCurrency::balance(project_id, contributor)); + assert_eq!(minted, amount); + } + } + + #[test] + fn ct_minted_for_community_buys_manually() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = + vec![(EVALUATOR_1, 50_000 * PLMC), (EVALUATOR_2, 25_000 * PLMC), (EVALUATOR_3, 32_000 * PLMC)]; + let bids = vec![ + TestBid::new(BIDDER_1, 50000 * ASSET_UNIT, 18_u128.into(), None, AcceptedFundingAsset::USDT), + TestBid::new(BIDDER_2, 40000 * ASSET_UNIT, 15_u128.into(), None, AcceptedFundingAsset::USDT), + ]; + let community_contributions = vec![ + TestContribution::new(BUYER_1, 100 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + TestContribution::new(BUYER_2, 200 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + TestContribution::new(BUYER_3, 2000 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + ]; + let remainder_contributions = vec![ + TestContribution::new(EVALUATOR_2, 300 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + TestContribution::new(BUYER_2, 600 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + TestContribution::new(BIDDER_1, 4000 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + ]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids, + community_contributions, + remainder_contributions.clone(), + ); + let project_id = finished_project.get_project_id(); + let details = finished_project.get_project_details(); + assert_eq!(details.status, ProjectStatus::FundingSuccessful); + assert_eq!(details.cleanup, Cleaner::NotReady); + + let stored_contributions = test_env.in_ext(|| { + let evaluator_contribution = + Contributions::::iter_prefix_values((project_id, EVALUATOR_2)).next().unwrap(); + let buyer_contribution = + Contributions::::iter_prefix_values((project_id, BUYER_2)).next().unwrap(); + let bidder_contribution = + Contributions::::iter_prefix_values((project_id, BIDDER_1)).next().unwrap(); + vec![evaluator_contribution.clone(), buyer_contribution.clone(), bidder_contribution.clone()] + }); + for contribution in stored_contributions.clone() { + test_env.in_ext(|| { + assert_noop!( + Pallet::::contribution_ct_mint_for( + RuntimeOrigin::signed(contribution.contributor), + project_id, + contribution.contributor, + contribution.id, + ), + Error::::CannotClaimYet + ); + }) + } + test_env.advance_time(1u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Initialized(PhantomData))); + + for contribution in stored_contributions.clone() { + test_env.in_ext(|| { + Pallet::::contribution_ct_mint_for( + RuntimeOrigin::signed(contribution.contributor), + project_id, + contribution.contributor, + contribution.id, + ) + .unwrap() + }); + } + + test_env.advance_time(10u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + + let evaluator_2_reward = extract_from_event!( + &test_env, + Event::::EvaluationRewarded { evaluator: EVALUATOR_2, amount, .. }, + amount + ) + .unwrap(); + + let total_remainder_participant_ct_amounts = vec![ + (EVALUATOR_2, 300 * ASSET_UNIT + evaluator_2_reward), + (BUYER_2, 600 * ASSET_UNIT + 200 * ASSET_UNIT), + (BIDDER_1, 50000 * ASSET_UNIT + 4000 * ASSET_UNIT), + ]; + for (contributor, amount) in total_remainder_participant_ct_amounts { + let minted = test_env + .in_ext(|| ::ContributionTokenCurrency::balance(project_id, contributor)); + assert_eq!(minted, amount); + } + } + + #[test] + pub fn cannot_mint_ct_twice_manually() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = + vec![(EVALUATOR_1, 50_000 * PLMC), (EVALUATOR_2, 25_000 * PLMC), (EVALUATOR_3, 32_000 * PLMC)]; + let bids = vec![ + TestBid::new(BIDDER_1, 50000 * ASSET_UNIT, 18_u128.into(), None, AcceptedFundingAsset::USDT), + TestBid::new(BIDDER_2, 40000 * ASSET_UNIT, 15_u128.into(), None, AcceptedFundingAsset::USDT), + ]; + let community_contributions = vec![ + TestContribution::new(BUYER_1, 100 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + TestContribution::new(BUYER_2, 200 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + TestContribution::new(BUYER_3, 2000 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + ]; + let remainder_contributions = vec![ + TestContribution::new(EVALUATOR_2, 300 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + TestContribution::new(BUYER_2, 600 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + TestContribution::new(BIDDER_1, 4000 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + ]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids, + community_contributions, + remainder_contributions.clone(), + ); + let project_id = finished_project.get_project_id(); + let details = finished_project.get_project_details(); + assert_eq!(details.status, ProjectStatus::FundingSuccessful); + assert_eq!(details.cleanup, Cleaner::NotReady); + + let stored_contributions = test_env.in_ext(|| { + let evaluator_contribution = + Contributions::::iter_prefix_values((project_id, EVALUATOR_2)).next().unwrap(); + let buyer_contribution = + Contributions::::iter_prefix_values((project_id, BUYER_2)).next().unwrap(); + let bidder_contribution = + Contributions::::iter_prefix_values((project_id, BIDDER_1)).next().unwrap(); + vec![evaluator_contribution.clone(), buyer_contribution.clone(), bidder_contribution.clone()] + }); + for contribution in stored_contributions.clone() { + test_env.in_ext(|| { + assert_noop!( + Pallet::::contribution_ct_mint_for( + RuntimeOrigin::signed(contribution.contributor), + project_id, + contribution.contributor, + contribution.id, + ), + Error::::CannotClaimYet + ); + }) + } + test_env.advance_time(1u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Initialized(PhantomData))); + + for contribution in stored_contributions.clone() { + test_env.in_ext(|| { + Pallet::::contribution_ct_mint_for( + RuntimeOrigin::signed(contribution.contributor), + project_id, + contribution.contributor, + contribution.id, + ) + .unwrap(); + + assert_noop!( + Pallet::::contribution_ct_mint_for( + RuntimeOrigin::signed(contribution.contributor), + project_id, + contribution.contributor, + contribution.id, + ), + Error::::NotAllowed + ); + }); + } + + test_env.advance_time(10u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + + let evaluator_2_reward = extract_from_event!( + &test_env, + Event::::EvaluationRewarded { evaluator: EVALUATOR_2, amount, .. }, + amount + ) + .unwrap(); + + let total_remainder_participant_ct_amounts = vec![ + (EVALUATOR_2, 300 * ASSET_UNIT + evaluator_2_reward), + (BUYER_2, 600 * ASSET_UNIT + 200 * ASSET_UNIT), + (BIDDER_1, 50000 * ASSET_UNIT + 4000 * ASSET_UNIT), + ]; + for (contributor, amount) in total_remainder_participant_ct_amounts { + let minted = test_env + .in_ext(|| ::ContributionTokenCurrency::balance(project_id, contributor)); + assert_eq!(minted, amount); + } + } + + #[test] + pub fn cannot_mint_ct_manually_after_automatic_mint() { + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = + vec![(EVALUATOR_1, 50_000 * PLMC), (EVALUATOR_2, 25_000 * PLMC), (EVALUATOR_3, 32_000 * PLMC)]; + let bids = vec![ + TestBid::new(BIDDER_1, 50000 * ASSET_UNIT, 18_u128.into(), None, AcceptedFundingAsset::USDT), + TestBid::new(BIDDER_2, 40000 * ASSET_UNIT, 15_u128.into(), None, AcceptedFundingAsset::USDT), + ]; + let community_contributions = vec![ + TestContribution::new(BUYER_1, 100 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + TestContribution::new(BUYER_2, 200 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + TestContribution::new(BUYER_3, 2000 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + ]; + let remainder_contributions = vec![ + TestContribution::new(EVALUATOR_2, 300 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + TestContribution::new(BUYER_2, 600 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + TestContribution::new(BIDDER_1, 4000 * ASSET_UNIT, None, AcceptedFundingAsset::USDT), + ]; + + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids, + community_contributions, + remainder_contributions.clone(), + ); + let project_id = finished_project.get_project_id(); + let details = finished_project.get_project_details(); + assert_eq!(details.status, ProjectStatus::FundingSuccessful); + assert_eq!(details.cleanup, Cleaner::NotReady); + + let stored_contributions = test_env.in_ext(|| { + let evaluator_contribution = + Contributions::::iter_prefix_values((project_id, EVALUATOR_2)).next().unwrap(); + let buyer_contribution = + Contributions::::iter_prefix_values((project_id, BUYER_2)).next().unwrap(); + let bidder_contribution = + Contributions::::iter_prefix_values((project_id, BIDDER_1)).next().unwrap(); + vec![evaluator_contribution.clone(), buyer_contribution.clone(), bidder_contribution.clone()] + }); + + test_env.advance_time(10u64).unwrap(); + let details = finished_project.get_project_details(); + assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + + let evaluator_2_reward = extract_from_event!( + &test_env, + Event::::EvaluationRewarded { evaluator: EVALUATOR_2, amount, .. }, + amount + ) + .unwrap(); + + let total_remainder_participant_ct_amounts = vec![ + (EVALUATOR_2, 300 * ASSET_UNIT + evaluator_2_reward), + (BUYER_2, 600 * ASSET_UNIT + 200 * ASSET_UNIT), + (BIDDER_1, 50000 * ASSET_UNIT + 4000 * ASSET_UNIT), + ]; + for (contributor, amount) in total_remainder_participant_ct_amounts { + let minted = test_env + .in_ext(|| ::ContributionTokenCurrency::balance(project_id, contributor)); + assert_eq!(minted, amount); + } + + for contribution in stored_contributions.clone() { + test_env.in_ext(|| { + assert_noop!( + Pallet::::contribution_ct_mint_for( + RuntimeOrigin::signed(contribution.contributor), + project_id, + contribution.contributor, + contribution.id, + ), + Error::::NotAllowed + ); + }); + } + } } mod funding_end { @@ -4583,8 +4933,12 @@ mod test_helper_functions { mod misc_features { use super::*; - use crate::UpdateType::{CommunityFundingStart, RemainderFundingStart}; + use crate::{ + tests::testing_macros::{extract_from_event, find_event}, + UpdateType::{CommunityFundingStart, RemainderFundingStart}, + }; use sp_arithmetic::Percent; + use std::assert_matches::assert_matches; #[test] fn remove_from_update_store_works() { @@ -4609,10 +4963,31 @@ mod misc_features { #[test] fn sandbox() { - let percent = Percent::from_percent(120); - let _x = percent * 100u64; + let test_env = TestEnvironment::new(); + let issuer = ISSUER; + let project = default_project(test_env.get_new_nonce()); + let evaluations = default_evaluations(); + let bids = default_bids(); + let community_contributions = default_community_buys(); + let remainder_contributions = vec![]; - assert!(true) + let finished_project = FinishedProject::new_with( + &test_env, + project, + issuer, + evaluations, + bids, + community_contributions.clone(), + remainder_contributions, + ); + let events = test_env.in_ext(|| System::events()); + let len = events.len(); + assert!(len > 0); + let event = find_event!(&test_env, Event::::Bid { project_id: 0, amount, .. }).unwrap(); + assert_matches!(event, Event::::Bid { .. }); + let e = + extract_from_event!(&test_env, Event::::Bid { project_id: 0, amount, .. }, amount).unwrap(); + assert_eq!(e, 100); } } @@ -4637,4 +5012,43 @@ mod testing_macros { }; } pub(crate) use call_and_is_ok; + + macro_rules! find_event { + ($env: expr, $pattern:pat) => { + $env.ext_env.borrow_mut().execute_with(|| { + let events = System::events(); + + events.iter().find_map(|event_record| { + if let frame_system::EventRecord { + event: RuntimeEvent::FundingModule(desired_event @ $pattern), + .. + } = event_record + { + Some(desired_event.clone()) + } else { + None + } + }) + }) + }; + } + pub(crate) use find_event; + + macro_rules! extract_from_event { + ($env: expr, $pattern:pat, $field:ident) => { + $env.ext_env.borrow_mut().execute_with(|| { + let events = System::events(); + + events.iter().find_map(|event_record| { + if let frame_system::EventRecord { event: RuntimeEvent::FundingModule($pattern), .. } = event_record + { + Some($field.clone()) + } else { + None + } + }) + }) + }; + } + pub(crate) use extract_from_event; } From c4c6607466202f6958b04d2de3bf3b91f4676527 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Thu, 3 Aug 2023 14:11:11 +0200 Subject: [PATCH 13/13] chore: imports and fmt --- pallets/funding/src/tests.rs | 38 +++++------------------------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index 6cf0d3ddf..9450d3ada 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -3769,7 +3769,7 @@ mod community_round_failure { mod remainder_round_success { use super::*; - use crate::tests::testing_macros::{extract_from_event, find_event}; + use crate::tests::testing_macros::extract_from_event; #[test] fn remainder_round_works() { @@ -4933,12 +4933,7 @@ mod test_helper_functions { mod misc_features { use super::*; - use crate::{ - tests::testing_macros::{extract_from_event, find_event}, - UpdateType::{CommunityFundingStart, RemainderFundingStart}, - }; - use sp_arithmetic::Percent; - use std::assert_matches::assert_matches; + use crate::UpdateType::{CommunityFundingStart, RemainderFundingStart}; #[test] fn remove_from_update_store_works() { @@ -4963,36 +4958,11 @@ mod misc_features { #[test] fn sandbox() { - let test_env = TestEnvironment::new(); - let issuer = ISSUER; - let project = default_project(test_env.get_new_nonce()); - let evaluations = default_evaluations(); - let bids = default_bids(); - let community_contributions = default_community_buys(); - let remainder_contributions = vec![]; - - let finished_project = FinishedProject::new_with( - &test_env, - project, - issuer, - evaluations, - bids, - community_contributions.clone(), - remainder_contributions, - ); - let events = test_env.in_ext(|| System::events()); - let len = events.len(); - assert!(len > 0); - let event = find_event!(&test_env, Event::::Bid { project_id: 0, amount, .. }).unwrap(); - assert_matches!(event, Event::::Bid { .. }); - let e = - extract_from_event!(&test_env, Event::::Bid { project_id: 0, amount, .. }, amount).unwrap(); - assert_eq!(e, 100); + assert!(true); } } mod testing_macros { - #[allow(unused_macros)] macro_rules! assert_close_enough { ($real:expr, $desired:expr, $max_approximation:expr) => { let real_parts = Perquintill::from_rational($real, $desired); @@ -5013,6 +4983,7 @@ mod testing_macros { } pub(crate) use call_and_is_ok; + #[allow(unused_macros)] macro_rules! find_event { ($env: expr, $pattern:pat) => { $env.ext_env.borrow_mut().execute_with(|| { @@ -5032,6 +5003,7 @@ mod testing_macros { }) }; } + #[allow(unused_imports)] pub(crate) use find_event; macro_rules! extract_from_event {