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 dec69f0ca..f76540e71 100644 --- a/pallets/funding/src/functions/6_settlement.rs +++ b/pallets/funding/src/functions/6_settlement.rs @@ -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()), @@ -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/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/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() }); + }); +}