diff --git a/integration-tests/src/tests/ct_migration.rs b/integration-tests/src/tests/ct_migration.rs index 281cf7004..c7f25c0ca 100644 --- a/integration-tests/src/tests/ct_migration.rs +++ b/integration-tests/src/tests/ct_migration.rs @@ -174,7 +174,10 @@ fn create_settled_project() -> (ProjectId, Vec) { default_community_contributions(), default_remainder_contributions(), ); - assert_eq!(inst.go_to_next_state(project_id), pallet_funding::ProjectStatus::SettlementStarted(FundingOutcome::Success)); + assert_eq!( + inst.go_to_next_state(project_id), + pallet_funding::ProjectStatus::SettlementStarted(FundingOutcome::Success) + ); let mut participants: Vec = pallet_funding::Evaluations::::iter_prefix_values((project_id,)) .map(|eval| eval.evaluator) @@ -229,7 +232,10 @@ fn create_project_with_unsettled_participation(participation_type: Participation default_remainder_contributions(), ); - assert_eq!(inst.go_to_next_state(project_id), pallet_funding::ProjectStatus::SettlementStarted(FundingOutcome::Success)); + assert_eq!( + inst.go_to_next_state(project_id), + pallet_funding::ProjectStatus::SettlementStarted(FundingOutcome::Success) + ); let evaluations_to_settle = pallet_funding::Evaluations::::iter_prefix_values((project_id,)).collect_vec(); let bids_to_settle = pallet_funding::Bids::::iter_prefix_values((project_id,)).collect_vec(); @@ -258,13 +264,7 @@ fn create_project_with_unsettled_participation(participation_type: Participation let start = if participation_type == ParticipationType::Bid { 1 } else { 0 }; for bid in bids_to_settle[start..].iter() { - PolimecFunding::settle_bid( - RuntimeOrigin::signed(alice()), - project_id, - bid.bidder.clone(), - bid.id, - ) - .unwrap() + PolimecFunding::settle_bid(RuntimeOrigin::signed(alice()), project_id, bid.bidder.clone(), bid.id).unwrap() } let start = if participation_type == ParticipationType::Contribution { 1 } else { 0 }; diff --git a/pallets/funding/src/functions/2_evaluation.rs b/pallets/funding/src/functions/2_evaluation.rs index 8c5f10acb..47138043d 100644 --- a/pallets/funding/src/functions/2_evaluation.rs +++ b/pallets/funding/src/functions/2_evaluation.rs @@ -37,7 +37,7 @@ impl Pallet { project_details, ProjectStatus::Application, ProjectStatus::EvaluationRound, - T::EvaluationDuration::get(), + Some(T::EvaluationDuration::get()), false, ) } @@ -91,7 +91,7 @@ impl Pallet { project_details, ProjectStatus::EvaluationRound, ProjectStatus::AuctionInitializePeriod, - T::AuctionInitializePeriodDuration::get(), + Some(T::AuctionInitializePeriodDuration::get()), false, ) // Unsuccessful path @@ -104,7 +104,7 @@ impl Pallet { project_details, ProjectStatus::EvaluationRound, ProjectStatus::FundingFailed, - One::one(), + Some(One::one()), false, ) } diff --git a/pallets/funding/src/functions/3_auction.rs b/pallets/funding/src/functions/3_auction.rs index f4b1b9653..ab2edacd9 100644 --- a/pallets/funding/src/functions/3_auction.rs +++ b/pallets/funding/src/functions/3_auction.rs @@ -35,7 +35,7 @@ impl Pallet { project_details, ProjectStatus::AuctionInitializePeriod, ProjectStatus::AuctionRound, - T::AuctionOpeningDuration::get(), + Some(T::AuctionOpeningDuration::get()), skip_round_end_check, ) } @@ -72,7 +72,7 @@ impl Pallet { updated_project_details, ProjectStatus::AuctionRound, ProjectStatus::CommunityRound(now.saturating_add(T::CommunityFundingDuration::get())), - T::CommunityFundingDuration::get() + T::RemainderFundingDuration::get(), + Some(T::CommunityFundingDuration::get() + T::RemainderFundingDuration::get()), false, )?; Ok(PostDispatchInfo { diff --git a/pallets/funding/src/functions/5_funding_end.rs b/pallets/funding/src/functions/5_funding_end.rs index f9671d72f..ee0314356 100644 --- a/pallets/funding/src/functions/5_funding_end.rs +++ b/pallets/funding/src/functions/5_funding_end.rs @@ -73,10 +73,14 @@ impl Pallet { ) }; - let round_end = now.saturating_add(duration).saturating_sub(One::one()); - project_details.round_duration.update(Some(now), Some(round_end)); - project_details.status = next_status; - ProjectsDetails::::insert(project_id, project_details); + Self::transition_project( + project_id, + project_details.clone(), + project_details.status, + next_status, + Some(duration), + true, + )?; Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes }) } diff --git a/pallets/funding/src/functions/6_settlement.rs b/pallets/funding/src/functions/6_settlement.rs index 916d9e510..f76540e71 100644 --- a/pallets/funding/src/functions/6_settlement.rs +++ b/pallets/funding/src/functions/6_settlement.rs @@ -4,8 +4,8 @@ use frame_support::{ dispatch::DispatchResult, ensure, traits::{ - fungible::{MutateHold as FungibleMutateHold}, - fungibles::{Mutate as FungiblesMutate}, + fungible::MutateHold as FungibleMutateHold, + fungibles::Mutate as FungiblesMutate, tokens::{Fortitude, Precision, Preservation, Restriction}, Get, }, @@ -22,22 +22,11 @@ use sp_runtime::{ impl Pallet { #[transactional] pub fn do_start_settlement(project_id: ProjectId) -> DispatchResultWithPostInfo { - // * Get variables * let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; let token_information = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?.token_information; let now = >::block_number(); - let round_end_block = project_details.round_duration.end().ok_or(Error::::ImpossibleState)?; - // * Validity checks * - ensure!( - project_details.status == ProjectStatus::FundingSuccessful || - project_details.status == ProjectStatus::FundingFailed, - Error::::IncorrectRound - ); - ensure!(now > round_end_block, Error::::TooEarlyForRound); - - // * Calculate new variables * project_details.funding_end_block = Some(now); let escrow_account = Self::fund_account_id(project_id); @@ -72,16 +61,28 @@ impl Pallet { liquidity_pools_ct_amount, )?; - project_details.status = ProjectStatus::SettlementStarted(FundingOutcome::Success); - ProjectsDetails::::insert(project_id, &project_details); + Self::transition_project( + project_id, + project_details, + ProjectStatus::FundingSuccessful, + ProjectStatus::SettlementStarted(FundingOutcome::Success), + None, + false, + )?; Ok(PostDispatchInfo { actual_weight: Some(WeightInfoOf::::start_settlement_funding_success()), pays_fee: Pays::Yes, }) } else { - project_details.status = ProjectStatus::SettlementStarted(FundingOutcome::Failure); - ProjectsDetails::::insert(project_id, &project_details); + Self::transition_project( + project_id, + project_details, + ProjectStatus::FundingFailed, + ProjectStatus::SettlementStarted(FundingOutcome::Failure), + None, + false, + )?; Ok(PostDispatchInfo { actual_weight: Some(WeightInfoOf::::start_settlement_funding_failure()), @@ -153,8 +154,8 @@ impl Pallet { Error::::SettlementNotStarted ); - // Return either the full amount to refund if bid is rejected/project failed, - // or a partial amount when the wap > paid price/bid is partially accepted + // Return either the full amount to refund if bid is rejected/project failed, + // or a partial amount when the wap > paid price/bid is partially accepted let (final_ct_price, final_ct_amount, refunded_plmc, refunded_funding_asset_amount) = Self::calculate_refund(&bid, funding_success, wap)?; @@ -305,9 +306,9 @@ impl Pallet { } pub fn do_mark_project_as_settled(project_id: ProjectId) -> DispatchResult { - let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; let outcome = match project_details.status { - ProjectStatus::SettlementStarted(outcome) => outcome, + ProjectStatus::SettlementStarted(ref outcome) => outcome.clone(), _ => return Err(Error::::IncorrectRound.into()), }; @@ -323,8 +324,14 @@ impl Pallet { ); // Mark the project as settled - project_details.status = ProjectStatus::SettlementFinished(outcome); - ProjectsDetails::::insert(project_id, project_details); + Self::transition_project( + project_id, + project_details, + ProjectStatus::SettlementStarted(outcome.clone()), + ProjectStatus::SettlementFinished(outcome), + None, + false, + )?; Ok(()) } diff --git a/pallets/funding/src/functions/7_ct_migration.rs b/pallets/funding/src/functions/7_ct_migration.rs index b09ec7e78..310fcd6f5 100644 --- a/pallets/funding/src/functions/7_ct_migration.rs +++ b/pallets/funding/src/functions/7_ct_migration.rs @@ -8,18 +8,18 @@ impl Pallet { let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; ensure!(project_details.issuer_account == caller, Error::::NotIssuer); - match project_details.status { - ProjectStatus::SettlementFinished(FundingOutcome::Success) => (), - ProjectStatus::FundingSuccessful | ProjectStatus::SettlementStarted(FundingOutcome::Success) => - return Err(Error::::SettlementNotComplete.into()), - _ => return Err(Error::::IncorrectRound.into()), - } project_details.migration_type = Some(MigrationType::Offchain); - project_details.status = ProjectStatus::CTMigrationStarted; - ProjectsDetails::::insert(project_id, project_details); - // * Emit events * + Self::transition_project( + project_id, + project_details, + ProjectStatus::SettlementFinished(FundingOutcome::Success), + ProjectStatus::CTMigrationStarted, + None, + false, + )?; + Ok(()) } @@ -72,11 +72,15 @@ impl Pallet { migration_readiness_check: None, }; project_details.migration_type = Some(MigrationType::Pallet(parachain_receiver_pallet_info)); - project_details.status = ProjectStatus::CTMigrationStarted; - ProjectsDetails::::insert(project_id, project_details); - // * Emit events * - Self::deposit_event(Event::PalletMigrationStarted { project_id, para_id }); + Self::transition_project( + project_id, + project_details, + ProjectStatus::SettlementFinished(FundingOutcome::Success), + ProjectStatus::CTMigrationStarted, + None, + false, + )?; Ok(()) } diff --git a/pallets/funding/src/functions/misc.rs b/pallets/funding/src/functions/misc.rs index a09e7e86b..0a4ca2d6b 100644 --- a/pallets/funding/src/functions/misc.rs +++ b/pallets/funding/src/functions/misc.rs @@ -393,7 +393,7 @@ impl Pallet { mut project_details: ProjectDetailsOf, current_round: ProjectStatus>, next_round: ProjectStatus>, - round_duration: BlockNumberFor, + maybe_round_duration: Option>, skip_end_check: bool, ) -> DispatchResult { /* Verify */ @@ -401,15 +401,19 @@ impl Pallet { ensure!(project_details.round_duration.ended(now) || skip_end_check, Error::::TooEarlyForRound); ensure!(project_details.status == current_round, Error::::IncorrectRound); - let round_end = now.saturating_add(round_duration).saturating_sub(One::one()); - project_details.round_duration.update(Some(now), Some(round_end)); - project_details.status = next_round; + let round_end = if let Some(round_duration) = maybe_round_duration { + Some(now.saturating_add(round_duration).saturating_sub(One::one())) + } else { + None + }; + project_details.round_duration.update(Some(now), round_end); + project_details.status = next_round.clone(); // * Update storage * ProjectsDetails::::insert(project_id, project_details); - // // * Emit events * - // Self::deposit_event(Event::ProjectPhaseTransition { project_id, phase: next_round }); + // * Emit events * + Self::deposit_event(Event::ProjectPhaseTransition { project_id, phase: next_round }); Ok(()) } diff --git a/pallets/funding/src/instantiator/mod.rs b/pallets/funding/src/instantiator/mod.rs index 4bddb819c..a5291fa91 100644 --- a/pallets/funding/src/instantiator/mod.rs +++ b/pallets/funding/src/instantiator/mod.rs @@ -39,9 +39,7 @@ use sp_arithmetic::{ traits::{SaturatedConversion, Saturating, Zero}, FixedPointNumber, Percent, Perquintill, }; -use sp_runtime::{ - traits::{Convert, Member, One}, -}; +use sp_runtime::traits::{Convert, Member, One}; use sp_std::{ cell::RefCell, collections::{btree_map::*, btree_set::*}, diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index dd0a01b98..952c323d4 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -574,7 +574,7 @@ pub mod pallet { /// Project transitioned to a new phase. ProjectPhaseTransition { project_id: ProjectId, - phase: ProjectPhases, + phase: ProjectStatus>, }, /// A `bonder` bonded an `amount` of PLMC for `project_id`. Evaluation { diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs index 7e35cda90..899f665cd 100644 --- a/pallets/funding/src/mock.rs +++ b/pallets/funding/src/mock.rs @@ -292,15 +292,16 @@ impl pallet_timestamp::Config for TestRuntime { pub const HOURS: BlockNumber = 300u64; // REMARK: In the production configuration we use DAYS instead of HOURS. +// We need all durations to use different times to catch bugs in the tests. parameter_types! { pub const EvaluationDuration: BlockNumber = 10u64; - pub const AuctionInitializePeriodDuration: BlockNumber = 10u64; - pub const AuctionOpeningDuration: BlockNumber = 10u64; - pub const AuctionClosingDuration: BlockNumber = 10u64; - pub const CommunityRoundDuration: BlockNumber = 10u64; - pub const RemainderFundingDuration: BlockNumber = 10u64; - pub const ManualAcceptanceDuration: BlockNumber = 10u64; - pub const SuccessToSettlementTime: BlockNumber = 10u64; + pub const AuctionInitializePeriodDuration: BlockNumber = 11u64; + pub const AuctionOpeningDuration: BlockNumber = 12u64; + pub const AuctionClosingDuration: BlockNumber = 13u64; + pub const CommunityRoundDuration: BlockNumber = 14u64; + pub const RemainderFundingDuration: BlockNumber = 15u64; + pub const ManualAcceptanceDuration: BlockNumber = 16u64; + pub const SuccessToSettlementTime: BlockNumber = 17u64; pub const FundingPalletId: PalletId = PalletId(*b"py/cfund"); pub FeeBrackets: Vec<(Percent, Balance)> = vec![ diff --git a/pallets/funding/src/runtime_api.rs b/pallets/funding/src/runtime_api.rs index 242c50df3..61edaa376 100644 --- a/pallets/funding/src/runtime_api.rs +++ b/pallets/funding/src/runtime_api.rs @@ -70,9 +70,7 @@ impl Pallet { pub fn top_bids(project_id: ProjectId, amount: u32) -> Vec> { Bids::::iter_prefix_values((project_id,)) - .sorted_by(|a, b| { - b.final_ct_amount().cmp(&a.final_ct_amount()) - }) + .sorted_by(|a, b| b.final_ct_amount().cmp(&a.final_ct_amount())) .take(amount as usize) .collect_vec() } diff --git a/pallets/funding/src/tests/3_auction.rs b/pallets/funding/src/tests/3_auction.rs index 18748e9c7..98f5eab27 100644 --- a/pallets/funding/src/tests/3_auction.rs +++ b/pallets/funding/src/tests/3_auction.rs @@ -9,7 +9,7 @@ mod round_flow { use super::*; use frame_support::traits::fungibles::metadata::Inspect; use sp_core::bounded_vec; - use std::{collections::HashSet}; + use std::collections::HashSet; #[test] fn auction_round_completed() { diff --git a/pallets/funding/src/tests/misc.rs b/pallets/funding/src/tests/misc.rs index 602680a52..86dfe4f9c 100644 --- a/pallets/funding/src/tests/misc.rs +++ b/pallets/funding/src/tests/misc.rs @@ -366,3 +366,50 @@ mod inner_functions { assert_eq!(multiplier_25_duration, FixedU128::from_rational(52008, 1000).saturating_mul_int((DAYS * 7) as u64)); } } + +#[test] +fn project_state_transition_event() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_id = inst.create_settled_project( + default_project_metadata(ISSUER_1), + ISSUER_1, + None, + default_evaluations(), + default_bids(), + default_community_contributions(), + default_remainder_contributions(), + ); + + let events = inst.execute(|| System::events()); + let transition_events = events + .into_iter() + .filter_map(|event| { + if let RuntimeEvent::PolimecFunding(e @ crate::Event::ProjectPhaseTransition { .. }) = event.event { + Some(e) + } else { + None + } + }) + .collect_vec(); + + let mut desired_transitions = vec![ + ProjectStatus::EvaluationRound, + ProjectStatus::AuctionInitializePeriod, + ProjectStatus::AuctionRound, + ProjectStatus::CommunityRound( + EvaluationDuration::get() + + AuctionInitializePeriodDuration::get() + + AuctionOpeningDuration::get() + + CommunityRoundDuration::get() + + 1u64, + ), + ProjectStatus::FundingSuccessful, + ProjectStatus::SettlementStarted(FundingOutcome::Success), + ProjectStatus::SettlementFinished(FundingOutcome::Success), + ] + .into_iter(); + + transition_events.into_iter().for_each(|event| { + assert_eq!(event, Event::ProjectPhaseTransition { project_id, phase: desired_transitions.next().unwrap() }); + }); +} diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index 082cb9437..b8f965f8d 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -155,8 +155,8 @@ pub mod config_types { } pub mod storage_types { + use super::*; use sp_runtime::traits::Zero; -use super::*; #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo, Serialize, Deserialize)] pub struct ProjectMetadata { @@ -386,9 +386,9 @@ use super::*; { pub fn final_ct_amount(&self) -> Balance { match self.status { - BidStatus::Accepted => {self.original_ct_amount} - BidStatus::PartiallyAccepted(amount) => {amount} - _ => Zero::zero() + BidStatus::Accepted => self.original_ct_amount, + BidStatus::PartiallyAccepted(amount) => amount, + _ => Zero::zero(), } } } diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index d56ccc9d0..a0931d497 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -246,7 +246,7 @@ impl Contains for BaseCallFilter { pallet_funding::Call::bid { .. } | pallet_funding::Call::end_auction { .. } | pallet_funding::Call::contribute { .. } | - pallet_funding::Call::end_funding { .. } | + pallet_funding::Call::end_funding { .. } | pallet_funding::Call::start_settlement { .. } | pallet_funding::Call::settle_evaluation { .. } | pallet_funding::Call::settle_bid { .. } |