Skip to content

Commit

Permalink
offchain migration confirmation
Browse files Browse the repository at this point in the history
  • Loading branch information
JuaniRios committed Jun 28, 2024
1 parent 8e8c998 commit 521f88e
Show file tree
Hide file tree
Showing 7 changed files with 285 additions and 42 deletions.
135 changes: 131 additions & 4 deletions integration-tests/src/tests/ct_migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@

use crate::*;
use frame_support::traits::{fungible::Mutate, fungibles::Inspect};
use itertools::Itertools;
use pallet_funding::{assert_close_enough, types::*, ProjectId};
use polimec_common::migration_types::{MigrationStatus, Migrations};
use polimec_runtime::Funding;
use polimec_common::migration_types::{MigrationStatus, Migrations, ParticipationType};
use polimec_runtime::{Funding, RuntimeOrigin};
use polkadot_service::chain_spec::get_account_id_from_seed;
use sp_runtime::Perquintill;
use std::collections::HashMap;
use tests::defaults::*;

fn alice() -> AccountId {
get_account_id_from_seed::<sr25519::Public>(ALICE)
}
fn mock_hrmp_establishment(project_id: u32) {
let ct_issued = PolimecNet::execute_with(|| {
<PolimecRuntime as pallet_funding::Config>::ContributionTokenCurrency::total_issuance(project_id)
Expand Down Expand Up @@ -61,6 +66,16 @@ fn assert_migration_is_ready(project_id: u32) {
});
}

fn assert_migration_not_ready(project_id: u32) {
PolimecNet::execute_with(|| {
let project_details = pallet_funding::ProjectsDetails::<PolimecRuntime>::get(project_id).unwrap();
let MigrationType::ParachainReceiverPallet(receiver_pallet_info) = project_details.migration_type else {
panic!("Migration type is not ParachainReceiverPallet");
};
assert!(!receiver_pallet_info.migration_readiness_check.unwrap().is_ready())
});
}

fn get_migrations_for_participants(
project_id: ProjectId,
participants: Vec<AccountId>,
Expand Down Expand Up @@ -181,10 +196,12 @@ fn create_settled_project() -> (ProjectId, Vec<AccountId>) {
}

#[test]
fn full_migration_test() {
fn full_receiver_pallet_migration_test() {
polimec::set_prices();
let (project_id, participants) = create_settled_project();

let project_status =
PolimecNet::execute_with(|| pallet_funding::ProjectsDetails::<PolimecRuntime>::get(project_id).unwrap().status);
dbg!(project_status);
mock_hrmp_establishment(project_id);

assert_migration_is_ready(project_id);
Expand All @@ -200,3 +217,113 @@ fn full_migration_test() {

migrations_are_vested(project_id, participants.clone());
}

/// Creates a project with all participations settled except for one.
fn create_project_with_unsettled_participation(participation_type: ParticipationType) -> (ProjectId, Vec<AccountId>) {
let mut inst = IntegrationInstantiator::new(None);
PolimecNet::execute_with(|| {
let project_id = inst.create_finished_project(
default_project_metadata(ISSUER.into()),
ISSUER.into(),
default_evaluations(),
default_bids(),
default_community_contributions(),
default_remainder_contributions(),
);

inst.advance_time(<PolimecRuntime as pallet_funding::Config>::SuccessToSettlementTime::get()).unwrap();
let evaluations_to_settle =
pallet_funding::Evaluations::<PolimecRuntime>::iter_prefix_values((project_id,)).collect_vec();
let bids_to_settle = pallet_funding::Bids::<PolimecRuntime>::iter_prefix_values((project_id,)).collect_vec();
let contributions_to_settle =
pallet_funding::Contributions::<PolimecRuntime>::iter_prefix_values((project_id,)).collect_vec();

let mut participants: Vec<AccountId> = evaluations_to_settle
.iter()
.map(|eval| eval.evaluator.clone())
.chain(bids_to_settle.iter().map(|bid| bid.bidder.clone()))
.chain(contributions_to_settle.iter().map(|contribution| contribution.contributor.clone()))
.collect();
participants.sort();
participants.dedup();

let start = if participation_type == ParticipationType::Evaluation { 1 } else { 0 };
for evaluation in evaluations_to_settle[start..].iter() {
PolimecFunding::settle_successful_evaluation(
RuntimeOrigin::signed(alice()),
project_id,
evaluation.evaluator.clone(),
evaluation.id,
)
.unwrap()
}

let start = if participation_type == ParticipationType::Bid { 1 } else { 0 };
for bid in bids_to_settle[start..].iter() {
PolimecFunding::settle_successful_bid(
RuntimeOrigin::signed(alice()),
project_id,
bid.bidder.clone(),
bid.id,
)
.unwrap()
}

let start = if participation_type == ParticipationType::Contribution { 1 } else { 0 };
for contribution in contributions_to_settle[start..].iter() {
PolimecFunding::settle_successful_contribution(
RuntimeOrigin::signed(alice()),
project_id,
contribution.contributor.clone(),
contribution.id,
)
.unwrap()
}

let evaluations =
pallet_funding::Evaluations::<PolimecRuntime>::iter_prefix_values((project_id,)).collect_vec();
let bids = pallet_funding::Bids::<PolimecRuntime>::iter_prefix_values((project_id,)).collect_vec();
let contributions =
pallet_funding::Contributions::<PolimecRuntime>::iter_prefix_values((project_id,)).collect_vec();

if participation_type == ParticipationType::Evaluation {
assert_eq!(evaluations.len(), 1);
assert_eq!(bids.len(), 0);
assert_eq!(contributions.len(), 0);
} else if participation_type == ParticipationType::Bid {
assert_eq!(evaluations.len(), 0);
assert_eq!(bids.len(), 1);
assert_eq!(contributions.len(), 0);
} else {
assert_eq!(evaluations.len(), 0);
assert_eq!(bids.len(), 0);
assert_eq!(contributions.len(), 1);
}

(project_id, participants)
})
}

#[test]
fn cannot_start_pallet_migration_with_unsettled_participations() {
polimec::set_prices();

let tup_1 = create_project_with_unsettled_participation(ParticipationType::Evaluation);
let tup_2 = create_project_with_unsettled_participation(ParticipationType::Bid);
let tup_3 = create_project_with_unsettled_participation(ParticipationType::Contribution);

let tups = vec![tup_1, tup_2, tup_3];

for (project_id, participants) in tups.into_iter() {
PolimecNet::execute_with(|| {
assert_noop!(
PolimecFunding::do_configure_receiver_pallet_migration(
&ISSUER.into(),
project_id,
ParaId::from(6969u32)
),
pallet_funding::Error::<PolimecRuntime>::SettlementNotComplete
);
});
}
}
95 changes: 80 additions & 15 deletions pallets/funding/src/functions/6_settlement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ impl<T: Config> Pallet<T> {
// * Calculate new variables *
project_details.funding_end_block = Some(now);

// * Update storage *
ProjectsDetails::<T>::insert(project_id, &project_details);

let escrow_account = Self::fund_account_id(project_id);
if project_details.status == ProjectStatus::FundingSuccessful {
T::ContributionTokenCurrency::create(project_id, escrow_account.clone(), false, 1_u32.into())?;
Expand Down Expand Up @@ -73,11 +70,17 @@ impl<T: Config> Pallet<T> {
liquidity_pools_ct_amount,
)?;

project_details.status = ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful);
ProjectsDetails::<T>::insert(project_id, &project_details);

Ok(PostDispatchInfo {
actual_weight: Some(WeightInfoOf::<T>::start_settlement_funding_success()),
pays_fee: Pays::Yes,
})
} else {
project_details.status = ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed);
ProjectsDetails::<T>::insert(project_id, &project_details);

Ok(PostDispatchInfo {
actual_weight: Some(WeightInfoOf::<T>::start_settlement_funding_failure()),
pays_fee: Pays::Yes,
Expand All @@ -87,8 +90,14 @@ impl<T: Config> Pallet<T> {

pub fn do_settle_successful_evaluation(evaluation: EvaluationInfoOf<T>, project_id: ProjectId) -> DispatchResult {
let project_details = ProjectsDetails::<T>::get(project_id).ok_or(Error::<T>::ProjectDetailsNotFound)?;
ensure!(matches!(project_details.funding_end_block, Some(_)), Error::<T>::SettlementNotStarted);
ensure!(matches!(project_details.status, ProjectStatus::FundingSuccessful), Error::<T>::WrongSettlementOutcome);
ensure!(
matches!(project_details.status, ProjectStatus::SettlementStarted(_)),
Error::<T>::SettlementNotStarted
);
ensure!(
matches!(project_details.status, ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful)),
Error::<T>::WrongSettlementOutcome
);

// Based on the results of the funding round, the evaluator is either:
// 1. Slashed
Expand Down Expand Up @@ -137,8 +146,14 @@ impl<T: Config> Pallet<T> {

pub fn do_settle_failed_evaluation(evaluation: EvaluationInfoOf<T>, project_id: ProjectId) -> DispatchResult {
let project_details = ProjectsDetails::<T>::get(project_id).ok_or(Error::<T>::ProjectDetailsNotFound)?;
ensure!(matches!(project_details.funding_end_block, Some(_)), Error::<T>::SettlementNotStarted);
ensure!(matches!(project_details.status, ProjectStatus::FundingFailed), Error::<T>::WrongSettlementOutcome);
ensure!(
matches!(project_details.status, ProjectStatus::SettlementStarted(_)),
Error::<T>::SettlementNotStarted
);
ensure!(
matches!(project_details.status, ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed)),
Error::<T>::WrongSettlementOutcome
);

let bond = if matches!(project_details.evaluation_round_info.evaluators_outcome, EvaluatorsOutcome::Slashed) {
Self::slash_evaluator(project_id, &evaluation)?
Expand Down Expand Up @@ -171,8 +186,14 @@ impl<T: Config> Pallet<T> {
let project_metadata = ProjectsMetadata::<T>::get(project_id).ok_or(Error::<T>::ProjectMetadataNotFound)?;
let project_details = ProjectsDetails::<T>::get(project_id).ok_or(Error::<T>::ProjectDetailsNotFound)?;

ensure!(matches!(project_details.funding_end_block, Some(_)), Error::<T>::SettlementNotStarted);
ensure!(matches!(project_details.status, ProjectStatus::FundingSuccessful), Error::<T>::WrongSettlementOutcome);
ensure!(
matches!(project_details.status, ProjectStatus::SettlementStarted(_)),
Error::<T>::SettlementNotStarted
);
ensure!(
matches!(project_details.status, ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful)),
Error::<T>::WrongSettlementOutcome
);
ensure!(
matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..)),
Error::<T>::ImpossibleState
Expand Down Expand Up @@ -234,8 +255,14 @@ impl<T: Config> Pallet<T> {

pub fn do_settle_failed_bid(bid: BidInfoOf<T>, project_id: ProjectId) -> DispatchResult {
let project_details = ProjectsDetails::<T>::get(project_id).ok_or(Error::<T>::ProjectDetailsNotFound)?;
ensure!(matches!(project_details.funding_end_block, Some(_)), Error::<T>::SettlementNotStarted);
ensure!(matches!(project_details.status, ProjectStatus::FundingFailed), Error::<T>::WrongSettlementOutcome);
ensure!(
matches!(project_details.status, ProjectStatus::SettlementStarted(_)),
Error::<T>::SettlementNotStarted
);
ensure!(
matches!(project_details.status, ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed)),
Error::<T>::WrongSettlementOutcome
);

let bidder = bid.bidder;

Expand All @@ -262,8 +289,14 @@ impl<T: Config> Pallet<T> {
// Ensure that:
// 1. The project is in the FundingSuccessful state
// 2. The contribution token exists
ensure!(matches!(project_details.funding_end_block, Some(_)), Error::<T>::SettlementNotStarted);
ensure!(matches!(project_details.status, ProjectStatus::FundingSuccessful), Error::<T>::WrongSettlementOutcome);
ensure!(
matches!(project_details.status, ProjectStatus::SettlementStarted(_)),
Error::<T>::SettlementNotStarted
);
ensure!(
matches!(project_details.status, ProjectStatus::SettlementStarted(FundingOutcome::FundingSuccessful)),
Error::<T>::WrongSettlementOutcome
);
ensure!(T::ContributionTokenCurrency::asset_exists(project_id), Error::<T>::TooEarlyForRound);

let contributor = contribution.contributor;
Expand Down Expand Up @@ -321,8 +354,15 @@ impl<T: Config> Pallet<T> {

pub fn do_settle_failed_contribution(contribution: ContributionInfoOf<T>, project_id: ProjectId) -> DispatchResult {
let project_details = ProjectsDetails::<T>::get(project_id).ok_or(Error::<T>::ProjectDetailsNotFound)?;
ensure!(matches!(project_details.funding_end_block, Some(_)), Error::<T>::SettlementNotStarted);
ensure!(matches!(project_details.status, ProjectStatus::FundingFailed), Error::<T>::WrongSettlementOutcome);

ensure!(
matches!(project_details.status, ProjectStatus::SettlementStarted(_)),
Error::<T>::SettlementNotStarted
);
ensure!(
matches!(project_details.status, ProjectStatus::SettlementStarted(FundingOutcome::FundingFailed)),
Error::<T>::WrongSettlementOutcome
);

// Check if the bidder has a future deposit held
let contributor = contribution.contributor;
Expand Down Expand Up @@ -351,6 +391,31 @@ impl<T: Config> Pallet<T> {
Ok(())
}

pub fn do_mark_project_as_settled(project_id: ProjectId) -> DispatchResult {
let mut project_details = ProjectsDetails::<T>::get(project_id).ok_or(Error::<T>::ProjectDetailsNotFound)?;
let outcome = match project_details.status {
ProjectStatus::SettlementStarted(outcome) => outcome,
_ => return Err(Error::<T>::SettlementNotStarted.into()),
};

// We use closers to do an early return if just one of these storage iterators returns a value.
let no_evaluations_remaining = || Evaluations::<T>::iter_prefix((project_id,)).next().is_none();
let no_bids_remaining = || Bids::<T>::iter_prefix((project_id,)).next().is_none();
let no_contributions_remaining = || Contributions::<T>::iter_prefix((project_id,)).next().is_none();

// Check if there are any evaluations, bids or contributions remaining
ensure!(
no_evaluations_remaining() && no_bids_remaining() && no_contributions_remaining(),
Error::<T>::SettlementNotComplete
);

// Mark the project as settled
project_details.status = ProjectStatus::SettlementFinished(outcome);
ProjectsDetails::<T>::insert(project_id, project_details);

Ok(())
}

fn mint_contribution_tokens(
project_id: ProjectId,
participant: &AccountIdOf<T>,
Expand Down
Loading

0 comments on commit 521f88e

Please sign in to comment.