diff --git a/Cargo.lock b/Cargo.lock index 20e8691b4..16d57ca15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8262,6 +8262,7 @@ dependencies = [ "serde", "sp-runtime", "sp-std", + "staging-xcm", ] [[package]] diff --git a/integration-tests/penpal/src/lib.rs b/integration-tests/penpal/src/lib.rs index 1ae1b7f11..970b66109 100644 --- a/integration-tests/penpal/src/lib.rs +++ b/integration-tests/penpal/src/lib.rs @@ -82,7 +82,6 @@ use parachains_common::{ }; use sp_runtime::traits::Convert; use xcm::latest::prelude::BodyId; -use xcm_executor::XcmExecutor; /// Balance of an account. pub type Balance = u128; diff --git a/integration-tests/penpal/src/weights/mod.rs b/integration-tests/penpal/src/weights/mod.rs index ed0b4dbcd..ef1ab0d1a 100644 --- a/integration-tests/penpal/src/weights/mod.rs +++ b/integration-tests/penpal/src/weights/mod.rs @@ -24,5 +24,4 @@ pub mod rocksdb_weights; pub use block_weights::constants::BlockExecutionWeight; pub use extrinsic_weights::constants::ExtrinsicBaseWeight; -pub use paritydb_weights::constants::ParityDbWeight; pub use rocksdb_weights::constants::RocksDbWeight; diff --git a/integration-tests/src/tests/ct_migration.rs b/integration-tests/src/tests/ct_migration.rs index 403e695cf..9407e592e 100644 --- a/integration-tests/src/tests/ct_migration.rs +++ b/integration-tests/src/tests/ct_migration.rs @@ -15,22 +15,13 @@ // along with this program. If not, see . use crate::*; -use pallet_funding::{ - assert_close_enough, traits::VestingDurationCalculation, BidStatus, EvaluatorsOutcome, MigrationStatus, Multiplier, - ProjectId, ProjectsToUpdate, RewardOrSlash, -}; -use polimec_common::migration_types::{Migration, MigrationInfo, MigrationOrigin, Migrations, ParticipationType}; +use pallet_funding::{assert_close_enough, ProjectId}; +use polimec_common::migration_types::{MigrationStatus, Migrations}; use politest_runtime::Funding; -use sp_runtime::{traits::Convert, FixedPointNumber, Perquintill}; +use sp_runtime::Perquintill; use std::collections::HashMap; use tests::defaults::*; -fn execute_cleaner(inst: &mut IntegrationInstantiator) { - PolitestNet::execute_with(|| { - dbg!(::SuccessToSettlementTime::get() + 25u32); - inst.advance_time(::SuccessToSettlementTime::get() + 25u32).unwrap(); - }); -} fn mock_hrmp_establishment(project_id: u32) { PolitestNet::execute_with(|| { assert_ok!(Funding::do_set_para_id_for_project(&ISSUER.into(), project_id, ParaId::from(6969u32))); @@ -46,10 +37,8 @@ fn mock_hrmp_establishment(project_id: u32) { assert_ok!(Funding::do_handle_channel_accepted(channel_accepted_message)); }); - PenNet::execute_with(|| { - println!("penpal events:"); - dbg!(PenNet::events()); - }); + // Required for passing migration ready check. + PenNet::execute_with(|| {}); } fn assert_migration_is_ready(project_id: u32) { @@ -59,437 +48,143 @@ fn assert_migration_is_ready(project_id: u32) { }); } -fn send_migrations(project_id: ProjectId, accounts: Vec) -> HashMap { - let mut output = HashMap::new(); +fn get_migrations_for_participants( + project_id: ProjectId, + participants: Vec, +) -> HashMap { + let mut user_migrations = HashMap::new(); PolitestNet::execute_with(|| { - for account in accounts { - assert_ok!(Funding::migrate_one_participant( - PolitestOrigin::signed(account.clone()), - project_id, - account.clone() - )); - - let user_evaluations = - pallet_funding::Evaluations::::iter_prefix_values((project_id, account.clone())); - let user_bids = pallet_funding::Bids::::iter_prefix_values((project_id, account.clone())) - .filter(|bid| matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..))); - let user_contributions = - pallet_funding::Contributions::::iter_prefix_values((project_id, account.clone())); - - let evaluation_migrations = user_evaluations.map(|evaluation| { - let evaluator_bytes = ::AccountId32Conversion::convert( - evaluation.evaluator.clone(), - ); - assert!( - matches!(evaluation.ct_migration_status, MigrationStatus::Sent(_)), - "{:?}'s evaluation was not sent {:?}", - names()[&evaluator_bytes], - evaluation - ); - if let Some(RewardOrSlash::Reward(amount)) = evaluation.rewarded_or_slashed { - Migration { - info: MigrationInfo { - contribution_token_amount: amount, - vesting_time: Multiplier::new(1u8) - .unwrap() - .calculate_vesting_duration::() - .into(), - }, - origin: MigrationOrigin { - user: account.clone().into(), - id: evaluation.id, - participation_type: ParticipationType::Evaluation, - }, - } - } else { - panic!("should be rewarded") - } - }); - let bid_migrations = user_bids.map(|bid| { - assert!(matches!(bid.ct_migration_status, MigrationStatus::Sent(_))); - Migration { - info: MigrationInfo { - contribution_token_amount: bid.final_ct_amount, - vesting_time: bid.multiplier.calculate_vesting_duration::().into(), - }, - origin: MigrationOrigin { - user: account.clone().into(), - id: bid.id, - participation_type: ParticipationType::Bid, - }, - } - }); - let contribution_migrations = user_contributions.map(|contribution| { - assert!(matches!(contribution.ct_migration_status, MigrationStatus::Sent(_))); - Migration { - info: MigrationInfo { - contribution_token_amount: contribution.ct_amount, - vesting_time: contribution.multiplier.calculate_vesting_duration::().into(), - }, - origin: MigrationOrigin { - user: account.clone().into(), - id: contribution.id, - participation_type: ParticipationType::Contribution, - }, - } - }); - - let migrations = - evaluation_migrations.chain(bid_migrations).chain(contribution_migrations).collect::(); - - if migrations.clone().inner().is_empty() { - panic!("no migrations for account: {:?}", account) - } - output.insert(account.clone(), migrations); + for participant in participants { + let (status, migrations) = + pallet_funding::UserMigrations::::get(project_id, participant.clone()).unwrap(); + user_migrations.insert(participant, (status, Migrations::from(migrations.into()))); } }); - output + user_migrations } -fn migrations_are_executed(grouped_migrations: Vec) { - let all_migrations = - grouped_migrations.iter().flat_map(|migrations| migrations.clone().inner()).collect::>(); - PenNet::execute_with(|| { - assert_expected_events!( - PenNet, - vec![ - PenpalEvent::PolimecReceiver(polimec_receiver::Event::MigrationExecuted{migration}) => { - migration: all_migrations.contains(&migration), - }, - ] - ); - }); - - // since current way to migrate is by bundling each user's migrations into one, we can assume that all migrations in a group are from the same user - for migration_group in grouped_migrations { - let user = migration_group.clone().inner()[0].origin.user; - assert!(migration_group.origins().iter().all(|origin| origin.user == user)); - - let user_info = PenNet::account_data_of(user.into()); - assert_close_enough!(user_info.free, migration_group.total_ct_amount(), Perquintill::from_float(0.99)); - - let vest_scheduled_cts = migration_group - .inner() - .iter() - .filter_map(|migration| { - if migration.info.vesting_time > 1 { - Some(migration.info.contribution_token_amount) - } else { - None - } - }) - .sum::(); +fn send_migrations(project_id: ProjectId, accounts: Vec) { + for user in accounts.into_iter() { + PolitestNet::execute_with(|| { + assert_ok!(Funding::migrate_one_participant( + PolitestOrigin::signed(user.clone()), + project_id, + user.clone() + )); + }); + } +} - assert_close_enough!(user_info.frozen, vest_scheduled_cts, Perquintill::from_float(0.99)); +fn migrations_are_executed(project_id: ProjectId, accounts: Vec) { + let user_migrations = get_migrations_for_participants(project_id, accounts.clone()); + for account in accounts.into_iter() { + let user_info = PenNet::account_data_of(account.clone()); + PenNet::execute_with(|| { + let (_, migrations) = user_migrations.get(&account).unwrap(); + + assert_close_enough!(user_info.free, migrations.total_ct_amount(), Perquintill::from_float(0.99)); + + let vest_scheduled_cts = migrations + .clone() + .inner() + .iter() + .filter_map(|migration| { + if migration.info.vesting_time > 1 { + Some(migration.info.contribution_token_amount) + } else { + None + } + }) + .sum::(); + assert_close_enough!(user_info.frozen, vest_scheduled_cts, Perquintill::from_float(0.99)); + }); } } -fn migrations_are_confirmed(project_id: u32, grouped_migrations: Vec) { - let ordered_grouped_origins = grouped_migrations - .clone() - .into_iter() - .map(|group| { - let mut origins = group.origins(); - origins.sort(); - origins - }) - .collect::>(); +fn migrations_are_confirmed(project_id: u32, accounts: Vec) { + let user_migrations = get_migrations_for_participants(project_id, accounts.clone()); PolitestNet::execute_with(|| { - assert_expected_events!( - PolitestNet, - vec![ - PolitestEvent::Funding(pallet_funding::Event::MigrationsConfirmed{project_id, migration_origins}) => { - project_id: project_id == project_id, - migration_origins: { - let mut migration_origins = migration_origins.to_vec(); - migration_origins.sort(); - ordered_grouped_origins.contains(&migration_origins) - }, - }, - ] - ); - let all_migration_origins = - grouped_migrations.iter().flat_map(|migrations| migrations.clone().origins()).collect::>(); - for migration_origin in all_migration_origins { - match migration_origin.participation_type { - ParticipationType::Evaluation => { - let evaluation = pallet_funding::Evaluations::::get(( - project_id, - AccountId::from(migration_origin.user), - migration_origin.id, - )) - .unwrap(); - assert_eq!(evaluation.ct_migration_status, MigrationStatus::Confirmed); - }, - ParticipationType::Bid => { - let bid = pallet_funding::Bids::::get(( - project_id, - AccountId::from(migration_origin.user), - migration_origin.id, - )) - .unwrap(); - assert_eq!(bid.ct_migration_status, MigrationStatus::Confirmed); - }, - ParticipationType::Contribution => { - let contribution = pallet_funding::Contributions::::get(( - project_id, - AccountId::from(migration_origin.user), - migration_origin.id, - )) - .unwrap(); - assert_eq!(contribution.ct_migration_status, MigrationStatus::Confirmed); - }, - } + for user in accounts.iter() { + let (current_status, _) = user_migrations.get(user).unwrap(); + assert_eq!(current_status, &MigrationStatus::Confirmed); } }); } -fn vest_migrations(grouped_migrations: Vec) { - let biggest_time = grouped_migrations.iter().map(|migrations| migrations.biggest_vesting_time()).max().unwrap(); +fn vest_migrations(project_id: u32, accounts: Vec) { + let user_migrations = get_migrations_for_participants(project_id, accounts.clone()); + let biggest_time = + user_migrations.iter().map(|(_, (_, migrations))| migrations.biggest_vesting_time()).max().unwrap(); + PenNet::execute_with(|| { PenpalSystem::set_block_number(biggest_time as u32 + 1u32); }); - for migration_group in grouped_migrations { - let user = migration_group.clone().inner()[0].origin.user; - assert!(migration_group.origins().iter().all(|origin| origin.user == user)); - // check if any vesting_time is bigger than 1, which means the balance was actually frozen - let has_frozen_balance = migration_group.inner().iter().any(|migration| migration.info.vesting_time > 1); - if has_frozen_balance { - PenNet::execute_with(|| { - assert_ok!(pallet_vesting::Pallet::::vest(PenpalOrigin::signed(user.into()))); - }); - } + for account in accounts { + let user_info = PenNet::account_data_of(account.clone()); + PenNet::execute_with(|| { + if user_info.frozen > 0 { + assert_ok!(pallet_vesting::Pallet::::vest(PenpalOrigin::signed(account))); + } + }); } } -fn migrations_are_vested(grouped_migrations: Vec) { - for migration_group in grouped_migrations { - let user = migration_group.clone().inner()[0].origin.user; - assert!(migration_group.origins().iter().all(|origin| origin.user == user)); - let user_info = PenNet::account_data_of(user.into()); +fn migrations_are_vested(project_id: u32, accounts: Vec) { + let user_migrations = get_migrations_for_participants(project_id, accounts.clone()); + user_migrations.iter().for_each(|(user, (_, migrations))| { + let user_info = PenNet::account_data_of(user.clone()); assert_eq!(user_info.frozen, 0); - assert_eq!(user_info.free, migration_group.total_ct_amount()); - } -} - -#[test] -fn migration_check() { - let mut inst = IntegrationInstantiator::new(None); - let project_id = PolitestNet::execute_with(|| { - let project_id = inst.create_finished_project( - default_project_metadata(0, ISSUER.into()), - ISSUER.into(), - default_evaluations(), - default_bids(), - default_community_contributions(), - vec![], - ); - - inst.advance_time(::SuccessToSettlementTime::get() + 1u32).unwrap(); - project_id + assert_eq!(user_info.free, migrations.clone().total_ct_amount()); }); - - mock_hrmp_establishment(project_id); - - assert_migration_is_ready(project_id); } -#[test] -fn migration_is_sent() { +fn create_settled_project() -> (ProjectId, Vec) { let mut inst = IntegrationInstantiator::new(None); - let participants = - vec![EVAL_1, EVAL_2, EVAL_3, BIDDER_1, BIDDER_2, BIDDER_3, BIDDER_4, BUYER_1, BUYER_2, BUYER_3, BUYER_4] - .into_iter() - .map(|x| AccountId::from(x)) - .collect::>(); - let project_id = PolitestNet::execute_with(|| { - inst.create_finished_project( - default_project_metadata(0, ISSUER.into()), - ISSUER.into(), - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), - ) - }); - PolitestNet::execute_with(|| { - dbg!(PolimecSystem::block_number()); - dbg!(ProjectsToUpdate::::iter().collect::>()); - }); - - execute_cleaner(&mut inst); - - mock_hrmp_establishment(project_id); - - assert_migration_is_ready(project_id); - - send_migrations(project_id, participants); -} - -#[test] -fn migration_is_executed_on_project_and_confirmed_on_polimec() { - let mut inst = IntegrationInstantiator::new(None); - let participants = - vec![EVAL_1, EVAL_2, EVAL_3, BIDDER_2, BIDDER_3, BIDDER_4, BIDDER_5, BUYER_2, BUYER_3, BUYER_4, BUYER_5] - .into_iter() - .map(|x| AccountId::from(x)) - .collect::>(); - let project_id = PolitestNet::execute_with(|| { - inst.create_finished_project( + let project_id = inst.create_finished_project( default_project_metadata(0, ISSUER.into()), ISSUER.into(), default_evaluations(), default_bids(), default_community_contributions(), default_remainder_contributions(), - ) - }); - execute_cleaner(&mut inst); - - mock_hrmp_establishment(project_id); - - assert_migration_is_ready(project_id); - - let migrations_map = send_migrations(project_id, participants); - let grouped_migrations = migrations_map.values().cloned().collect::>(); - - migrations_are_executed(grouped_migrations.clone()); - - migrations_are_confirmed(project_id, grouped_migrations.clone()); -} - -#[test] -fn vesting_over_several_blocks_on_project() { - let mut inst = IntegrationInstantiator::new(None); - let participants = vec![EVAL_1, EVAL_2, EVAL_3, BIDDER_1, BIDDER_2, BUYER_1, BUYER_2] - .into_iter() - .map(|x| AccountId::from(x)) - .collect::>(); - - let bids = default_bids(); - let community_contributions = default_community_contributions(); - let remainder_contributions = default_remainder_contributions(); - - let project_id = PolitestNet::execute_with(|| { - inst.create_finished_project( - default_project_metadata(0, ISSUER.into()), - ISSUER.into(), - default_evaluations(), - bids, - community_contributions, - remainder_contributions, - ) - }); - execute_cleaner(&mut inst); - - mock_hrmp_establishment(project_id); - - assert_migration_is_ready(project_id); - - // Migrate is sent - let user_migrations = send_migrations(project_id, participants); - let grouped_migrations = user_migrations.values().cloned().collect::>(); - - migrations_are_executed(grouped_migrations.clone()); - - migrations_are_confirmed(project_id, grouped_migrations.clone()); - - vest_migrations(grouped_migrations.clone()); - - migrations_are_vested(grouped_migrations.clone()); + ); + inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); + let mut participants: Vec = + pallet_funding::Evaluations::::iter_prefix_values((project_id,)) + .map(|eval| eval.evaluator) + .chain(pallet_funding::Bids::::iter_prefix_values((project_id,)).map(|bid| bid.bidder)) + .chain( + pallet_funding::Contributions::::iter_prefix_values((project_id,)) + .map(|contribution| contribution.contributor), + ) + .collect(); + participants.sort(); + participants.dedup(); + + inst.settle_project(project_id).unwrap(); + (project_id, participants) + }) } #[test] -fn disallow_duplicated_migrations_on_receiver_pallet() { - let mut inst = IntegrationInstantiator::new(None); - - let project_id = PolitestNet::execute_with(|| { - inst.create_finished_project( - default_project_metadata(0, ISSUER.into()), - ISSUER.into(), - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), - ) - }); - - let project_details = PolitestNet::execute_with(|| inst.get_project_details(project_id)); - if let EvaluatorsOutcome::Rewarded(info) = project_details.evaluation_round_info.evaluators_outcome { - println!("rewarded: {:?}", info); - } else { - panic!("should be rewarded") - } - - let participants = vec![ - EVAL_1, EVAL_2, EVAL_3, EVAL_4, BIDDER_1, BIDDER_2, BIDDER_3, BIDDER_4, BIDDER_5, BIDDER_6, BUYER_1, BUYER_2, - BUYER_3, BUYER_4, BUYER_5, BUYER_6, - ] - .into_iter() - .map(|x| AccountId::from(x)) - .collect::>(); +fn full_migration_test() { + let (project_id, participants) = create_settled_project(); - execute_cleaner(&mut inst); + dbg!(get_migrations_for_participants(project_id, participants.clone())); mock_hrmp_establishment(project_id); assert_migration_is_ready(project_id); // Migrate is sent - let user_migrations = send_migrations(project_id, participants); - let grouped_migrations = user_migrations.values().cloned().collect::>(); - - migrations_are_executed(grouped_migrations.clone()); - - migrations_are_confirmed(project_id, grouped_migrations.clone()); + send_migrations(project_id, participants.clone()); - vest_migrations(grouped_migrations.clone()); + migrations_are_executed(project_id, participants.clone()); - migrations_are_vested(grouped_migrations.clone()); + migrations_are_confirmed(project_id, participants.clone()); - // just any number that lets us execute our xcm's - for migrations in grouped_migrations.clone() { - for (_, xcm) in PolitestFundingPallet::construct_migration_xcm_messages(migrations) { - let _call: ::RuntimeCall = - pallet_funding::Call::confirm_migrations { query_id: Default::default(), response: Default::default() } - .into(); - - let max_weight = Weight::from_parts(700_000_000, 10_000); - let mut instructions = xcm.into_inner(); - instructions.push(ReportTransactStatus(QueryResponseInfo { - destination: ParentThen(X1(Parachain(PolitestNet::para_id().into()))).into(), - query_id: 69, - max_weight, - })); - let xcm = Xcm(instructions); - let project_multilocation = MultiLocation { parents: 1, interior: X1(Parachain(PenNet::para_id().into())) }; - - PolitestNet::execute_with(|| { - PolitestXcmPallet::send_xcm(Here, project_multilocation, xcm).unwrap(); - }); - } - } + vest_migrations(project_id, participants.clone()); - // each duplicated migration was skipped (in this case we duplicated all of them) - let all_migrations = - grouped_migrations.iter().flat_map(|migrations| migrations.clone().inner()).collect::>(); - PenNet::execute_with(|| { - assert_expected_events!( - PenNet, - vec![ - PenpalEvent::PolimecReceiver(polimec_receiver::Event::DuplicatedMigrationSkipped{migration}) => { - migration: all_migrations.contains(&migration), - }, - ] - ); - }); - - migrations_are_vested(grouped_migrations.clone()); -} - -#[ignore] -#[test] -fn failing_bid_doesnt_get_migrated() { - todo!(); + migrations_are_vested(project_id, participants.clone()); } diff --git a/integration-tests/src/tests/e2e.rs b/integration-tests/src/tests/e2e.rs index 65ce2a8ee..cd134543c 100644 --- a/integration-tests/src/tests/e2e.rs +++ b/integration-tests/src/tests/e2e.rs @@ -398,7 +398,7 @@ fn ct_minted() { let mut inst = IntegrationInstantiator::new(None); PolitestNet::execute_with(|| { - let _ = inst.create_finished_project( + let project_id = inst.create_finished_project( excel_project(0), ISSUER.into(), excel_evaluators(), @@ -408,7 +408,7 @@ fn ct_minted() { ); inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - inst.advance_time(10).unwrap(); + inst.settle_project(project_id).unwrap(); for (contributor, expected_amount, project_id) in excel_ct_amounts() { let minted = inst @@ -433,7 +433,7 @@ fn ct_migrated() { ); inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - inst.advance_time(10).unwrap(); + inst.settle_project(project_id).unwrap(); for (contributor, expected_amount, project_id) in excel_ct_amounts() { let minted = inst diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs index 2047b8469..b957b8c35 100644 --- a/pallets/funding/src/benchmarking.rs +++ b/pallets/funding/src/benchmarking.rs @@ -736,21 +736,16 @@ mod benchmarks { .last() .unwrap(); - match stored_evaluation { - EvaluationInfo { - project_id, - evaluator, - original_plmc_bond, - current_plmc_bond, - rewarded_or_slashed, - .. - } if project_id == project_id && - evaluator == evaluation.account.clone() && - original_plmc_bond == extrinsic_plmc_bonded && - current_plmc_bond == extrinsic_plmc_bonded && - rewarded_or_slashed.is_none() => {}, - _ => assert!(false, "Evaluation is not stored correctly"), - } + let correct = match stored_evaluation { + EvaluationInfo { project_id, evaluator, original_plmc_bond, current_plmc_bond, .. } + if project_id == project_id && + evaluator == evaluation.account.clone() && + original_plmc_bond == extrinsic_plmc_bonded && + current_plmc_bond == extrinsic_plmc_bonded => + true, + _ => false, + }; + assert!(correct, "Evaluation is not stored correctly"); // Balances let bonded_plmc = inst.get_reserved_plmc_balances_for( @@ -1031,10 +1026,7 @@ mod benchmarks { funding_asset_amount_locked: None, multiplier: Some(bid_params.multiplier), plmc_bond: None, - plmc_vesting_info: Some(None), when: None, - funds_released: Some(false), - ct_minted: Some(false), }; Bids::::iter_prefix_values((project_id, bidder.clone())) .find(|stored_bid| bid_filter.matches_bid(stored_bid)) @@ -1458,255 +1450,36 @@ mod benchmarks { } #[benchmark] - fn evaluation_unbond_for() { - // setup - let mut inst = BenchInstantiator::::new(None); - - // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); - - let issuer = account::>("issuer", 0, 0); - let evaluations = default_evaluations::(); - let evaluator = evaluations[0].account.clone(); - whitelist_account!(evaluator); - - let project_id = inst.create_finished_project( - default_project::(inst.get_new_nonce(), issuer.clone()), - issuer, - evaluations, - default_bids::(), - default_community_contributions::(), - vec![], - ); - - run_blocks_to_execute_next_transition(project_id, UpdateType::StartSettlement, &mut inst); - - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Initialized(PhantomData)) - ); - - let evaluation_to_unbond = - inst.execute(|| Evaluations::::iter_prefix_values((project_id, evaluator.clone())).next().unwrap()); - - inst.execute(|| { - PalletFunding::::evaluation_reward_payout_for( - ::RuntimeOrigin::signed(evaluator.clone().into()), - project_id, - evaluator.clone(), - evaluation_to_unbond.id, - ) - .expect("") - }); - - #[extrinsic_call] - evaluation_unbond_for( - RawOrigin::Signed(evaluator.clone()), - project_id, - evaluator.clone(), - evaluation_to_unbond.id, - ); - - // * validity checks * - // Balance - let bonded_plmc = inst - .get_reserved_plmc_balances_for(vec![evaluator.clone()], HoldReason::Evaluation(project_id).into())[0] - .plmc_amount; - assert_eq!(bonded_plmc, 0.into()); - - // Events - frame_system::Pallet::::assert_last_event( - Event::BondReleased { - project_id, - amount: evaluation_to_unbond.current_plmc_bond, - bonder: evaluator.clone(), - releaser: evaluator, - } - .into(), - ); - } - - #[benchmark] - fn evaluation_reward_payout_for_with_ct_account_creation() { - // setup - let mut inst = BenchInstantiator::::new(None); - - // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); - - let issuer = account::>("issuer", 0, 0); - let evaluations: Vec> = default_evaluations::(); - let evaluator: AccountIdOf = evaluations[0].account.clone(); - whitelist_account!(evaluator); - - let project_id = inst.create_finished_project( - default_project::(inst.get_new_nonce(), issuer.clone()), - issuer, - evaluations, - default_bids::(), - default_community_contributions::(), - vec![], - ); - - run_blocks_to_execute_next_transition(project_id, UpdateType::StartSettlement, &mut inst); - - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Initialized(PhantomData)) - ); - - let evaluation_to_unbond = - inst.execute(|| Evaluations::::iter_prefix_values((project_id, evaluator.clone())).next().unwrap()); - - #[extrinsic_call] - evaluation_reward_payout_for( - RawOrigin::Signed(evaluator.clone()), - project_id, - evaluator.clone(), - evaluation_to_unbond.id, - ); - - // * validity checks * - // Storage - let stored_evaluation = - Evaluations::::get((project_id, evaluator.clone(), evaluation_to_unbond.id)).unwrap(); - assert!(stored_evaluation.rewarded_or_slashed.is_some()); - - // Balances - let project_details = ProjectsDetails::::get(project_id).unwrap(); - let reward_info = match project_details.evaluation_round_info.evaluators_outcome { - EvaluatorsOutcome::Rewarded(reward_info) => reward_info, - _ => panic!("EvaluatorsOutcome should be Rewarded"), - }; - let total_reward = - BenchInstantiator::::calculate_total_reward_for_evaluation(stored_evaluation.clone(), reward_info); - let ct_amount = inst.get_ct_asset_balances_for(project_id, vec![evaluator.clone()])[0]; - assert_eq!(ct_amount, total_reward); - - // Events - frame_system::Pallet::::assert_last_event( - Event::EvaluationRewarded { - project_id, - evaluator: evaluator.clone(), - id: stored_evaluation.id, - amount: total_reward, - caller: evaluator, - } - .into(), - ); - } - - #[benchmark] - fn evaluation_reward_payout_for_no_ct_account_creation() { + fn decide_project_outcome( + // Insertion attempts in add_to_update_store. Total amount of storage items iterated through in `ProjectsToUpdate`. Leave one free to make the extrinsic pass + x: Linear<1, { ::MaxProjectsToUpdateInsertionAttempts::get() - 1 }>, + ) { // setup let mut inst = BenchInstantiator::::new(None); - // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); - - let issuer = account::>("issuer", 0, 0); - let mut evaluations = default_evaluations::(); - let evaluator: AccountIdOf = evaluations[0].account.clone(); - evaluations[1].account = evaluator.clone(); - whitelist_account!(evaluator); - - let project_id = inst.create_finished_project( - default_project::(inst.get_new_nonce(), issuer.clone()), - issuer, - evaluations, - default_bids::(), - default_community_contributions::(), - vec![], - ); - - run_blocks_to_execute_next_transition(project_id, UpdateType::StartSettlement, &mut inst); - - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Initialized(PhantomData)) - ); - - let mut evaluations_to_unbond = - inst.execute(|| Evaluations::::iter_prefix_values((project_id, evaluator.clone()))); - - let pre_evaluation = evaluations_to_unbond.next().unwrap(); - let bench_evaluation = evaluations_to_unbond.next().unwrap(); - - Pallet::::evaluation_reward_payout_for( - RawOrigin::Signed(evaluator.clone()).into(), - project_id, - evaluator.clone(), - pre_evaluation.id, - ) - .unwrap(); - - #[extrinsic_call] - evaluation_reward_payout_for( - RawOrigin::Signed(evaluator.clone()), - project_id, - evaluator.clone(), - bench_evaluation.id, - ); - - // * validity checks * - // Storage - let stored_evaluation = Evaluations::::get((project_id, evaluator.clone(), bench_evaluation.id)).unwrap(); - assert!(stored_evaluation.rewarded_or_slashed.is_some()); - - // Balances - let project_details = ProjectsDetails::::get(project_id).unwrap(); - let reward_info = match project_details.evaluation_round_info.evaluators_outcome { - EvaluatorsOutcome::Rewarded(reward_info) => reward_info, - _ => panic!("EvaluatorsOutcome should be Rewarded"), - }; - - let pre_reward = - BenchInstantiator::::calculate_total_reward_for_evaluation(pre_evaluation.clone(), reward_info.clone()); - let bench_reward = - BenchInstantiator::::calculate_total_reward_for_evaluation(bench_evaluation.clone(), reward_info); - let ct_amount = inst.get_ct_asset_balances_for(project_id, vec![evaluator.clone()])[0]; - assert_eq!(ct_amount, pre_reward + bench_reward); - - // Events - frame_system::Pallet::::assert_last_event( - Event::EvaluationRewarded { - project_id, - evaluator: evaluator.clone(), - id: stored_evaluation.id, - amount: bench_reward, - caller: evaluator, - } - .into(), - ); - } - - #[benchmark] - fn evaluation_slash_for() { - // setup - let mut inst = BenchInstantiator::::new(None); + // We need to leave enough block numbers to fill `ProjectsToUpdate` before our project insertion - // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); + let time_advance: u32 = x + 2; + frame_system::Pallet::::set_block_number(time_advance.into()); let issuer = account::>("issuer", 0, 0); - let evaluations = default_evaluations::(); - let evaluator = evaluations[0].account.clone(); - whitelist_account!(evaluator); + whitelist_account!(issuer); let project_metadata = default_project::(inst.get_new_nonce(), issuer.clone()); let target_funding_amount: BalanceOf = project_metadata.minimum_price.saturating_mul_int(project_metadata.total_allocation_size); + let evaluations = default_evaluations::(); let bids = BenchInstantiator::generate_bids_from_total_usd( - Percent::from_percent(15) * target_funding_amount, - 10u128.into(), + Percent::from_percent(30) * target_funding_amount, + project_metadata.minimum_price, default_weights(), default_bidders::(), default_bidder_multipliers(), ); + let contributions = BenchInstantiator::generate_contributions_from_total_usd( - Percent::from_percent(10) * target_funding_amount, + Percent::from_percent(40) * target_funding_amount, project_metadata.minimum_price, default_weights(), default_community_contributors::(), @@ -1714,59 +1487,33 @@ mod benchmarks { ); let project_id = - inst.create_finished_project(project_metadata, issuer, evaluations, bids, contributions, vec![]); + inst.create_finished_project(project_metadata, issuer.clone(), evaluations, bids, contributions, vec![]); inst.advance_time(One::one()).unwrap(); - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Failure(CleanerState::Initialized(PhantomData)) - ); - let evaluation_to_unbond = - inst.execute(|| Evaluations::::iter_prefix_values((project_id, evaluator.clone())).next().unwrap()); + let current_block = inst.current_block(); + let insertion_block_number: BlockNumberFor = current_block + One::one(); + + fill_projects_to_update::(x, insertion_block_number); + let jwt = get_mock_jwt(issuer.clone(), InvestorType::Institutional, generate_did_from_account(issuer.clone())); #[extrinsic_call] - evaluation_slash_for( - RawOrigin::Signed(evaluator.clone()), - project_id, - evaluator.clone(), - evaluation_to_unbond.id, - ); + decide_project_outcome(RawOrigin::Signed(issuer), jwt, project_id, FundingOutcomeDecision::AcceptFunding); // * validity checks * // Storage - let stored_evaluation = - Evaluations::::get((project_id, evaluator.clone(), evaluation_to_unbond.id)).unwrap(); - assert!(stored_evaluation.rewarded_or_slashed.is_some()); - let slashed_amount = T::EvaluatorSlash::get() * evaluation_to_unbond.original_plmc_bond; - let current_plmc_bond = evaluation_to_unbond.current_plmc_bond.saturating_sub(slashed_amount); - assert_eq!(stored_evaluation.current_plmc_bond, current_plmc_bond); - - // Balance - let treasury_account = T::ProtocolGrowthTreasury::get(); - let bonded_plmc = inst - .get_reserved_plmc_balances_for(vec![evaluator.clone()], HoldReason::Evaluation(project_id).into())[0] - .plmc_amount; - assert_eq!(bonded_plmc, stored_evaluation.current_plmc_bond); - let free_treasury_plmc = inst.get_free_plmc_balances_for(vec![treasury_account])[0].plmc_amount; - let ed = BenchInstantiator::::get_ed(); - assert_eq!(free_treasury_plmc, slashed_amount + ed); + let maybe_transition = + inst.get_update_block(project_id, &UpdateType::ProjectDecision(FundingOutcomeDecision::AcceptFunding)); + assert!(maybe_transition.is_some()); // Events frame_system::Pallet::::assert_last_event( - Event::EvaluationSlashed { - project_id, - evaluator: evaluator.clone(), - id: stored_evaluation.id, - amount: slashed_amount, - caller: evaluator, - } - .into(), + Event::ProjectOutcomeDecided { project_id, decision: FundingOutcomeDecision::AcceptFunding }.into(), ); } #[benchmark] - fn bid_ct_mint_for_with_ct_account_creation() { + fn settle_successful_evaluation() { // setup let mut inst = BenchInstantiator::::new(None); @@ -1774,511 +1521,86 @@ mod benchmarks { inst.advance_time(1u32.into()).unwrap(); let issuer = account::>("issuer", 0, 0); - let bids = default_bids::(); - let bidder = bids[0].bidder.clone(); - whitelist_account!(bidder); + let evaluations: Vec> = default_evaluations::(); + let evaluator: AccountIdOf = evaluations[0].account.clone(); + whitelist_account!(evaluator); let project_id = inst.create_finished_project( default_project::(inst.get_new_nonce(), issuer.clone()), issuer, - default_evaluations::(), - bids, + evaluations, + default_bids::(), default_community_contributions::(), vec![], ); run_blocks_to_execute_next_transition(project_id, UpdateType::StartSettlement, &mut inst); - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Initialized(PhantomData)) - ); - - let bid_to_mint_ct = - inst.execute(|| Bids::::iter_prefix_values((project_id, bidder.clone())).next().unwrap()); + let evaluation_to_settle = + inst.execute(|| Evaluations::::iter_prefix_values((project_id, evaluator.clone())).next().unwrap()); #[extrinsic_call] - bid_ct_mint_for(RawOrigin::Signed(bidder.clone()), project_id, bidder.clone(), bid_to_mint_ct.id); + settle_successful_evaluation( + RawOrigin::Signed(evaluator.clone()), + project_id, + evaluator.clone(), + evaluation_to_settle.id, + ); // * validity checks * - // Storage - let stored_bid = Bids::::get((project_id, bidder.clone(), bid_to_mint_ct.id)).unwrap(); - assert!(stored_bid.ct_minted); + // Evaluation should be removed + assert!(Evaluations::::get((project_id, evaluator.clone(), evaluation_to_settle.id)).is_none()); // Balances - let ct_amount = inst.get_ct_asset_balances_for(project_id, vec![bidder.clone()])[0]; - assert_eq!(stored_bid.final_ct_amount, ct_amount); - - // Events - frame_system::Pallet::::assert_last_event( - Event::ContributionTokenMinted { releaser: bidder.clone(), project_id, claimer: bidder, amount: ct_amount } - .into(), - ); - } - - #[benchmark] - fn bid_ct_mint_for_no_ct_account_creation() { - // setup - let mut inst = BenchInstantiator::::new(None); - - // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); - - let issuer = account::>("issuer", 0, 0); - let bids: Vec> = default_bids::(); - let bidder = bids[0].bidder.clone(); - whitelist_account!(bidder); - - let project_id = inst.create_finished_project( - default_project::(inst.get_new_nonce(), issuer.clone()), - issuer, - default_evaluations::(), - bids, - default_community_contributions::(), - vec![], - ); - - run_blocks_to_execute_next_transition(project_id, UpdateType::StartSettlement, &mut inst); - - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Initialized(PhantomData)) - ); - - let mut bids_to_mint_ct = inst.execute(|| Bids::::iter_prefix_values((project_id, bidder.clone()))); - - let bench_bid_to_mint_ct = bids_to_mint_ct.next().unwrap(); - - #[extrinsic_call] - bid_ct_mint_for(RawOrigin::Signed(bidder.clone()), project_id, bidder.clone(), bench_bid_to_mint_ct.id); - - // * validity checks * - // Storage - let stored_bid = Bids::::get((project_id, bidder.clone(), bench_bid_to_mint_ct.id)).unwrap(); - assert!(stored_bid.ct_minted); - - // Balances - let ct_amount = inst.get_ct_asset_balances_for(project_id, vec![bidder.clone()])[0]; - assert_eq!(ct_amount, stored_bid.final_ct_amount); - - // Events - frame_system::Pallet::::assert_last_event( - Event::ContributionTokenMinted { - releaser: bidder.clone(), - project_id, - claimer: bidder, - amount: bench_bid_to_mint_ct.final_ct_amount, - } - .into(), - ); - } - - #[benchmark] - fn contribution_ct_mint_for_with_ct_account_creation() { - // setup - let mut inst = BenchInstantiator::::new(None); - - // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); - - let issuer = account::>("issuer", 0, 0); - let contributions = default_community_contributions::(); - let contributor = contributions[0].contributor.clone(); - whitelist_account!(contributor); - - let project_id = inst.create_finished_project( - default_project::(inst.get_new_nonce(), issuer.clone()), - issuer, - default_evaluations::(), - default_bids::(), - contributions, - vec![], - ); - - run_blocks_to_execute_next_transition(project_id, UpdateType::StartSettlement, &mut inst); - - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Initialized(PhantomData)) - ); - - let contribution_to_mint_ct = - inst.execute(|| Contributions::::iter_prefix_values((project_id, contributor.clone())).next().unwrap()); - - #[extrinsic_call] - contribution_ct_mint_for( - RawOrigin::Signed(contributor.clone()), - project_id, - contributor.clone(), - contribution_to_mint_ct.id, - ); - - // * validity checks * - // Storage - let stored_contribution = - Contributions::::get((project_id, contributor.clone(), contribution_to_mint_ct.id)).unwrap(); - assert!(stored_contribution.ct_minted); - - // Balances - let ct_amount = inst.get_ct_asset_balances_for(project_id, vec![contributor.clone()])[0]; - assert_eq!(stored_contribution.ct_amount, ct_amount); - - // Events - frame_system::Pallet::::assert_last_event( - Event::ContributionTokenMinted { - releaser: contributor.clone(), - project_id, - claimer: contributor, - amount: ct_amount, - } - .into(), - ); - } - - #[benchmark] - fn contribution_ct_mint_for_no_ct_account_creation() { - // setup - let mut inst = BenchInstantiator::::new(None); - - // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); - - let issuer = account::>("issuer", 0, 0); - let mut contributions: Vec> = default_community_contributions::(); - let contributor = contributions[0].contributor.clone(); - contributions[1].contributor = contributor.clone(); - whitelist_account!(contributor); - - let project_id = inst.create_finished_project( - default_project::(inst.get_new_nonce(), issuer.clone()), - issuer, - default_evaluations::(), - default_bids::(), - contributions, - vec![], - ); - - run_blocks_to_execute_next_transition(project_id, UpdateType::StartSettlement, &mut inst); - - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Initialized(PhantomData)) - ); - - let mut contributions_to_mint_ct = - inst.execute(|| Contributions::::iter_prefix_values((project_id, contributor.clone()))); - - let pre_contribution_to_mint_ct = contributions_to_mint_ct.next().unwrap(); - let bench_contribution_to_mint_ct = contributions_to_mint_ct.next().unwrap(); - - Pallet::::contribution_ct_mint_for( - RawOrigin::Signed(contributor.clone()).into(), - project_id, - contributor.clone(), - pre_contribution_to_mint_ct.id, - ) - .unwrap(); - - #[extrinsic_call] - contribution_ct_mint_for( - RawOrigin::Signed(contributor.clone()), - project_id, - contributor.clone(), - bench_contribution_to_mint_ct.id, - ); - - // * validity checks * - // Storage - let stored_contribution = - Contributions::::get((project_id, contributor.clone(), bench_contribution_to_mint_ct.id)).unwrap(); - assert!(stored_contribution.ct_minted); - - // Balances - let ct_amount = inst.get_ct_asset_balances_for(project_id, vec![contributor.clone()])[0]; - assert_eq!(ct_amount, pre_contribution_to_mint_ct.ct_amount + bench_contribution_to_mint_ct.ct_amount); - - // Events - frame_system::Pallet::::assert_last_event( - Event::ContributionTokenMinted { - releaser: contributor.clone(), - project_id, - claimer: contributor, - amount: bench_contribution_to_mint_ct.ct_amount, - } - .into(), - ); - } - - #[benchmark] - fn start_bid_vesting_schedule_for() { - // setup - let mut inst = BenchInstantiator::::new(None); - - // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); - - let issuer = account::>("issuer", 0, 0); - let bids = default_bids::(); - let bidder = bids[0].bidder.clone(); - whitelist_account!(bidder); - - let project_id = inst.create_finished_project( - default_project::(inst.get_new_nonce(), issuer.clone()), - issuer, - default_evaluations::(), - bids, - default_community_contributions::(), - vec![], - ); - - run_blocks_to_execute_next_transition(project_id, UpdateType::StartSettlement, &mut inst); - - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Initialized(PhantomData)) - ); - - let bid_to_vest = inst.execute(|| Bids::::iter_prefix_values((project_id, bidder.clone())).next().unwrap()); - - #[extrinsic_call] - start_bid_vesting_schedule_for(RawOrigin::Signed(bidder.clone()), project_id, bidder.clone(), bid_to_vest.id); - - // * validity checks * - // Storage - let stored_bid = Bids::::get((project_id, bidder.clone(), bid_to_vest.id)).unwrap(); - assert!(stored_bid.plmc_vesting_info.is_some()); - let vest_info = stored_bid.plmc_vesting_info.unwrap(); - let total_vested = - T::Vesting::total_scheduled_amount(&bidder, HoldReason::Participation(project_id).into()).unwrap(); - assert_eq!(vest_info.total_amount, total_vested); - - // Events - frame_system::Pallet::::assert_last_event( - Event::BidPlmcVestingScheduled { - project_id, - bidder: bidder.clone(), - id: stored_bid.id, - amount: vest_info.total_amount, - caller: bidder, - } - .into(), - ); - } - - #[benchmark] - fn start_contribution_vesting_schedule_for() { - // setup - let mut inst = BenchInstantiator::::new(None); - - // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); - - let issuer = account::>("issuer", 0, 0); - let contributions = default_community_contributions::(); - let contributor = contributions[0].contributor.clone(); - whitelist_account!(contributor); - - let project_id = inst.create_finished_project( - default_project::(inst.get_new_nonce(), issuer.clone()), - issuer, - default_evaluations::(), - default_bids::(), - contributions, - vec![], - ); - - run_blocks_to_execute_next_transition(project_id, UpdateType::StartSettlement, &mut inst); - - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Initialized(PhantomData)) - ); - - let contribution_to_vest = - inst.execute(|| Contributions::::iter_prefix_values((project_id, contributor.clone())).next().unwrap()); - - #[extrinsic_call] - start_contribution_vesting_schedule_for( - RawOrigin::Signed(contributor.clone()), - project_id, - contributor.clone(), - contribution_to_vest.id, - ); - - // * validity checks * - // Storage - let stored_contribution = - Contributions::::get((project_id, contributor.clone(), contribution_to_vest.id)).unwrap(); - assert!(stored_contribution.plmc_vesting_info.is_some()); - let vest_info = stored_contribution.plmc_vesting_info.unwrap(); - let total_vested = - T::Vesting::total_scheduled_amount(&contributor, HoldReason::Participation(project_id).into()).unwrap(); - assert_eq!(vest_info.total_amount, total_vested); - - // Events - frame_system::Pallet::::assert_last_event( - Event::ContributionPlmcVestingScheduled { - project_id, - contributor: contributor.clone(), - id: stored_contribution.id, - amount: vest_info.total_amount, - caller: contributor, - } - .into(), - ); - } - - #[benchmark] - fn payout_bid_funds_for() { - // setup - let mut inst = BenchInstantiator::::new(None); - - // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); - - let issuer = account::>("issuer", 0, 0); - let bids = default_bids::(); - let bidder = bids[0].bidder.clone(); - whitelist_account!(bidder); - - let project_id = inst.create_finished_project( - default_project::(inst.get_new_nonce(), issuer.clone()), - issuer.clone(), - default_evaluations::(), - bids, - default_community_contributions::(), - vec![], - ); - - run_blocks_to_execute_next_transition(project_id, UpdateType::StartSettlement, &mut inst); - - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Initialized(PhantomData)) - ); - - let bid_to_payout = - inst.execute(|| Bids::::iter_prefix_values((project_id, bidder.clone())).next().unwrap()); - - #[extrinsic_call] - payout_bid_funds_for(RawOrigin::Signed(issuer.clone()), project_id, bidder.clone(), bid_to_payout.id); - - // * validity checks * - // Storage - let stored_bid = Bids::::get((project_id, bidder.clone(), bid_to_payout.id)).unwrap(); - assert!(stored_bid.funds_released); - - // Balances - let asset = stored_bid.funding_asset.to_assethub_id(); let project_details = ProjectsDetails::::get(project_id).unwrap(); - let free_assets = - inst.get_free_foreign_asset_balances_for(asset, vec![project_details.issuer_account])[0].asset_amount; - assert_eq!(free_assets, stored_bid.funding_asset_amount_locked); - - // Events - frame_system::Pallet::::assert_last_event( - Event::BidFundingPaidOut { project_id, bidder, id: stored_bid.id, amount: free_assets, caller: issuer } - .into(), - ); - } - - #[benchmark] - fn payout_contribution_funds_for() { - // setup - let mut inst = BenchInstantiator::::new(None); - - // real benchmark starts at block 0, and we can't call `events()` at block 0 - inst.advance_time(1u32.into()).unwrap(); - - let issuer = account::>("issuer", 0, 0); - let contributions = default_community_contributions::(); - let contributor = contributions[0].contributor.clone(); - whitelist_account!(contributor); - - let project_id = inst.create_finished_project( - default_project::(inst.get_new_nonce(), issuer.clone()), - issuer.clone(), - default_evaluations::(), - default_bids::(), - contributions, - vec![], - ); - - run_blocks_to_execute_next_transition(project_id, UpdateType::StartSettlement, &mut inst); - - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Initialized(PhantomData)) - ); - - let contribution_to_payout = - inst.execute(|| Contributions::::iter_prefix_values((project_id, contributor.clone())).next().unwrap()); - - #[extrinsic_call] - payout_contribution_funds_for( - RawOrigin::Signed(issuer.clone()), - project_id, - contributor.clone(), - contribution_to_payout.id, - ); - - // * validity checks * - // Storage - let stored_contribution = - Contributions::::get((project_id, contributor.clone(), contribution_to_payout.id)).unwrap(); - assert!(stored_contribution.funds_released); + let reward_info = match project_details.evaluation_round_info.evaluators_outcome { + EvaluatorsOutcome::Rewarded(reward_info) => reward_info, + _ => panic!("EvaluatorsOutcome should be Rewarded"), + }; + let reward = Pallet::::calculate_evaluator_reward(&evaluation_to_settle, &reward_info); - // Balances - let asset = stored_contribution.funding_asset.to_assethub_id(); - let project_details = ProjectsDetails::::get(project_id).unwrap(); - let free_assets = - inst.get_free_foreign_asset_balances_for(asset, vec![project_details.issuer_account])[0].asset_amount; - assert_eq!(free_assets, stored_contribution.funding_asset_amount); + let ct_amount = inst.get_ct_asset_balances_for(project_id, vec![evaluator.clone()])[0]; + assert_eq!(ct_amount, reward); // Events frame_system::Pallet::::assert_last_event( - Event::ContributionFundingPaidOut { + Event::EvaluationSettled { project_id, - contributor, - id: stored_contribution.id, - amount: free_assets, - caller: issuer, + account: evaluator.clone(), + id: evaluation_to_settle.id, + ct_amount: reward, + slashed_amount: 0.into(), } .into(), ); } #[benchmark] - fn decide_project_outcome( - // Insertion attempts in add_to_update_store. Total amount of storage items iterated through in `ProjectsToUpdate`. Leave one free to make the extrinsic pass - x: Linear<1, { ::MaxProjectsToUpdateInsertionAttempts::get() - 1 }>, - ) { + fn settle_failed_evaluation() { // setup let mut inst = BenchInstantiator::::new(None); - // We need to leave enough block numbers to fill `ProjectsToUpdate` before our project insertion - - let time_advance: u32 = x + 2; - frame_system::Pallet::::set_block_number(time_advance.into()); + // real benchmark starts at block 0, and we can't call `events()` at block 0 + inst.advance_time(1u32.into()).unwrap(); let issuer = account::>("issuer", 0, 0); - whitelist_account!(issuer); + let evaluations = default_evaluations::(); + let evaluator = evaluations[0].account.clone(); + whitelist_account!(evaluator); let project_metadata = default_project::(inst.get_new_nonce(), issuer.clone()); let target_funding_amount: BalanceOf = project_metadata.minimum_price.saturating_mul_int(project_metadata.total_allocation_size); - let evaluations = default_evaluations::(); let bids = BenchInstantiator::generate_bids_from_total_usd( - Percent::from_percent(30) * target_funding_amount, - project_metadata.minimum_price, + Percent::from_percent(15) * target_funding_amount, + 10u128.into(), default_weights(), default_bidders::(), default_bidder_multipliers(), ); - let contributions = BenchInstantiator::generate_contributions_from_total_usd( - Percent::from_percent(40) * target_funding_amount, + Percent::from_percent(10) * target_funding_amount, project_metadata.minimum_price, default_weights(), default_community_contributors::(), @@ -2286,33 +1608,53 @@ mod benchmarks { ); let project_id = - inst.create_finished_project(project_metadata, issuer.clone(), evaluations, bids, contributions, vec![]); + inst.create_finished_project(project_metadata, issuer, evaluations, bids, contributions, vec![]); inst.advance_time(One::one()).unwrap(); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); - let current_block = inst.current_block(); - let insertion_block_number: BlockNumberFor = current_block + One::one(); - - fill_projects_to_update::(x, insertion_block_number); - let jwt = get_mock_jwt(issuer.clone(), InvestorType::Institutional, generate_did_from_account(issuer.clone())); + let evaluation_to_settle = + inst.execute(|| Evaluations::::iter_prefix_values((project_id, evaluator.clone())).next().unwrap()); #[extrinsic_call] - decide_project_outcome(RawOrigin::Signed(issuer), jwt, project_id, FundingOutcomeDecision::AcceptFunding); + settle_failed_evaluation( + RawOrigin::Signed(evaluator.clone()), + project_id, + evaluator.clone(), + evaluation_to_settle.id, + ); // * validity checks * // Storage - let maybe_transition = - inst.get_update_block(project_id, &UpdateType::ProjectDecision(FundingOutcomeDecision::AcceptFunding)); - assert!(maybe_transition.is_some()); + // Evaluation should be removed + assert!(Evaluations::::get((project_id, evaluator.clone(), evaluation_to_settle.id)).is_none()); + let slashed_amount = T::EvaluatorSlash::get() * evaluation_to_settle.original_plmc_bond; + + let reserved_plmc = inst + .get_reserved_plmc_balances_for(vec![evaluator.clone()], HoldReason::Evaluation(project_id).into())[0] + .plmc_amount; + assert_eq!(reserved_plmc, 0.into()); + + let treasury_account = T::ProtocolGrowthTreasury::get(); + let free_treasury_plmc = inst.get_free_plmc_balances_for(vec![treasury_account])[0].plmc_amount; + let ed = BenchInstantiator::::get_ed(); + assert_eq!(free_treasury_plmc, slashed_amount + ed); // Events frame_system::Pallet::::assert_last_event( - Event::ProjectOutcomeDecided { project_id, decision: FundingOutcomeDecision::AcceptFunding }.into(), + Event::EvaluationSettled { + project_id, + account: evaluator.clone(), + id: evaluation_to_settle.id, + ct_amount: 0.into(), + slashed_amount, + } + .into(), ); } #[benchmark] - fn release_bid_funds_for() { + fn settle_successful_bid() { // setup let mut inst = BenchInstantiator::::new(None); @@ -2320,69 +1662,45 @@ mod benchmarks { inst.advance_time(1u32.into()).unwrap(); let issuer = account::>("issuer", 0, 0); - let evaluations = default_evaluations::(); - - let project_metadata = default_project::(inst.get_new_nonce(), issuer.clone()); - let target_funding_amount: BalanceOf = - project_metadata.minimum_price.saturating_mul_int(project_metadata.total_allocation_size); - - let bids: Vec> = BenchInstantiator::generate_bids_from_total_usd( - Percent::from_percent(15) * target_funding_amount, - 10u128.into(), - default_weights(), - default_bidders::(), - default_bidder_multipliers(), - ); + let bids = default_bids::(); let bidder = bids[0].bidder.clone(); whitelist_account!(bidder); - let contributions = BenchInstantiator::generate_contributions_from_total_usd( - Percent::from_percent(10) * target_funding_amount, - project_metadata.minimum_price, - default_weights(), - default_community_contributors::(), - default_community_contributor_multipliers(), + + let project_id = inst.create_finished_project( + default_project::(inst.get_new_nonce(), issuer.clone()), + issuer, + default_evaluations::(), + bids, + default_community_contributions::(), + vec![], ); - let project_id = - inst.create_finished_project(project_metadata, issuer.clone(), evaluations, bids, contributions, vec![]); + run_blocks_to_execute_next_transition(project_id, UpdateType::StartSettlement, &mut inst); - inst.advance_time(One::one()).unwrap(); - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Failure(CleanerState::Initialized(PhantomData)) - ); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); - let bid_to_payout = + let bid_to_settle = inst.execute(|| Bids::::iter_prefix_values((project_id, bidder.clone())).next().unwrap()); - let asset = bid_to_payout.funding_asset.to_assethub_id(); - let free_assets_before = inst.get_free_foreign_asset_balances_for(asset, vec![bidder.clone()])[0].asset_amount; + #[extrinsic_call] - release_bid_funds_for(RawOrigin::Signed(issuer.clone()), project_id, bidder.clone(), bid_to_payout.id); + settle_successful_bid(RawOrigin::Signed(bidder.clone()), project_id, bidder.clone(), bid_to_settle.id); // * validity checks * // Storage - let stored_bid = Bids::::get((project_id, bidder.clone(), bid_to_payout.id)).unwrap(); - assert!(stored_bid.funds_released); + assert!(Bids::::get((project_id, bidder.clone(), bid_to_settle.id)).is_none()); // Balances - let free_assets = inst.get_free_foreign_asset_balances_for(asset, vec![bidder.clone()])[0].asset_amount; - assert_eq!(free_assets, stored_bid.funding_asset_amount_locked + free_assets_before); + let ct_amount = inst.get_ct_asset_balances_for(project_id, vec![bidder.clone()])[0]; + assert_eq!(bid_to_settle.final_ct_amount, ct_amount); // Events frame_system::Pallet::::assert_last_event( - Event::BidFundingReleased { - project_id, - bidder, - id: stored_bid.id, - amount: stored_bid.funding_asset_amount_locked, - caller: issuer, - } - .into(), + Event::BidSettled { project_id, account: bidder.clone(), id: bid_to_settle.id, ct_amount }.into(), ); } #[benchmark] - fn release_contribution_funds_for() { + fn settle_failed_bid() { // setup let mut inst = BenchInstantiator::::new(None); @@ -2398,69 +1716,50 @@ mod benchmarks { let bids: Vec> = BenchInstantiator::generate_bids_from_total_usd( Percent::from_percent(15) * target_funding_amount, - project_metadata.minimum_price, + 10u128.into(), default_weights(), default_bidders::(), default_bidder_multipliers(), ); - let contributions: Vec> = BenchInstantiator::generate_contributions_from_total_usd( + let bidder = bids[0].bidder.clone(); + whitelist_account!(bidder); + let contributions = BenchInstantiator::generate_contributions_from_total_usd( Percent::from_percent(10) * target_funding_amount, project_metadata.minimum_price, default_weights(), default_community_contributors::(), default_community_contributor_multipliers(), ); - let contributor = contributions[0].contributor.clone(); - whitelist_account!(contributor); let project_id = - inst.create_finished_project(project_metadata, issuer, evaluations, bids, contributions, vec![]); + inst.create_finished_project(project_metadata, issuer.clone(), evaluations, bids, contributions, vec![]); inst.advance_time(One::one()).unwrap(); - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Failure(CleanerState::Initialized(PhantomData)) - ); - - let contribution_to_payout = - inst.execute(|| Contributions::::iter_prefix_values((project_id, contributor.clone())).next().unwrap()); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); - let asset = contribution_to_payout.funding_asset.to_assethub_id(); - let free_assets_before = - inst.get_free_foreign_asset_balances_for(asset, vec![contributor.clone()])[0].asset_amount; + let bid_to_settle = + inst.execute(|| Bids::::iter_prefix_values((project_id, bidder.clone())).next().unwrap()); + let asset = bid_to_settle.funding_asset.to_assethub_id(); + let free_assets_before = inst.get_free_foreign_asset_balances_for(asset, vec![bidder.clone()])[0].asset_amount; #[extrinsic_call] - release_contribution_funds_for( - RawOrigin::Signed(contributor.clone()), - project_id, - contributor.clone(), - contribution_to_payout.id, - ); + settle_failed_bid(RawOrigin::Signed(issuer.clone()), project_id, bidder.clone(), bid_to_settle.id); // * validity checks * // Storage - let stored_contribution = - Contributions::::get((project_id, contributor.clone(), contribution_to_payout.id)).unwrap(); - assert!(stored_contribution.funds_released); + assert!(Bids::::get((project_id, bidder.clone(), bid_to_settle.id)).is_none()); // Balances - let free_assets = inst.get_free_foreign_asset_balances_for(asset, vec![contributor.clone()])[0].asset_amount; - assert_eq!(free_assets, stored_contribution.funding_asset_amount + free_assets_before); + let free_assets = inst.get_free_foreign_asset_balances_for(asset, vec![bidder.clone()])[0].asset_amount; + assert_eq!(free_assets, bid_to_settle.funding_asset_amount_locked + free_assets_before); // Events frame_system::Pallet::::assert_last_event( - Event::ContributionFundingReleased { - project_id, - contributor: contributor.clone(), - id: stored_contribution.id, - amount: stored_contribution.funding_asset_amount, - caller: contributor, - } - .into(), + Event::BidSettled { project_id, account: bidder.clone(), id: bid_to_settle.id, ct_amount: 0.into() }.into(), ); } #[benchmark] - fn bid_unbond_for() { + fn settle_successful_contribution() { // setup let mut inst = BenchInstantiator::::new(None); @@ -2468,71 +1767,56 @@ mod benchmarks { inst.advance_time(1u32.into()).unwrap(); let issuer = account::>("issuer", 0, 0); - let evaluations = default_evaluations::(); - - let project_metadata = default_project::(inst.get_new_nonce(), issuer.clone()); - let target_funding_amount: BalanceOf = - project_metadata.minimum_price.saturating_mul_int(project_metadata.total_allocation_size); + let contributions = default_community_contributions::(); + let contributor = contributions[0].contributor.clone(); + whitelist_account!(contributor); - let bids: Vec> = BenchInstantiator::generate_bids_from_total_usd( - Percent::from_percent(15) * target_funding_amount, - 10u128.into(), - default_weights(), - default_bidders::(), - default_bidder_multipliers(), - ); - let bidder = bids[0].bidder.clone(); - whitelist_account!(bidder); - let contributions = BenchInstantiator::generate_contributions_from_total_usd( - Percent::from_percent(10) * target_funding_amount, - project_metadata.minimum_price, - default_weights(), - default_community_contributors::(), - default_community_contributor_multipliers(), + let project_id = inst.create_finished_project( + default_project::(inst.get_new_nonce(), issuer.clone()), + issuer, + default_evaluations::(), + default_bids::(), + contributions, + vec![], ); - let project_id = - inst.create_finished_project(project_metadata, issuer, evaluations, bids, contributions, vec![]); - - inst.advance_time(One::one()).unwrap(); - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Failure(CleanerState::Initialized(PhantomData)) - ); + run_blocks_to_execute_next_transition(project_id, UpdateType::StartSettlement, &mut inst); - let stored_bid = inst.execute(|| Bids::::iter_prefix_values((project_id, bidder.clone())).next().unwrap()); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); - inst.execute(|| { - PalletFunding::::release_bid_funds_for( - ::RuntimeOrigin::signed(bidder.clone().into()), - project_id, - bidder.clone(), - stored_bid.id, - ) - .expect("Funds are released") - }); + let contribution_to_settle = + inst.execute(|| Contributions::::iter_prefix_values((project_id, contributor.clone())).next().unwrap()); #[extrinsic_call] - bid_unbond_for(RawOrigin::Signed(bidder.clone()), project_id, bidder.clone(), stored_bid.id); + settle_successful_contribution( + RawOrigin::Signed(contributor.clone()), + project_id, + contributor.clone(), + contribution_to_settle.id, + ); // * validity checks * // Storage - assert!(!Bids::::contains_key((project_id, bidder.clone(), stored_bid.id))); + assert!(Contributions::::get((project_id, contributor.clone(), contribution_to_settle.id)).is_none()); + // Balances - let reserved_plmc = inst - .get_reserved_plmc_balances_for(vec![bidder.clone()], HoldReason::Participation(project_id).into())[0] - .plmc_amount; - assert_eq!(reserved_plmc, 0.into()); + let ct_amount = inst.get_ct_asset_balances_for(project_id, vec![contributor.clone()])[0]; + assert_eq!(contribution_to_settle.ct_amount, ct_amount); // Events frame_system::Pallet::::assert_last_event( - Event::BondReleased { project_id, amount: stored_bid.plmc_bond, bonder: bidder.clone(), releaser: bidder } - .into(), + Event::ContributionSettled { + project_id, + account: contributor.clone(), + id: contribution_to_settle.id, + ct_amount, + } + .into(), ); } #[benchmark] - fn contribution_unbond_for() { + fn settle_failed_contribution() { // setup let mut inst = BenchInstantiator::::new(None); @@ -2564,60 +1848,45 @@ mod benchmarks { whitelist_account!(contributor); let project_id = - inst.create_finished_project(project_metadata, issuer.clone(), evaluations, bids, contributions, vec![]); + inst.create_finished_project(project_metadata, issuer, evaluations, bids, contributions, vec![]); inst.advance_time(One::one()).unwrap(); - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Failure(CleanerState::Initialized(PhantomData)) - ); + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); - let stored_contribution = + let contribution_to_settle = inst.execute(|| Contributions::::iter_prefix_values((project_id, contributor.clone())).next().unwrap()); - inst.execute(|| { - PalletFunding::::release_contribution_funds_for( - ::RuntimeOrigin::signed(contributor.clone().into()), - project_id, - contributor.clone(), - stored_contribution.id, - ) - .expect("Funds are released") - }); - + let asset = contribution_to_settle.funding_asset.to_assethub_id(); + let free_assets_before = + inst.get_free_foreign_asset_balances_for(asset, vec![contributor.clone()])[0].asset_amount; #[extrinsic_call] - contribution_unbond_for( - RawOrigin::Signed(issuer.clone()), + settle_failed_contribution( + RawOrigin::Signed(contributor.clone()), project_id, contributor.clone(), - stored_contribution.id, + contribution_to_settle.id, ); // * validity checks * // Storage - assert!(!Contributions::::contains_key((project_id, contributor.clone(), stored_contribution.id))); + assert!(Contributions::::get((project_id, contributor.clone(), contribution_to_settle.id)).is_none()); + // Balances - let reserved_plmc = inst - .get_reserved_plmc_balances_for(vec![contributor.clone()], HoldReason::Participation(project_id).into())[0] - .plmc_amount; - assert_eq!(reserved_plmc, 0.into()); + let free_assets = inst.get_free_foreign_asset_balances_for(asset, vec![contributor.clone()])[0].asset_amount; + assert_eq!(free_assets, contribution_to_settle.funding_asset_amount + free_assets_before); // Events frame_system::Pallet::::assert_last_event( - Event::BondReleased { + Event::ContributionSettled { project_id, - amount: stored_contribution.plmc_bond, - bonder: contributor, - releaser: issuer, + account: contributor.clone(), + id: contribution_to_settle.id, + ct_amount: 0.into(), } .into(), ); } - // - // on_initialize - // - //do_evaluation_end #[benchmark] fn end_evaluation_success( @@ -2917,7 +2186,7 @@ mod benchmarks { Bids::::iter_prefix_values((project_id,)).filter(|b| matches!(b.status, BidStatus::Accepted)).count(); let rejected_bids_count = Bids::::iter_prefix_values((project_id,)).filter(|b| matches!(b.status, BidStatus::Rejected(_))).count(); - assert_eq!(rejected_bids_count, z as usize); + assert_eq!(rejected_bids_count, 0); assert_eq!(accepted_bids_count, y as usize); // Events @@ -3404,7 +2673,7 @@ mod benchmarks { // * validity checks * let project_details = inst.get_project_details(project_id); - assert_eq!(project_details.cleanup, Cleaner::Success(CleanerState::Initialized(PhantomData))); + assert_eq!(project_details.status, ProjectStatus::FundingSuccessful); } #[benchmark] @@ -3452,7 +2721,7 @@ mod benchmarks { // * validity checks * let project_details = inst.get_project_details(project_id); - assert_eq!(project_details.cleanup, Cleaner::Failure(CleanerState::Initialized(PhantomData))); + assert_eq!(project_details.status, ProjectStatus::FundingFailed); } #[cfg(test)] @@ -3531,121 +2800,51 @@ mod benchmarks { } #[test] - fn bench_evaluation_unbond_for() { - new_test_ext().execute_with(|| { - assert_ok!(PalletFunding::::test_evaluation_unbond_for()); - }); - } - - #[test] - fn bench_evaluation_reward_payout_for_with_ct_account_creation() { - new_test_ext().execute_with(|| { - assert_ok!(PalletFunding::::test_evaluation_reward_payout_for_with_ct_account_creation()); - }); - } - - #[test] - fn bench_evaluation_reward_payout_for_no_ct_account_creation() { - new_test_ext().execute_with(|| { - assert_ok!(PalletFunding::::test_evaluation_reward_payout_for_no_ct_account_creation()); - }); - } - - #[test] - fn bench_evaluation_slash_for() { - new_test_ext().execute_with(|| { - assert_ok!(PalletFunding::::test_evaluation_slash_for()); - }); - } - - #[test] - fn bench_bid_ct_mint_for_with_ct_account_creation() { - new_test_ext().execute_with(|| { - assert_ok!(PalletFunding::::test_bid_ct_mint_for_with_ct_account_creation()); - }); - } - - #[test] - fn bench_bid_ct_mint_for_no_ct_account_creation() { - new_test_ext().execute_with(|| { - assert_ok!(PalletFunding::::test_bid_ct_mint_for_no_ct_account_creation()); - }); - } - - #[test] - fn bench_contribution_ct_mint_for_with_ct_account_creation() { - new_test_ext().execute_with(|| { - assert_ok!(PalletFunding::::test_contribution_ct_mint_for_with_ct_account_creation()); - }); - } - - #[test] - fn bench_contribution_ct_mint_for_no_ct_account_creation() { - new_test_ext().execute_with(|| { - assert_ok!(PalletFunding::::test_contribution_ct_mint_for_no_ct_account_creation()); - }); - } - - #[test] - fn bench_start_bid_vesting_schedule_for() { - new_test_ext().execute_with(|| { - assert_ok!(PalletFunding::::test_start_bid_vesting_schedule_for()); - }); - } - - #[test] - fn bench_start_contribution_vesting_schedule_for() { - new_test_ext().execute_with(|| { - assert_ok!(PalletFunding::::test_start_contribution_vesting_schedule_for()); - }); - } - - #[test] - fn bench_payout_bid_funds_for() { + fn bench_decide_project_outcome() { new_test_ext().execute_with(|| { - assert_ok!(PalletFunding::::test_payout_bid_funds_for()); + assert_ok!(PalletFunding::::test_decide_project_outcome()); }); } #[test] - fn bench_payout_contribution_funds_for() { + fn bench_settle_successful_evaluation() { new_test_ext().execute_with(|| { - assert_ok!(PalletFunding::::test_payout_contribution_funds_for()); + assert_ok!(PalletFunding::::test_settle_successful_evaluation()); }); } #[test] - fn bench_decide_project_outcome() { + fn bench_settle_failed_evaluation() { new_test_ext().execute_with(|| { - assert_ok!(PalletFunding::::test_decide_project_outcome()); + assert_ok!(PalletFunding::::test_settle_failed_evaluation()); }); } #[test] - fn bench_release_bid_funds_for() { + fn bench_settle_successful_bid() { new_test_ext().execute_with(|| { - assert_ok!(PalletFunding::::test_release_bid_funds_for()); + assert_ok!(PalletFunding::::test_settle_successful_bid()); }); } #[test] - fn bench_release_contribution_funds_for() { + fn bench_settle_failed_bid() { new_test_ext().execute_with(|| { - assert_ok!(PalletFunding::::test_release_contribution_funds_for()); + assert_ok!(PalletFunding::::test_settle_failed_bid()); }); } #[test] - fn bench_bid_unbond_for() { + fn bench_settle_successful_contribution() { new_test_ext().execute_with(|| { - assert_ok!(PalletFunding::::test_bid_unbond_for()); + assert_ok!(PalletFunding::::test_settle_successful_contribution()); }); } #[test] - fn bench_contribution_unbond_for() { + fn bench_settle_failed_contribution() { new_test_ext().execute_with(|| { - assert_ok!(PalletFunding::::test_contribution_unbond_for()); + assert_ok!(PalletFunding::::test_settle_failed_contribution()); }); } diff --git a/pallets/funding/src/functions.rs b/pallets/funding/src/functions.rs index 19521d963..ddf705549 100644 --- a/pallets/funding/src/functions.rs +++ b/pallets/funding/src/functions.rs @@ -18,35 +18,30 @@ //! Functions for the Funding pallet. use crate::ProjectStatus::FundingSuccessful; +use core::ops::Not; use frame_support::{ dispatch::{DispatchErrorWithPostInfo, DispatchResult, DispatchResultWithPostInfo, PostDispatchInfo}, ensure, pallet_prelude::*, traits::{ fungible::{Mutate, MutateHold as FungibleMutateHold}, - fungibles::{metadata::Mutate as MetadataMutate, Create, Inspect, Mutate as FungiblesMutate}, - tokens::{Fortitude, Precision, Preservation, Restriction}, + fungibles::{metadata::Mutate as MetadataMutate, Create, Mutate as FungiblesMutate}, + tokens::{Precision, Preservation}, Get, }, transactional, }; use frame_system::pallet_prelude::BlockNumberFor; -use itertools::Itertools; -use polimec_common::{ - credentials::{Did, InvestorType}, - ReleaseSchedule, -}; +use polimec_common::credentials::{Did, InvestorType}; use sp_arithmetic::{ traits::{CheckedDiv, CheckedSub, Zero}, Percent, Perquintill, }; -use sp_runtime::traits::{Convert, ConvertBack}; -use sp_std::{marker::PhantomData, ops::Not}; -use xcm::v3::MaxDispatchErrorLen; +use sp_runtime::traits::Convert; use super::*; use crate::traits::{BondingRequirementCalculation, ProvideAssetPrice, VestingDurationCalculation}; -use polimec_common::migration_types::{MigrationInfo, MigrationOrigin, Migrations, ParticipationType}; +use polimec_common::migration_types::{MigrationInfo, Migrations}; const POLIMEC_PARA_ID: u32 = 3344u32; const QUERY_RESPONSE_TIME_WINDOW_BLOCKS: u32 = 20u32; @@ -201,7 +196,6 @@ impl Pallet { } else { // * Update storage * project_details.status = ProjectStatus::EvaluationFailed; - project_details.cleanup = Cleaner::Failure(CleanerState::Initialized(PhantomData::)); ProjectsDetails::::insert(project_id, project_details.clone()); let issuer_did = project_details.issuer_did.clone(); DidWithActiveProjects::::set(issuer_did, None); @@ -730,8 +724,6 @@ impl Pallet { ); // * Calculate new variables * - project_details.cleanup = - Cleaner::try_from(project_details.status.clone()).map_err(|_| Error::::NotAllowed)?; project_details.funding_end_block = Some(now); // * Update storage * @@ -812,7 +804,6 @@ impl Pallet { phase_transition_points: PhaseTransitionPoints::new(now), remaining_contribution_tokens: metadata.total_allocation_size, funding_amount_reached: BalanceOf::::zero(), - cleanup: Cleaner::NotReady, evaluation_round_info: EvaluationRoundInfoOf:: { total_bonded_usd: Zero::zero(), total_bonded_plmc: Zero::zero(), @@ -1012,8 +1003,6 @@ impl Pallet { early_usd_amount, late_usd_amount, when: now, - rewarded_or_slashed: None, - ct_migration_status: MigrationStatus::NotStarted, }; if caller_existing_evaluations.len() < T::MaxEvaluationsPerUser::get() as usize { @@ -1231,11 +1220,7 @@ impl Pallet { funding_asset_amount_locked, multiplier, plmc_bond, - plmc_vesting_info: None, when: now, - funds_released: false, - ct_minted: false, - ct_migration_status: MigrationStatus::NotStarted, }; Self::try_plmc_participation_lock(bidder, project_id, plmc_bond)?; @@ -1274,7 +1259,7 @@ impl Pallet { let did_has_winning_bid = DidWithWinningBids::::get(project_id, did.clone()); ensure!(project_details.status == ProjectStatus::CommunityRound, Error::::AuctionNotStarted); - ensure!(did_has_winning_bid.not(), Error::::UserHasWinningBids); + ensure!(!did_has_winning_bid, Error::::UserHasWinningBids); let buyable_tokens = token_amount.min(project_details.remaining_contribution_tokens); project_details.remaining_contribution_tokens.saturating_reduce(buyable_tokens); @@ -1425,10 +1410,6 @@ impl Pallet { funding_asset, funding_asset_amount, plmc_bond, - plmc_vesting_info: None, - funds_released: false, - ct_minted: false, - ct_migration_status: MigrationStatus::NotStarted, }; // Try adding the new contribution to the system @@ -1511,653 +1492,6 @@ impl Pallet { }) } - #[transactional] - pub fn do_bid_ct_mint_for( - releaser: &AccountIdOf, - project_id: ProjectId, - bidder: &AccountIdOf, - bid_id: u32, - ) -> DispatchResultWithPostInfo { - // * Get variables * - let mut bid = Bids::::get((project_id, bidder, bid_id)).ok_or(Error::::BidNotFound)?; - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectNotFound)?; - let ct_amount = bid.final_ct_amount; - - // weight return variables - let mut ct_account_created = false; - - // * Validity checks * - ensure!(project_details.status == ProjectStatus::FundingSuccessful, Error::::NotAllowed); - ensure!(!bid.ct_minted, Error::::NotAllowed); - ensure!(matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..)), Error::::NotAllowed); - ensure!(T::ContributionTokenCurrency::asset_exists(project_id), Error::::CannotClaimYet); - - // * Calculate variables * - bid.ct_minted = true; - - // * Update storage * - if !T::ContributionTokenCurrency::contains(&project_id, &bid.bidder) { - ct_account_created = true; - T::ContributionTokenCurrency::touch(project_id, &bid.bidder, &bid.bidder)?; - } - T::ContributionTokenCurrency::mint_into(project_id, &bid.bidder, ct_amount)?; - Bids::::insert((project_id, bidder, bid_id), &bid); - - // * Emit events * - Self::deposit_event(Event::ContributionTokenMinted { - releaser: releaser.clone(), - project_id: bid.project_id, - claimer: bidder.clone(), - amount: ct_amount, - }); - - Ok(PostDispatchInfo { - actual_weight: Some(if ct_account_created { - WeightInfoOf::::bid_ct_mint_for_with_ct_account_creation() - } else { - WeightInfoOf::::bid_ct_mint_for_no_ct_account_creation() - }), - pays_fee: Pays::Yes, - }) - } - - #[transactional] - pub fn do_contribution_ct_mint_for( - releaser: &AccountIdOf, - project_id: ProjectId, - contributor: &AccountIdOf, - contribution_id: u32, - ) -> DispatchResultWithPostInfo { - // * Get variables * - let mut contribution = - Contributions::::get((project_id, contributor, contribution_id)).ok_or(Error::::BidNotFound)?; - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectNotFound)?; - let ct_amount = contribution.ct_amount; - - // weight return variables - let mut ct_account_created = false; - - // * Validity checks * - ensure!(project_details.status == ProjectStatus::FundingSuccessful, Error::::NotAllowed); - ensure!(!contribution.ct_minted, Error::::NotAllowed); - ensure!(T::ContributionTokenCurrency::asset_exists(project_id), Error::::CannotClaimYet); - - // * Calculate variables * - contribution.ct_minted = true; - - // * Update storage * - if !T::ContributionTokenCurrency::contains(&project_id, &contribution.contributor) { - ct_account_created = true; - T::ContributionTokenCurrency::touch(project_id, &contribution.contributor, &contribution.contributor)?; - } - T::ContributionTokenCurrency::mint_into(project_id, &contribution.contributor, ct_amount)?; - Contributions::::insert((project_id, contributor, contribution_id), contribution); - - // * Emit events * - Self::deposit_event(Event::ContributionTokenMinted { - releaser: releaser.clone(), - project_id, - claimer: contributor.clone(), - amount: ct_amount, - }); - - Ok(PostDispatchInfo { - actual_weight: Some(if ct_account_created { - WeightInfoOf::::contribution_ct_mint_for_with_ct_account_creation() - } else { - WeightInfoOf::::contribution_ct_mint_for_no_ct_account_creation() - }), - pays_fee: Pays::Yes, - }) - } - - #[transactional] - pub fn do_evaluation_unbond_for( - releaser: &AccountIdOf, - project_id: ProjectId, - evaluator: &AccountIdOf, - evaluation_id: u32, - ) -> DispatchResult { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let mut released_evaluation = - Evaluations::::get((project_id, evaluator, evaluation_id)).ok_or(Error::::EvaluationNotFound)?; - let release_amount = released_evaluation.current_plmc_bond; - - // * Validity checks * - ensure!( - (project_details.evaluation_round_info.evaluators_outcome == EvaluatorsOutcomeOf::::Unchanged || - released_evaluation.rewarded_or_slashed.is_some()) && - matches!( - project_details.status, - ProjectStatus::EvaluationFailed | ProjectStatus::FundingFailed | ProjectStatus::FundingSuccessful - ), - Error::::NotAllowed - ); - - // * Update Storage * - T::NativeCurrency::release( - &HoldReason::Evaluation(project_id).into(), - evaluator, - released_evaluation.current_plmc_bond, - Precision::Exact, - )?; - - released_evaluation.current_plmc_bond = Zero::zero(); - Evaluations::::insert((project_id, evaluator, evaluation_id), released_evaluation); - - // FIXME: same question as removing bid - // Evaluations::::remove((project_id, evaluator, evaluation_id)); - - // * Emit events * - Self::deposit_event(Event::BondReleased { - project_id, - amount: release_amount, - bonder: evaluator.clone(), - releaser: releaser.clone(), - }); - - Ok(()) - } - - #[transactional] - pub fn do_evaluation_reward_payout_for( - caller: &AccountIdOf, - project_id: ProjectId, - evaluator: &AccountIdOf, - evaluation_id: u32, - ) -> DispatchResultWithPostInfo { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let reward_info = - if let EvaluatorsOutcome::Rewarded(info) = project_details.evaluation_round_info.evaluators_outcome { - info - } else { - return Err(Error::::NotAllowed.into()); - }; - let mut evaluation = - Evaluations::::get((project_id, evaluator, evaluation_id)).ok_or(Error::::EvaluationNotFound)?; - - // weight return variables - let mut ct_account_created = false; - - // * Validity checks * - ensure!( - evaluation.rewarded_or_slashed.is_none() && - matches!(project_details.status, ProjectStatus::FundingSuccessful), - Error::::NotAllowed - ); - - // * Calculate variables * - let early_reward_weight = - Perquintill::from_rational(evaluation.early_usd_amount, reward_info.early_evaluator_total_bonded_usd); - let normal_reward_weight = Perquintill::from_rational( - evaluation.late_usd_amount.saturating_add(evaluation.early_usd_amount), - reward_info.normal_evaluator_total_bonded_usd, - ); - let early_evaluators_rewards = early_reward_weight * reward_info.early_evaluator_reward_pot; - let normal_evaluators_rewards = normal_reward_weight * reward_info.normal_evaluator_reward_pot; - let total_reward_amount = early_evaluators_rewards.saturating_add(normal_evaluators_rewards); - - // * Update storage * - if !T::ContributionTokenCurrency::contains(&project_id, &evaluation.evaluator) { - ct_account_created = true; - T::ContributionTokenCurrency::touch(project_id, &evaluation.evaluator, &evaluation.evaluator)?; - } - T::ContributionTokenCurrency::mint_into(project_id, &evaluation.evaluator, total_reward_amount)?; - evaluation.rewarded_or_slashed = Some(RewardOrSlash::Reward(total_reward_amount)); - Evaluations::::insert((project_id, evaluator, evaluation_id), evaluation); - - // * Emit events * - Self::deposit_event(Event::EvaluationRewarded { - project_id, - evaluator: evaluator.clone(), - id: evaluation_id, - amount: total_reward_amount, - caller: caller.clone(), - }); - - Ok(if ct_account_created { - PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::evaluation_reward_payout_for_with_ct_account_creation()), - pays_fee: Pays::Yes, - } - } else { - PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::evaluation_reward_payout_for_no_ct_account_creation()), - pays_fee: Pays::Yes, - } - }) - } - - #[transactional] - pub fn do_evaluation_slash_for( - caller: &AccountIdOf, - project_id: ProjectId, - evaluator: &AccountIdOf, - evaluation_id: u32, - ) -> DispatchResult { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let slash_percentage = T::EvaluatorSlash::get(); - let treasury_account = T::ProtocolGrowthTreasury::get(); - - let mut evaluation = - Evaluations::::get((project_id, evaluator, evaluation_id)).ok_or(Error::::EvaluationNotFound)?; - - // * Validity checks * - ensure!( - evaluation.rewarded_or_slashed.is_none() && - matches!(project_details.evaluation_round_info.evaluators_outcome, EvaluatorsOutcome::Slashed), - Error::::NotAllowed - ); - - // * Calculate variables * - // 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 = Some(RewardOrSlash::Slash(slashed_amount)); - - T::NativeCurrency::transfer_on_hold( - &HoldReason::Evaluation(project_id).into(), - evaluator, - &treasury_account, - slashed_amount, - Precision::Exact, - Restriction::Free, - Fortitude::Force, - )?; - - evaluation.current_plmc_bond.saturating_reduce(slashed_amount); - Evaluations::::insert((project_id, evaluator, evaluation.id), evaluation); - - // * Emit events * - Self::deposit_event(Event::EvaluationSlashed { - project_id, - evaluator: evaluator.clone(), - id: evaluation_id, - amount: slashed_amount, - caller: caller.clone(), - }); - - Ok(()) - } - - #[transactional] - pub fn do_start_bid_vesting_schedule_for( - caller: &AccountIdOf, - project_id: ProjectId, - bidder: &AccountIdOf, - bid_id: u32, - ) -> DispatchResult { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let mut bid = Bids::::get((project_id, bidder, bid_id)).ok_or(Error::::BidNotFound)?; - let funding_end_block = project_details.funding_end_block.ok_or(Error::::ImpossibleState)?; - - // * Validity checks * - ensure!( - bid.plmc_vesting_info.is_none() && - project_details.status == ProjectStatus::FundingSuccessful && - matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..)), - Error::::NotAllowed - ); - - // * Calculate variables * - let vest_info = - Self::calculate_vesting_info(bidder, bid.multiplier, bid.plmc_bond).map_err(|_| Error::::BadMath)?; - bid.plmc_vesting_info = Some(vest_info); - - // * Update storage * - T::Vesting::add_release_schedule( - bidder, - vest_info.total_amount, - vest_info.amount_per_block, - funding_end_block, - HoldReason::Participation(project_id).into(), - )?; - Bids::::insert((project_id, bidder, bid_id), bid); - - // * Emit events * - Self::deposit_event(Event::BidPlmcVestingScheduled { - project_id, - bidder: bidder.clone(), - id: bid_id, - amount: vest_info.total_amount, - caller: caller.clone(), - }); - - Ok(()) - } - - #[transactional] - pub fn do_start_contribution_vesting_schedule_for( - caller: &AccountIdOf, - project_id: ProjectId, - contributor: &AccountIdOf, - contribution_id: u32, - ) -> DispatchResult { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let mut contribution = - Contributions::::get((project_id, contributor, contribution_id)).ok_or(Error::::BidNotFound)?; - let funding_end_block = project_details.funding_end_block.ok_or(Error::::ImpossibleState)?; - - // * Validity checks * - ensure!( - contribution.plmc_vesting_info.is_none() && project_details.status == ProjectStatus::FundingSuccessful, - Error::::NotAllowed - ); - - // * Calculate variables * - let vest_info = Self::calculate_vesting_info(contributor, contribution.multiplier, contribution.plmc_bond) - .map_err(|_| Error::::BadMath)?; - contribution.plmc_vesting_info = Some(vest_info); - - // * Update storage * - T::Vesting::add_release_schedule( - contributor, - vest_info.total_amount, - vest_info.amount_per_block, - funding_end_block, - HoldReason::Participation(project_id).into(), - )?; - Contributions::::insert((project_id, contributor, contribution_id), contribution); - - // * Emit events * - Self::deposit_event(Event::ContributionPlmcVestingScheduled { - project_id, - contributor: contributor.clone(), - id: contribution_id, - amount: vest_info.total_amount, - caller: caller.clone(), - }); - - Ok(()) - } - - #[transactional] - pub fn do_vest_plmc_for( - caller: AccountIdOf, - project_id: ProjectId, - participant: AccountIdOf, - ) -> DispatchResult { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - - // * Validity checks * - ensure!(matches!(project_details.status, ProjectStatus::FundingSuccessful), Error::::NotAllowed); - - // * Update storage * - let vested_amount = T::Vesting::vest(participant.clone(), HoldReason::Participation(project_id).into())?; - - // * Emit events * - Self::deposit_event(Event::ParticipantPlmcVested { project_id, participant, amount: vested_amount, caller }); - - Ok(()) - } - - #[transactional] - pub fn do_release_bid_funds_for( - caller: &AccountIdOf, - project_id: ProjectId, - bidder: &AccountIdOf, - bid_id: u32, - ) -> DispatchResult { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let mut bid = Bids::::get((project_id, bidder, bid_id)).ok_or(Error::::BidNotFound)?; - - // * Validity checks * - ensure!( - project_details.status == ProjectStatus::FundingFailed && - matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..)), - Error::::NotAllowed - ); - - // * Calculate variables * - let project_pot = Self::fund_account_id(project_id); - let payout_amount = bid.funding_asset_amount_locked; - let payout_asset = bid.funding_asset; - - // * Update storage * - T::FundingCurrency::transfer( - payout_asset.to_assethub_id(), - &project_pot, - bidder, - payout_amount, - Preservation::Expendable, - )?; - bid.funds_released = true; - Bids::::insert((project_id, bidder, bid_id), bid); - - // * Emit events * - Self::deposit_event(Event::BidFundingReleased { - project_id, - bidder: bidder.clone(), - id: bid_id, - amount: payout_amount, - caller: caller.clone(), - }); - - Ok(()) - } - - // Unbond the PLMC of a bid instantly, following a failed funding outcome. - // Unbonding of PLMC in a successful funding outcome is handled by the vesting schedule. - #[transactional] - pub fn do_bid_unbond_for( - caller: &AccountIdOf, - project_id: ProjectId, - bidder: &AccountIdOf, - bid_id: u32, - ) -> DispatchResult { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let bid = Bids::::get((project_id, bidder, bid_id)).ok_or(Error::::EvaluationNotFound)?; - - // * Validity checks * - ensure!( - project_details.status == ProjectStatus::FundingFailed && - matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..)) && - bid.funds_released, - Error::::NotAllowed - ); - - // * Update Storage * - T::NativeCurrency::release( - &HoldReason::Participation(project_id).into(), - bidder, - bid.plmc_bond, - Precision::Exact, - )?; - - Bids::::remove((project_id, bidder, bid_id)); - - // * Emit events * - Self::deposit_event(Event::BondReleased { - project_id, - amount: bid.plmc_bond, - bonder: bidder.clone(), - releaser: caller.clone(), - }); - - Ok(()) - } - - #[transactional] - pub fn do_release_contribution_funds_for( - caller: &AccountIdOf, - project_id: ProjectId, - contributor: &AccountIdOf, - contribution_id: u32, - ) -> DispatchResult { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let mut contribution = Contributions::::get((project_id, contributor, contribution_id)) - .ok_or(Error::::ContributionNotFound)?; - - // * Validity checks * - ensure!(project_details.status == ProjectStatus::FundingFailed, Error::::NotAllowed); - - // * Calculate variables * - let project_pot = Self::fund_account_id(project_id); - let payout_amount = contribution.funding_asset_amount; - let payout_asset = contribution.funding_asset; - - // * Update storage * - T::FundingCurrency::transfer( - payout_asset.to_assethub_id(), - &project_pot, - contributor, - payout_amount, - Preservation::Expendable, - )?; - contribution.funds_released = true; - Contributions::::insert((project_id, contributor, contribution_id), contribution); - - // * Emit events * - Self::deposit_event(Event::ContributionFundingReleased { - project_id, - contributor: contributor.clone(), - id: contribution_id, - amount: payout_amount, - caller: caller.clone(), - }); - - Ok(()) - } - - // Unbond the PLMC of a contribution instantly, following a failed funding outcome. - // Unbonding of PLMC in a successful funding outcome is handled by the vesting schedule. - #[transactional] - pub fn do_contribution_unbond_for( - caller: &AccountIdOf, - project_id: ProjectId, - contributor: &AccountIdOf, - contribution_id: u32, - ) -> DispatchResult { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let bid = Contributions::::get((project_id, contributor, contribution_id)) - .ok_or(Error::::EvaluationNotFound)?; - - // * Validity checks * - ensure!(project_details.status == ProjectStatus::FundingFailed, Error::::NotAllowed); - - // * Update Storage * - T::NativeCurrency::release( - &HoldReason::Participation(project_id).into(), - contributor, - bid.plmc_bond, - Precision::Exact, - )?; - - Contributions::::remove((project_id, contributor, contribution_id)); - - // * Emit events * - Self::deposit_event(Event::BondReleased { - project_id, - amount: bid.plmc_bond, - bonder: contributor.clone(), - releaser: caller.clone(), - }); - - Ok(()) - } - - #[transactional] - pub fn do_payout_bid_funds_for( - caller: &AccountIdOf, - project_id: ProjectId, - bidder: &AccountIdOf, - bid_id: u32, - ) -> DispatchResult { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let mut bid = Bids::::get((project_id, bidder, bid_id)).ok_or(Error::::BidNotFound)?; - - // * Validity checks * - ensure!( - project_details.status == ProjectStatus::FundingSuccessful && - matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..)), - Error::::NotAllowed - ); - - // * Calculate variables * - let issuer = project_details.issuer_account; - let project_pot = Self::fund_account_id(project_id); - let payout_amount = bid.funding_asset_amount_locked; - let payout_asset = bid.funding_asset; - - // * Update storage * - T::FundingCurrency::transfer( - payout_asset.to_assethub_id(), - &project_pot, - &issuer, - payout_amount, - Preservation::Expendable, - )?; - bid.funds_released = true; - Bids::::insert((project_id, bidder, bid_id), &bid); - - // * Emit events * - Self::deposit_event(Event::BidFundingPaidOut { - project_id, - bidder: bidder.clone(), - id: bid_id, - amount: payout_amount, - caller: caller.clone(), - }); - - Ok(()) - } - - #[transactional] - pub fn do_payout_contribution_funds_for( - caller: &AccountIdOf, - project_id: ProjectId, - contributor: &AccountIdOf, - contribution_id: u32, - ) -> DispatchResult { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let mut contribution = - Contributions::::get((project_id, contributor, contribution_id)).ok_or(Error::::BidNotFound)?; - - // * Validity checks * - ensure!(project_details.status == ProjectStatus::FundingSuccessful, Error::::NotAllowed); - - // * Calculate variables * - let issuer = project_details.issuer_account; - let project_pot = Self::fund_account_id(project_id); - let payout_amount = contribution.funding_asset_amount; - let payout_asset = contribution.funding_asset; - - // * Update storage * - T::FundingCurrency::transfer( - payout_asset.to_assethub_id(), - &project_pot, - &issuer, - payout_amount, - Preservation::Expendable, - )?; - contribution.funds_released = true; - Contributions::::insert((project_id, contributor, contribution_id), contribution); - - // * Emit events * - Self::deposit_event(Event::ContributionFundingPaidOut { - project_id, - contributor: contributor.clone(), - id: contribution_id, - amount: payout_amount, - caller: caller.clone(), - }); - - Ok(()) - } - #[transactional] pub fn do_set_para_id_for_project( caller: &AccountIdOf, @@ -2473,128 +1807,67 @@ impl Pallet { } #[transactional] - pub fn do_start_migration(caller: &AccountIdOf, project_id: ProjectId) -> DispatchResult { - // * Get variables * - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let migration_readiness_check = project_details.migration_readiness_check.ok_or(Error::::NotAllowed)?; - - // * Validity Checks * - ensure!(caller.clone() == project_details.issuer_account, Error::::NotAllowed); - - ensure!(migration_readiness_check.is_ready(), Error::::NotAllowed); - - // Start automated migration process - - // * Emit events * - Self::deposit_event(Event::::MigrationStarted { project_id }); - - Ok(()) - } - - #[transactional] - pub fn do_migrate_one_participant( - caller: AccountIdOf, - project_id: ProjectId, - participant: AccountIdOf, - ) -> DispatchResult { + pub fn do_migrate_one_participant(project_id: ProjectId, participant: AccountIdOf) -> DispatchResult { // * Get variables * let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; let migration_readiness_check = project_details.migration_readiness_check.ok_or(Error::::NotAllowed)?; - let user_evaluations = Evaluations::::iter_prefix_values((project_id, participant.clone())); - let user_bids = Bids::::iter_prefix_values((project_id, participant.clone())); - let user_contributions = Contributions::::iter_prefix_values((project_id, participant.clone())); let project_para_id = project_details.parachain_id.ok_or(Error::::ImpossibleState)?; let now = >::block_number(); + ensure!(Self::user_has_no_participations(project_id, participant.clone()), Error::::NotAllowed); + let (_, migrations) = + UserMigrations::::get(project_id, participant.clone()).ok_or(Error::::NoMigrationsFound)?; // * Validity Checks * ensure!(migration_readiness_check.is_ready(), Error::::NotAllowed); - // * Process Data * - // u128 is a balance, u64 is now a BlockNumber, but will be a Moment/Timestamp in the future - let evaluation_migrations = - user_evaluations.filter_map(|evaluation| MigrationGenerator::::evaluation_migration(evaluation)); - let bid_migrations = user_bids.filter_map(|bid| MigrationGenerator::::bid_migration(bid)); - let contribution_migrations = - user_contributions.filter_map(|contribution| MigrationGenerator::::contribution_migration(contribution)); - - let migrations = evaluation_migrations.chain(bid_migrations).chain(contribution_migrations).collect_vec(); - let migrations = Migrations::from(migrations); - - let constructed_migrations = Self::construct_migration_xcm_messages(migrations); - for (migrations, xcm) in constructed_migrations { - let project_multilocation = MultiLocation { parents: 1, interior: X1(Parachain(project_para_id.into())) }; - let project_migration_origins = ProjectMigrationOriginsOf:: { - project_id, - migration_origins: migrations - .origins() - .try_into() - .expect("construct function uses same constraint T::MaxMigrationsPerXcm"), - }; + let project_multilocation = MultiLocation { parents: 1, interior: X1(Parachain(project_para_id.into())) }; + let call: ::RuntimeCall = + Call::confirm_migrations { query_id: Default::default(), response: Default::default() }.into(); + let query_id = + pallet_xcm::Pallet::::new_notify_query(project_multilocation, call.into(), now + 20u32.into(), Here); - let call: ::RuntimeCall = - Call::confirm_migrations { query_id: Default::default(), response: Default::default() }.into(); - let transact_response_query_id = - pallet_xcm::Pallet::::new_notify_query(project_multilocation, call.into(), now + 20u32.into(), Here); - // TODO: check these values - let max_weight = Weight::from_parts(700_000_000, 10_000); + Self::change_migration_status(project_id, participant.clone(), MigrationStatus::Sent(query_id))?; - let mut instructions = xcm.into_inner(); - instructions.push(ReportTransactStatus(QueryResponseInfo { - destination: ParentThen(X1(Parachain(POLIMEC_PARA_ID))).into(), - query_id: transact_response_query_id, - max_weight, - })); - let xcm = Xcm(instructions); + // * Process Data * + let xcm = Self::construct_migration_xcm_message(migrations.into(), query_id); - >::send_xcm(Here, project_multilocation, xcm).map_err(|_| Error::::XcmFailed)?; - Self::mark_migrations_as_sent(project_migration_origins.clone(), transact_response_query_id); - UnconfirmedMigrations::::insert(transact_response_query_id, project_migration_origins); + >::send_xcm(Here, project_multilocation, xcm).map_err(|_| Error::::XcmFailed)?; + ActiveMigrationQueue::::insert(query_id, (project_id, participant.clone())); + + Self::deposit_event(Event::::MigrationStatusUpdated { + project_id, + account: participant, + status: MigrationStatus::Sent(query_id), + }); - Self::deposit_event(Event::::UserMigrationSent { - project_id, - caller: caller.clone(), - participant: participant.clone(), - }); - } Ok(()) } #[transactional] pub fn do_confirm_migrations(location: MultiLocation, query_id: QueryId, response: Response) -> DispatchResult { use xcm::v3::prelude::*; - let unconfirmed_migrations = UnconfirmedMigrations::::take(query_id).ok_or(Error::::NotAllowed)?; - let project_id = unconfirmed_migrations.project_id; - let para_id = if let MultiLocation { parents: 1, interior: X1(Parachain(para_id)) } = location { - ParaId::from(para_id) - } else { - return Err(Error::::NotAllowed.into()); - }; + let (project_id, participant) = ActiveMigrationQueue::::take(query_id)?; let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - ensure!(project_details.parachain_id == Some(para_id), Error::::NotAllowed); + ensure!( + matches!(location, MultiLocation { parents: 1, interior: X1(Parachain(para_id))} if Some(ParaId::from(para_id)) == project_details.parachain_id), + Error::::NotAllowed + ); - match response { + let status = match response { Response::DispatchResult(MaybeErrorCode::Success) => { - Self::mark_migrations_as_confirmed(unconfirmed_migrations.clone()); - Self::deposit_event(Event::MigrationsConfirmed { - project_id, - migration_origins: unconfirmed_migrations.migration_origins, - }); - // Self::deposit_event(Event::MigrationsConfirmed { project_id }); - Ok(()) + Self::change_migration_status(project_id, participant.clone(), MigrationStatus::Confirmed)?; + MigrationStatus::Confirmed }, - Response::DispatchResult(MaybeErrorCode::Error(e)) | - Response::DispatchResult(MaybeErrorCode::TruncatedError(e)) => { - Self::mark_migrations_as_failed(unconfirmed_migrations.clone(), e); - Self::deposit_event(Event::MigrationsFailed { - project_id, - migration_origins: unconfirmed_migrations.migration_origins, - }); - // Self::deposit_event(Event::MigrationsFailed { project_id}); - Ok(()) + Response::DispatchResult(MaybeErrorCode::Error(_)) | + Response::DispatchResult(MaybeErrorCode::TruncatedError(_)) => { + Self::change_migration_status(project_id, participant.clone(), MigrationStatus::Failed)?; + MigrationStatus::Failed }, - _ => Err(Error::::NotAllowed.into()), - } + _ => return Err(Error::::NotAllowed.into()), + }; + Self::deposit_event(Event::::MigrationStatusUpdated { project_id, account: participant, status }); + Ok(()) } } @@ -2682,37 +1955,29 @@ impl Pallet { let project_account = Self::fund_account_id(project_id); let plmc_price = T::PriceProvider::get_price(PLMC_FOREIGN_ID).ok_or(Error::::PLMCPriceNotAvailable)?; - // Weight calculation variables - let mut accepted_bids_count = 0u32; - let mut rejected_bids_count = 0u32; - // sort bids by price, and equal prices sorted by id bids.sort_by(|a, b| b.cmp(a)); // accept only bids that were made before `end_block` i.e end of candle auction - let bids: Result, DispatchError> = bids + let (accepted_bids, rejected_bids): (Vec<_>, Vec<_>) = bids .into_iter() .map(|mut bid| { if bid.when > end_block { - rejected_bids_count += 1; - return Self::refund_bid(&mut bid, project_id, &project_account, RejectionReason::AfterCandleEnd) - .and(Ok(bid)); + bid.status = BidStatus::Rejected(RejectionReason::AfterCandleEnd); + return bid; } let buyable_amount = auction_allocation_size.saturating_sub(bid_token_amount_sum); if buyable_amount.is_zero() { - rejected_bids_count += 1; - return Self::refund_bid(&mut bid, project_id, &project_account, RejectionReason::NoTokensLeft) - .and(Ok(bid)); + bid.status = BidStatus::Rejected(RejectionReason::NoTokensLeft); } else if bid.original_ct_amount <= buyable_amount { - accepted_bids_count += 1; let ticket_size = bid.original_ct_usd_price.saturating_mul_int(bid.original_ct_amount); bid_token_amount_sum.saturating_accrue(bid.original_ct_amount); bid_usd_value_sum.saturating_accrue(ticket_size); + bid.final_ct_amount = bid.original_ct_amount; bid.status = BidStatus::Accepted; DidWithWinningBids::::mutate(project_id, bid.did.clone(), |flag| { *flag = true; }); } else { - accepted_bids_count += 1; let ticket_size = bid.original_ct_usd_price.saturating_mul_int(buyable_amount); bid_usd_value_sum.saturating_accrue(ticket_size); bid_token_amount_sum.saturating_accrue(buyable_amount); @@ -2721,46 +1986,22 @@ impl Pallet { *flag = true; }); bid.final_ct_amount = buyable_amount; - - let funding_asset_price = T::PriceProvider::get_price(bid.funding_asset.to_assethub_id()) - .ok_or(Error::::PriceNotFound)?; - let funding_asset_amount_needed = funding_asset_price - .reciprocal() - .ok_or(Error::::BadMath)? - .checked_mul_int(ticket_size) - .ok_or(Error::::BadMath)?; - T::FundingCurrency::transfer( - bid.funding_asset.to_assethub_id(), - &project_account, - &bid.bidder, - bid.funding_asset_amount_locked.saturating_sub(funding_asset_amount_needed), - Preservation::Preserve, - )?; - - let usd_bond_needed = bid - .multiplier - .calculate_bonding_requirement::(ticket_size) - .map_err(|_| Error::::BadMath)?; - let plmc_bond_needed = plmc_price - .reciprocal() - .ok_or(Error::::BadMath)? - .checked_mul_int(usd_bond_needed) - .ok_or(Error::::BadMath)?; - T::NativeCurrency::release( - &HoldReason::Participation(project_id.into()).into(), - &bid.bidder, - bid.plmc_bond.saturating_sub(plmc_bond_needed), - Precision::Exact, - )?; - - bid.funding_asset_amount_locked = funding_asset_amount_needed; - bid.plmc_bond = plmc_bond_needed; } - - Ok(bid) + bid }) - .collect(); - let bids = bids?; + .partition(|bid| matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..))); + + // Weight calculation variables + let accepted_bids_count = accepted_bids.len() as u32; + let rejected_bids_count = rejected_bids.len() as u32; + + // Refund rejected bids. We do it here, so we don't have to calculate all the project + // prices and then fail to refund the bids. + for bid in rejected_bids.into_iter() { + Self::refund_bid(&bid, project_id, &project_account)?; + Bids::::remove((project_id, &bid.bidder, &bid.id)); + } + // Calculate the weighted price of the token for the next funding rounds, using winning bids. // for example: if there are 3 winning bids, // A: 10K tokens @ USD15 per token = 150K USD value @@ -2779,36 +2020,38 @@ impl Pallet { // lastly, sum all the weighted prices to get the final weighted price for the next funding round // 3 + 10.6 + 2.6 = 16.333... + ensure!(!accepted_bids.is_empty(), Error::::NoBidsFound); let current_bucket = Buckets::::get(project_id).ok_or(Error::::ProjectNotFound)?; let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectNotFound)?; let is_first_bucket = current_bucket.current_price == project_metadata.minimum_price; - let calc_weighted_price_fn = |bid: &BidInfoOf, amount: BalanceOf| -> Option> { - let ticket_size = bid.original_ct_usd_price.saturating_mul_int(amount); + let calc_weighted_price_fn = |bid: &BidInfoOf| -> PriceOf { + let ticket_size = bid.original_ct_usd_price.saturating_mul_int(bid.final_ct_amount); let bid_weight = ::saturating_from_rational(ticket_size, bid_usd_value_sum); let weighted_price = bid.original_ct_usd_price.saturating_mul(bid_weight); - Some(weighted_price) + weighted_price }; - let weighted_token_price = match is_first_bucket && !bids.is_empty() { - true => project_metadata.minimum_price, - false => bids + let weighted_token_price = if is_first_bucket { + project_metadata.minimum_price + } else { + accepted_bids .iter() - .filter_map(|bid| match bid.status { - BidStatus::Accepted => calc_weighted_price_fn(bid, bid.original_ct_amount), - BidStatus::PartiallyAccepted(amount, _) => calc_weighted_price_fn(bid, amount), - _ => None, - }) - .reduce(|a, b| a.saturating_add(b)) - .ok_or(Error::::NoBidsFound)?, + .map(calc_weighted_price_fn) + .fold(Zero::zero(), |a: T::Price, b: T::Price| a.saturating_add(b)) }; let mut final_total_funding_reached_by_bids = BalanceOf::::zero(); + + // Update storage // Update the bid in the storage - for mut bid in bids.into_iter() { - if bid.final_ct_usd_price > weighted_token_price { - bid.final_ct_usd_price = weighted_token_price; + for mut bid in accepted_bids.into_iter() { + if bid.final_ct_usd_price > weighted_token_price || matches!(bid.status, BidStatus::PartiallyAccepted(..)) { + if bid.final_ct_usd_price > weighted_token_price { + bid.final_ct_usd_price = weighted_token_price; + } + let new_ticket_size = - weighted_token_price.checked_mul_int(bid.final_ct_amount).ok_or(Error::::BadMath)?; + bid.final_ct_usd_price.checked_mul_int(bid.final_ct_amount).ok_or(Error::::BadMath)?; let funding_asset_price = T::PriceProvider::get_price(bid.funding_asset.to_assethub_id()).ok_or(Error::::PriceNotFound)?; @@ -2853,7 +2096,6 @@ impl Pallet { Bids::::insert((project_id, &bid.bidder, &bid.id), &bid); } - // Update storage ProjectsDetails::::mutate(project_id, |maybe_info| -> DispatchResult { if let Some(info) = maybe_info { info.weighted_average_price = Some(weighted_token_price); @@ -2869,16 +2111,11 @@ impl Pallet { } /// Refund a bid because of `reason`. - fn refund_bid<'a>( - bid: &'a mut BidInfoOf, + fn refund_bid( + bid: &BidInfoOf, project_id: ProjectId, - project_account: &'a AccountIdOf, - reason: RejectionReason, + project_account: &AccountIdOf, ) -> Result<(), DispatchError> { - bid.status = BidStatus::Rejected(reason); - bid.final_ct_amount = Zero::zero(); - bid.final_ct_usd_price = Zero::zero(); - T::FundingCurrency::transfer( bid.funding_asset.to_assethub_id(), project_account, @@ -2892,8 +2129,12 @@ impl Pallet { bid.plmc_bond, Precision::Exact, )?; - bid.funding_asset_amount_locked = Zero::zero(); - bid.plmc_bond = Zero::zero(); + + // Refund bid should only be called when the bid is rejected, so this if let should + // always match. + if let BidStatus::Rejected(reason) = bid.status { + Self::deposit_event(Event::BidRefunded { project_id, account: bid.bidder.clone(), bid_id: bid.id, reason }); + } Ok(()) } @@ -3182,147 +2423,52 @@ impl Pallet { available_bytes_for_migration_per_message.saturating_div(one_migration_bytes) } - pub fn construct_migration_xcm_messages(migrations: Migrations) -> Vec<(Migrations, Xcm<()>)> { + /// Check if the user has no participations (left) in the project. + pub fn user_has_no_participations(project_id: ProjectId, user: AccountIdOf) -> bool { + Evaluations::::iter_prefix_values((project_id, user.clone())).next().is_none() && + Bids::::iter_prefix_values((project_id, user.clone())).next().is_none() && + Contributions::::iter_prefix_values((project_id, user)).next().is_none() + } + + pub fn construct_migration_xcm_message( + migrations: BoundedVec>, + query_id: QueryId, + ) -> Xcm<()> { // TODO: adjust this as benchmarks for polimec-receiver are written const MAX_WEIGHT: Weight = Weight::from_parts(10_000, 0); - + const MAX_RESPONSE_WEIGHT: Weight = Weight::from_parts(700_000_000, 10_000); // const MAX_WEIGHT: Weight = Weight::from_parts(100_003_000_000_000, 10_000_196_608); let _polimec_receiver_info = T::PolimecReceiverInfo::get(); - // TODO: use the actual pallet index when the fields are not private anymore (https://github.com/paritytech/polkadot-sdk/pull/2231) - let mut output = Vec::new(); - - for migrations_slice in migrations.inner().chunks(MaxMigrationsPerXcm::::get() as usize) { - let migrations_vec = migrations_slice.to_vec(); - let migrations_item = Migrations::from(migrations_vec); - - let mut encoded_call = vec![51u8, 0]; - encoded_call.extend_from_slice(migrations_item.encode().as_slice()); - let xcm: Xcm<()> = Xcm(vec![ - UnpaidExecution { weight_limit: WeightLimit::Unlimited, check_origin: None }, - Transact { - origin_kind: OriginKind::Native, - require_weight_at_most: MAX_WEIGHT, - call: encoded_call.into(), - }, - // ReportTransactStatus should be appended here after knowing the query_id - ]); - - output.push((migrations_item, xcm)); - } - - // TODO: we probably want to ensure we dont build too many messages to overflow the queue. Which we know from the parameter `T::RequiredMaxCapacity`. - // the problem is that we don't know the existing messages in the destination queue. So for now we assume all messages will succeed - output - } - - pub fn mark_migrations_as_sent(project_migration_origins: ProjectMigrationOriginsOf, query_id: QueryId) { - let project_id = project_migration_origins.project_id; - let migration_origins = project_migration_origins.migration_origins; - for MigrationOrigin { user, id, participation_type } in migration_origins { - match participation_type { - ParticipationType::Evaluation => { - Evaluations::::mutate( - (project_id, T::AccountId32Conversion::convert_back(user), id), - |maybe_evaluation| { - if let Some(evaluation) = maybe_evaluation { - evaluation.ct_migration_status = MigrationStatus::Sent(query_id); - } - }, - ); - }, - ParticipationType::Bid => { - Bids::::mutate((project_id, T::AccountId32Conversion::convert_back(user), id), |maybe_bid| { - if let Some(bid) = maybe_bid { - bid.ct_migration_status = MigrationStatus::Sent(query_id); - } - }); - }, - ParticipationType::Contribution => { - Contributions::::mutate( - (project_id, T::AccountId32Conversion::convert_back(user), id), - |maybe_contribution| { - if let Some(contribution) = maybe_contribution { - contribution.ct_migration_status = MigrationStatus::Sent(query_id); - } - }, - ); - }, - } - } - } - - pub fn mark_migrations_as_confirmed(project_migration_origins: ProjectMigrationOriginsOf) { - let project_id = project_migration_origins.project_id; - let migration_origins = project_migration_origins.migration_origins; - for MigrationOrigin { user, id, participation_type } in migration_origins { - match participation_type { - ParticipationType::Evaluation => { - Evaluations::::mutate( - (project_id, T::AccountId32Conversion::convert_back(user), id), - |maybe_evaluation| { - if let Some(evaluation) = maybe_evaluation { - evaluation.ct_migration_status = MigrationStatus::Confirmed; - } - }, - ); - }, - ParticipationType::Bid => { - Bids::::mutate((project_id, T::AccountId32Conversion::convert_back(user), id), |maybe_bid| { - if let Some(bid) = maybe_bid { - bid.ct_migration_status = MigrationStatus::Confirmed; - } - }); - }, - ParticipationType::Contribution => { - Contributions::::mutate( - (project_id, T::AccountId32Conversion::convert_back(user), id), - |maybe_contribution| { - if let Some(contribution) = maybe_contribution { - contribution.ct_migration_status = MigrationStatus::Confirmed; - } - }, - ); - }, - } - } - } + let migrations_item = Migrations::from(migrations.into()); - pub fn mark_migrations_as_failed( - project_migration_origins: ProjectMigrationOriginsOf, - error: BoundedVec, - ) { - let project_id = project_migration_origins.project_id; - let migration_origins = project_migration_origins.migration_origins; - for MigrationOrigin { user, id, participation_type } in migration_origins { - match participation_type { - ParticipationType::Evaluation => { - Evaluations::::mutate( - (project_id, T::AccountId32Conversion::convert_back(user), id), - |maybe_evaluation| { - if let Some(evaluation) = maybe_evaluation { - evaluation.ct_migration_status = MigrationStatus::Failed(error.clone()); - } - }, - ); - }, - ParticipationType::Bid => { - Bids::::mutate((project_id, T::AccountId32Conversion::convert_back(user), id), |maybe_bid| { - if let Some(bid) = maybe_bid { - bid.ct_migration_status = MigrationStatus::Failed(error.clone()); - } - }); - }, - ParticipationType::Contribution => { - Contributions::::mutate( - (project_id, T::AccountId32Conversion::convert_back(user), id), - |maybe_contribution| { - if let Some(contribution) = maybe_contribution { - contribution.ct_migration_status = MigrationStatus::Failed(error.clone()); - } - }, - ); - }, - } - } + let mut encoded_call = vec![51u8, 0]; + // migrations_item can contain a Maximum of MaxParticipationsPerUser migrations which + // is 48. So we know that there is an upper limit to this encoded call, namely 48 * + // Migration encode size. + encoded_call.extend_from_slice(migrations_item.encode().as_slice()); + Xcm(vec![ + UnpaidExecution { weight_limit: WeightLimit::Unlimited, check_origin: None }, + Transact { origin_kind: OriginKind::Native, require_weight_at_most: MAX_WEIGHT, call: encoded_call.into() }, + ReportTransactStatus(QueryResponseInfo { + destination: ParentThen(X1(Parachain(POLIMEC_PARA_ID))).into(), + query_id, + max_weight: MAX_RESPONSE_WEIGHT, + }), + ]) + } + + fn change_migration_status(project_id: ProjectId, user: T::AccountId, status: MigrationStatus) -> DispatchResult { + let (current_status, migrations) = + UserMigrations::::get(project_id, user.clone()).ok_or(Error::::NoMigrationsFound)?; + let status = match status { + MigrationStatus::Sent(_) + if matches!(current_status, MigrationStatus::NotStarted | MigrationStatus::Failed) => + status, + MigrationStatus::Confirmed if matches!(current_status, MigrationStatus::Sent(_)) => status, + MigrationStatus::Failed if matches!(current_status, MigrationStatus::Sent(_)) => status, + _ => return Err(Error::::NotAllowed.into()), + }; + UserMigrations::::insert(project_id, user, (status, migrations)); + Ok(()) } } diff --git a/pallets/funding/src/impls.rs b/pallets/funding/src/impls.rs deleted file mode 100644 index 76c3929ac..000000000 --- a/pallets/funding/src/impls.rs +++ /dev/null @@ -1,722 +0,0 @@ -// Polimec Blockchain – https://www.polimec.org/ -// Copyright (C) Polimec 2022. All rights reserved. - -// The Polimec Blockchain is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Polimec Blockchain is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use frame_support::{ - dispatch::{DispatchErrorWithPostInfo, GetDispatchInfo}, - traits::Get, - weights::Weight, -}; -use sp_arithmetic::traits::Zero; -use sp_runtime::{traits::AccountIdConversion, DispatchError}; -use sp_std::marker::PhantomData; - -use crate::{traits::DoRemainingOperation, *}; - -impl DoRemainingOperation for Cleaner { - fn has_remaining_operations(&self) -> bool { - match self { - Cleaner::NotReady => false, - Cleaner::Success(state) => - as DoRemainingOperation>::has_remaining_operations(state), - Cleaner::Failure(state) => - as DoRemainingOperation>::has_remaining_operations(state), - } - } - - fn do_one_operation(&mut self, project_id: ProjectId) -> Result { - match self { - Cleaner::NotReady => Err(DispatchError::Other("Cleaner not ready")), - Cleaner::Success(state) => - as DoRemainingOperation>::do_one_operation(state, project_id), - Cleaner::Failure(state) => - as DoRemainingOperation>::do_one_operation(state, project_id), - } - } -} - -impl DoRemainingOperation for CleanerState { - fn has_remaining_operations(&self) -> bool { - !matches!(self, CleanerState::Finished(_)) - } - - fn do_one_operation(&mut self, project_id: ProjectId) -> Result { - let evaluators_outcome = ProjectsDetails::::get(project_id) - .ok_or(Error::::ImpossibleState)? - .evaluation_round_info - .evaluators_outcome; - let base_weight = Weight::from_parts(10_000_000, 0); - - match self { - CleanerState::Initialized(PhantomData) => { - *self = Self::EvaluationRewardOrSlash( - remaining_evaluators_to_reward_or_slash::(project_id, evaluators_outcome), - PhantomData, - ); - Ok(base_weight) - }, - CleanerState::EvaluationRewardOrSlash(remaining, PhantomData) => - if *remaining == 0 { - *self = Self::EvaluationUnbonding(remaining_evaluations::(project_id), PhantomData); - Ok(base_weight) - } else { - let (consumed_weight, remaining_evaluations) = - reward_or_slash_one_evaluation::(project_id).map_err(|error_info| error_info.error)?; - *self = CleanerState::EvaluationRewardOrSlash(remaining_evaluations, PhantomData); - Ok(consumed_weight) - }, - CleanerState::EvaluationUnbonding(remaining, PhantomData) => - if *remaining == 0 { - *self = CleanerState::StartBidderVestingSchedule( - remaining_successful_bids::(project_id), - PhantomData, - ); - Ok(base_weight) - } else { - let (consumed_weight, remaining_evaluations) = unbond_one_evaluation::(project_id); - *self = CleanerState::EvaluationUnbonding(remaining_evaluations, PhantomData); - Ok(consumed_weight) - }, - CleanerState::StartBidderVestingSchedule(remaining, PhantomData) => - if *remaining == 0 { - *self = CleanerState::StartContributorVestingSchedule( - remaining_contributions::(project_id), - PhantomData, - ); - Ok(base_weight) - } else { - let (consumed_weight, remaining_evaluations) = start_one_bid_vesting_schedule::(project_id); - *self = CleanerState::StartBidderVestingSchedule(remaining_evaluations, PhantomData); - Ok(consumed_weight) - }, - CleanerState::StartContributorVestingSchedule(remaining, PhantomData) => - if *remaining == 0 { - *self = CleanerState::BidCTMint(remaining_bids_without_ct_minted::(project_id), PhantomData); - Ok(base_weight) - } else { - let (consumed_weight, remaining_evaluations) = - start_one_contribution_vesting_schedule::(project_id); - *self = CleanerState::StartContributorVestingSchedule(remaining_evaluations, PhantomData); - Ok(consumed_weight) - }, - CleanerState::BidCTMint(remaining, PhantomData) => - if *remaining == 0 { - *self = CleanerState::ContributionCTMint( - remaining_contributions_without_ct_minted::(project_id), - PhantomData, - ); - Ok(base_weight) - } else { - let (consumed_weight, remaining_bids) = mint_ct_for_one_bid::(project_id); - *self = CleanerState::BidCTMint(remaining_bids, PhantomData); - Ok(consumed_weight) - }, - CleanerState::ContributionCTMint(remaining, PhantomData) => - if *remaining == 0 { - *self = CleanerState::BidFundingPayout( - remaining_bids_without_issuer_payout::(project_id), - PhantomData, - ); - Ok(base_weight) - } else { - let (consumed_weight, remaining_contributions) = mint_ct_for_one_contribution::(project_id); - *self = CleanerState::ContributionCTMint(remaining_contributions, PhantomData); - Ok(consumed_weight) - }, - CleanerState::BidFundingPayout(remaining, PhantomData) => - if *remaining == 0 { - *self = CleanerState::ContributionFundingPayout( - remaining_contributions_without_issuer_payout::(project_id), - PhantomData, - ); - Ok(base_weight) - } else { - let (consumed_weight, remaining_contributions) = issuer_funding_payout_one_bid::(project_id); - *self = CleanerState::BidFundingPayout(remaining_contributions, PhantomData); - Ok(consumed_weight) - }, - CleanerState::ContributionFundingPayout(remaining, PhantomData) => - if *remaining == 0 { - *self = CleanerState::Finished(PhantomData); - Ok(base_weight) - } else { - let (consumed_weight, remaining_contributions) = - issuer_funding_payout_one_contribution::(project_id); - *self = CleanerState::ContributionFundingPayout(remaining_contributions, PhantomData); - Ok(consumed_weight) - }, - CleanerState::Finished(PhantomData) => Err(Error::::FinalizerFinished.into()), - - _ => Err(Error::::ImpossibleState.into()), - } - } -} -impl DoRemainingOperation for CleanerState { - fn has_remaining_operations(&self) -> bool { - !matches!(self, CleanerState::Finished(PhantomData::)) - } - - fn do_one_operation(&mut self, project_id: ProjectId) -> Result { - let evaluators_outcome = ProjectsDetails::::get(project_id) - .ok_or(Error::::ImpossibleState)? - .evaluation_round_info - .evaluators_outcome; - let base_weight = Weight::from_parts(10_000_000, 0); - - match self { - CleanerState::Initialized(PhantomData::) => { - *self = CleanerState::EvaluationRewardOrSlash( - remaining_evaluators_to_reward_or_slash::(project_id, evaluators_outcome), - PhantomData::, - ); - Ok(base_weight) - }, - - CleanerState::EvaluationRewardOrSlash(remaining, PhantomData::) => - if *remaining == 0 { - *self = CleanerState::EvaluationUnbonding( - remaining_evaluations::(project_id), - PhantomData::, - ); - Ok(base_weight) - } else { - let (consumed_weight, remaining_evaluators) = - reward_or_slash_one_evaluation::(project_id).map_err(|error_info| error_info.error)?; - *self = CleanerState::EvaluationRewardOrSlash(remaining_evaluators, PhantomData); - Ok(consumed_weight) - }, - - CleanerState::EvaluationUnbonding(remaining, PhantomData::) => - if *remaining == 0 { - *self = CleanerState::BidFundingRelease( - remaining_bids_to_release_funds::(project_id), - PhantomData::, - ); - Ok(base_weight) - } else { - let (consumed_weight, remaining_evaluators) = unbond_one_evaluation::(project_id); - *self = CleanerState::EvaluationUnbonding(remaining_evaluators, PhantomData); - Ok(consumed_weight) - }, - - CleanerState::BidFundingRelease(remaining, PhantomData::) => - if *remaining == 0 { - *self = CleanerState::BidUnbonding(remaining_bids::(project_id), PhantomData::); - Ok(base_weight) - } else { - let (consumed_weight, remaining_bids) = release_funds_one_bid::(project_id); - *self = CleanerState::BidFundingRelease(remaining_bids, PhantomData); - Ok(consumed_weight) - }, - - CleanerState::BidUnbonding(remaining, PhantomData::) => - if *remaining == 0 { - *self = CleanerState::ContributionFundingRelease( - remaining_contributions_to_release_funds::(project_id), - PhantomData::, - ); - Ok(base_weight) - } else { - let (consumed_weight, remaining_bids) = unbond_one_bid::(project_id); - *self = CleanerState::BidUnbonding(remaining_bids, PhantomData::); - Ok(consumed_weight) - }, - - CleanerState::ContributionFundingRelease(remaining, PhantomData::) => - if *remaining == 0 { - *self = CleanerState::ContributionUnbonding( - remaining_contributions::(project_id), - PhantomData::, - ); - Ok(base_weight) - } else { - let (consumed_weight, remaining_contributions) = release_funds_one_contribution::(project_id); - *self = CleanerState::ContributionFundingRelease(remaining_contributions, PhantomData::); - Ok(consumed_weight) - }, - - CleanerState::ContributionUnbonding(remaining, PhantomData::) => - if *remaining == 0 { - *self = CleanerState::Finished(PhantomData::); - Ok(base_weight) - } else { - let (consumed_weight, remaining_contributions) = unbond_one_contribution::(project_id); - *self = CleanerState::ContributionUnbonding(remaining_contributions, PhantomData::); - Ok(consumed_weight) - }, - - CleanerState::Finished(PhantomData::) => Err(Error::::FinalizerFinished.into()), - - _ => Err(Error::::ImpossibleState.into()), - } - } -} - -fn remaining_evaluators_to_reward_or_slash(project_id: ProjectId, outcome: EvaluatorsOutcomeOf) -> u64 { - if outcome == EvaluatorsOutcomeOf::::Unchanged { - 0u64 - } else { - Evaluations::::iter_prefix_values((project_id,)) - .filter(|evaluation| evaluation.rewarded_or_slashed.is_none()) - .count() as u64 - } -} - -fn remaining_evaluations(project_id: ProjectId) -> u64 { - Evaluations::::iter_prefix_values((project_id,)).count() as u64 -} - -fn remaining_bids_to_release_funds(project_id: ProjectId) -> u64 { - Bids::::iter_prefix_values((project_id,)).filter(|bid| !bid.funds_released).count() as u64 -} - -fn remaining_bids(project_id: ProjectId) -> u64 { - Bids::::iter_prefix_values((project_id,)).count() as u64 -} - -fn remaining_successful_bids(project_id: ProjectId) -> u64 { - Bids::::iter_prefix_values((project_id,)) - .filter(|bid| matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..))) - .count() as u64 -} - -fn remaining_contributions_to_release_funds(project_id: ProjectId) -> u64 { - Contributions::::iter_prefix_values((project_id,)).filter(|contribution| !contribution.funds_released).count() - as u64 -} - -fn remaining_contributions(project_id: ProjectId) -> u64 { - Contributions::::iter_prefix_values((project_id,)).count() as u64 -} - -fn remaining_bids_without_ct_minted(project_id: ProjectId) -> u64 { - let project_bids = Bids::::iter_prefix_values((project_id,)); - project_bids.filter(|bid| !bid.ct_minted).count() as u64 -} - -fn remaining_contributions_without_ct_minted(project_id: ProjectId) -> 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: ProjectId) -> u64 { - Bids::::iter_prefix_values((project_id,)).filter(|bid| !bid.funds_released).count() as u64 -} - -fn remaining_contributions_without_issuer_payout(project_id: ProjectId) -> u64 { - Contributions::::iter_prefix_values((project_id,)).filter(|bid| !bid.funds_released).count() as u64 -} - -fn reward_or_slash_one_evaluation( - project_id: ProjectId, -) -> Result<(Weight, u64), DispatchErrorWithPostInfo> { - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectNotFound)?; - let project_evaluations = Evaluations::::iter_prefix_values((project_id,)); - let mut remaining_evaluations = project_evaluations.filter(|evaluation| evaluation.rewarded_or_slashed.is_none()); - let base_weight = Weight::from_parts(10_000_000, 0); - - if let Some(evaluation) = remaining_evaluations.next() { - // TODO: This base weight and the one in all other functions below should be calculated with a benchmark - let remaining = remaining_evaluations.count() as u64; - match project_details.evaluation_round_info.evaluators_outcome { - EvaluatorsOutcome::Rewarded(_) => { - let mut weight_consumed = crate::Call::::evaluation_reward_payout_for { - project_id: evaluation.project_id, - evaluator: evaluation.evaluator.clone(), - bond_id: evaluation.id, - } - .get_dispatch_info() - .weight; - - match Pallet::::do_evaluation_reward_payout_for( - &T::PalletId::get().into_account_truncating(), - evaluation.project_id, - &evaluation.evaluator, - evaluation.id, - ) { - Ok(result) => { - if let Some(weight) = result.actual_weight { - weight_consumed = weight - }; - }, - Err(e) => { - if let Some(weight) = e.post_info.actual_weight { - weight_consumed = weight - }; - Pallet::::deposit_event(Event::EvaluationRewardFailed { - project_id: evaluation.project_id, - evaluator: evaluation.evaluator.clone(), - id: evaluation.id, - error: e.error, - }) - }, - }; - - Ok((base_weight.saturating_add(weight_consumed), remaining)) - }, - EvaluatorsOutcome::Slashed => { - match Pallet::::do_evaluation_slash_for( - &T::PalletId::get().into_account_truncating(), - evaluation.project_id, - &evaluation.evaluator, - evaluation.id, - ) { - Ok(_) => (), - Err(e) => Pallet::::deposit_event(Event::EvaluationSlashFailed { - project_id: evaluation.project_id, - evaluator: evaluation.evaluator.clone(), - id: evaluation.id, - error: e, - }), - }; - - Ok((base_weight.saturating_add(WeightInfoOf::::evaluation_slash_for()), remaining)) - }, - _ => { - #[cfg(debug_assertions)] - unreachable!("EvaluatorsOutcome should be either Slashed or Rewarded if this function is called"); - #[cfg(not(debug_assertions))] - Err(Error::::ImpossibleState.into()) - }, - } - } else { - Ok((base_weight, 0u64)) - } -} - -fn unbond_one_evaluation(project_id: ProjectId) -> (Weight, u64) { - let project_evaluations = Evaluations::::iter_prefix_values((project_id,)); - let mut remaining_evaluations = - project_evaluations.filter(|evaluation| evaluation.current_plmc_bond > Zero::zero()); - let base_weight = Weight::from_parts(10_000_000, 0); - if let Some(evaluation) = remaining_evaluations.next() { - match Pallet::::do_evaluation_unbond_for( - &T::PalletId::get().into_account_truncating(), - evaluation.project_id, - &evaluation.evaluator, - evaluation.id, - ) { - Ok(_) => (), - Err(e) => Pallet::::deposit_event(Event::EvaluationUnbondFailed { - project_id: evaluation.project_id, - evaluator: evaluation.evaluator.clone(), - id: evaluation.id, - error: e, - }), - }; - (base_weight.saturating_add(WeightInfoOf::::evaluation_unbond_for()), remaining_evaluations.count() as u64) - } else { - (base_weight, 0u64) - } -} - -fn release_funds_one_bid(project_id: ProjectId) -> (Weight, u64) { - let project_bids = Bids::::iter_prefix_values((project_id,)); - let mut remaining_bids = project_bids.filter(|bid| !bid.funds_released); - let base_weight = Weight::from_parts(10_000_000, 0); - - if let Some(bid) = remaining_bids.next() { - match Pallet::::do_release_bid_funds_for( - &T::PalletId::get().into_account_truncating(), - bid.project_id, - &bid.bidder, - bid.id, - ) { - Ok(_) => (), - Err(e) => Pallet::::deposit_event(Event::ReleaseBidFundsFailed { - project_id: bid.project_id, - bidder: bid.bidder.clone(), - id: bid.id, - error: e, - }), - }; - - (base_weight.saturating_add(WeightInfoOf::::release_bid_funds_for()), remaining_bids.count() as u64) - } else { - (base_weight, 0u64) - } -} - -fn unbond_one_bid(project_id: ProjectId) -> (Weight, u64) { - let project_bids = Bids::::iter_prefix_values((project_id,)); - let mut remaining_bids = project_bids.filter(|bid| bid.funds_released); - let base_weight = Weight::from_parts(10_000_000, 0); - - if let Some(bid) = remaining_bids.next() { - match Pallet::::do_bid_unbond_for( - &T::PalletId::get().into_account_truncating(), - bid.project_id, - &bid.bidder, - bid.id, - ) { - Ok(_) => (), - Err(e) => Pallet::::deposit_event(Event::BidUnbondFailed { - project_id: bid.project_id, - bidder: bid.bidder.clone(), - id: bid.id, - error: e, - }), - }; - (base_weight.saturating_add(WeightInfoOf::::bid_unbond_for()), remaining_bids.count() as u64) - } else { - (base_weight, 0u64) - } -} - -fn release_funds_one_contribution(project_id: ProjectId) -> (Weight, u64) { - let project_contributions = Contributions::::iter_prefix_values((project_id,)); - let mut remaining_contributions = project_contributions.filter(|contribution| !contribution.funds_released); - let base_weight = Weight::from_parts(10_000_000, 0); - - if let Some(contribution) = remaining_contributions.next() { - match Pallet::::do_release_contribution_funds_for( - &T::PalletId::get().into_account_truncating(), - contribution.project_id, - &contribution.contributor, - contribution.id, - ) { - Ok(_) => (), - Err(e) => Pallet::::deposit_event(Event::ReleaseContributionFundsFailed { - project_id: contribution.project_id, - contributor: contribution.contributor.clone(), - id: contribution.id, - error: e, - }), - }; - - ( - base_weight.saturating_add(WeightInfoOf::::release_contribution_funds_for()), - remaining_contributions.count() as u64, - ) - } else { - (base_weight, 0u64) - } -} - -fn unbond_one_contribution(project_id: ProjectId) -> (Weight, u64) { - let project_contributions = Contributions::::iter_prefix_values((project_id,)); - - let mut remaining_contributions = - project_contributions.into_iter().filter(|contribution| contribution.funds_released); - let base_weight = Weight::from_parts(10_000_000, 0); - - if let Some(contribution) = remaining_contributions.next() { - match Pallet::::do_contribution_unbond_for( - &T::PalletId::get().into_account_truncating(), - contribution.project_id, - &contribution.contributor, - contribution.id, - ) { - Ok(_) => (), - Err(e) => Pallet::::deposit_event(Event::ContributionUnbondFailed { - project_id: contribution.project_id, - contributor: contribution.contributor.clone(), - id: contribution.id, - error: e, - }), - }; - ( - base_weight.saturating_add(WeightInfoOf::::contribution_unbond_for()), - remaining_contributions.count() as u64, - ) - } else { - (base_weight, 0u64) - } -} - -fn start_one_bid_vesting_schedule(project_id: ProjectId) -> (Weight, u64) { - let project_bids = Bids::::iter_prefix_values((project_id,)); - let mut unscheduled_bids = project_bids.filter(|bid| { - bid.plmc_vesting_info.is_none() && matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..)) - }); - let base_weight = Weight::from_parts(10_000_000, 0); - - if let Some(bid) = unscheduled_bids.next() { - match Pallet::::do_start_bid_vesting_schedule_for( - &T::PalletId::get().into_account_truncating(), - project_id, - &bid.bidder, - bid.id, - ) { - Ok(_) => {}, - Err(e) => { - // TODO: Handle `MAX_VESTING_SCHEDULES` error - - Pallet::::deposit_event(Event::StartBidderVestingScheduleFailed { - project_id: bid.project_id, - bidder: bid.bidder.clone(), - id: bid.id, - error: e, - }); - }, - } - ( - base_weight.saturating_add(WeightInfoOf::::start_bid_vesting_schedule_for()), - unscheduled_bids.count() as u64, - ) - } else { - (base_weight, 0u64) - } -} - -fn start_one_contribution_vesting_schedule(project_id: ProjectId) -> (Weight, u64) { - let project_bids = Contributions::::iter_prefix_values((project_id,)); - let mut unscheduled_contributions = project_bids.filter(|contribution| contribution.plmc_vesting_info.is_none()); - let base_weight = Weight::from_parts(10_000_000, 0); - - if let Some(contribution) = unscheduled_contributions.next() { - match Pallet::::do_start_contribution_vesting_schedule_for( - &T::PalletId::get().into_account_truncating(), - project_id, - &contribution.contributor, - contribution.id, - ) { - Ok(_) => {}, - Err(e) => { - Pallet::::deposit_event(Event::StartContributionVestingScheduleFailed { - project_id: contribution.project_id, - contributor: contribution.contributor.clone(), - id: contribution.id, - error: e, - }); - }, - } - ( - base_weight.saturating_add(WeightInfoOf::::start_contribution_vesting_schedule_for()), - unscheduled_contributions.count() as u64, - ) - } else { - (base_weight, 0u64) - } -} - -fn mint_ct_for_one_bid(project_id: ProjectId) -> (Weight, u64) { - let project_bids = Bids::::iter_prefix_values((project_id,)); - let mut remaining_bids = project_bids - .filter(|bid| !bid.ct_minted && matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..))); - let base_weight = Weight::from_parts(10_000_000, 0); - - 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, - bid.id, - ) { - Ok(_) => (), - Err(e) => Pallet::::deposit_event(Event::CTMintFailed { - project_id: bid.project_id, - claimer: bid.bidder.clone(), - id: bid.id, - error: e.error, - }), - }; - ( - base_weight.saturating_add(WeightInfoOf::::bid_ct_mint_for_with_ct_account_creation()), - remaining_bids.count() as u64, - ) - } else { - (base_weight, 0u64) - } -} - -fn mint_ct_for_one_contribution(project_id: ProjectId) -> (Weight, u64) { - let project_contributions = Contributions::::iter_prefix_values((project_id,)); - let mut remaining_contributions = project_contributions.filter(|contribution| !contribution.ct_minted); - let base_weight = Weight::from_parts(10_000_000, 0); - - 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, - contribution.id, - ) { - Ok(_) => (), - Err(e) => Pallet::::deposit_event(Event::CTMintFailed { - project_id: contribution.project_id, - claimer: contribution.contributor.clone(), - id: contribution.id, - error: e.error, - }), - }; - ( - base_weight.saturating_add(WeightInfoOf::::contribution_ct_mint_for_with_ct_account_creation()), - remaining_contributions.count() as u64, - ) - } else { - (base_weight, 0u64) - } -} - -fn issuer_funding_payout_one_bid(project_id: ProjectId) -> (Weight, u64) { - let project_bids = Bids::::iter_prefix_values((project_id,)); - - let mut remaining_bids = project_bids.filter(|bid| { - !bid.funds_released && matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..)) - }); - let base_weight = Weight::from_parts(10_000_000, 0); - - if let Some(bid) = remaining_bids.next() { - match Pallet::::do_payout_bid_funds_for( - &T::PalletId::get().into_account_truncating(), - bid.project_id, - &bid.bidder, - bid.id, - ) { - Ok(_) => (), - Err(e) => Pallet::::deposit_event(Event::PayoutContributionFundsFailed { - project_id: bid.project_id, - contributor: bid.bidder.clone(), - id: bid.id, - error: e, - }), - }; - (base_weight.saturating_add(WeightInfoOf::::payout_bid_funds_for()), remaining_bids.count() as u64) - } else { - (base_weight, 0u64) - } -} - -fn issuer_funding_payout_one_contribution(project_id: ProjectId) -> (Weight, u64) { - let project_contributions = Contributions::::iter_prefix_values((project_id,)); - - let mut remaining_contributions = project_contributions.filter(|contribution| !contribution.funds_released); - let base_weight = Weight::from_parts(10_000_000, 0); - - if let Some(contribution) = remaining_contributions.next() { - match Pallet::::do_payout_contribution_funds_for( - &T::PalletId::get().into_account_truncating(), - contribution.project_id, - &contribution.contributor, - contribution.id, - ) { - Ok(_) => (), - Err(e) => Pallet::::deposit_event(Event::PayoutContributionFundsFailed { - project_id: contribution.project_id, - contributor: contribution.contributor.clone(), - id: contribution.id, - error: e, - }), - }; - - ( - base_weight.saturating_add(WeightInfoOf::::payout_contribution_funds_for()), - remaining_contributions.count() as u64, - ) - } else { - (base_weight, 0u64) - } -} diff --git a/pallets/funding/src/instantiator.rs b/pallets/funding/src/instantiator.rs index 84810bf8a..6cae353be 100644 --- a/pallets/funding/src/instantiator.rs +++ b/pallets/funding/src/instantiator.rs @@ -34,7 +34,7 @@ use frame_support::{ use frame_system::pallet_prelude::BlockNumberFor; use itertools::Itertools; use parity_scale_codec::Decode; -use polimec_common::credentials::InvestorType; +use polimec_common::{credentials::InvestorType, migration_types::MigrationOrigin}; #[cfg(any(test, feature = "std", feature = "runtime-benchmarks"))] use polimec_common_test_utils::generate_did_from_account; use sp_arithmetic::{ @@ -42,7 +42,7 @@ use sp_arithmetic::{ FixedPointNumber, Percent, Perquintill, }; use sp_runtime::{ - traits::{Member, One}, + traits::{Convert, Member, One}, DispatchError, }; use sp_std::{ @@ -365,7 +365,6 @@ impl< .unwrap(), remaining_contribution_tokens: expected_metadata.total_allocation_size, funding_amount_reached: BalanceOf::::zero(), - cleanup: Cleaner::NotReady, evaluation_round_info: EvaluationRoundInfoOf:: { total_bonded_usd: Zero::zero(), total_bonded_plmc: Zero::zero(), @@ -1343,6 +1342,173 @@ impl< Ok(()) } + pub fn settle_project(&mut self, project_id: ProjectId) -> Result<(), DispatchError> { + let details = self.get_project_details(project_id); + self.execute(|| match details.status { + ProjectStatus::FundingSuccessful => Self::settle_successful_project(project_id), + ProjectStatus::FundingFailed | ProjectStatus::EvaluationFailed => Self::settle_failed_project(project_id), + _ => panic!("Project should be in FundingSuccessful, FundingFailed or EvaluationFailed status"), + }) + } + + fn settle_successful_project(project_id: ProjectId) -> Result<(), DispatchError> { + Evaluations::::iter_prefix((project_id,)) + .try_for_each(|(_, evaluation)| Pallet::::do_settle_successful_evaluation(evaluation, project_id))?; + + Bids::::iter_prefix((project_id,)) + .try_for_each(|(_, bid)| Pallet::::do_settle_successful_bid(bid, project_id))?; + + Contributions::::iter_prefix((project_id,)) + .try_for_each(|(_, contribution)| Pallet::::do_settle_successful_contribution(contribution, project_id)) + } + + fn settle_failed_project(project_id: ProjectId) -> Result<(), DispatchError> { + Evaluations::::iter_prefix((project_id,)) + .try_for_each(|(_, evaluation)| Pallet::::do_settle_failed_evaluation(evaluation, project_id))?; + + Bids::::iter_prefix((project_id,)) + .try_for_each(|(_, bid)| Pallet::::do_settle_failed_bid(bid, project_id))?; + + Contributions::::iter_prefix((project_id,)) + .try_for_each(|(_, contribution)| Pallet::::do_settle_failed_contribution(contribution, project_id))?; + + Ok(()) + } + + pub fn get_evaluations(&mut self, project_id: ProjectId) -> Vec> { + self.execute(|| Evaluations::::iter_prefix_values((project_id,)).collect()) + } + + pub fn get_bids(&mut self, project_id: ProjectId) -> Vec> { + self.execute(|| Bids::::iter_prefix_values((project_id,)).collect()) + } + + pub fn get_contributions(&mut self, project_id: ProjectId) -> Vec> { + self.execute(|| Contributions::::iter_prefix_values((project_id,)).collect()) + } + + // Used to check if all evaluations are settled correctly. We cannot check amount of + // contributions minted for the user, as they could have received more tokens from other participations. + pub fn assert_evaluations_migrations_created( + &mut self, + project_id: ProjectId, + evaluations: Vec>, + percentage: u64, + ) { + let details = self.get_project_details(project_id); + assert!(matches!( + details.status, + ProjectStatus::FundingSuccessful | ProjectStatus::FundingFailed | ProjectStatus::EvaluationFailed + )); + + self.execute(|| { + for evaluation in evaluations { + let reward_info = + ProjectsDetails::::get(project_id).unwrap().evaluation_round_info.evaluators_outcome; + let account = evaluation.evaluator.clone(); + assert_eq!(Evaluations::::iter_prefix_values((&project_id, &account)).count(), 0); + + let (amount, should_exist) = match percentage { + 0..=75 => { + assert!(matches!(reward_info, EvaluatorsOutcome::Slashed)); + (0u64.into(), false) + }, + 76..=89 => { + assert!(matches!(reward_info, EvaluatorsOutcome::Unchanged)); + (0u64.into(), false) + }, + 90..=100 => { + let reward = match reward_info { + EvaluatorsOutcome::Rewarded(info) => + Pallet::::calculate_evaluator_reward(&evaluation, &info), + _ => panic!("Evaluators should be rewarded"), + }; + (reward, true) + }, + _ => panic!("Percentage should be between 0 and 100"), + }; + Self::assert_migration( + project_id, + account, + amount, + evaluation.id, + ParticipationType::Evaluation, + should_exist, + ); + } + }); + } + + // Testing if a list of bids are settled correctly. + pub fn assert_bids_migrations_created( + &mut self, + project_id: ProjectId, + bids: Vec>, + is_successful: bool, + ) { + self.execute(|| { + for bid in bids { + let account = bid.bidder.clone(); + assert_eq!(Bids::::iter_prefix_values((&project_id, &account)).count(), 0); + let amount: BalanceOf = if is_successful { bid.final_ct_amount } else { 0u64.into() }; + Self::assert_migration(project_id, account, amount, bid.id, ParticipationType::Bid, is_successful); + } + }); + } + + // Testing if a list of contributions are settled correctly. + pub fn assert_contributions_migrations_created( + &mut self, + project_id: ProjectId, + contributions: Vec>, + is_successful: bool, + ) { + self.execute(|| { + for contribution in contributions { + let account = contribution.contributor.clone(); + assert_eq!(Bids::::iter_prefix_values((&project_id, &account)).count(), 0); + let amount: BalanceOf = if is_successful { contribution.ct_amount } else { 0u64.into() }; + Self::assert_migration( + project_id, + account, + amount, + contribution.id, + ParticipationType::Contribution, + is_successful, + ); + } + }); + } + + fn assert_migration( + project_id: ProjectId, + account: AccountIdOf, + amount: BalanceOf, + id: u32, + participation_type: ParticipationType, + should_exist: bool, + ) { + let correct = match (should_exist, UserMigrations::::get(project_id, account.clone())) { + // User has migrations, so we need to check if any matches our criteria + (_, Some((_, migrations))) => { + let maybe_migration = migrations.into_iter().find(|migration| { + let user = T::AccountId32Conversion::convert(account.clone()); + matches!(migration.origin, MigrationOrigin { user: m_user, id: m_id, participation_type: m_participation_type } if m_user == user && m_id == id && m_participation_type == participation_type) + }); + match maybe_migration { + // Migration exists so we check if the amount is correct and if it should exist + Some(migration) => migration.info.contribution_token_amount == amount.into() && should_exist, + // Migration doesn't exist so we check if it should not exist + None => !should_exist, + } + }, + // User does not have any migrations, so the migration should not exist + (false, None) => true, + (true, None) => false, + }; + assert!(correct); + } + pub fn create_remainder_contributing_project( &mut self, project_metadata: ProjectMetadataOf, @@ -2899,10 +3065,7 @@ pub struct BidInfoFilter { pub funding_asset_amount_locked: Option>, pub multiplier: Option>, pub plmc_bond: Option>, - pub plmc_vesting_info: Option>>, pub when: Option>, - pub funds_released: Option, - pub ct_minted: Option, } impl BidInfoFilter { pub(crate) fn matches_bid(&self, bid: &BidInfoOf) -> bool { @@ -2944,18 +3107,9 @@ impl BidInfoFilter { if self.plmc_bond.is_some() && self.plmc_bond.unwrap() != bid.plmc_bond { return false; } - if self.plmc_vesting_info.is_some() && self.plmc_vesting_info.unwrap() != bid.plmc_vesting_info { - return false; - } if self.when.is_some() && self.when.unwrap() != bid.when { return false; } - if self.funds_released.is_some() && self.funds_released.unwrap() != bid.funds_released { - return false; - } - if self.ct_minted.is_some() && self.ct_minted.unwrap() != bid.ct_minted { - return false; - } true } @@ -2975,10 +3129,7 @@ impl Default for BidInfoFilter { funding_asset_amount_locked: None, multiplier: None, plmc_bond: None, - plmc_vesting_info: None, when: None, - funds_released: None, - ct_minted: None, } } } diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 1545b18c2..84cfa287d 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -132,11 +132,11 @@ use polkadot_parachain_primitives::primitives::Id as ParaId; use sp_arithmetic::traits::{One, Saturating}; use sp_runtime::{traits::AccountIdConversion, FixedPointNumber, FixedPointOperand, FixedU128}; use sp_std::{marker::PhantomData, prelude::*}; -use traits::DoRemainingOperation; pub use types::*; use xcm::v3::{opaque::Instruction, prelude::*, SendXcm}; pub mod functions; +pub mod settlement; #[cfg(test)] pub mod mock; @@ -148,7 +148,6 @@ pub mod tests; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; -pub mod impls; #[cfg(any(feature = "runtime-benchmarks", feature = "std"))] pub mod instantiator; pub mod traits; @@ -172,23 +171,11 @@ pub type ProjectMetadataOf = pub type ProjectDetailsOf = ProjectDetails, Did, BlockNumberFor, PriceOf, BalanceOf, EvaluationRoundInfoOf>; pub type EvaluationRoundInfoOf = EvaluationRoundInfo>; -pub type VestingInfoOf = VestingInfo, BalanceOf>; pub type EvaluationInfoOf = EvaluationInfo, BalanceOf, BlockNumberFor>; -pub type BidInfoOf = BidInfo< - ProjectId, - Did, - BalanceOf, - PriceOf, - AccountIdOf, - BlockNumberFor, - MultiplierOf, - VestingInfoOf, ->; -pub type ContributionInfoOf = - ContributionInfo, BalanceOf, MultiplierOf, VestingInfoOf>; - -pub type ProjectMigrationOriginsOf = - ProjectMigrationOrigins>>; +pub type BidInfoOf = + BidInfo, PriceOf, AccountIdOf, BlockNumberFor, MultiplierOf>; + +pub type ContributionInfoOf = ContributionInfo, BalanceOf, MultiplierOf>; pub type BucketOf = Bucket, PriceOf>; pub type WeightInfoOf = ::WeightInfo; @@ -555,17 +542,39 @@ pub mod pallet { StorageMap<_, Blake2_128Concat, Did, BoundedVec, ValueQuery>; #[pallet::storage] + pub type UserMigrations = StorageDoubleMap< + _, + Blake2_128Concat, + ProjectId, + Blake2_128Concat, + T::AccountId, + (MigrationStatus, BoundedVec>), + >; + + pub struct MaxParticipationsPerUser(PhantomData); + impl Get for MaxParticipationsPerUser { + fn get() -> u32 { + T::MaxContributionsPerUser::get() + T::MaxBidsPerUser::get() + T::MaxEvaluationsPerUser::get() + } + } + + #[pallet::storage] + pub type ActiveMigrationQueue = StorageMap< + _, + Blake2_128Concat, + QueryId, + (ProjectId, T::AccountId), + ResultQuery::NoActiveMigrationsFound>, + >; + /// A map to keep track of what issuer's did has an active project. It prevents one issuer having multiple active projects + #[pallet::storage] pub type DidWithActiveProjects = StorageMap<_, Blake2_128Concat, Did, ProjectId, OptionQuery>; #[pallet::storage] pub type DidWithWinningBids = StorageDoubleMap<_, Blake2_128Concat, ProjectId, Blake2_128Concat, Did, bool, ValueQuery>; - #[pallet::storage] - /// Migrations sent and awaiting for confirmation - pub type UnconfirmedMigrations = StorageMap<_, Blake2_128Concat, QueryId, ProjectMigrationOriginsOf>; - #[pallet::event] #[pallet::generate_deposit(pub (super) fn deposit_event)] pub enum Event { @@ -617,13 +626,6 @@ pub mod pallet { amount: BalanceOf, bonder: AccountIdOf, }, - /// Someone paid for the release of a user's PLMC bond for a project. - BondReleased { - project_id: ProjectId, - amount: BalanceOf, - bonder: AccountIdOf, - releaser: AccountIdOf, - }, /// A bid was made for a project Bid { project_id: ProjectId, @@ -656,155 +658,34 @@ pub mod pallet { project_id: ProjectId, error: DispatchError, }, - /// Something terribly wrong happened where the bond could not be unbonded. Most likely a programming error - EvaluationUnbondFailed { - project_id: ProjectId, - evaluator: AccountIdOf, - id: u32, - error: DispatchError, - }, - /// Contribution tokens were minted to a user - ContributionTokenMinted { - releaser: AccountIdOf, - project_id: ProjectId, - claimer: AccountIdOf, - amount: BalanceOf, - }, - /// A transfer of tokens failed, but because it was done inside on_initialize it cannot be solved. - TransferError { - error: DispatchError, - }, - EvaluationRewardFailed { - project_id: ProjectId, - evaluator: AccountIdOf, - id: u32, - error: DispatchError, - }, - EvaluationSlashFailed { - project_id: ProjectId, - evaluator: AccountIdOf, - id: u32, - error: DispatchError, - }, - ReleaseBidFundsFailed { - project_id: ProjectId, - bidder: AccountIdOf, - id: u32, - error: DispatchError, - }, - BidUnbondFailed { - project_id: ProjectId, - bidder: AccountIdOf, - id: u32, - error: DispatchError, - }, - ReleaseContributionFundsFailed { - project_id: ProjectId, - contributor: AccountIdOf, - id: u32, - error: DispatchError, - }, - ContributionUnbondFailed { - project_id: ProjectId, - contributor: AccountIdOf, - id: u32, - error: DispatchError, - }, - PayoutContributionFundsFailed { - project_id: ProjectId, - contributor: AccountIdOf, - id: u32, - error: DispatchError, - }, - PayoutBidFundsFailed { - project_id: ProjectId, - bidder: AccountIdOf, - id: u32, - error: DispatchError, - }, - EvaluationRewarded { - project_id: ProjectId, - evaluator: AccountIdOf, - id: u32, - amount: BalanceOf, - caller: AccountIdOf, - }, - EvaluationSlashed { - project_id: ProjectId, - evaluator: AccountIdOf, - id: u32, - amount: BalanceOf, - caller: AccountIdOf, - }, - CTMintFailed { - project_id: ProjectId, - claimer: AccountIdOf, - id: u32, - error: DispatchError, - }, - StartBidderVestingScheduleFailed { - project_id: ProjectId, - bidder: AccountIdOf, - id: u32, - error: DispatchError, - }, - StartContributionVestingScheduleFailed { - project_id: ProjectId, - contributor: AccountIdOf, - id: u32, - error: DispatchError, - }, - BidPlmcVestingScheduled { - project_id: ProjectId, - bidder: AccountIdOf, - id: u32, - amount: BalanceOf, - caller: AccountIdOf, - }, - ContributionPlmcVestingScheduled { - project_id: ProjectId, - contributor: AccountIdOf, - id: u32, - amount: BalanceOf, - caller: AccountIdOf, - }, - ParticipantPlmcVested { + ProjectOutcomeDecided { project_id: ProjectId, - participant: AccountIdOf, - amount: BalanceOf, - caller: AccountIdOf, + decision: FundingOutcomeDecision, }, - BidFundingPaidOut { + EvaluationSettled { project_id: ProjectId, - bidder: AccountIdOf, + account: AccountIdOf, id: u32, - amount: BalanceOf, - caller: AccountIdOf, + ct_amount: BalanceOf, + slashed_amount: BalanceOf, }, - ContributionFundingPaidOut { + BidRefunded { project_id: ProjectId, - contributor: AccountIdOf, - id: u32, - amount: BalanceOf, - caller: AccountIdOf, + account: AccountIdOf, + bid_id: u32, + reason: RejectionReason, }, - BidFundingReleased { + BidSettled { project_id: ProjectId, - bidder: AccountIdOf, + account: AccountIdOf, id: u32, - amount: BalanceOf, - caller: AccountIdOf, + ct_amount: BalanceOf, }, - ContributionFundingReleased { + ContributionSettled { project_id: ProjectId, - contributor: AccountIdOf, + account: AccountIdOf, id: u32, - amount: BalanceOf, - caller: AccountIdOf, - }, - ProjectOutcomeDecided { - project_id: ProjectId, - decision: FundingOutcomeDecision, + ct_amount: BalanceOf, }, ProjectParaIdSet { project_id: ProjectId, @@ -836,31 +717,10 @@ pub mod pallet { query_id: QueryId, response: Response, }, - MigrationStarted { + MigrationStatusUpdated { project_id: ProjectId, - }, - UserMigrationSent { - project_id: ProjectId, - caller: AccountIdOf, - participant: AccountIdOf, - }, - MigrationsConfirmed { - project_id: ProjectId, - migration_origins: BoundedVec>, - }, - MigrationsFailed { - project_id: ProjectId, - migration_origins: BoundedVec>, - }, - ReleaseFutureCTDepositFailed { - project_id: ProjectId, - participant: AccountIdOf, - error: DispatchError, - }, - FutureCTDepositReleased { - project_id: ProjectId, - participant: AccountIdOf, - caller: AccountIdOf, + account: AccountIdOf, + status: MigrationStatus, }, } @@ -936,8 +796,8 @@ pub mod pallet { FieldIsNone, /// Checked math failed BadMath, - /// Tried to retrieve a bid but it does not exist - BidNotFound, + /// Tried to retrieve a evaluation, bid or contribution but it does not exist + ParticipationNotFound, /// Tried to contribute but its too low to be accepted ContributionTooLow, /// Contribution is higher than the limit set by the issuer @@ -950,12 +810,8 @@ pub mod pallet { PriceNotFound, /// Bond is either lower than the minimum set by the issuer, or the vec is full and can't replace an old one with a lower value EvaluationBondTooLow, - /// Tried to do an operation on an evaluation that does not exist - EvaluationNotFound, /// Tried to do an operation on a finalizer that already finished FinalizerFinished, - /// - ContributionNotFound, /// Tried to start a migration check but the bidirectional channel is not yet open CommsNotEstablished, XcmFailed, @@ -963,10 +819,6 @@ pub mod pallet { BadConversion, /// The issuer doesn't have enough funds (ExistentialDeposit), to create the escrow account NotEnoughFundsForEscrowCreation, - /// The issuer doesn't have enough funds to pay for the metadata of their contribution token - NotEnoughFundsForCTMetadata, - /// The issuer doesn't have enough funds to pay for the deposit of their contribution token - NotEnoughFundsForCTDeposit, /// Too many attempts to insert project in to ProjectsToUpdate storage TooManyInsertionAttempts, /// Reached bid limit for this user on this project @@ -977,6 +829,12 @@ pub mod pallet { TooManyEvaluationsForProject, /// Reached contribution limit for this user on this project TooManyContributionsForUser, + /// Reached the migration queue limit for a user. + TooManyMigrations, + /// User has no migrations to execute. + NoMigrationsFound, + /// User has no active migrations in the queue. + NoActiveMigrationsFound, // Participant tried to do a community contribution but it already had a winning bid on the auction round. UserHasWinningBids, // Round transition already happened. @@ -1147,187 +1005,103 @@ pub mod pallet { Self::do_remaining_contribute(&account, project_id, amount, multiplier, asset, did, investor_type) } - /// Release evaluation-bonded PLMC when a project finishes its funding round. #[pallet::call_index(8)] - #[pallet::weight(WeightInfoOf::::evaluation_unbond_for())] - pub fn evaluation_unbond_for( + #[pallet::weight(WeightInfoOf::::decide_project_outcome( + ::MaxProjectsToUpdateInsertionAttempts::get() - 1 + ))] + pub fn decide_project_outcome( origin: OriginFor, + jwt: UntrustedToken, project_id: ProjectId, - evaluator: AccountIdOf, - bond_id: u32, - ) -> DispatchResult { - let releaser = ensure_signed(origin)?; - Self::do_evaluation_unbond_for(&releaser, project_id, &evaluator, bond_id) + outcome: FundingOutcomeDecision, + ) -> DispatchResultWithPostInfo { + let (account, _did, investor_type) = + T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?; + ensure!(investor_type == InvestorType::Institutional, Error::::NotAllowed); + + Self::do_decide_project_outcome(account, project_id, outcome) } #[pallet::call_index(9)] - #[pallet::weight(WeightInfoOf::::evaluation_slash_for())] - pub fn evaluation_slash_for( + #[pallet::weight(Weight::from_parts(0, 0))] + pub fn settle_successful_evaluation( origin: OriginFor, project_id: ProjectId, evaluator: AccountIdOf, - bond_id: u32, + evaluation_id: u32, ) -> DispatchResult { - let caller = ensure_signed(origin)?; - Self::do_evaluation_slash_for(&caller, project_id, &evaluator, bond_id) + let _caller = ensure_signed(origin)?; + let bid = Evaluations::::get((project_id, evaluator, evaluation_id)) + .ok_or(Error::::ParticipationNotFound)?; + Self::do_settle_successful_evaluation(bid, project_id) } #[pallet::call_index(10)] - #[pallet::weight( - WeightInfoOf::::evaluation_reward_payout_for_with_ct_account_creation() - .max(WeightInfoOf::::evaluation_reward_payout_for_no_ct_account_creation()) - )] - pub fn evaluation_reward_payout_for( - origin: OriginFor, - project_id: ProjectId, - evaluator: AccountIdOf, - bond_id: u32, - ) -> DispatchResultWithPostInfo { - let caller = ensure_signed(origin)?; - Self::do_evaluation_reward_payout_for(&caller, project_id, &evaluator, bond_id) - } - - #[pallet::call_index(11)] - #[pallet::weight( - WeightInfoOf::::bid_ct_mint_for_with_ct_account_creation() - .max(WeightInfoOf::::bid_ct_mint_for_no_ct_account_creation()) - )] - pub fn bid_ct_mint_for( - origin: OriginFor, - project_id: ProjectId, - bidder: AccountIdOf, - bid_id: u32, - ) -> DispatchResultWithPostInfo { - let caller = ensure_signed(origin)?; - Self::do_bid_ct_mint_for(&caller, project_id, &bidder, bid_id) - } - - #[pallet::call_index(12)] - #[pallet::weight( - WeightInfoOf::::contribution_ct_mint_for_with_ct_account_creation() - .max(WeightInfoOf::::contribution_ct_mint_for_no_ct_account_creation()) - )] - pub fn contribution_ct_mint_for( - origin: OriginFor, - project_id: ProjectId, - contributor: AccountIdOf, - contribution_id: u32, - ) -> DispatchResultWithPostInfo { - let caller = ensure_signed(origin)?; - Self::do_contribution_ct_mint_for(&caller, project_id, &contributor, contribution_id) - } - - #[pallet::call_index(13)] - #[pallet::weight(WeightInfoOf::::start_bid_vesting_schedule_for())] - pub fn start_bid_vesting_schedule_for( - origin: OriginFor, - project_id: ProjectId, - bidder: AccountIdOf, - bid_id: u32, - ) -> DispatchResult { - let caller = ensure_signed(origin)?; - Self::do_start_bid_vesting_schedule_for(&caller, project_id, &bidder, bid_id) - } - - #[pallet::call_index(14)] - #[pallet::weight(WeightInfoOf::::start_contribution_vesting_schedule_for())] - pub fn start_contribution_vesting_schedule_for( - origin: OriginFor, - project_id: ProjectId, - contributor: AccountIdOf, - contribution_id: u32, - ) -> DispatchResult { - let caller = ensure_signed(origin)?; - Self::do_start_contribution_vesting_schedule_for(&caller, project_id, &contributor, contribution_id) - } - - #[pallet::call_index(15)] - #[pallet::weight(WeightInfoOf::::payout_bid_funds_for())] - pub fn payout_bid_funds_for( + #[pallet::weight(Weight::from_parts(0, 0))] + pub fn settle_successful_bid( origin: OriginFor, project_id: ProjectId, bidder: AccountIdOf, bid_id: u32, ) -> DispatchResult { - let caller = ensure_signed(origin)?; - Self::do_payout_bid_funds_for(&caller, project_id, &bidder, bid_id) + let _caller = ensure_signed(origin)?; + let bid = Bids::::get((project_id, bidder, bid_id)).ok_or(Error::::ParticipationNotFound)?; + Self::do_settle_successful_bid(bid, project_id) } - #[pallet::call_index(16)] - #[pallet::weight(WeightInfoOf::::payout_contribution_funds_for())] - pub fn payout_contribution_funds_for( + #[pallet::call_index(11)] + #[pallet::weight(Weight::from_parts(0, 0))] + pub fn settle_successful_contribution( origin: OriginFor, project_id: ProjectId, contributor: AccountIdOf, contribution_id: u32, ) -> DispatchResult { - let caller = ensure_signed(origin)?; - Self::do_payout_contribution_funds_for(&caller, project_id, &contributor, contribution_id) + let _caller = ensure_signed(origin)?; + let bid = Contributions::::get((project_id, contributor, contribution_id)) + .ok_or(Error::::ParticipationNotFound)?; + Self::do_settle_successful_contribution(bid, project_id) } - #[pallet::call_index(17)] - #[pallet::weight(WeightInfoOf::::decide_project_outcome( - ::MaxProjectsToUpdateInsertionAttempts::get() - 1 - ))] - pub fn decide_project_outcome( - origin: OriginFor, - jwt: UntrustedToken, - project_id: ProjectId, - outcome: FundingOutcomeDecision, - ) -> DispatchResultWithPostInfo { - let (account, _did, investor_type) = - T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?; - ensure!(investor_type == InvestorType::Institutional, Error::::NotAllowed); - - Self::do_decide_project_outcome(account, project_id, outcome) - } - - #[pallet::call_index(18)] - #[pallet::weight(WeightInfoOf::::release_bid_funds_for())] - pub fn release_bid_funds_for( + #[pallet::call_index(12)] + #[pallet::weight(Weight::from_parts(0, 0))] + pub fn settle_failed_evaluation( origin: OriginFor, project_id: ProjectId, - bidder: AccountIdOf, - bid_id: u32, + evaluator: AccountIdOf, + evaluation_id: u32, ) -> DispatchResult { - let caller = ensure_signed(origin)?; - Self::do_release_bid_funds_for(&caller, project_id, &bidder, bid_id) + let _caller = ensure_signed(origin)?; + let bid = Evaluations::::get((project_id, evaluator, evaluation_id)) + .ok_or(Error::::ParticipationNotFound)?; + Self::do_settle_failed_evaluation(bid, project_id) } - #[pallet::call_index(19)] - #[pallet::weight(WeightInfoOf::::bid_unbond_for())] - pub fn bid_unbond_for( + #[pallet::call_index(13)] + #[pallet::weight(Weight::from_parts(0, 0))] + pub fn settle_failed_bid( origin: OriginFor, project_id: ProjectId, bidder: AccountIdOf, bid_id: u32, ) -> DispatchResult { - let caller = ensure_signed(origin)?; - Self::do_bid_unbond_for(&caller, project_id, &bidder, bid_id) + let _caller = ensure_signed(origin)?; + let bid = Bids::::get((project_id, bidder, bid_id)).ok_or(Error::::ParticipationNotFound)?; + Self::do_settle_failed_bid(bid, project_id) } - #[pallet::call_index(20)] - #[pallet::weight(WeightInfoOf::::release_contribution_funds_for())] - pub fn release_contribution_funds_for( - origin: OriginFor, - project_id: ProjectId, - contributor: AccountIdOf, - contribution_id: u32, - ) -> DispatchResult { - let caller = ensure_signed(origin)?; - Self::do_release_contribution_funds_for(&caller, project_id, &contributor, contribution_id) - } - - #[pallet::call_index(21)] - #[pallet::weight(WeightInfoOf::::contribution_unbond_for())] - pub fn contribution_unbond_for( + #[pallet::call_index(14)] + #[pallet::weight(Weight::from_parts(0, 0))] + pub fn settle_failed_contribution( origin: OriginFor, project_id: ProjectId, contributor: AccountIdOf, contribution_id: u32, ) -> DispatchResult { - let caller = ensure_signed(origin)?; - Self::do_contribution_unbond_for(&caller, project_id, &contributor, contribution_id) + let _caller = ensure_signed(origin)?; + let bid = Contributions::::get((project_id, contributor, contribution_id)) + .ok_or(Error::::ParticipationNotFound)?; + Self::do_settle_failed_contribution(bid, project_id) } #[pallet::call_index(22)] @@ -1371,15 +1145,6 @@ pub mod pallet { Self::do_migration_check_response(location, query_id, response) } - #[pallet::call_index(25)] - #[pallet::weight(Weight::from_parts(1000, 0))] - pub fn start_migration(origin: OriginFor, jwt: UntrustedToken, project_id: ProjectId) -> DispatchResult { - let (account, _did, investor_type) = - T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?; - ensure!(investor_type == InvestorType::Institutional, Error::::NotAllowed); - Self::do_start_migration(&account, project_id) - } - #[pallet::call_index(26)] #[pallet::weight(Weight::from_parts(1000, 0))] pub fn migrate_one_participant( @@ -1387,8 +1152,8 @@ pub mod pallet { project_id: ProjectId, participant: AccountIdOf, ) -> DispatchResult { - let caller = ensure_signed(origin)?; - Self::do_migrate_one_participant(caller, project_id, participant) + let _caller = ensure_signed(origin)?; + Self::do_migrate_one_participant(project_id, participant) } #[pallet::call_index(27)] @@ -1584,53 +1349,6 @@ pub mod pallet { } used_weight } - - fn on_idle(_now: BlockNumberFor, max_weight: Weight) -> Weight { - let mut remaining_weight = max_weight; - - let projects_needing_cleanup = ProjectsDetails::::iter() - .filter_map(|(project_id, info)| match info.cleanup { - cleaner if >::has_remaining_operations(&cleaner) => - Some((project_id, cleaner)), - _ => None, - }) - .collect::>(); - - let projects_amount = projects_needing_cleanup.len() as u64; - if projects_amount == 0 { - return max_weight; - } - - let mut max_weight_per_project = remaining_weight.saturating_div(projects_amount); - - for (remaining_projects, (project_id, mut cleaner)) in - projects_needing_cleanup.into_iter().enumerate().rev() - { - // TODO: Create this benchmark - // let mut consumed_weight = WeightInfoOf::::insert_cleaned_project(); - let mut consumed_weight = Weight::from_parts(6_034_000, 0); - while !consumed_weight.any_gt(max_weight_per_project) { - if let Ok(weight) = >::do_one_operation(&mut cleaner, project_id) - { - consumed_weight.saturating_accrue(weight); - } else { - break; - } - } - - let mut details = - if let Some(details) = ProjectsDetails::::get(project_id) { details } else { continue }; - details.cleanup = cleaner; - ProjectsDetails::::insert(project_id, details); - - remaining_weight = remaining_weight.saturating_sub(consumed_weight); - if remaining_projects > 0 { - max_weight_per_project = remaining_weight.saturating_div(remaining_projects as u64); - } - } - - max_weight.saturating_sub(remaining_weight) - } } #[pallet::genesis_config] diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs index b51dd1e9f..d1532d769 100644 --- a/pallets/funding/src/mock.rs +++ b/pallets/funding/src/mock.rs @@ -361,7 +361,7 @@ pub struct DummyConverter; impl sp_runtime::traits::Convert for DummyConverter { fn convert(a: AccountId) -> [u8; 32] { let mut account: [u8; 32] = [0u8; 32]; - account[0..7].copy_from_slice(a.to_le_bytes().as_slice()); + account[0..4].copy_from_slice(a.to_le_bytes().as_slice()); account } } diff --git a/pallets/funding/src/settlement.rs b/pallets/funding/src/settlement.rs new file mode 100644 index 000000000..617c1cab5 --- /dev/null +++ b/pallets/funding/src/settlement.rs @@ -0,0 +1,395 @@ +use super::*; +use crate::traits::VestingDurationCalculation; +use frame_support::{ + dispatch::DispatchResult, + ensure, + pallet_prelude::*, + traits::{ + fungible::MutateHold as FungibleMutateHold, + fungibles::{Inspect, Mutate as FungiblesMutate}, + tokens::{Fortitude, Precision, Preservation, Restriction}, + Get, + }, +}; +use polimec_common::{ + migration_types::{MigrationInfo, MigrationOrigin, MigrationStatus, ParticipationType}, + ReleaseSchedule, +}; +use sp_runtime::{ + traits::{Convert, Zero}, + Perquintill, +}; + +impl Pallet { + pub fn do_settle_successful_evaluation(evaluation: EvaluationInfoOf, project_id: ProjectId) -> DispatchResult { + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + ensure!(matches!(project_details.status, ProjectStatus::FundingSuccessful), Error::::NotAllowed); + + // Based on the results of the funding round, the evaluator is either: + // 1. Slashed + // 2. Rewarded with CT tokens + // 3. Not slashed or Rewarded. + let (bond, reward): (BalanceOf, BalanceOf) = + match project_details.evaluation_round_info.evaluators_outcome { + EvaluatorsOutcome::Slashed => (Self::slash_evaluator(project_id, &evaluation)?, Zero::zero()), + EvaluatorsOutcome::Rewarded(info) => Self::reward_evaluator(project_id, &evaluation, &info)?, + EvaluatorsOutcome::Unchanged => (evaluation.current_plmc_bond, Zero::zero()), + }; + + // Release the held PLMC bond + T::NativeCurrency::release( + &HoldReason::Evaluation(project_id).into(), + &evaluation.evaluator, + bond, + Precision::Exact, + )?; + + // Create Migration + if reward > Zero::zero() { + let multiplier = MultiplierOf::::try_from(1u8).map_err(|_| Error::::BadMath)?; + let duration = multiplier.calculate_vesting_duration::(); + Self::create_migration( + project_id, + &evaluation.evaluator, + evaluation.id, + ParticipationType::Evaluation, + reward, + duration, + )?; + } + Evaluations::::remove((project_id, evaluation.evaluator.clone(), evaluation.id)); + + Self::deposit_event(Event::EvaluationSettled { + project_id, + account: evaluation.evaluator, + id: evaluation.id, + ct_amount: reward, + slashed_amount: evaluation.current_plmc_bond.saturating_sub(bond), + }); + + Ok(()) + } + + pub fn do_settle_failed_evaluation(evaluation: EvaluationInfoOf, project_id: ProjectId) -> DispatchResult { + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + ensure!( + matches!(project_details.status, ProjectStatus::FundingFailed | ProjectStatus::EvaluationFailed), + Error::::NotAllowed + ); + + let bond = if matches!(project_details.evaluation_round_info.evaluators_outcome, EvaluatorsOutcome::Slashed) { + Self::slash_evaluator(project_id, &evaluation)? + } else { + evaluation.current_plmc_bond + }; + + // Release the held PLMC bond + T::NativeCurrency::release( + &HoldReason::Evaluation(project_id).into(), + &evaluation.evaluator, + bond, + Precision::Exact, + )?; + + Evaluations::::remove((project_id, evaluation.evaluator.clone(), evaluation.id)); + + Self::deposit_event(Event::EvaluationSettled { + project_id, + account: evaluation.evaluator, + id: evaluation.id, + ct_amount: Zero::zero(), + slashed_amount: evaluation.current_plmc_bond.saturating_sub(bond), + }); + + Ok(()) + } + + pub fn do_settle_successful_bid(bid: BidInfoOf, project_id: ProjectId) -> DispatchResult { + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + + 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); + + let bidder = bid.bidder; + + // Calculate the vesting info and add the release schedule + let funding_end_block = project_details.funding_end_block.ok_or(Error::::ImpossibleState)?; + let vest_info = + Self::calculate_vesting_info(&bidder, bid.multiplier, bid.plmc_bond).map_err(|_| Error::::BadMath)?; + + // If the multiplier is greater than 1, add the release schedule else release the held PLMC bond + if bid.multiplier.into() > 1u8 { + T::Vesting::add_release_schedule( + &bidder, + vest_info.total_amount, + vest_info.amount_per_block, + funding_end_block, + HoldReason::Participation(project_id).into(), + )?; + } else { + // Release the held PLMC bond + Self::release_participation_bond(project_id, &bidder, bid.plmc_bond)?; + } + + // Mint the contribution tokens + Self::mint_contribution_tokens(project_id, &bidder, bid.final_ct_amount)?; + + // Payout the bid funding asset amount to the project account + Self::release_funding_asset( + project_id, + &project_details.issuer_account, + bid.funding_asset_amount_locked, + bid.funding_asset, + )?; + + Self::create_migration( + project_id, + &bidder, + bid.id, + ParticipationType::Bid, + bid.final_ct_amount, + vest_info.duration, + )?; + + Bids::::remove((project_id, bidder.clone(), bid.id)); + + Self::deposit_event(Event::BidSettled { + project_id, + account: bidder, + id: bid.id, + ct_amount: bid.final_ct_amount, + }); + + Ok(()) + } + + pub fn do_settle_failed_bid(bid: BidInfoOf, project_id: ProjectId) -> DispatchResult { + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + ensure!(matches!(project_details.status, ProjectStatus::FundingFailed), Error::::NotAllowed); + + let bidder = bid.bidder; + + // Return the funding assets to the bidder + Self::release_funding_asset(project_id, &bidder, bid.funding_asset_amount_locked, bid.funding_asset)?; + + // Release the held PLMC bond + Self::release_participation_bond(project_id, &bidder, bid.plmc_bond)?; + + // Remove the bid from the storage + Bids::::remove((project_id, bidder.clone(), bid.id)); + + Self::deposit_event(Event::BidSettled { project_id, account: bidder, id: bid.id, ct_amount: Zero::zero() }); + + Ok(()) + } + + pub fn do_settle_successful_contribution( + contribution: ContributionInfoOf, + project_id: ProjectId, + ) -> DispatchResult { + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + // Ensure that: + // 1. The project is in the FundingSuccessful state + // 2. The contribution token exists + ensure!(project_details.status == ProjectStatus::FundingSuccessful, Error::::NotAllowed); + ensure!(T::ContributionTokenCurrency::asset_exists(project_id), Error::::CannotClaimYet); + + let contributor = contribution.contributor; + + // Calculate the vesting info and add the release schedule + let funding_end_block = project_details.funding_end_block.ok_or(Error::::ImpossibleState)?; + let vest_info = Self::calculate_vesting_info(&contributor, contribution.multiplier, contribution.plmc_bond) + .map_err(|_| Error::::BadMath)?; + + if contribution.multiplier.into() > 1u8 { + T::Vesting::add_release_schedule( + &contributor, + vest_info.total_amount, + vest_info.amount_per_block, + funding_end_block, + HoldReason::Participation(project_id).into(), + )?; + } else { + // Release the held PLMC bond + Self::release_participation_bond(project_id, &contributor, contribution.plmc_bond)?; + } + + // Mint the contribution tokens + Self::mint_contribution_tokens(project_id, &contributor, contribution.ct_amount)?; + + // Payout the bid funding asset amount to the project account + Self::release_funding_asset( + project_id, + &project_details.issuer_account, + contribution.funding_asset_amount, + contribution.funding_asset, + )?; + + // Create Migration + Self::create_migration( + project_id, + &contributor, + contribution.id, + ParticipationType::Contribution, + contribution.ct_amount, + vest_info.duration, + )?; + + Contributions::::remove((project_id, contributor.clone(), contribution.id)); + + Self::deposit_event(Event::ContributionSettled { + project_id, + account: contributor, + id: contribution.id, + ct_amount: contribution.ct_amount, + }); + + Ok(()) + } + + pub fn do_settle_failed_contribution(contribution: ContributionInfoOf, project_id: ProjectId) -> DispatchResult { + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + ensure!(matches!(project_details.status, ProjectStatus::FundingFailed), Error::::NotAllowed); + + // Check if the bidder has a future deposit held + let contributor = contribution.contributor; + + // Return the funding assets to the contributor + Self::release_funding_asset( + project_id, + &contributor, + contribution.funding_asset_amount, + contribution.funding_asset, + )?; + + // Release the held PLMC bond + Self::release_participation_bond(project_id, &contributor, contribution.plmc_bond)?; + + // Remove the bid from the storage + Contributions::::remove((project_id, contributor.clone(), contribution.id)); + + Self::deposit_event(Event::ContributionSettled { + project_id, + account: contributor, + id: contribution.id, + ct_amount: Zero::zero(), + }); + + Ok(()) + } + + fn mint_contribution_tokens( + project_id: ProjectId, + participant: &AccountIdOf, + amount: BalanceOf, + ) -> DispatchResult { + if !T::ContributionTokenCurrency::contains(&project_id, participant) { + T::ContributionTokenCurrency::touch(project_id, participant, participant)?; + } + T::ContributionTokenCurrency::mint_into(project_id, participant, amount)?; + Ok(()) + } + + fn release_funding_asset( + project_id: ProjectId, + participant: &AccountIdOf, + amount: BalanceOf, + asset: AcceptedFundingAsset, + ) -> DispatchResult { + let project_pot = Self::fund_account_id(project_id); + T::FundingCurrency::transfer( + asset.to_assethub_id(), + &project_pot, + &participant, + amount, + Preservation::Expendable, + )?; + Ok(()) + } + + fn release_participation_bond( + project_id: ProjectId, + participant: &AccountIdOf, + amount: BalanceOf, + ) -> DispatchResult { + // Release the held PLMC bond + T::NativeCurrency::release( + &HoldReason::Participation(project_id).into(), + &participant, + amount, + Precision::Exact, + )?; + Ok(()) + } + + fn slash_evaluator(project_id: ProjectId, evaluation: &EvaluationInfoOf) -> Result, DispatchError> { + let slash_percentage = T::EvaluatorSlash::get(); + let treasury_account = T::ProtocolGrowthTreasury::get(); + + // * Calculate variables * + // 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; + + T::NativeCurrency::transfer_on_hold( + &HoldReason::Evaluation(project_id).into(), + &evaluation.evaluator, + &treasury_account, + slashed_amount, + Precision::Exact, + Restriction::Free, + Fortitude::Force, + )?; + + Ok(evaluation.current_plmc_bond.saturating_sub(slashed_amount)) + } + + fn reward_evaluator( + project_id: ProjectId, + evaluation: &EvaluationInfoOf, + info: &RewardInfoOf, + ) -> Result<(BalanceOf, BalanceOf), DispatchError> { + let reward = Self::calculate_evaluator_reward(evaluation, &info); + Self::mint_contribution_tokens(project_id, &evaluation.evaluator, reward)?; + + Ok((evaluation.current_plmc_bond, reward)) + } + + pub fn calculate_evaluator_reward(evaluation: &EvaluationInfoOf, info: &RewardInfoOf) -> BalanceOf { + let early_reward_weight = + Perquintill::from_rational(evaluation.early_usd_amount, info.early_evaluator_total_bonded_usd); + let normal_reward_weight = Perquintill::from_rational( + evaluation.late_usd_amount.saturating_add(evaluation.early_usd_amount), + info.normal_evaluator_total_bonded_usd, + ); + let early_evaluators_rewards = early_reward_weight * info.early_evaluator_reward_pot; + let normal_evaluators_rewards = normal_reward_weight * info.normal_evaluator_reward_pot; + early_evaluators_rewards.saturating_add(normal_evaluators_rewards) + } + + pub fn create_migration( + project_id: ProjectId, + origin: &AccountIdOf, + id: u32, + participation_type: ParticipationType, + ct_amount: BalanceOf, + vesting_time: BlockNumberFor, + ) -> DispatchResult { + UserMigrations::::try_mutate(project_id, origin, |maybe_migrations| -> DispatchResult { + let migration_origin = + MigrationOrigin { user: T::AccountId32Conversion::convert(origin.clone()), id, participation_type }; + let vesting_time: u64 = vesting_time.try_into().map_err(|_| Error::::BadMath)?; + let migration_info: MigrationInfo = (ct_amount.into(), vesting_time.into()).into(); + let migration = Migration::new(migration_origin, migration_info); + if let Some((_, migrations)) = maybe_migrations { + migrations.try_push(migration).map_err(|_| Error::::TooManyMigrations)?; + } else { + let mut migrations = BoundedVec::<_, MaxParticipationsPerUser>::new(); + migrations.try_push(migration).map_err(|_| Error::::TooManyMigrations)?; + *maybe_migrations = Some((MigrationStatus::NotStarted, migrations)) + } + + Ok(()) + }) + } +} diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index 9433e2807..df24795b5 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -40,7 +40,7 @@ use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt}; use sp_arithmetic::{traits::Zero, Percent, Perquintill}; use sp_runtime::{BuildStorage, TokenError}; use sp_std::{cell::RefCell, marker::PhantomData}; -use std::{cmp::min, iter::zip, ops::Not}; +use std::iter::zip; type MockInstantiator = Instantiator::AllPalletsWithoutSystem, RuntimeEvent>; @@ -60,9 +60,6 @@ const ISSUER_3: AccountId = 13; const ISSUER_4: AccountId = 14; const ISSUER_5: AccountId = 15; const ISSUER_6: AccountId = 16; -const ISSUER_7: AccountId = 17; -const ISSUER_8: AccountId = 18; -const ISSUER_9: AccountId = 19; const EVALUATOR_1: AccountId = 20; const EVALUATOR_2: AccountId = 21; const EVALUATOR_3: AccountId = 22; @@ -294,6 +291,19 @@ pub mod defaults { vec![EVALUATOR_1, BIDDER_3, BUYER_4, BUYER_6, BIDDER_6] } + pub fn default_all_participants() -> Vec { + let mut accounts: Vec = default_evaluators() + .iter() + .chain(default_bidders().iter()) + .chain(default_community_contributors().iter()) + .chain(default_remainder_contributors().iter()) + .copied() + .collect(); + accounts.sort(); + accounts.dedup(); + accounts + } + pub fn project_from_funding_reached(instantiator: &mut MockInstantiator, percent: u64) -> ProjectId { let project_metadata = default_project_metadata(instantiator.get_new_nonce(), ISSUER_1); let min_price = project_metadata.minimum_price; @@ -973,7 +983,7 @@ mod evaluation { inst.create_finished_project(project_metadata, ISSUER_1, evaluations, bids, contributions, vec![]); inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - inst.advance_time(10).unwrap(); + inst.settle_project(project_id).unwrap(); let actual_reward_balances = inst.execute(|| { vec![ @@ -1030,8 +1040,7 @@ mod evaluation { assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::EvaluationFailed); - // Check that on_idle has unlocked the failed bonds - inst.advance_time(10).unwrap(); + inst.settle_project(project_id).unwrap(); inst.do_free_plmc_assertions(expected_evaluator_balances); } @@ -1413,18 +1422,7 @@ mod auction { } for bid in excluded_bids { - let mut stored_bids = inst.execute(|| Bids::::iter_prefix_values((project_id, bid.bidder))); - let desired_bid: BidInfoFilter = BidInfoFilter { - project_id: Some(project_id), - bidder: Some(bid.bidder), - original_ct_amount: Some(bid.amount), - status: Some(BidStatus::Rejected(RejectionReason::AfterCandleEnd)), - ..Default::default() - }; - assert!( - inst.execute(|| stored_bids.any(|bid| desired_bid.matches_bid(&bid))), - "Stored bid does not match the given filter" - ); + assert!(inst.execute(|| Bids::::iter_prefix_values((project_id, bid.bidder)).count() == 0)); } } @@ -1544,7 +1542,7 @@ mod auction { auction_token_allocation.saturating_sub(bids.iter().fold(0, |acc, bid| acc + bid.amount)); let rejected_bid = vec![BidParams::new(BIDDER_5, available_tokens, 1u8, AcceptedFundingAsset::USDT)]; - let accepted_bid = vec![BidParams::new(BIDDER_4, available_tokens, 1u8, AcceptedFundingAsset::USDT)]; + let accepted_bid = vec![BidParams::new(BIDDER_4, available_tokens, 2u8, AcceptedFundingAsset::USDT)]; bids.extend(rejected_bid.clone()); bids.extend(accepted_bid.clone()); @@ -1588,9 +1586,8 @@ mod auction { inst.start_remainder_or_end_funding(project_id).unwrap(); inst.finish_funding(project_id).unwrap(); - inst.advance_time(::SuccessToSettlementTime::get() + 1).unwrap(); - let details = inst.get_project_details(project_id); - assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); + inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); + inst.settle_project(project_id).unwrap(); let plmc_locked_for_accepted_bid = MockInstantiator::calculate_auction_plmc_charged_with_given_price(&accepted_bid, final_price); @@ -1608,15 +1605,14 @@ mod auction { assert_close_enough!(schedule.unwrap(), accepted_plmc_amount, Perquintill::from_float(0.99)); let UserToPLMCBalance { account: rejected_user, .. } = plmc_locked_for_rejected_bid[0]; - let schedule_exists = inst + assert!(inst .execute(|| { ::Vesting::total_scheduled_amount( &rejected_user, HoldReason::Participation(project_id).into(), ) }) - .is_some(); - assert!(!schedule_exists); + .is_none()); } // We use the already tested instantiator functions to calculate the correct post-wap returns @@ -2483,7 +2479,6 @@ mod auction { // only functionalities that happen in the COMMUNITY FUNDING period of a project mod community_contribution { use super::*; - use std::collections::HashMap; pub const HOURS: BlockNumber = 300u64; #[test] @@ -2501,7 +2496,6 @@ mod community_contribution { #[test] fn multiple_contribution_projects_completed() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; let project1 = default_project_metadata(inst.get_new_nonce(), ISSUER_1); let project2 = default_project_metadata(inst.get_new_nonce(), ISSUER_2); let project3 = default_project_metadata(inst.get_new_nonce(), ISSUER_3); @@ -3651,7 +3645,7 @@ mod community_contribution { default_bids(), ) }; - let mut contribute = |inst: &mut MockInstantiator, project_id, multiplier| { + let contribute = |inst: &mut MockInstantiator, project_id, multiplier| { let jwt = get_mock_jwt(BUYER_1, InvestorType::Retail, generate_did_from_account(BUYER_1)); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); let contributor_plmc = MockInstantiator::calculate_contributed_plmc_spent( @@ -4593,7 +4587,7 @@ mod remainder_contribution { vec![], ) }; - let mut contribute = |inst: &mut MockInstantiator, project_id, multiplier| { + let contribute = |inst: &mut MockInstantiator, project_id, multiplier| { let jwt = get_mock_jwt(BUYER_1, InvestorType::Retail, generate_did_from_account(BUYER_1)); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); let contributor_plmc = MockInstantiator::calculate_contributed_plmc_spent( @@ -4672,9 +4666,73 @@ mod remainder_contribution { } // only functionalities that happen after the REMAINDER FUNDING period of a project and before the CT Migration -mod funding_end { +mod funding_end_and_settlement { use super::*; + pub fn create_project_with_funding_percentage( + percentage: u64, + maybe_decision: Option, + ) -> (MockInstantiator, ProjectId) { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(inst.get_new_nonce(), ISSUER_1); + let min_price = project_metadata.minimum_price; + let percentage_funded_usd = Perquintill::from_percent(percentage) * + (project_metadata.minimum_price.checked_mul_int(project_metadata.total_allocation_size).unwrap()); + let evaluations = default_evaluations(); + let bids = MockInstantiator::generate_bids_from_total_usd( + Percent::from_percent(50u8) * percentage_funded_usd, + min_price, + default_weights(), + default_bidders(), + default_multipliers(), + ); + let contributions = MockInstantiator::generate_contributions_from_total_usd( + Percent::from_percent(50u8) * percentage_funded_usd, + min_price, + default_weights(), + default_community_contributors(), + default_multipliers(), + ); + let project_id = + inst.create_finished_project(project_metadata, ISSUER_1, evaluations, bids, contributions, vec![]); + + match inst.get_project_details(project_id).status { + ProjectStatus::AwaitingProjectDecision => { + assert!(percentage > 33 && percentage < 90); + assert!(maybe_decision.is_some()); + inst.execute(|| { + PolimecFunding::do_decide_project_outcome(ISSUER_1, project_id, maybe_decision.unwrap()) + }) + .unwrap(); + }, + ProjectStatus::FundingSuccessful => { + assert!(percentage >= 90); + }, + ProjectStatus::FundingFailed => { + assert!(percentage <= 33); + }, + _ => panic!("unexpected project status"), + }; + + inst.advance_time(::SuccessToSettlementTime::get() + 1u64).unwrap(); + let funding_sucessful = match percentage { + 0..=33 => false, + 34..=89 if matches!(maybe_decision, Some(FundingOutcomeDecision::RejectFunding)) => false, + 34..=89 if matches!(maybe_decision, Some(FundingOutcomeDecision::AcceptFunding)) => true, + 90..=100 => true, + _ => panic!("unexpected percentage"), + }; + + if funding_sucessful { + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); + inst.test_ct_created_for(project_id); + } else { + assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); + inst.test_ct_not_created_for(project_id); + } + (inst, project_id) + } + #[test] fn failed_auction_is_settled() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); @@ -4686,104 +4744,42 @@ mod funding_end { assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); // execute `do_start_settlement` inst.advance_time(1).unwrap(); - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Failure(CleanerState::Initialized(PhantomData)) - ); - // run settlement machine a.k.a cleaner - inst.advance_time(1).unwrap(); - assert_eq!(inst.get_project_details(project_id).cleanup, Cleaner::Failure(CleanerState::Finished(PhantomData))); + // Settle the project. + inst.settle_project(project_id).unwrap(); } #[test] fn automatic_fail_less_eq_33_percent() { for funding_percent in (1..=33).step_by(5) { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(inst.get_new_nonce(), ISSUER_1); - let min_price = project_metadata.minimum_price; - let twenty_percent_funding_usd = Perquintill::from_percent(funding_percent) * - (project_metadata.minimum_price.checked_mul_int(project_metadata.total_allocation_size).unwrap()); - let evaluations = default_evaluations(); - let bids = MockInstantiator::generate_bids_from_total_usd( - Percent::from_percent(50u8) * twenty_percent_funding_usd, - min_price, - vec![100u8], - vec![BIDDER_1], - vec![10u8], - ); - let contributions = MockInstantiator::generate_contributions_from_total_usd( - Percent::from_percent(50u8) * twenty_percent_funding_usd, - min_price, - default_weights(), - default_community_contributors(), - default_multipliers(), - ); - let project_id = - inst.create_finished_project(project_metadata, ISSUER_1, evaluations, bids, contributions, vec![]); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); + let _ = create_project_with_funding_percentage(funding_percent, None); } } #[test] fn automatic_success_bigger_eq_90_percent() { for funding_percent in (90..=100).step_by(2) { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(inst.get_new_nonce(), ISSUER_1); - let min_price = project_metadata.minimum_price; - let twenty_percent_funding_usd = Perquintill::from_percent(funding_percent) * - (project_metadata.minimum_price.checked_mul_int(project_metadata.total_allocation_size).unwrap()); - let evaluations = default_evaluations(); - let bids = MockInstantiator::generate_bids_from_total_usd( - Percent::from_percent(50u8) * twenty_percent_funding_usd, - min_price, - default_weights(), - default_bidders(), - default_multipliers(), - ); - let contributions = MockInstantiator::generate_contributions_from_total_usd( - Percent::from_percent(50u8) * twenty_percent_funding_usd, - min_price, - default_weights(), - default_community_contributors(), - default_multipliers(), - ); - let project_id = - inst.create_finished_project(project_metadata, ISSUER_1, evaluations, bids, contributions, vec![]); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); + let _ = create_project_with_funding_percentage(funding_percent, None); } } #[test] - fn manual_outcome_above33_to_below90() { - for funding_percent in (34..90).step_by(5) { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(inst.get_new_nonce(), ISSUER_1); - let min_price = project_metadata.minimum_price; - let twenty_percent_funding_usd = Perquintill::from_percent(funding_percent) * - (project_metadata.minimum_price.checked_mul_int(project_metadata.total_allocation_size).unwrap()); - let evaluations = default_evaluations(); - let bids = MockInstantiator::generate_bids_from_total_usd( - Percent::from_percent(50u8) * twenty_percent_funding_usd, - min_price, - default_weights(), - default_bidders(), - default_multipliers(), - ); - let contributions = MockInstantiator::generate_contributions_from_total_usd( - Percent::from_percent(50u8) * twenty_percent_funding_usd, - min_price, - default_weights(), - default_community_contributors(), - default_multipliers(), - ); - let project_id = - inst.create_finished_project(project_metadata, ISSUER_1, evaluations, bids, contributions, vec![]); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AwaitingProjectDecision); + fn manual_acceptance_percentage_between_34_89() { + for funding_percent in (34..=89).step_by(5) { + let _ = + create_project_with_funding_percentage(funding_percent, Some(FundingOutcomeDecision::AcceptFunding)); + } + } + + #[test] + fn manual_rejection_percentage_between_34_89() { + for funding_percent in (34..=89).step_by(5) { + let _ = + create_project_with_funding_percentage(funding_percent, Some(FundingOutcomeDecision::RejectFunding)); } } #[test] - fn manual_acceptance() { + fn automatic_acceptance_on_manual_decision_after_time_delta() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_metadata = default_project_metadata(inst.get_new_nonce(), ISSUER_1); let min_price = project_metadata.minimum_price; @@ -4809,2005 +4805,371 @@ mod funding_end { assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AwaitingProjectDecision); let project_id = project_id; - inst.execute(|| { - PolimecFunding::do_decide_project_outcome(ISSUER_1, project_id, FundingOutcomeDecision::AcceptFunding) - }) - .unwrap(); - - inst.advance_time(1u64).unwrap(); + inst.advance_time(1u64 + ::ManualAcceptanceDuration::get()).unwrap(); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - assert_matches!(inst.get_project_details(project_id).cleanup, Cleaner::Success(CleanerState::Initialized(_))); inst.test_ct_created_for(project_id); - inst.advance_time(10u64).unwrap(); - assert_matches!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Finished(PhantomData)) - ); + inst.settle_project(project_id).unwrap(); } #[test] - fn manual_rejection() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(inst.get_new_nonce(), ISSUER_1); - let min_price = project_metadata.minimum_price; - let twenty_percent_funding_usd = Perquintill::from_percent(55) * - (project_metadata.minimum_price.checked_mul_int(project_metadata.total_allocation_size).unwrap()); - let evaluations = default_evaluations(); - let bids = MockInstantiator::generate_bids_from_total_usd( - Percent::from_percent(50u8) * twenty_percent_funding_usd, - min_price, - default_weights(), - default_bidders(), - default_multipliers(), - ); - let contributions = MockInstantiator::generate_contributions_from_total_usd( - Percent::from_percent(50u8) * twenty_percent_funding_usd, - min_price, - default_weights(), - default_community_contributors(), - default_multipliers(), - ); - let project_id = - inst.create_finished_project(project_metadata, ISSUER_1, evaluations, bids, contributions, vec![]); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AwaitingProjectDecision); + fn can_settle_accepted_project() { + let percentage = 100u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); + let evaluations = inst.get_evaluations(project_id); + let bids = inst.get_bids(project_id); + let contributions = inst.get_contributions(project_id); - let project_id = project_id; - inst.execute(|| { - PolimecFunding::do_decide_project_outcome(ISSUER_1, project_id, FundingOutcomeDecision::RejectFunding) - }) - .unwrap(); + inst.settle_project(project_id).unwrap(); - inst.advance_time(1u64).unwrap(); + inst.assert_evaluations_migrations_created(project_id, evaluations, percentage); + inst.assert_bids_migrations_created(project_id, bids, true); + inst.assert_contributions_migrations_created(project_id, contributions, true); + } - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - assert_matches!( - inst.get_project_details(project_id).cleanup, - Cleaner::Failure(CleanerState::Initialized(PhantomData)) - ); + #[test] + fn can_settle_failed_project() { + let percentage = 33u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); + let evaluations = inst.get_evaluations(project_id); + let bids = inst.get_bids(project_id); + let contributions = inst.get_contributions(project_id); - inst.test_ct_not_created_for(project_id); + inst.settle_project(project_id).unwrap(); - inst.advance_time(10u64).unwrap(); - assert_matches!( - inst.get_project_details(project_id).cleanup, - Cleaner::Failure(CleanerState::Finished(PhantomData)) - ); + inst.assert_evaluations_migrations_created(project_id, evaluations, percentage); + inst.assert_bids_migrations_created(project_id, bids, false); + inst.assert_contributions_migrations_created(project_id, contributions, false); } #[test] - fn automatic_acceptance_on_manual_decision_after_time_delta() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(inst.get_new_nonce(), ISSUER_1); - let min_price = project_metadata.minimum_price; - let twenty_percent_funding_usd = Perquintill::from_percent(55) * - (project_metadata.minimum_price.checked_mul_int(project_metadata.total_allocation_size).unwrap()); - let evaluations = default_evaluations(); - let bids = MockInstantiator::generate_bids_from_total_usd( - Percent::from_percent(50u8) * twenty_percent_funding_usd, - min_price, - default_weights(), - default_bidders(), - default_multipliers(), - ); - let contributions = MockInstantiator::generate_contributions_from_total_usd( - Percent::from_percent(50u8) * twenty_percent_funding_usd, - min_price, - default_weights(), - default_community_contributors(), - default_multipliers(), - ); - let project_id = - inst.create_finished_project(project_metadata, ISSUER_1, evaluations, bids, contributions, vec![]); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AwaitingProjectDecision); + fn cannot_settle_successful_project_twice() { + let percentage = 100u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); - let project_id = project_id; - inst.advance_time(1u64 + ::ManualAcceptanceDuration::get()).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); + let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); + let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); + let first_contribution = inst.get_contributions(project_id).into_iter().next().unwrap(); - assert_matches!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Initialized(PhantomData)) - ); - inst.test_ct_created_for(project_id); + inst.execute(|| { + let evaluator = first_evaluation.evaluator; + assert_ok!(crate::Pallet::::settle_successful_evaluation( + RuntimeOrigin::signed(evaluator.clone()), + project_id, + evaluator, + first_evaluation.id + )); + assert_noop!( + crate::Pallet::::settle_successful_evaluation( + RuntimeOrigin::signed(evaluator.clone()), + project_id, + evaluator, + first_evaluation.id + ), + Error::::ParticipationNotFound + ); - inst.advance_time(10u64).unwrap(); - assert_matches!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Finished(PhantomData)) - ); + let bidder = first_bid.bidder; + assert_ok!(crate::Pallet::::settle_successful_bid( + RuntimeOrigin::signed(bidder.clone()), + project_id, + bidder, + first_bid.id + )); + assert_noop!( + crate::Pallet::::settle_successful_bid( + RuntimeOrigin::signed(bidder.clone()), + project_id, + bidder, + first_bid.id + ), + Error::::ParticipationNotFound + ); + + let contributor = first_contribution.contributor; + assert_ok!(crate::Pallet::::settle_successful_contribution( + RuntimeOrigin::signed(contributor.clone()), + project_id, + contributor, + first_contribution.id + )); + assert_noop!( + crate::Pallet::::settle_successful_contribution( + RuntimeOrigin::signed(contributor.clone()), + project_id, + contributor, + first_contribution.id + ), + Error::::ParticipationNotFound + ); + }); } #[test] - fn evaluators_get_slashed_funding_accepted() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = project_from_funding_reached(&mut inst, 43u64); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AwaitingProjectDecision); + fn cannot_settle_failed_project_twice() { + let percentage = 33u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); - let old_evaluation_locked_plmc = inst - .get_all_reserved_plmc_balances(HoldReason::Evaluation(project_id).into()) - .into_iter() - .filter(|item| item.plmc_amount > Zero::zero()) - .collect::>>(); + let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); + let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); + let first_contribution = inst.get_contributions(project_id).into_iter().next().unwrap(); - let evaluators = old_evaluation_locked_plmc.accounts(); + inst.execute(|| { + let evaluator = first_evaluation.evaluator; + assert_ok!(crate::Pallet::::settle_failed_evaluation( + RuntimeOrigin::signed(evaluator.clone()), + project_id, + evaluator, + first_evaluation.id + )); + assert_noop!( + crate::Pallet::::settle_failed_evaluation( + RuntimeOrigin::signed(evaluator.clone()), + project_id, + evaluator, + first_evaluation.id + ), + Error::::ParticipationNotFound + ); - let old_participation_locked_plmc = - inst.get_reserved_plmc_balances_for(evaluators.clone(), HoldReason::Participation(project_id).into()); - let old_free_plmc = inst.get_free_plmc_balances_for(evaluators.clone()); + let bidder = first_bid.bidder; + assert_ok!(crate::Pallet::::settle_failed_bid( + RuntimeOrigin::signed(bidder.clone()), + project_id, + bidder, + first_bid.id + )); + assert_noop!( + crate::Pallet::::settle_failed_bid( + RuntimeOrigin::signed(bidder.clone()), + project_id, + bidder, + first_bid.id + ), + Error::::ParticipationNotFound + ); - call_and_is_ok!( - inst, - PolimecFunding::do_decide_project_outcome(ISSUER_1, project_id, FundingOutcomeDecision::AcceptFunding) - ); - inst.advance_time(1u64).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); - inst.advance_time(::SuccessToSettlementTime::get() + 10u64).unwrap(); - assert_matches!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Finished(PhantomData)) - ); + let contributor = first_contribution.contributor; + assert_ok!(crate::Pallet::::settle_failed_contribution( + RuntimeOrigin::signed(contributor.clone()), + project_id, + contributor, + first_contribution.id + )); + assert_noop!( + crate::Pallet::::settle_failed_contribution( + RuntimeOrigin::signed(contributor.clone()), + project_id, + contributor, + first_contribution.id + ), + Error::::ParticipationNotFound + ); + }); + } - let slashed_evaluation_locked_plmc = MockInstantiator::slash_evaluator_balances(old_evaluation_locked_plmc); - let expected_evaluator_free_balances = MockInstantiator::generic_map_operation( - vec![slashed_evaluation_locked_plmc, old_participation_locked_plmc, old_free_plmc], - MergeOperation::Add, - ); + /// Test that the correct amount of PLMC is slashed from the evaluator independent of the + /// project outcome. + #[test] + fn evaluator_slashed_if_between_33_and_75() { + let percentage = 50u64; + let project_1 = create_project_with_funding_percentage(percentage, Some(FundingOutcomeDecision::AcceptFunding)); + let project_2 = create_project_with_funding_percentage(percentage, Some(FundingOutcomeDecision::RejectFunding)); + let projects = vec![project_1, project_2]; - let actual_evaluator_free_balances = inst.get_free_plmc_balances_for(evaluators.clone()); + for (mut inst, project_id) in projects { + let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); + let evaluator = first_evaluation.evaluator; - assert_eq!(actual_evaluator_free_balances, expected_evaluator_free_balances); + inst.execute(|| { + let prev_balance = ::NativeCurrency::balance(&evaluator); + match ProjectsDetails::::get(project_id).unwrap().status { + ProjectStatus::FundingSuccessful => { + assert_ok!(crate::Pallet::::settle_successful_evaluation( + RuntimeOrigin::signed(evaluator.clone()), + project_id, + evaluator, + first_evaluation.id + )); + }, + ProjectStatus::FundingFailed => { + assert_ok!(crate::Pallet::::settle_failed_evaluation( + RuntimeOrigin::signed(evaluator.clone()), + project_id, + evaluator, + first_evaluation.id + )); + }, + _ => panic!("unexpected project status"), + } + let balance = ::NativeCurrency::balance(&evaluator); + assert_eq!( + balance, + prev_balance + + (Percent::from_percent(100) - ::EvaluatorSlash::get()) * + first_evaluation.current_plmc_bond + ); + }); + } } + // Test that the evaluators PLMC bond is not slashed if the project is between 76 and 89 + // percent funded independent of the project outcome. #[test] - fn evaluators_get_slashed_funding_funding_rejected() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = project_from_funding_reached(&mut inst, 56u64); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AwaitingProjectDecision); + fn evaluator_plmc_unchanged_between_76_and_89() { + let percentage = 80u64; + let project_1 = create_project_with_funding_percentage(percentage, Some(FundingOutcomeDecision::AcceptFunding)); + let project_2 = create_project_with_funding_percentage(percentage, Some(FundingOutcomeDecision::RejectFunding)); + let projects = vec![project_1, project_2]; - let old_evaluation_locked_plmc = inst - .get_all_reserved_plmc_balances(HoldReason::Evaluation(project_id).into()) - .into_iter() - .filter(|item| item.plmc_amount > Zero::zero()) - .collect::>>(); + for (mut inst, project_id) in projects { + let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); + let evaluator = first_evaluation.evaluator; + + inst.execute(|| { + let prev_balance = ::NativeCurrency::balance(&evaluator); + match ProjectsDetails::::get(project_id).unwrap().status { + ProjectStatus::FundingSuccessful => { + assert_ok!(crate::Pallet::::settle_successful_evaluation( + RuntimeOrigin::signed(evaluator.clone()), + project_id, + evaluator, + first_evaluation.id + )); + }, + ProjectStatus::FundingFailed => { + assert_ok!(crate::Pallet::::settle_failed_evaluation( + RuntimeOrigin::signed(evaluator.clone()), + project_id, + evaluator, + first_evaluation.id + )); + }, + _ => panic!("unexpected project status"), + } + let balance = ::NativeCurrency::balance(&evaluator); + assert_eq!(balance, prev_balance + first_evaluation.current_plmc_bond); + }); + } + } - let evaluators = old_evaluation_locked_plmc.accounts(); + #[test] + fn bid_is_correctly_settled_for_successful_project() { + let percentage = 100u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); + let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); + let issuer = &inst.get_issuer(project_id); + inst.execute(|| { + let bidder = first_bid.bidder; - let old_participation_locked_plmc = - inst.get_reserved_plmc_balances_for(evaluators.clone(), HoldReason::Participation(project_id).into()); - let old_free_plmc = inst.get_free_plmc_balances_for(evaluators.clone()); + assert_ok!(crate::Pallet::::settle_successful_bid( + RuntimeOrigin::signed(bidder.clone()), + project_id, + bidder, + first_bid.id + )); - call_and_is_ok!( - inst, - PolimecFunding::do_decide_project_outcome(ISSUER_1, project_id, FundingOutcomeDecision::RejectFunding) - ); - inst.advance_time(1u64).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); - inst.advance_time(::SuccessToSettlementTime::get() + 10u64).unwrap(); - assert_matches!( - inst.get_project_details(project_id).cleanup, - Cleaner::Failure(CleanerState::Finished(PhantomData)) - ); + let reason: RuntimeHoldReason = HoldReason::Participation(project_id).into(); + let held_bidder = ::NativeCurrency::balance_on_hold(&reason, &bidder); + assert_eq!(held_bidder, 0u32.into()); - let slashed_evaluation_locked_plmc = MockInstantiator::slash_evaluator_balances(old_evaluation_locked_plmc); - let expected_evaluator_free_balances = MockInstantiator::generic_map_operation( - vec![slashed_evaluation_locked_plmc, old_participation_locked_plmc, old_free_plmc], - MergeOperation::Add, - ); - let actual_evaluator_free_balances = inst.get_free_plmc_balances_for(evaluators.clone()); + let balance_issuer = + ::FundingCurrency::balance(first_bid.funding_asset.to_assethub_id(), issuer); + assert_eq!(balance_issuer, first_bid.funding_asset_amount_locked); - assert_eq!(actual_evaluator_free_balances, expected_evaluator_free_balances); + let ct_amount = ::ContributionTokenCurrency::balance(project_id, &bidder); + assert_eq!(ct_amount, first_bid.final_ct_amount); + }); } #[test] - fn evaluators_get_slashed_funding_failed() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = project_from_funding_reached(&mut inst, 24u64); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); + fn bid_is_correctly_settled_for_failed_project() { + let percentage = 33u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); + let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); + inst.execute(|| { + let bidder = first_bid.bidder; + assert_ok!(crate::Pallet::::settle_failed_bid( + RuntimeOrigin::signed(bidder.clone()), + project_id, + bidder, + first_bid.id + )); - let old_evaluation_locked_plmc = inst - .get_all_reserved_plmc_balances(HoldReason::Evaluation(project_id).into()) - .into_iter() - .filter(|item| item.plmc_amount > Zero::zero()) - .collect::>(); + let reason: RuntimeHoldReason = HoldReason::Participation(project_id).into(); + let held_bidder = ::NativeCurrency::balance_on_hold(&reason, &bidder); + assert_eq!(held_bidder, 0u32.into()); - let evaluators = old_evaluation_locked_plmc.accounts(); + let funding_asset_bidder = + ::FundingCurrency::balance(first_bid.funding_asset.to_assethub_id(), &bidder); + assert_eq!(funding_asset_bidder, first_bid.funding_asset_amount_locked); - let old_participation_locked_plmc = - inst.get_reserved_plmc_balances_for(evaluators.clone(), HoldReason::Participation(project_id).into()); - let old_free_plmc = inst.get_free_plmc_balances_for(evaluators.clone()); + let ct_amount = ::ContributionTokenCurrency::balance(project_id, &bidder); + assert_eq!(ct_amount, Zero::zero()); + }); + } - inst.advance_time(::SuccessToSettlementTime::get() + 10u64).unwrap(); - assert_matches!( - inst.get_project_details(project_id).cleanup, - Cleaner::Failure(CleanerState::Finished(PhantomData)) - ); + #[test] + fn contribution_is_correctly_settled_for_successful_project() { + let percentage = 100u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); + let first_contribution = inst.get_contributions(project_id).into_iter().next().unwrap(); + let issuer = &inst.get_issuer(project_id); + inst.execute(|| { + let contributor = first_contribution.contributor; - let slashed_evaluation_locked_plmc = MockInstantiator::slash_evaluator_balances(old_evaluation_locked_plmc); - let expected_evaluator_free_balances = MockInstantiator::generic_map_operation( - vec![slashed_evaluation_locked_plmc, old_participation_locked_plmc, old_free_plmc], - MergeOperation::Add, - ); + assert_ok!(crate::Pallet::::settle_successful_contribution( + RuntimeOrigin::signed(contributor.clone()), + project_id, + contributor, + first_contribution.id + )); + + let reason: RuntimeHoldReason = HoldReason::Participation(project_id).into(); + let held_contributor = ::NativeCurrency::balance_on_hold(&reason, &contributor); + assert_eq!(held_contributor, 0u32.into()); - let actual_evaluator_free_balances = inst.get_free_plmc_balances_for(evaluators.clone()); + let balance_issuer = ::FundingCurrency::balance( + first_contribution.funding_asset.to_assethub_id(), + issuer, + ); + assert_eq!(balance_issuer, first_contribution.usd_contribution_amount); - assert_eq!(actual_evaluator_free_balances, expected_evaluator_free_balances); + let ct_amount = ::ContributionTokenCurrency::balance(project_id, &contributor); + assert_eq!(ct_amount, first_contribution.ct_amount); + }); } #[test] - fn ct_minted_automatically() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(inst.get_new_nonce(), issuer); - let evaluations = default_evaluations(); - let bids = default_bids(); - let community_contributions = default_community_buys(); - let remainder_contributions = default_remainder_buys(); + fn contribution_is_correctly_settled_for_failed_project() { + let percentage = 33u64; + let (mut inst, project_id) = create_project_with_funding_percentage(percentage, None); + let first_contribution = inst.get_contributions(project_id).into_iter().next().unwrap(); + inst.execute(|| { + let contributor = first_contribution.contributor; - let project_id = inst.create_finished_project( - project_metadata, - issuer, - evaluations.clone(), - bids.clone(), - community_contributions.clone(), - remainder_contributions.clone(), - ); - let details = inst.get_project_details(project_id); - assert_eq!(details.status, ProjectStatus::FundingSuccessful); - assert_eq!(details.cleanup, Cleaner::NotReady); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - - inst.advance_time(10u64).unwrap(); - let details = inst.get_project_details(project_id); - assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); - - let evaluators = evaluations.accounts(); - let evaluator_ct_amounts = evaluators - .iter() - .map(|account| { - let evaluations = inst.execute(|| { - Evaluations::::iter_prefix_values((project_id, account.clone())).collect::>() - }); - let total_evaluator_ct_rewarded = evaluations - .iter() - .map(|evaluation| evaluation.rewarded_or_slashed) - .map(|reward_or_slash| { - if let Some(RewardOrSlash::Reward(balance)) = reward_or_slash { - balance - } else { - Zero::zero() - } - }) - .sum::(); - - (account, total_evaluator_ct_rewarded) - }) - .collect_vec(); - - let bidders = bids.accounts(); - let bidder_ct_amounts = bidders - .iter() - .map(|account| { - let bids = inst.execute(|| { - Bids::::iter_prefix_values((project_id, account.clone())).collect::>() - }); - let total_bidder_ct_rewarded = bids.iter().map(|bid| bid.final_ct_amount).sum::(); - - (account, total_bidder_ct_rewarded) - }) - .collect_vec(); - - let community_accounts = community_contributions.accounts(); - let remainder_accounts = remainder_contributions.accounts(); - let all_contributors = community_accounts.iter().chain(remainder_accounts.iter()).unique(); - let contributor_ct_amounts = all_contributors - .map(|account| { - let contributions = inst.execute(|| { - Contributions::::iter_prefix_values((project_id, account.clone())).collect::>() - }); - let total_contributor_ct_rewarded = - contributions.iter().map(|contribution| contribution.ct_amount).sum::(); - - (account, total_contributor_ct_rewarded) - }) - .collect_vec(); - - let all_ct_expectations = MockInstantiator::generic_map_merge_reduce( - vec![evaluator_ct_amounts, bidder_ct_amounts, contributor_ct_amounts], - |item| item.0, - Zero::zero(), - |item, accumulator| accumulator + item.1, - ); - - for (account, amount) in all_ct_expectations { - let minted = - inst.execute(|| ::ContributionTokenCurrency::balance(project_id, account)); - assert_eq!(minted, amount); - } - } - - #[test] - fn ct_minted_manually() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(inst.get_new_nonce(), issuer); - let evaluations = default_evaluations(); - let bids = default_bids(); - let community_contributions = default_community_buys(); - let remainder_contributions = default_remainder_buys(); - - let project_id = inst.create_finished_project( - project_metadata, - issuer, - evaluations.clone(), - bids.clone(), - community_contributions.clone(), - remainder_contributions.clone(), - ); - let details = inst.get_project_details(project_id); - assert_eq!(details.status, ProjectStatus::FundingSuccessful); - assert_eq!(details.cleanup, Cleaner::NotReady); - // do_end_funding - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Initialized(PhantomData)) - ); - - let evaluators = evaluations.accounts(); - let evaluator_ct_amounts = evaluators - .iter() - .map(|account| { - let evaluations = inst.execute(|| { - Evaluations::::iter_prefix_values((project_id, account.clone())).collect::>() - }); - for evaluation in evaluations.iter() { - inst.execute(|| { - assert_ok!(Pallet::::evaluation_reward_payout_for( - RuntimeOrigin::signed(evaluation.evaluator), - project_id, - evaluation.evaluator, - evaluation.id, - )); - }); - } - let evaluations = inst.execute(|| { - Evaluations::::iter_prefix_values((project_id, account.clone())).collect::>() - }); - let total_evaluator_ct_rewarded = evaluations - .iter() - .map(|evaluation| evaluation.rewarded_or_slashed) - .map(|reward_or_slash| { - if let Some(RewardOrSlash::Reward(balance)) = reward_or_slash { - balance - } else { - Zero::zero() - } - }) - .sum::(); - - (account, total_evaluator_ct_rewarded) - }) - .collect_vec(); - - let bidders = bids.accounts(); - let bidder_ct_amounts = bidders - .iter() - .map(|account| { - let bids = inst.execute(|| { - Bids::::iter_prefix_values((project_id, account.clone())).collect::>() - }); - for bid in bids.iter() { - inst.execute(|| { - assert_ok!(Pallet::::bid_ct_mint_for( - RuntimeOrigin::signed(bid.bidder), - project_id, - bid.bidder, - bid.id, - )); - }); - } - - let total_bidder_ct_rewarded = bids.iter().map(|bid| bid.final_ct_amount).sum::(); - - (account, total_bidder_ct_rewarded) - }) - .collect_vec(); - - let community_accounts = community_contributions.accounts(); - let remainder_accounts = remainder_contributions.accounts(); - let all_contributors = community_accounts.iter().chain(remainder_accounts.iter()).unique(); - let contributor_ct_amounts = all_contributors - .map(|account| { - let contributions = inst.execute(|| { - Contributions::::iter_prefix_values((project_id, account.clone())).collect::>() - }); - for contribution in contributions.iter() { - inst.execute(|| { - assert_ok!(Pallet::::contribution_ct_mint_for( - RuntimeOrigin::signed(contribution.contributor), - project_id, - contribution.contributor, - contribution.id, - )); - }); - } - - let total_contributor_ct_rewarded = - contributions.iter().map(|contribution| contribution.ct_amount).sum::(); - - (account, total_contributor_ct_rewarded) - }) - .collect_vec(); - - let all_ct_expectations = MockInstantiator::generic_map_merge_reduce( - vec![evaluator_ct_amounts, bidder_ct_amounts, contributor_ct_amounts], - |item| item.0, - Zero::zero(), - |item, accumulator| accumulator + item.1, - ); - - for (account, amount) in all_ct_expectations { - let minted = - inst.execute(|| ::ContributionTokenCurrency::balance(project_id, account)); - assert_eq!(minted, amount, "Account: {}", account); - } - - let details = inst.get_project_details(project_id); - assert_eq!(details.status, ProjectStatus::FundingSuccessful); - assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Initialized(PhantomData))); - } - - #[test] - fn cannot_mint_ct_twice_manually() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(inst.get_new_nonce(), issuer); - let evaluations = default_evaluations(); - let bids = default_bids(); - let community_contributions = default_community_buys(); - let remainder_contributions = default_remainder_buys(); - - let project_id = inst.create_finished_project( - project_metadata, - issuer, - evaluations.clone(), - bids.clone(), - community_contributions.clone(), - remainder_contributions.clone(), - ); - let details = inst.get_project_details(project_id); - assert_eq!(details.status, ProjectStatus::FundingSuccessful); - assert_eq!(details.cleanup, Cleaner::NotReady); - // do_end_funding - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Initialized(PhantomData)) - ); - - let evaluators = evaluations.accounts(); - let evaluator_ct_amounts = evaluators - .iter() - .map(|account| { - let evaluations = inst.execute(|| { - Evaluations::::iter_prefix_values((project_id, account.clone())).collect::>() - }); - for evaluation in evaluations.iter() { - inst.execute(|| { - assert_ok!(Pallet::::evaluation_reward_payout_for( - RuntimeOrigin::signed(evaluation.evaluator), - project_id, - evaluation.evaluator, - evaluation.id, - )); - assert_noop!( - Pallet::::evaluation_reward_payout_for( - RuntimeOrigin::signed(evaluation.evaluator), - project_id, - evaluation.evaluator, - evaluation.id, - ), - Error::::NotAllowed - ); - }); - } - let evaluations = inst.execute(|| { - Evaluations::::iter_prefix_values((project_id, account.clone())).collect::>() - }); - let total_evaluator_ct_rewarded = evaluations - .iter() - .map(|evaluation| evaluation.rewarded_or_slashed) - .map(|reward_or_slash| { - if let Some(RewardOrSlash::Reward(balance)) = reward_or_slash { - balance - } else { - Zero::zero() - } - }) - .sum::(); - - (account, total_evaluator_ct_rewarded) - }) - .collect_vec(); - - let bidders = bids.accounts(); - let bidder_ct_amounts = bidders - .iter() - .map(|account| { - let bids = inst.execute(|| { - Bids::::iter_prefix_values((project_id, account.clone())).collect::>() - }); - for bid in bids.iter() { - inst.execute(|| { - assert_ok!(Pallet::::bid_ct_mint_for( - RuntimeOrigin::signed(bid.bidder), - project_id, - bid.bidder, - bid.id, - )); - }); - inst.execute(|| { - assert_noop!( - Pallet::::bid_ct_mint_for( - RuntimeOrigin::signed(bid.bidder), - project_id, - bid.bidder, - bid.id, - ), - Error::::NotAllowed - ); - }); - } - - let total_bidder_ct_rewarded = bids.iter().map(|bid| bid.final_ct_amount).sum::(); - - (account, total_bidder_ct_rewarded) - }) - .collect_vec(); - - let community_accounts = community_contributions.accounts(); - let remainder_accounts = remainder_contributions.accounts(); - let all_contributors = community_accounts.iter().chain(remainder_accounts.iter()).unique(); - let contributor_ct_amounts = all_contributors - .map(|account| { - let contributions = inst.execute(|| { - Contributions::::iter_prefix_values((project_id, account.clone())).collect::>() - }); - for contribution in contributions.iter() { - inst.execute(|| { - assert_ok!(Pallet::::contribution_ct_mint_for( - RuntimeOrigin::signed(contribution.contributor), - project_id, - contribution.contributor, - contribution.id, - )); - }); - inst.execute(|| { - assert_noop!( - Pallet::::contribution_ct_mint_for( - RuntimeOrigin::signed(contribution.contributor), - project_id, - contribution.contributor, - contribution.id, - ), - Error::::NotAllowed - ); - }); - } - - let total_contributor_ct_rewarded = - contributions.iter().map(|contribution| contribution.ct_amount).sum::(); - - (account, total_contributor_ct_rewarded) - }) - .collect_vec(); - - let all_ct_expectations = MockInstantiator::generic_map_merge_reduce( - vec![evaluator_ct_amounts, bidder_ct_amounts, contributor_ct_amounts], - |item| item.0, - Zero::zero(), - |item, accumulator| accumulator + item.1, - ); - - for (account, amount) in all_ct_expectations { - let minted = - inst.execute(|| ::ContributionTokenCurrency::balance(project_id, account)); - assert_eq!(minted, amount, "Account: {}", account); - } - - let details = inst.get_project_details(project_id); - assert_eq!(details.status, ProjectStatus::FundingSuccessful); - assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Initialized(PhantomData))); - } - - #[test] - fn cannot_mint_ct_manually_after_automatic_mint() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(inst.get_new_nonce(), issuer); - let evaluations = default_evaluations(); - let bids = default_bids(); - let community_contributions = default_community_buys(); - let remainder_contributions = default_remainder_buys(); - - let project_id = inst.create_finished_project( - project_metadata, - issuer, - evaluations.clone(), - bids.clone(), - community_contributions.clone(), - remainder_contributions.clone(), - ); - let details = inst.get_project_details(project_id); - assert_eq!(details.status, ProjectStatus::FundingSuccessful); - assert_eq!(details.cleanup, Cleaner::NotReady); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Initialized(PhantomData)) - ); - inst.advance_time(1).unwrap(); - assert_eq!(inst.get_project_details(project_id).cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); - - let evaluators = evaluations.accounts(); - let evaluator_ct_amounts = evaluators - .iter() - .map(|account| { - let evaluations = inst.execute(|| { - Evaluations::::iter_prefix_values((project_id, account.clone())).collect::>() - }); - for evaluation in evaluations.iter() { - inst.execute(|| { - assert_noop!( - Pallet::::evaluation_reward_payout_for( - RuntimeOrigin::signed(evaluation.evaluator), - project_id, - evaluation.evaluator, - evaluation.id, - ), - Error::::NotAllowed - ); - }); - } - let evaluations = inst.execute(|| { - Evaluations::::iter_prefix_values((project_id, account.clone())).collect::>() - }); - let total_evaluator_ct_rewarded = evaluations - .iter() - .map(|evaluation| evaluation.rewarded_or_slashed) - .map(|reward_or_slash| { - if let Some(RewardOrSlash::Reward(balance)) = reward_or_slash { - balance - } else { - Zero::zero() - } - }) - .sum::(); - - (account, total_evaluator_ct_rewarded) - }) - .collect_vec(); - - let bidders = bids.accounts(); - let bidder_ct_amounts = bidders - .iter() - .map(|account| { - let bids = inst.execute(|| { - Bids::::iter_prefix_values((project_id, account.clone())).collect::>() - }); - for bid in bids.iter() { - inst.execute(|| { - assert_noop!( - Pallet::::bid_ct_mint_for( - RuntimeOrigin::signed(bid.bidder), - project_id, - bid.bidder, - bid.id, - ), - Error::::NotAllowed - ); - }); - } - - let total_bidder_ct_rewarded = bids.iter().map(|bid| bid.final_ct_amount).sum::(); - - (account, total_bidder_ct_rewarded) - }) - .collect_vec(); - - let community_accounts = community_contributions.accounts(); - let remainder_accounts = remainder_contributions.accounts(); - let all_contributors = community_accounts.iter().chain(remainder_accounts.iter()).unique(); - let contributor_ct_amounts = all_contributors - .map(|account| { - let contributions = inst.execute(|| { - Contributions::::iter_prefix_values((project_id, account.clone())).collect::>() - }); - for contribution in contributions.iter() { - inst.execute(|| { - assert_noop!( - Pallet::::contribution_ct_mint_for( - RuntimeOrigin::signed(contribution.contributor), - project_id, - contribution.contributor, - contribution.id, - ), - Error::::NotAllowed - ); - }); - } - - let total_contributor_ct_rewarded = - contributions.iter().map(|contribution| contribution.ct_amount).sum::(); - - (account, total_contributor_ct_rewarded) - }) - .collect_vec(); - - let all_ct_expectations = MockInstantiator::generic_map_merge_reduce( - vec![evaluator_ct_amounts, bidder_ct_amounts, contributor_ct_amounts], - |item| item.0, - Zero::zero(), - |item, accumulator| accumulator + item.1, - ); - - for (account, amount) in all_ct_expectations { - let minted = - inst.execute(|| ::ContributionTokenCurrency::balance(project_id, account)); - assert_eq!(minted, amount, "Account: {}", account); - } - } - - #[test] - fn multiplier_gets_correct_vesting_duration() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(inst.get_new_nonce(), issuer); - let evaluations = default_evaluations(); - let bids = vec![ - BidParams::new(BIDDER_1, 325_000 * ASSET_UNIT, 1u8, AcceptedFundingAsset::USDT), - BidParams::new(BIDDER_2, 75_000 * ASSET_UNIT, 2u8, AcceptedFundingAsset::USDT), - BidParams::new(BIDDER_3, 50_000 * ASSET_UNIT, 3u8, AcceptedFundingAsset::USDT), - ]; - let community_contributions = default_community_buys(); - let remainder_contributions = default_remainder_buys(); - - let project_id = inst.create_finished_project( - project_metadata, - issuer, - evaluations, - bids, - community_contributions, - remainder_contributions, - ); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - - inst.advance_time(10u64).unwrap(); - let details = inst.get_project_details(project_id); - assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); - - let mut stored_bids = - inst.execute(|| Bids::::iter_prefix_values((project_id,)).collect::>()); - - stored_bids.sort_by_key(|bid| bid.bidder); - let one_week_in_blocks = DAYS * 7; - assert_eq!(stored_bids[0].plmc_vesting_info.unwrap().duration, 1u64); - assert_eq!( - stored_bids[1].plmc_vesting_info.unwrap().duration, - FixedU128::from_rational(2167, 1000).saturating_mul_int(one_week_in_blocks as u64) - ); - assert_eq!( - stored_bids[2].plmc_vesting_info.unwrap().duration, - FixedU128::from_rational(4334, 1000).saturating_mul_int(one_week_in_blocks as u64) - ); - } - - #[test] - fn funding_assets_are_paid_manually_to_issuer() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(inst.get_new_nonce(), issuer); - let evaluations = default_evaluations(); - let bids = default_bids(); - let community_contributions = default_community_buys(); - let remainder_contributions = default_remainder_buys(); - - let project_id = inst.create_finished_project( - project_metadata, - issuer, - evaluations, - bids, - community_contributions, - remainder_contributions, - ); - - let final_winning_bids = inst.execute(|| { - Bids::::iter_prefix_values((project_id,)) - .filter(|bid| matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..))) - .collect::>() - }); - let final_bid_payouts = inst.execute(|| { - Bids::::iter_prefix_values((project_id,)) - .filter(|bid| matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..))) - .map(|bid| { - UserToForeignAssets::new( - bid.bidder, - bid.funding_asset_amount_locked, - bid.funding_asset.to_assethub_id(), - ) - }) - .collect::>>() - }); - let final_contributions = - inst.execute(|| Contributions::::iter_prefix_values((project_id,)).collect::>()); - let final_contribution_payouts = inst.execute(|| { - Contributions::::iter_prefix_values((project_id,)) - .map(|contribution| { - UserToForeignAssets::new( - contribution.contributor, - contribution.funding_asset_amount, - contribution.funding_asset.to_assethub_id(), - ) - }) - .collect::>>() - }); - - let total_expected_bid_payout = - final_bid_payouts.iter().map(|bid| bid.asset_amount).sum::>(); - let total_expected_contribution_payout = final_contribution_payouts - .iter() - .map(|contribution| contribution.asset_amount) - .sum::>(); - - let prev_issuer_funding_balance = - inst.get_free_foreign_asset_balances_for(final_bid_payouts[0].asset_id, vec![issuer])[0].asset_amount; - - let prev_project_pot_funding_balance = inst.get_free_foreign_asset_balances_for( - final_bid_payouts[0].asset_id, - vec![Pallet::::fund_account_id(project_id)], - )[0] - .asset_amount; - - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Initialized(PhantomData)) - ); - for bid in final_winning_bids { - inst.execute(|| { - Pallet::::payout_bid_funds_for( - RuntimeOrigin::signed(issuer), - project_id, - bid.bidder, - bid.id, - ) - }) - .unwrap(); - } - for contribution in final_contributions { - inst.execute(|| { - Pallet::::payout_contribution_funds_for( - RuntimeOrigin::signed(issuer), - project_id, - contribution.contributor, - contribution.id, - ) - }) - .unwrap(); - } - let post_issuer_funding_balance = - inst.get_free_foreign_asset_balances_for(final_bid_payouts[0].asset_id, vec![issuer])[0].asset_amount; - - let post_project_pot_funding_balance = inst.get_free_foreign_asset_balances_for( - final_bid_payouts[0].asset_id, - vec![Pallet::::fund_account_id(project_id)], - )[0] - .asset_amount; - let issuer_funding_delta = post_issuer_funding_balance - prev_issuer_funding_balance; - let project_pot_funding_delta = prev_project_pot_funding_balance - post_project_pot_funding_balance; - - assert_eq!(issuer_funding_delta - total_expected_bid_payout, total_expected_contribution_payout); - assert_eq!(issuer_funding_delta, project_pot_funding_delta); - - assert_eq!(post_project_pot_funding_balance, 0u128); - } - - #[test] - fn funding_assets_are_paid_automatically_to_issuer() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(inst.get_new_nonce(), issuer); - let evaluations = default_evaluations(); - let bids = default_bids(); - let community_contributions = default_community_buys(); - let remainder_contributions = default_remainder_buys(); - - let project_id = inst.create_finished_project( - project_metadata, - issuer, - evaluations, - bids, - community_contributions, - remainder_contributions, - ); - - let final_bid_payouts = inst.execute(|| { - Bids::::iter_prefix_values((project_id,)) - .filter(|bid| matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..))) - .map(|bid| { - UserToForeignAssets::new( - bid.bidder, - bid.funding_asset_amount_locked, - bid.funding_asset.to_assethub_id(), - ) - }) - .collect::>>() - }); - let final_contribution_payouts = inst.execute(|| { - Contributions::::iter_prefix_values((project_id,)) - .map(|contribution| { - UserToForeignAssets::new( - contribution.contributor, - contribution.funding_asset_amount, - contribution.funding_asset.to_assethub_id(), - ) - }) - .collect::>>() - }); - - let total_expected_bid_payout = - final_bid_payouts.iter().map(|bid| bid.asset_amount).sum::>(); - let total_expected_contribution_payout = final_contribution_payouts - .iter() - .map(|contribution| contribution.asset_amount) - .sum::>(); - - let prev_issuer_funding_balance = - inst.get_free_foreign_asset_balances_for(final_bid_payouts[0].asset_id, vec![issuer])[0].asset_amount; - - let prev_project_pot_funding_balance = inst.get_free_foreign_asset_balances_for( - final_bid_payouts[0].asset_id, - vec![Pallet::::fund_account_id(project_id)], - )[0] - .asset_amount; - - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Success(CleanerState::Initialized(PhantomData)) - ); - inst.advance_time(1u64).unwrap(); - assert_eq!(inst.get_project_details(project_id).cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); - - let post_issuer_funding_balance = - inst.get_free_foreign_asset_balances_for(final_bid_payouts[0].asset_id, vec![issuer])[0].asset_amount; - - let post_project_pot_funding_balance = inst.get_free_foreign_asset_balances_for( - final_bid_payouts[0].asset_id, - vec![Pallet::::fund_account_id(project_id)], - )[0] - .asset_amount; - let issuer_funding_delta = post_issuer_funding_balance - prev_issuer_funding_balance; - let project_pot_funding_delta = prev_project_pot_funding_balance - post_project_pot_funding_balance; - - assert_eq!(issuer_funding_delta - total_expected_bid_payout, total_expected_contribution_payout); - assert_eq!(issuer_funding_delta, project_pot_funding_delta); - - assert_eq!(post_project_pot_funding_balance, 0u128); - } - - #[test] - fn funding_assets_are_released_automatically_on_funding_fail() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(inst.get_new_nonce(), issuer); - - let auction_allocation = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - let evaluations = default_evaluations(); - let bids = MockInstantiator::generate_bids_from_total_usd( - project_metadata.minimum_price.saturating_mul_int(auction_allocation), - project_metadata.minimum_price, - default_weights(), - default_bidders(), - default_bidder_multipliers(), - ); - let community_contributions = MockInstantiator::generate_contributions_from_total_usd( - project_metadata.minimum_price.saturating_mul_int( - Percent::from_percent(50u8) * (project_metadata.total_allocation_size - auction_allocation) / 2, - ), - project_metadata.minimum_price, - default_weights(), - default_community_contributors(), - default_community_contributor_multipliers(), - ); - let remainder_contributions = MockInstantiator::generate_contributions_from_total_usd( - project_metadata.minimum_price.saturating_mul_int( - Percent::from_percent(50u8) * (project_metadata.total_allocation_size - auction_allocation) / 2, - ), - project_metadata.minimum_price, - default_weights(), - default_remainder_contributors(), - default_remainder_contributor_multipliers(), - ); - - let project_id = inst.create_finished_project( - project_metadata, - issuer, - evaluations, - bids, - community_contributions.clone(), - remainder_contributions.clone(), - ); - - let final_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); - let expected_bid_payouts = inst.execute(|| { - Bids::::iter_prefix_values((project_id,)) - .map(|bid| { - UserToForeignAssets::::new( - bid.bidder, - bid.funding_asset_amount_locked, - bid.funding_asset.to_assethub_id(), - ) - }) - .sorted_by_key(|bid| bid.account) - .collect::>>() - }); - let expected_community_contribution_payouts = - MockInstantiator::calculate_contributed_funding_asset_spent(community_contributions, final_price); - let expected_remainder_contribution_payouts = - MockInstantiator::calculate_contributed_funding_asset_spent(remainder_contributions, final_price); - let all_expected_payouts = MockInstantiator::generic_map_operation( - vec![ - expected_bid_payouts.clone(), - expected_community_contribution_payouts, - expected_remainder_contribution_payouts, - ], - MergeOperation::Add, - ); - - let prev_issuer_funding_balance = - inst.get_free_foreign_asset_balances_for(expected_bid_payouts[0].asset_id, vec![issuer])[0].asset_amount; - let all_participants = all_expected_payouts.accounts(); - let prev_participants_funding_balances = - inst.get_free_foreign_asset_balances_for(expected_bid_payouts[0].asset_id, all_participants.clone()); - - call_and_is_ok!( - inst, - Pallet::::decide_project_outcome( - RuntimeOrigin::signed(issuer), - get_mock_jwt(issuer.clone(), InvestorType::Institutional, generate_did_from_account(issuer.clone())), - project_id, - FundingOutcomeDecision::RejectFunding - ) - ); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - inst.advance_time(10).unwrap(); - assert_eq!(inst.get_project_details(project_id).cleanup, Cleaner::Failure(CleanerState::Finished(PhantomData))); - - let post_issuer_funding_balance = - inst.get_free_foreign_asset_balances_for(expected_bid_payouts[0].asset_id, vec![issuer])[0].asset_amount; - let post_participants_funding_balances = - inst.get_free_foreign_asset_balances_for(expected_bid_payouts[0].asset_id, all_participants); - let post_project_pot_funding_balance = inst.get_free_foreign_asset_balances_for( - expected_bid_payouts[0].asset_id, - vec![Pallet::::fund_account_id(project_id)], - )[0] - .asset_amount; - - let all_participants_funding_delta = MockInstantiator::generic_map_operation( - vec![prev_participants_funding_balances, post_participants_funding_balances], - MergeOperation::Add, - ); - - let issuer_funding_delta = post_issuer_funding_balance - prev_issuer_funding_balance; - - assert_eq!(issuer_funding_delta, 0); - assert_eq!(post_project_pot_funding_balance, 0u128); - assert_eq!(all_expected_payouts, all_participants_funding_delta); - } - - #[test] - fn funding_assets_are_released_manually_on_funding_fail() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(inst.get_new_nonce(), issuer); - let auction_allocation = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - let evaluations = default_evaluations(); - let bids = MockInstantiator::generate_bids_from_total_usd( - project_metadata.minimum_price.saturating_mul_int(auction_allocation), - project_metadata.minimum_price, - default_weights(), - default_bidders(), - default_bidder_multipliers(), - ); - let community_contributions = MockInstantiator::generate_contributions_from_total_usd( - project_metadata.minimum_price.saturating_mul_int( - Percent::from_percent(50u8) * (project_metadata.total_allocation_size - auction_allocation) / 2, - ), - project_metadata.minimum_price, - default_weights(), - default_community_contributors(), - default_community_contributor_multipliers(), - ); - let remainder_contributions = MockInstantiator::generate_contributions_from_total_usd( - project_metadata.minimum_price.saturating_mul_int( - Percent::from_percent(50u8) * (project_metadata.total_allocation_size - auction_allocation) / 2, - ), - project_metadata.minimum_price, - default_weights(), - default_remainder_contributors(), - default_remainder_contributor_multipliers(), - ); - - let project_id = inst.create_finished_project( - project_metadata, - issuer, - evaluations, - bids, - community_contributions.clone(), - remainder_contributions.clone(), - ); - let final_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); - let expected_bid_payouts = inst.execute(|| { - Bids::::iter_prefix_values((project_id,)) - .map(|bid| { - UserToForeignAssets::::new( - bid.bidder, - bid.funding_asset_amount_locked, - bid.funding_asset.to_assethub_id(), - ) - }) - .sorted_by_key(|item| item.account) - .collect::>>() - }); - let expected_community_contribution_payouts = - MockInstantiator::calculate_contributed_funding_asset_spent(community_contributions, final_price); - let expected_remainder_contribution_payouts = - MockInstantiator::calculate_contributed_funding_asset_spent(remainder_contributions, final_price); - let all_expected_payouts = MockInstantiator::generic_map_operation( - vec![ - expected_bid_payouts.clone(), - expected_community_contribution_payouts, - expected_remainder_contribution_payouts, - ], - MergeOperation::Add, - ); - - let prev_issuer_funding_balance = - inst.get_free_foreign_asset_balances_for(expected_bid_payouts[0].asset_id, vec![issuer])[0].asset_amount; - let all_participants = all_expected_payouts.accounts(); - let prev_participants_funding_balances = - inst.get_free_foreign_asset_balances_for(expected_bid_payouts[0].asset_id, all_participants.clone()); - - call_and_is_ok!( - inst, - Pallet::::decide_project_outcome( - RuntimeOrigin::signed(issuer), - get_mock_jwt(issuer.clone(), InvestorType::Institutional, generate_did_from_account(issuer.clone())), - project_id, - FundingOutcomeDecision::RejectFunding - ) - ); - - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - - let stored_bids = inst.execute(|| { - Bids::::iter_prefix_values((project_id,)) - .filter(|bid| matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..))) - .collect::>() - }); - let stored_contributions = - inst.execute(|| Contributions::::iter_prefix_values((project_id,)).collect::>()); - - for bid in stored_bids { - call_and_is_ok!( - inst, - Pallet::::release_bid_funds_for( - RuntimeOrigin::signed(issuer), - project_id, - bid.bidder, - bid.id, - ) - ) - } - - for contribution in stored_contributions { - call_and_is_ok!( - inst, - Pallet::::release_contribution_funds_for( - RuntimeOrigin::signed(issuer), - project_id, - contribution.contributor, - contribution.id, - ) - ) - } - - let post_issuer_funding_balance = - inst.get_free_foreign_asset_balances_for(expected_bid_payouts[0].asset_id, vec![issuer])[0].asset_amount; - let post_participants_funding_balances = - inst.get_free_foreign_asset_balances_for(expected_bid_payouts[0].asset_id, all_participants); - let post_project_pot_funding_balance = inst.get_free_foreign_asset_balances_for( - expected_bid_payouts[0].asset_id, - vec![Pallet::::fund_account_id(project_id)], - )[0] - .asset_amount; - - let all_participants_funding_delta = MockInstantiator::generic_map_operation( - vec![prev_participants_funding_balances, post_participants_funding_balances], - MergeOperation::Add, - ); - - let issuer_funding_delta = post_issuer_funding_balance - prev_issuer_funding_balance; - - assert_eq!(issuer_funding_delta, 0); - assert_eq!(post_project_pot_funding_balance, 0u128); - assert_eq!(all_expected_payouts, all_participants_funding_delta); - } - - #[test] - fn plmc_bonded_is_returned_automatically_on_funding_fail() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(inst.get_new_nonce(), issuer); - let auction_allocation = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - let evaluations = default_evaluations(); - let bids = MockInstantiator::generate_bids_from_total_usd( - project_metadata.minimum_price.saturating_mul_int(auction_allocation), - project_metadata.minimum_price, - default_weights(), - default_bidders(), - default_bidder_multipliers(), - ); - let community_contributions = MockInstantiator::generate_contributions_from_total_usd( - project_metadata.minimum_price.saturating_mul_int( - Percent::from_percent(50u8) * (project_metadata.total_allocation_size - auction_allocation) / 2, - ), - project_metadata.minimum_price, - default_weights(), - default_community_contributors(), - default_community_contributor_multipliers(), - ); - let remainder_contributions = MockInstantiator::generate_contributions_from_total_usd( - project_metadata.minimum_price.saturating_mul_int( - Percent::from_percent(50u8) * (project_metadata.total_allocation_size - auction_allocation) / 2, - ), - project_metadata.minimum_price, - default_weights(), - default_remainder_contributors(), - default_remainder_contributor_multipliers(), - ); - - let project_id = inst.create_finished_project( - project_metadata, - issuer, - evaluations.clone(), - bids.clone(), - community_contributions.clone(), - remainder_contributions.clone(), - ); - let final_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - let expected_evaluators_and_contributors_payouts = - MockInstantiator::calculate_total_plmc_locked_from_evaluations_and_remainder_contributions( - evaluations.clone(), - remainder_contributions.clone(), - final_price, - true, - ); - let expected_bid_payouts = - MockInstantiator::calculate_auction_plmc_charged_with_given_price(&bids, final_price); - let expected_community_contribution_payouts = - MockInstantiator::calculate_contributed_plmc_spent(community_contributions.clone(), final_price); - let all_expected_payouts = MockInstantiator::generic_map_operation( - vec![ - expected_evaluators_and_contributors_payouts.clone(), - expected_bid_payouts, - expected_community_contribution_payouts, - ], - MergeOperation::Add, - ); - println!("all expected payouts {:?}", all_expected_payouts); - for payout in all_expected_payouts.clone() { - let evaluation_hold = inst.execute(|| { - <::NativeCurrency as fungible::InspectHold>>::balance_on_hold( - &HoldReason::Evaluation(project_id).into(), - &payout.account, - ) - }); - let participation_hold = inst.execute(|| { - <::NativeCurrency as fungible::InspectHold>>::balance_on_hold( - &HoldReason::Participation(project_id).into(), - &payout.account, - ) - }); - println!("account {:?} has evaluation hold {:?}", payout.account, evaluation_hold); - println!("account {:?} has participation hold {:?}", payout.account, participation_hold); - } - let deposit_required = <::ContributionTokenCurrency as AccountTouch< - ProjectId, - AccountIdOf, - >>::deposit_required(project_id); - let all_expected_payouts = all_expected_payouts - .into_iter() - .map(|UserToPLMCBalance { account, plmc_amount }| { - UserToPLMCBalance::new(account, plmc_amount + deposit_required) - }) - .collect::>(); - - let prev_issuer_funding_balance = inst.get_free_plmc_balances_for(vec![issuer])[0].plmc_amount; - - let all_participants = all_expected_payouts.accounts(); - let prev_participants_plmc_balances = inst.get_free_plmc_balances_for(all_participants.clone()); - - call_and_is_ok!( - inst, - Pallet::::decide_project_outcome( - RuntimeOrigin::signed(issuer), - get_mock_jwt(issuer.clone(), InvestorType::Institutional, generate_did_from_account(issuer.clone())), - project_id, - FundingOutcomeDecision::RejectFunding - ) - ); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - inst.advance_time(10).unwrap(); - assert_eq!(inst.get_project_details(project_id).cleanup, Cleaner::Failure(CleanerState::Finished(PhantomData))); - - let post_issuer_funding_balance = inst.get_free_plmc_balances_for(vec![issuer])[0].plmc_amount; - let post_participants_plmc_balances = inst.get_free_plmc_balances_for(all_participants); - - let all_participants_plmc_deltas = MockInstantiator::generic_map_operation( - vec![post_participants_plmc_balances, prev_participants_plmc_balances], - MergeOperation::Subtract, - ); - - let issuer_funding_delta = post_issuer_funding_balance - prev_issuer_funding_balance; - - assert_eq!(issuer_funding_delta, 0); - assert_eq!(all_participants_plmc_deltas, all_expected_payouts); - } - - #[test] - fn plmc_bonded_is_returned_manually_on_funding_fail() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(inst.get_new_nonce(), issuer); - let auction_allocation = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - let evaluations = default_evaluations(); - let bids = MockInstantiator::generate_bids_from_total_usd( - project_metadata.minimum_price.saturating_mul_int(auction_allocation), - project_metadata.minimum_price, - default_weights(), - default_bidders(), - default_bidder_multipliers(), - ); - let community_contributions = MockInstantiator::generate_contributions_from_total_usd( - project_metadata.minimum_price.saturating_mul_int( - Percent::from_percent(50u8) * (project_metadata.total_allocation_size - auction_allocation) / 2, - ), - project_metadata.minimum_price, - default_weights(), - default_community_contributors(), - default_community_contributor_multipliers(), - ); - let remainder_contributions = MockInstantiator::generate_contributions_from_total_usd( - project_metadata.minimum_price.saturating_mul_int( - Percent::from_percent(50u8) * (project_metadata.total_allocation_size - auction_allocation) / 2, - ), - project_metadata.minimum_price, - default_weights(), - default_remainder_contributors(), - default_remainder_contributor_multipliers(), - ); - - let project_id = inst.create_finished_project( - project_metadata, - issuer, - evaluations.clone(), - bids.clone(), - community_contributions.clone(), - remainder_contributions.clone(), - ); - let final_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - let expected_evaluators_and_contributors_payouts = - MockInstantiator::calculate_total_plmc_locked_from_evaluations_and_remainder_contributions( - evaluations.clone(), - remainder_contributions.clone(), - final_price, - true, - ); - let expected_bid_payouts = - MockInstantiator::calculate_auction_plmc_charged_with_given_price(&bids, final_price); - let expected_community_contribution_payouts = - MockInstantiator::calculate_contributed_plmc_spent(community_contributions.clone(), final_price); - let all_expected_payouts = MockInstantiator::generic_map_operation( - vec![ - expected_evaluators_and_contributors_payouts.clone(), - expected_bid_payouts, - expected_community_contribution_payouts, - ], - MergeOperation::Add, - ); - println!("all expected payouts {:?}", all_expected_payouts); - for payout in all_expected_payouts.clone() { - let evaluation_hold = inst.execute(|| { - <::NativeCurrency as fungible::InspectHold>>::balance_on_hold( - &HoldReason::Evaluation(project_id).into(), - &payout.account, - ) - }); - let participation_hold = inst.execute(|| { - <::NativeCurrency as fungible::InspectHold>>::balance_on_hold( - &HoldReason::Participation(project_id).into(), - &payout.account, - ) - }); - println!("account {:?} has evaluation hold {:?}", payout.account, evaluation_hold); - println!("account {:?} has participation hold {:?}", payout.account, participation_hold); - } - let _deposit_required = <::ContributionTokenCurrency as AccountTouch< - ProjectId, - AccountIdOf, - >>::deposit_required(project_id); - - let prev_issuer_funding_balance = inst.get_free_plmc_balances_for(vec![issuer])[0].plmc_amount; - let all_participants = all_expected_payouts.accounts(); - let prev_participants_plmc_balances = inst.get_free_plmc_balances_for(all_participants.clone()); - - call_and_is_ok!( - inst, - Pallet::::decide_project_outcome( - RuntimeOrigin::signed(issuer), - get_mock_jwt(issuer.clone(), InvestorType::Institutional, generate_did_from_account(issuer.clone())), - project_id, - FundingOutcomeDecision::RejectFunding - ) - ); - inst.advance_time(::SuccessToSettlementTime::get() + 1).unwrap(); - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Failure(CleanerState::Initialized(PhantomData)) - ); - - let stored_evaluations = - inst.execute(|| Evaluations::::iter_prefix_values((project_id,)).collect::>()); - let stored_bids = inst.execute(|| { - Bids::::iter_prefix_values((project_id,)) - .filter(|bid| matches!(bid.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..))) - .collect::>() - }); - let stored_contributions = - inst.execute(|| Contributions::::iter_prefix_values((project_id,)).collect::>()); - - for evaluation in stored_evaluations { - call_and_is_ok!( - inst, - Pallet::::evaluation_slash_for( - RuntimeOrigin::signed(evaluation.evaluator), - project_id, - evaluation.evaluator, - evaluation.id, - ), - Pallet::::evaluation_unbond_for( - RuntimeOrigin::signed(evaluation.evaluator), - project_id, - evaluation.evaluator, - evaluation.id, - ) - ) - } - - for bid in stored_bids { - call_and_is_ok!( - inst, - Pallet::::release_bid_funds_for( - RuntimeOrigin::signed(issuer), - project_id, - bid.bidder, - bid.id, - ), - Pallet::::bid_unbond_for( - RuntimeOrigin::signed(bid.bidder), - project_id, - bid.bidder, - bid.id, - ) - ) - } - - for contribution in stored_contributions { - call_and_is_ok!( - inst, - Pallet::::release_contribution_funds_for( - RuntimeOrigin::signed(issuer), - project_id, - contribution.contributor, - contribution.id, - ), - Pallet::::contribution_unbond_for( - RuntimeOrigin::signed(contribution.contributor), - project_id, - contribution.contributor, - contribution.id, - ) - ) - } - - let post_issuer_funding_balance = inst.get_free_plmc_balances_for(vec![issuer])[0].plmc_amount; - let post_participants_plmc_balances = inst.get_free_plmc_balances_for(all_participants); - - let all_participants_plmc_deltas = MockInstantiator::generic_map_operation( - vec![post_participants_plmc_balances, prev_participants_plmc_balances], - MergeOperation::Subtract, - ); - - let issuer_funding_delta = post_issuer_funding_balance - prev_issuer_funding_balance; - assert_eq!( - inst.get_project_details(project_id).cleanup, - Cleaner::Failure(CleanerState::Initialized(PhantomData)) - ); - assert_eq!(issuer_funding_delta, 0); - assert_eq!(all_participants_plmc_deltas, all_expected_payouts); - } - - // i.e consumer increase bug fixed with touch on pallet-assets - #[test] - fn no_limit_on_project_contributions_per_user() { - let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - - let project = |x| TestProjectParams { - expected_state: ProjectStatus::FundingSuccessful, - metadata: default_project_metadata(x, ISSUER_1), - evaluations: default_evaluations(), - bids: default_bids(), - community_contributions: default_community_buys(), - remainder_contributions: default_remainder_buys(), - issuer: x as u32, - }; - let projects = (0..20).into_iter().map(|x| project(x)).collect_vec(); - async_features::create_multiple_projects_at(inst, projects); - } - - #[test] - fn evaluation_plmc_unbonded_after_funding_success() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let evaluations = default_evaluations(); - let evaluators = evaluations.accounts(); - - let project_id = inst.create_remainder_contributing_project( - default_project_metadata(inst.get_new_nonce(), ISSUER_1), - ISSUER_1, - evaluations.clone(), - default_bids(), - default_community_buys(), - ); - - let prev_reserved_plmc = - inst.get_reserved_plmc_balances_for(evaluators.clone(), HoldReason::Evaluation(project_id).into()); - - let prev_free_plmc = inst.get_free_plmc_balances_for(evaluators.clone()); - - inst.finish_funding(project_id).unwrap(); - inst.advance_time(::ManualAcceptanceDuration::get() + 1).unwrap(); - inst.advance_time(::SuccessToSettlementTime::get() + 1).unwrap(); - assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); - assert_eq!(inst.get_project_details(project_id).cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); - inst.advance_time(10).unwrap(); - let post_unbond_amounts: Vec> = prev_reserved_plmc - .iter() - .map(|UserToPLMCBalance { account, .. }| UserToPLMCBalance::new(*account, Zero::zero())) - .collect(); - - inst.do_reserved_plmc_assertions(post_unbond_amounts.clone(), HoldReason::Evaluation(project_id).into()); - inst.do_reserved_plmc_assertions(post_unbond_amounts, HoldReason::Participation(project_id).into()); - - let post_free_plmc = inst.get_free_plmc_balances_for(evaluators); - - let increased_amounts = - MockInstantiator::generic_map_operation(vec![post_free_plmc, prev_free_plmc], MergeOperation::Subtract); - - assert_eq!(increased_amounts, MockInstantiator::calculate_evaluation_plmc_spent(evaluations)) - } - - #[test] - fn plmc_vesting_schedule_starts_automatically() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(inst.get_new_nonce(), issuer); - let evaluations = default_evaluations(); - let mut bids = default_bids(); - let community_contributions = default_community_buys(); - let remainder_contributions = default_remainder_buys(); - - let project_id = inst.create_finished_project( - project_metadata, - issuer, - evaluations, - bids.clone(), - community_contributions.clone(), - remainder_contributions.clone(), - ); - - let price = inst.get_project_details(project_id).weighted_average_price.unwrap(); - let stored_bids = inst.execute(|| Bids::::iter_prefix_values((project_id,)).collect_vec()); - bids = - stored_bids.into_iter().map(|bid| BidParams::new_with_defaults(bid.bidder, bid.final_ct_amount)).collect(); - let auction_locked_plmc = MockInstantiator::calculate_auction_plmc_charged_with_given_price(&bids, price); - let community_locked_plmc = MockInstantiator::calculate_contributed_plmc_spent(community_contributions, price); - let remainder_locked_plmc = MockInstantiator::calculate_contributed_plmc_spent(remainder_contributions, price); - let all_plmc_locks = MockInstantiator::generic_map_operation( - vec![auction_locked_plmc, community_locked_plmc, remainder_locked_plmc], - MergeOperation::Add, - ); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - - inst.advance_time(10u64).unwrap(); - let details = inst.get_project_details(project_id); - assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); - - for UserToPLMCBalance { account, plmc_amount } in all_plmc_locks { - let schedule = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &account, - HoldReason::Participation(project_id).into(), - ) - }); - - assert_eq!(schedule.unwrap(), plmc_amount); - } - } - - #[test] - fn plmc_vesting_schedule_starts_manually() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(inst.get_new_nonce(), issuer); - let evaluations = default_evaluations(); - let bids = default_bids(); - let community_contributions = default_community_buys(); - let remainder_contributions = default_remainder_buys(); - - let project_id = inst.create_finished_project( - project_metadata, - issuer, - evaluations, - bids.clone(), - community_contributions.clone(), - remainder_contributions.clone(), - ); - - let price = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - let details = inst.get_project_details(project_id); - assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Initialized(PhantomData))); - - let stored_successful_bids = inst.execute(|| { - Bids::::iter_prefix_values((project_id,)) - .filter(|bid| matches!(bid.status, BidStatus::Rejected(_)).not()) - .collect::>() - }); - let stored_contributions = - inst.execute(|| Contributions::::iter_prefix_values((project_id,)).collect::>()); - - for bid in stored_successful_bids.clone() { - call_and_is_ok!( - inst, - Pallet::::do_start_bid_vesting_schedule_for(&bid.bidder, project_id, &bid.bidder, bid.id,) - ); - } - for contribution in stored_contributions { - call_and_is_ok!( - inst, - Pallet::::do_start_contribution_vesting_schedule_for( - &contribution.contributor, - project_id, - &contribution.contributor, - contribution.id, - ) - ); - } - - let auction_locked_plmc = MockInstantiator::calculate_auction_plmc_charged_with_given_price(&bids, price); - let community_locked_plmc = MockInstantiator::calculate_contributed_plmc_spent(community_contributions, price); - let remainder_locked_plmc = MockInstantiator::calculate_contributed_plmc_spent(remainder_contributions, price); - let all_plmc_locks = MockInstantiator::generic_map_operation( - vec![auction_locked_plmc, community_locked_plmc, remainder_locked_plmc], - MergeOperation::Add, - ); - - for UserToPLMCBalance { account, plmc_amount } in all_plmc_locks { - let schedule = inst.execute(|| { - ::Vesting::total_scheduled_amount( - &account, - HoldReason::Participation(project_id).into(), - ) - }); - - assert_eq!(schedule.unwrap(), plmc_amount); - } - } - - #[test] - fn plmc_vesting_full_amount() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(inst.get_new_nonce(), issuer); - let evaluations = default_evaluations(); - let bids = default_bids(); - let community_contributions = default_community_buys(); - let remainder_contributions = default_remainder_buys(); - - let project_id = inst.create_finished_project( - project_metadata, - issuer, - evaluations, - bids, - community_contributions, - remainder_contributions, - ); - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - - inst.advance_time(10u64).unwrap(); - let details = inst.get_project_details(project_id); - assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); - - let stored_successful_bids = inst.execute(|| { - Bids::::iter_prefix_values((project_id,)) - .filter(|bid| matches!(bid.status, BidStatus::Rejected(_)).not()) - .collect::>() - }); - - let stored_contributions = - inst.execute(|| Contributions::::iter_prefix_values((project_id,)).collect::>()); - - let total_bid_plmc_in_vesting: Vec> = stored_successful_bids - .iter() - .map(|bid| (bid.bidder, bid.plmc_vesting_info.unwrap().total_amount).into()) - .collect_vec(); - - let total_contribution_plmc_in_vesting: Vec> = stored_contributions - .iter() - .map(|contribution| (contribution.contributor, contribution.plmc_vesting_info.unwrap().total_amount).into()) - .collect_vec(); - - let total_participant_plmc_in_vesting = MockInstantiator::generic_map_operation( - vec![total_bid_plmc_in_vesting, total_contribution_plmc_in_vesting], - MergeOperation::Add, - ); - - inst.advance_time((10 * DAYS).into()).unwrap(); - - for UserToPLMCBalance { account, plmc_amount } in total_participant_plmc_in_vesting { - let prev_free_balance = inst.execute(|| ::NativeCurrency::balance(&account)); - - inst.execute(|| Pallet::::do_vest_plmc_for(account, project_id, account)).unwrap(); - - let post_free_balance = inst.execute(|| ::NativeCurrency::balance(&account)); - assert_eq!(plmc_amount, post_free_balance - prev_free_balance); - } - } - - #[test] - fn plmc_vesting_partial_amount() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(inst.get_new_nonce(), issuer); - let evaluations = default_evaluations(); - let bids = default_bids(); - let community_contributions = default_community_buys(); - let remainder_contributions = default_remainder_buys(); - - let project_id = inst.create_finished_project( - project_metadata, - issuer, - evaluations, - bids, - community_contributions, - remainder_contributions, - ); - - inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); - inst.advance_time(15u64).unwrap(); - let details = inst.get_project_details(project_id); - assert_eq!(details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); - let vest_start_block = details.funding_end_block.unwrap(); - let stored_successful_bids = inst.execute(|| { - Bids::::iter_prefix_values((project_id,)) - .filter(|bid| matches!(bid.status, BidStatus::Rejected(_)).not()) - .collect::>() - }); - let stored_contributions = - inst.execute(|| Contributions::::iter_prefix_values((project_id,)).collect::>()); + assert_ok!(crate::Pallet::::settle_failed_contribution( + RuntimeOrigin::signed(contributor.clone()), + project_id, + contributor, + first_contribution.id + )); - let bidder_vesting = - stored_successful_bids.iter().map(|bid| (bid.bidder, bid.plmc_vesting_info.unwrap())).collect_vec(); - let contributor_vesting = stored_contributions - .iter() - .map(|contribution| (contribution.contributor, contribution.plmc_vesting_info.unwrap())) - .collect_vec(); + let reason: RuntimeHoldReason = HoldReason::Participation(project_id).into(); + let held_contributor = ::NativeCurrency::balance_on_hold(&reason, &contributor); + assert_eq!(held_contributor, 0u32.into()); - let participant_vesting_infos: Vec<(AccountIdOf, Vec>)> = - MockInstantiator::generic_map_merge_reduce( - vec![bidder_vesting, contributor_vesting], - |map| map.0, - Vec::new(), - |map, mut vestings| { - vestings.push(map.1); - vestings - }, + let funding_asset_contributor = ::FundingCurrency::balance( + first_contribution.funding_asset.to_assethub_id(), + &contributor, ); + assert_eq!(funding_asset_contributor, first_contribution.usd_contribution_amount); - let now = inst.current_block(); - for (participant, vesting_infos) in participant_vesting_infos { - let vested_amount = vesting_infos.into_iter().fold(0u128, |acc, vesting_info| { - acc + vesting_info.amount_per_block * min(vesting_info.duration, now - vest_start_block) as u128 - }); - - let prev_free_balance = inst.execute(|| ::NativeCurrency::balance(&participant)); - - inst.execute(|| Pallet::::do_vest_plmc_for(participant, project_id, participant)).unwrap(); - - let post_free_balance = inst.execute(|| ::NativeCurrency::balance(&participant)); - assert_eq!(vested_amount, post_free_balance - prev_free_balance); - } - } - - #[test] - fn ct_treasury_mints() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - - let treasury_account = ::ContributionTreasury::get(); - - let project_metadata = ProjectMetadataOf:: { - token_information: default_token_information(), - mainnet_token_max_supply: 8_000_000 * ASSET_UNIT, - total_allocation_size: 1_000_000 * ASSET_UNIT, - auction_round_allocation_percentage: Percent::from_percent(50u8), - minimum_price: PriceOf::::from_float(10.0), - bidding_ticket_sizes: BiddingTicketSizes { - professional: TicketSize::new(Some(5000 * US_DOLLAR), None), - institutional: TicketSize::new(Some(5000 * US_DOLLAR), None), - phantom: Default::default(), - }, - contributing_ticket_sizes: ContributingTicketSizes { - retail: TicketSize::new(None, None), - professional: TicketSize::new(None, None), - institutional: TicketSize::new(None, None), - phantom: Default::default(), - }, - participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), - funding_destination_account: ISSUER_1, - offchain_information_hash: Some(hashed(METADATA)), - }; - let mut counter: u8 = 0u8; - let mut with_different_metadata = |mut project: ProjectMetadataOf| { - let mut binding = project.offchain_information_hash.unwrap(); - let h256_bytes = binding.as_fixed_bytes_mut(); - h256_bytes[0] = counter; - counter += 1u8; - project.offchain_information_hash = Some(binding); - project - }; - - let price = project_metadata.minimum_price; - - // Failed project has no mints on the treasury - let project_20_percent = inst.create_finished_project( - with_different_metadata(project_metadata.clone()), - ISSUER_1, - default_evaluations(), - default_bids_from_ct_percent(10), - default_community_contributions_from_ct_percent(10), - vec![], - ); - inst.advance_time(::ManualAcceptanceDuration::get()).unwrap(); - inst.advance_time(::SuccessToSettlementTime::get() + 1).unwrap(); - let ct_balance = inst.execute(|| { - ::ContributionTokenCurrency::balance(project_20_percent, treasury_account) - }); - assert_eq!(ct_balance, 0); - - // 50% funded project can have mints on the treasury if issuer accepts or enough time passes for automatic acceptance - let fee_10_percent = Percent::from_percent(10) * 1_000_000 * US_DOLLAR; - let fee_8_percent = Percent::from_percent(8) * 4_000_000 * US_DOLLAR; - let fee_6_percent = Percent::from_percent(6) * 0 * US_DOLLAR; - let total_usd_fee = fee_10_percent + fee_8_percent + fee_6_percent; - let total_ct_fee = price.reciprocal().unwrap().saturating_mul_int(total_usd_fee); - - let project_50_percent = inst.create_finished_project( - with_different_metadata(project_metadata.clone()), - ISSUER_1, - default_evaluations(), - default_bids_from_ct_percent(25), - default_community_contributions_from_ct_percent(20), - default_remainder_contributions_from_ct_percent(5), - ); - inst.advance_time(::ManualAcceptanceDuration::get()).unwrap(); - inst.advance_time(::SuccessToSettlementTime::get() + 1).unwrap(); - let ct_balance = inst.execute(|| { - ::ContributionTokenCurrency::balance(project_50_percent, treasury_account) - }); - let expected_liquidity_pool_minted = Percent::from_percent(50) * total_ct_fee; - let expected_long_term_holder_bonus_minted = Percent::from_percent(50) * total_ct_fee; - assert_eq!(ct_balance, expected_liquidity_pool_minted + expected_long_term_holder_bonus_minted); - - // 80% funded project can have mints on the treasury if issuer accepts or enough time passes for automatic acceptance - let fee_10_percent = Percent::from_percent(10) * 1_000_000 * US_DOLLAR; - let fee_8_percent = Percent::from_percent(8) * 5_000_000 * US_DOLLAR; - let fee_6_percent = Percent::from_percent(6) * 2_000_000 * US_DOLLAR; - let total_usd_fee = fee_10_percent + fee_8_percent + fee_6_percent; - let total_ct_fee = price.reciprocal().unwrap().saturating_mul_int(total_usd_fee); - - let project_80_percent = inst.create_finished_project( - with_different_metadata(project_metadata.clone()), - ISSUER_1, - default_evaluations(), - default_bids_from_ct_percent(40), - default_community_contributions_from_ct_percent(30), - default_remainder_contributions_from_ct_percent(10), - ); - inst.advance_time(::ManualAcceptanceDuration::get()).unwrap(); - inst.advance_time(::SuccessToSettlementTime::get() + 1).unwrap(); - let ct_balance = inst.execute(|| { - ::ContributionTokenCurrency::balance(project_80_percent, treasury_account) - }); - let expected_liquidity_pool_minted = Percent::from_percent(50) * total_ct_fee; - let expected_long_term_holder_bonus_minted = Percent::from_percent(50) * total_ct_fee; - assert_eq!(ct_balance, expected_liquidity_pool_minted + expected_long_term_holder_bonus_minted); - - // 98% funded project always has mints on the treasury - let fee_10_percent = Percent::from_percent(10) * 1_000_000 * US_DOLLAR; - let fee_8_percent = Percent::from_percent(8) * 5_000_000 * US_DOLLAR; - let fee_6_percent = Percent::from_percent(6) * 3_800_000 * US_DOLLAR; - let total_usd_fee = fee_10_percent + fee_8_percent + fee_6_percent; - let total_ct_fee = price.reciprocal().unwrap().saturating_mul_int(total_usd_fee); - - let project_98_percent = inst.create_finished_project( - with_different_metadata(project_metadata.clone()), - ISSUER_1, - default_evaluations(), - default_bids_from_ct_percent(49), - default_community_contributions_from_ct_percent(39), - default_remainder_contributions_from_ct_percent(10), - ); - inst.advance_time(::SuccessToSettlementTime::get() + 1).unwrap(); - let ct_balance = inst.execute(|| { - ::ContributionTokenCurrency::balance(project_98_percent, treasury_account) + let ct_amount = ::ContributionTokenCurrency::balance(project_id, &contributor); + assert_eq!(ct_amount, Zero::zero()); }); - let expected_liquidity_pool_minted = Percent::from_percent(50) * total_ct_fee; - let lthb_percent = Perquintill::from_percent(20) + Perquintill::from_percent(30) * Perquintill::from_percent(2); - let expected_long_term_holder_bonus_minted = lthb_percent * total_ct_fee; - assert_eq!(ct_balance, expected_liquidity_pool_minted + expected_long_term_holder_bonus_minted); - - // Test the touch on the treasury ct account by the issuer. - // We create more CT accounts that the account can use provider references for, - // so if it succeeds, then it means the touch was successful. - let consumer_limit: u32 = ::MaxConsumers::get(); - - // we want to test ct mints on treasury of 1 over the consumer limit, - // and we already minted 3 contribution tokens on previous tests. - for i in 0..consumer_limit + 1u32 - 3u32 { - let _ = inst.create_finished_project( - with_different_metadata(project_metadata.clone()), - ISSUER_1 + i + 1000, - default_evaluations(), - default_bids_from_ct_percent(49), - default_community_contributions_from_ct_percent(39), - default_remainder_contributions_from_ct_percent(10), - ); - inst.advance_time(::SuccessToSettlementTime::get() + 1).unwrap(); - } } } @@ -6829,9 +5191,6 @@ mod ct_migration { ); inst.advance_time(::SuccessToSettlementTime::get() + 20u64).unwrap(); - let project_details = inst.get_project_details(project_id); - assert_eq!(project_details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); - inst.execute(|| { assert_ok!(crate::Pallet::::do_set_para_id_for_project( &ISSUER_1, @@ -6855,8 +5214,6 @@ mod ct_migration { default_remainder_buys(), ); inst.advance_time(::SuccessToSettlementTime::get() + 20u64).unwrap(); - let project_details = inst.get_project_details(project_id); - assert_eq!(project_details.cleanup, Cleaner::Success(CleanerState::Finished(PhantomData))); inst.execute(|| { assert_err!( @@ -6879,12 +5236,6 @@ mod ct_migration { let project_details = inst.get_project_details(project_id); assert_eq!(project_details.parachain_id, None); } - - #[test] - fn check_migrations_per_xcm() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - inst.execute(|| dbg!(Pallet::::migrations_per_xcm_message_allowed())); - } } // check that functions created to facilitate testing return the expected results @@ -6994,11 +5345,7 @@ mod helper_functions { funding_destination_account: ISSUER_1, offchain_information_hash: Some(hashed(METADATA)), }; - let plmc_charged = MockInstantiator::calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &bids, - project_metadata.clone(), - None, - ); + let project_id = inst.create_community_contributing_project( project_metadata.clone(), ISSUER_1, @@ -7006,9 +5353,6 @@ mod helper_functions { bids.clone(), ); - let stored_bids = inst.execute(|| { - Bids::::iter_values().into_iter().sorted_by(|b1, b2| b1.id.cmp(&b2.id)).collect_vec() - }); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); let expected_returns = vec![ diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index f2136c58b..6240b5e95 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -18,17 +18,13 @@ //! Types for Funding pallet. -use crate::{ - traits::{BondingRequirementCalculation, VestingDurationCalculation}, - BalanceOf, BidInfoOf, Config, ContributionInfoOf, EvaluationInfoOf, MultiplierOf, -}; +use crate::{traits::BondingRequirementCalculation, BalanceOf}; use frame_support::{pallet_prelude::*, traits::tokens::Balance as BalanceT}; use frame_system::pallet_prelude::BlockNumberFor; -use polimec_common::migration_types::{Migration, MigrationInfo, MigrationOrigin, ParticipationType}; use polkadot_parachain_primitives::primitives::Id as ParaId; use serde::{Deserialize, Serialize}; use sp_arithmetic::{FixedPointNumber, FixedPointOperand}; -use sp_runtime::traits::{CheckedDiv, Convert, Get, Zero}; +use sp_runtime::traits::CheckedDiv; use sp_std::{cmp::Eq, prelude::*}; pub use config_types::*; @@ -272,8 +268,6 @@ pub mod storage_types { pub remaining_contribution_tokens: Balance, /// Funding reached amount in USD equivalent pub funding_amount_reached: Balance, - /// Cleanup operations remaining - pub cleanup: Cleaner, /// Information about the total amount bonded, and the outcome in regards to reward/slash/nothing pub evaluation_round_info: EvaluationRoundInfo, /// When the Funding Round ends @@ -309,22 +303,10 @@ pub mod storage_types { pub early_usd_amount: Balance, pub late_usd_amount: Balance, pub when: BlockNumber, - // Will be Some after a reward of slash was made on this evaluation. - pub rewarded_or_slashed: Option>, - pub ct_migration_status: MigrationStatus, } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] - pub struct BidInfo< - ProjectId, - Did, - Balance: BalanceT, - Price: FixedPointNumber, - AccountId, - BlockNumber, - Multiplier, - VestingInfo, - > { + pub struct BidInfo { pub id: u32, pub project_id: ProjectId, pub bidder: AccountId, @@ -339,11 +321,7 @@ pub mod storage_types { pub funding_asset_amount_locked: Balance, pub multiplier: Multiplier, pub plmc_bond: Balance, - pub plmc_vesting_info: Option, pub when: BlockNumber, - pub funds_released: bool, - pub ct_minted: bool, - pub ct_migration_status: MigrationStatus, } impl< @@ -354,8 +332,7 @@ pub mod storage_types { AccountId: Eq, BlockNumber: Eq + Ord, Multiplier: Eq, - VestingInfo: Eq, - > Ord for BidInfo + > Ord for BidInfo { fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { match self.original_ct_usd_price.cmp(&other.original_ct_usd_price) { @@ -373,8 +350,7 @@ pub mod storage_types { AccountId: Eq, BlockNumber: Eq + Ord, Multiplier: Eq, - VestingInfo: Eq, - > PartialOrd for BidInfo + > PartialOrd for BidInfo { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -382,7 +358,7 @@ pub mod storage_types { } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] - pub struct ContributionInfo { + pub struct ContributionInfo { pub id: Id, pub project_id: ProjectId, pub contributor: AccountId, @@ -392,10 +368,6 @@ pub mod storage_types { pub funding_asset: AcceptedFundingAsset, pub funding_asset_amount: Balance, pub plmc_bond: Balance, - pub plmc_vesting_info: Option, - pub funds_released: bool, - pub ct_minted: bool, - pub ct_migration_status: MigrationStatus, } /// Represents a bucket that holds a specific amount of tokens at a given price. @@ -447,8 +419,6 @@ pub mod storage_types { pub mod inner_types { use super::*; use variant_count::VariantCount; - use xcm::v3::MaxDispatchErrorLen; - #[derive(Default, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct CurrencyMetadata { @@ -731,55 +701,6 @@ pub mod inner_types { Unknown, } - #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] - pub struct Success; - - #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] - pub struct Failure; - - #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] - pub enum CleanerState { - Initialized(PhantomData), - // Success or Failure - EvaluationRewardOrSlash(u64, PhantomData), - EvaluationUnbonding(u64, PhantomData), - // Branch - // A. Success only - BidCTMint(u64, PhantomData), - ContributionCTMint(u64, PhantomData), - StartBidderVestingSchedule(u64, PhantomData), - StartContributorVestingSchedule(u64, PhantomData), - BidFundingPayout(u64, PhantomData), - ContributionFundingPayout(u64, PhantomData), - // B. Failure only - BidFundingRelease(u64, PhantomData), - BidUnbonding(u64, PhantomData), - ContributionFundingRelease(u64, PhantomData), - ContributionUnbonding(u64, PhantomData), - // Merge - // Success or Failure - Finished(PhantomData), - } - - #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] - pub enum Cleaner { - NotReady, - Success(CleanerState), - Failure(CleanerState), - } - impl TryFrom for Cleaner { - type Error = (); - - fn try_from(value: ProjectStatus) -> Result { - match value { - ProjectStatus::FundingSuccessful => Ok(Cleaner::Success(CleanerState::Initialized(PhantomData))), - ProjectStatus::FundingFailed | ProjectStatus::EvaluationFailed => - Ok(Cleaner::Failure(CleanerState::Initialized(PhantomData))), - _ => Err(()), - } - } - } - #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct EvaluationRoundInfo { pub total_bonded_usd: Balance, @@ -856,75 +777,4 @@ pub mod inner_types { pub project_id: ProjectId, pub migration_origins: MigrationOrigins, } - #[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, MaxEncodedLen)] - pub enum MigrationStatus { - NotStarted, - Sent(xcm::v3::QueryId), - Confirmed, - Failed(BoundedVec), - } - - #[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, MaxEncodedLen)] - pub struct MaxMigrationsPerXcm(PhantomData); - impl Get for MaxMigrationsPerXcm { - fn get() -> u32 { - crate::Pallet::::migrations_per_xcm_message_allowed() - } - } -} - -pub struct MigrationGenerator(PhantomData); - -impl MigrationGenerator { - pub fn evaluation_migration(evaluation: EvaluationInfoOf) -> Option { - if matches!(evaluation.ct_migration_status, MigrationStatus::Confirmed | MigrationStatus::Sent(_)) { - return None; - } - if let Some(RewardOrSlash::Reward(ct_amount)) = evaluation.rewarded_or_slashed { - let multiplier = MultiplierOf::::try_from(1u8).ok()?; - let vesting_duration = multiplier.calculate_vesting_duration::(); - let vesting_duration_local_type = ::BlockNumber::from(vesting_duration); - let migration_origin = MigrationOrigin { - user: T::AccountId32Conversion::convert(evaluation.evaluator), - id: evaluation.id, - participation_type: ParticipationType::Evaluation, - }; - let migration_info: MigrationInfo = (ct_amount.into(), vesting_duration_local_type.into()).into(); - Some(Migration::new(migration_origin, migration_info)) - } else { - None - } - } - - pub fn bid_migration(bid: BidInfoOf) -> Option { - if bid.final_ct_amount == Zero::zero() || - matches!(bid.ct_migration_status, MigrationStatus::Confirmed | MigrationStatus::Sent(_)) - { - return None; - } - let vesting_duration = bid.multiplier.calculate_vesting_duration::(); - let vesting_duration_local_type = ::BlockNumber::from(vesting_duration); - let migration_origin = MigrationOrigin { - user: ::AccountId32Conversion::convert(bid.bidder), - id: bid.id, - participation_type: ParticipationType::Bid, - }; - let migration_info: MigrationInfo = (bid.final_ct_amount.into(), vesting_duration_local_type.into()).into(); - Some(Migration::new(migration_origin, migration_info)) - } - - pub fn contribution_migration(contribution: ContributionInfoOf) -> Option { - if matches!(contribution.ct_migration_status, MigrationStatus::Confirmed | MigrationStatus::Sent(_)) { - return None; - } - let vesting_duration = contribution.multiplier.calculate_vesting_duration::(); - let vesting_duration_local_type = ::BlockNumber::from(vesting_duration); - let migration_origin = MigrationOrigin { - user: ::AccountId32Conversion::convert(contribution.contributor), - id: contribution.id, - participation_type: ParticipationType::Contribution, - }; - let migration_info: MigrationInfo = (contribution.ct_amount.into(), vesting_duration_local_type.into()).into(); - Some(Migration::new(migration_origin, migration_info)) - } } diff --git a/pallets/funding/src/weights.rs b/pallets/funding/src/weights.rs index 8e8cf9cb3..222ce973b 100644 --- a/pallets/funding/src/weights.rs +++ b/pallets/funding/src/weights.rs @@ -58,23 +58,13 @@ pub trait WeightInfo { fn bid(x: u32, y: u32, ) -> Weight; fn contribution(x: u32, ) -> Weight; fn contribution_ends_round(x: u32, y: u32 ) -> Weight; - fn evaluation_unbond_for() -> Weight; - fn evaluation_reward_payout_for_with_ct_account_creation() -> Weight; - fn evaluation_reward_payout_for_no_ct_account_creation() -> Weight; - fn evaluation_slash_for() -> Weight; - fn bid_ct_mint_for_with_ct_account_creation() -> Weight; - fn bid_ct_mint_for_no_ct_account_creation() -> Weight; - fn contribution_ct_mint_for_with_ct_account_creation() -> Weight; - fn contribution_ct_mint_for_no_ct_account_creation() -> Weight; - fn start_bid_vesting_schedule_for() -> Weight; - fn start_contribution_vesting_schedule_for() -> Weight; - fn payout_bid_funds_for() -> Weight; - fn payout_contribution_funds_for() -> Weight; fn decide_project_outcome(x: u32 ) -> Weight; - fn release_bid_funds_for() -> Weight; - fn release_contribution_funds_for() -> Weight; - fn bid_unbond_for() -> Weight; - fn contribution_unbond_for() -> Weight; + fn settle_successful_evaluation() -> Weight; + fn settle_failed_evaluation() -> Weight; + fn settle_successful_bid() -> Weight; + fn settle_failed_bid() -> Weight; + fn settle_successful_contribution() -> Weight; + fn settle_failed_contribution() -> Weight; fn end_evaluation_success(x: u32, ) -> Weight; fn end_evaluation_failure() -> Weight; fn start_candle_phase(x: u32, ) -> Weight; @@ -329,214 +319,6 @@ impl WeightInfo for SubstrateWeight { } /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Evaluations` (r:1 w:1) - /// Proof: `PolimecFunding::Evaluations` (`max_values`: None, `max_size`: Some(345), added: 2820, mode: `MaxEncodedLen`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(1149), added: 3624, mode: `MaxEncodedLen`) - fn evaluation_unbond_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `1477` - // Estimated: `4614` - // Minimum execution time: 54_000_000 picoseconds. - Weight::from_parts(57_000_000, 4614) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Evaluations` (r:1 w:1) - /// Proof: `PolimecFunding::Evaluations` (`max_values`: None, `max_size`: Some(345), added: 2820, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Account` (r:1 w:1) - /// Proof: `LocalAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(1149), added: 3624, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Asset` (r:1 w:1) - /// Proof: `LocalAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn evaluation_reward_payout_for_with_ct_account_creation() -> Weight { - // Proof Size summary in bytes: - // Measured: `1654` - // Estimated: `4614` - // Minimum execution time: 94_000_000 picoseconds. - Weight::from_parts(98_000_000, 4614) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) - } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Evaluations` (r:1 w:1) - /// Proof: `PolimecFunding::Evaluations` (`max_values`: None, `max_size`: Some(345), added: 2820, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Account` (r:1 w:1) - /// Proof: `LocalAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Asset` (r:1 w:1) - /// Proof: `LocalAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn evaluation_reward_payout_for_no_ct_account_creation() -> Weight { - // Proof Size summary in bytes: - // Measured: `1322` - // Estimated: `3814` - // Minimum execution time: 48_000_000 picoseconds. - Weight::from_parts(50_000_000, 3814) - .saturating_add(T::DbWeight::get().reads(4_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) - } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Evaluations` (r:1 w:1) - /// Proof: `PolimecFunding::Evaluations` (`max_values`: None, `max_size`: Some(345), added: 2820, mode: `MaxEncodedLen`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(1149), added: 3624, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn evaluation_slash_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `1485` - // Estimated: `4614` - // Minimum execution time: 67_000_000 picoseconds. - Weight::from_parts(69_000_000, 4614) - .saturating_add(T::DbWeight::get().reads(4_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) - } - /// Storage: `PolimecFunding::Bids` (r:1 w:1) - /// Proof: `PolimecFunding::Bids` (`max_values`: None, `max_size`: Some(418), added: 2893, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Asset` (r:1 w:1) - /// Proof: `LocalAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Account` (r:1 w:1) - /// Proof: `LocalAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(1149), added: 3624, mode: `MaxEncodedLen`) - fn bid_ct_mint_for_with_ct_account_creation() -> Weight { - // Proof Size summary in bytes: - // Measured: `1735` - // Estimated: `4614` - // Minimum execution time: 99_000_000 picoseconds. - Weight::from_parts(103_000_000, 4614) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) - } - /// Storage: `PolimecFunding::Bids` (r:1 w:1) - /// Proof: `PolimecFunding::Bids` (`max_values`: None, `max_size`: Some(418), added: 2893, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Asset` (r:1 w:1) - /// Proof: `LocalAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Account` (r:1 w:1) - /// Proof: `LocalAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn bid_ct_mint_for_no_ct_account_creation() -> Weight { - // Proof Size summary in bytes: - // Measured: `1250` - // Estimated: `3883` - // Minimum execution time: 46_000_000 picoseconds. - Weight::from_parts(50_000_000, 3883) - .saturating_add(T::DbWeight::get().reads(4_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) - } - /// Storage: `PolimecFunding::Contributions` (r:1 w:1) - /// Proof: `PolimecFunding::Contributions` (`max_values`: None, `max_size`: Some(364), added: 2839, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Asset` (r:1 w:1) - /// Proof: `LocalAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Account` (r:1 w:1) - /// Proof: `LocalAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(1149), added: 3624, mode: `MaxEncodedLen`) - fn contribution_ct_mint_for_with_ct_account_creation() -> Weight { - // Proof Size summary in bytes: - // Measured: `1651` - // Estimated: `4614` - // Minimum execution time: 94_000_000 picoseconds. - Weight::from_parts(100_000_000, 4614) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) - } - /// Storage: `PolimecFunding::Contributions` (r:1 w:1) - /// Proof: `PolimecFunding::Contributions` (`max_values`: None, `max_size`: Some(364), added: 2839, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Asset` (r:1 w:1) - /// Proof: `LocalAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Account` (r:1 w:1) - /// Proof: `LocalAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn contribution_ct_mint_for_no_ct_account_creation() -> Weight { - // Proof Size summary in bytes: - // Measured: `1287` - // Estimated: `3829` - // Minimum execution time: 47_000_000 picoseconds. - Weight::from_parts(50_000_000, 3829) - .saturating_add(T::DbWeight::get().reads(4_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) - } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Bids` (r:1 w:1) - /// Proof: `PolimecFunding::Bids` (`max_values`: None, `max_size`: Some(418), added: 2893, mode: `MaxEncodedLen`) - /// Storage: `LinearRelease::Vesting` (r:1 w:1) - /// Proof: `LinearRelease::Vesting` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn start_bid_vesting_schedule_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `851` - // Estimated: `4316` - // Minimum execution time: 29_000_000 picoseconds. - Weight::from_parts(31_000_000, 4316) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Contributions` (r:1 w:1) - /// Proof: `PolimecFunding::Contributions` (`max_values`: None, `max_size`: Some(364), added: 2839, mode: `MaxEncodedLen`) - /// Storage: `LinearRelease::Vesting` (r:1 w:1) - /// Proof: `LinearRelease::Vesting` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn start_contribution_vesting_schedule_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `880` - // Estimated: `4345` - // Minimum execution time: 30_000_000 picoseconds. - Weight::from_parts(31_000_000, 4345) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Bids` (r:1 w:1) - /// Proof: `PolimecFunding::Bids` (`max_values`: None, `max_size`: Some(418), added: 2893, mode: `MaxEncodedLen`) - /// Storage: `StatemintAssets::Asset` (r:1 w:1) - /// Proof: `StatemintAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `StatemintAssets::Account` (r:2 w:2) - /// Proof: `StatemintAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn payout_bid_funds_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `1405` - // Estimated: `6208` - // Minimum execution time: 67_000_000 picoseconds. - Weight::from_parts(68_000_000, 6208) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(5_u64)) - } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Contributions` (r:1 w:1) - /// Proof: `PolimecFunding::Contributions` (`max_values`: None, `max_size`: Some(364), added: 2839, mode: `MaxEncodedLen`) - /// Storage: `StatemintAssets::Asset` (r:1 w:1) - /// Proof: `StatemintAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `StatemintAssets::Account` (r:2 w:2) - /// Proof: `StatemintAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn payout_contribution_funds_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `1434` - // Estimated: `6208` - // Minimum execution time: 66_000_000 picoseconds. - Weight::from_parts(69_000_000, 6208) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(5_u64)) - } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) /// Storage: `PolimecFunding::ProjectsToUpdate` (r:2980 w:1) /// Proof: `PolimecFunding::ProjectsToUpdate` (`max_values`: None, `max_size`: Some(27), added: 2502, mode: `MaxEncodedLen`) /// The range of component `x` is `[1, 99]`. @@ -555,69 +337,24 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 3946).saturating_mul(x.into())) } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Bids` (r:1 w:1) - /// Proof: `PolimecFunding::Bids` (`max_values`: None, `max_size`: Some(418), added: 2893, mode: `MaxEncodedLen`) - /// Storage: `StatemintAssets::Asset` (r:1 w:1) - /// Proof: `StatemintAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `StatemintAssets::Account` (r:2 w:2) - /// Proof: `StatemintAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn release_bid_funds_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `1514` - // Estimated: `6208` - // Minimum execution time: 65_000_000 picoseconds. - Weight::from_parts(70_000_000, 6208) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + + fn settle_successful_evaluation() -> Weight { + Weight::from_parts(0, 0) } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Contributions` (r:1 w:1) - /// Proof: `PolimecFunding::Contributions` (`max_values`: None, `max_size`: Some(364), added: 2839, mode: `MaxEncodedLen`) - /// Storage: `StatemintAssets::Asset` (r:1 w:1) - /// Proof: `StatemintAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `StatemintAssets::Account` (r:2 w:2) - /// Proof: `StatemintAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn release_contribution_funds_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `1507` - // Estimated: `6208` - // Minimum execution time: 63_000_000 picoseconds. - Weight::from_parts(69_000_000, 6208) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + fn settle_failed_evaluation() -> Weight { + Weight::from_parts(0, 0) } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Bids` (r:1 w:1) - /// Proof: `PolimecFunding::Bids` (`max_values`: None, `max_size`: Some(418), added: 2893, mode: `MaxEncodedLen`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(1149), added: 3624, mode: `MaxEncodedLen`) - fn bid_unbond_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `1519` - // Estimated: `4614` - // Minimum execution time: 54_000_000 picoseconds. - Weight::from_parts(56_000_000, 4614) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) + fn settle_successful_bid() -> Weight { + Weight::from_parts(0, 0) } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Contributions` (r:1 w:1) - /// Proof: `PolimecFunding::Contributions` (`max_values`: None, `max_size`: Some(364), added: 2839, mode: `MaxEncodedLen`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(1149), added: 3624, mode: `MaxEncodedLen`) - fn contribution_unbond_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `1474` - // Estimated: `4614` - // Minimum execution time: 54_000_000 picoseconds. - Weight::from_parts(57_000_000, 4614) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) + fn settle_failed_bid() -> Weight { + Weight::from_parts(0, 0) + } + fn settle_successful_contribution() -> Weight { + Weight::from_parts(0, 0) + } + fn settle_failed_contribution() -> Weight { + Weight::from_parts(0, 0) } /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:1) /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) @@ -1152,214 +889,6 @@ impl WeightInfo for () { } /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Evaluations` (r:1 w:1) - /// Proof: `PolimecFunding::Evaluations` (`max_values`: None, `max_size`: Some(345), added: 2820, mode: `MaxEncodedLen`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(1149), added: 3624, mode: `MaxEncodedLen`) - fn evaluation_unbond_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `1477` - // Estimated: `4614` - // Minimum execution time: 54_000_000 picoseconds. - Weight::from_parts(57_000_000, 4614) - .saturating_add(RocksDbWeight::get().reads(3_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) - } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Evaluations` (r:1 w:1) - /// Proof: `PolimecFunding::Evaluations` (`max_values`: None, `max_size`: Some(345), added: 2820, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Account` (r:1 w:1) - /// Proof: `LocalAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(1149), added: 3624, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Asset` (r:1 w:1) - /// Proof: `LocalAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn evaluation_reward_payout_for_with_ct_account_creation() -> Weight { - // Proof Size summary in bytes: - // Measured: `1654` - // Estimated: `4614` - // Minimum execution time: 94_000_000 picoseconds. - Weight::from_parts(98_000_000, 4614) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) - } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Evaluations` (r:1 w:1) - /// Proof: `PolimecFunding::Evaluations` (`max_values`: None, `max_size`: Some(345), added: 2820, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Account` (r:1 w:1) - /// Proof: `LocalAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Asset` (r:1 w:1) - /// Proof: `LocalAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn evaluation_reward_payout_for_no_ct_account_creation() -> Weight { - // Proof Size summary in bytes: - // Measured: `1322` - // Estimated: `3814` - // Minimum execution time: 48_000_000 picoseconds. - Weight::from_parts(50_000_000, 3814) - .saturating_add(RocksDbWeight::get().reads(4_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) - } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Evaluations` (r:1 w:1) - /// Proof: `PolimecFunding::Evaluations` (`max_values`: None, `max_size`: Some(345), added: 2820, mode: `MaxEncodedLen`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(1149), added: 3624, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn evaluation_slash_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `1485` - // Estimated: `4614` - // Minimum execution time: 67_000_000 picoseconds. - Weight::from_parts(69_000_000, 4614) - .saturating_add(RocksDbWeight::get().reads(4_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) - } - /// Storage: `PolimecFunding::Bids` (r:1 w:1) - /// Proof: `PolimecFunding::Bids` (`max_values`: None, `max_size`: Some(418), added: 2893, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Asset` (r:1 w:1) - /// Proof: `LocalAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Account` (r:1 w:1) - /// Proof: `LocalAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(1149), added: 3624, mode: `MaxEncodedLen`) - fn bid_ct_mint_for_with_ct_account_creation() -> Weight { - // Proof Size summary in bytes: - // Measured: `1735` - // Estimated: `4614` - // Minimum execution time: 99_000_000 picoseconds. - Weight::from_parts(103_000_000, 4614) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) - } - /// Storage: `PolimecFunding::Bids` (r:1 w:1) - /// Proof: `PolimecFunding::Bids` (`max_values`: None, `max_size`: Some(418), added: 2893, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Asset` (r:1 w:1) - /// Proof: `LocalAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Account` (r:1 w:1) - /// Proof: `LocalAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn bid_ct_mint_for_no_ct_account_creation() -> Weight { - // Proof Size summary in bytes: - // Measured: `1250` - // Estimated: `3883` - // Minimum execution time: 46_000_000 picoseconds. - Weight::from_parts(50_000_000, 3883) - .saturating_add(RocksDbWeight::get().reads(4_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) - } - /// Storage: `PolimecFunding::Contributions` (r:1 w:1) - /// Proof: `PolimecFunding::Contributions` (`max_values`: None, `max_size`: Some(364), added: 2839, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Asset` (r:1 w:1) - /// Proof: `LocalAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Account` (r:1 w:1) - /// Proof: `LocalAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(1149), added: 3624, mode: `MaxEncodedLen`) - fn contribution_ct_mint_for_with_ct_account_creation() -> Weight { - // Proof Size summary in bytes: - // Measured: `1651` - // Estimated: `4614` - // Minimum execution time: 94_000_000 picoseconds. - Weight::from_parts(100_000_000, 4614) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) - } - /// Storage: `PolimecFunding::Contributions` (r:1 w:1) - /// Proof: `PolimecFunding::Contributions` (`max_values`: None, `max_size`: Some(364), added: 2839, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Asset` (r:1 w:1) - /// Proof: `LocalAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `LocalAssets::Account` (r:1 w:1) - /// Proof: `LocalAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn contribution_ct_mint_for_no_ct_account_creation() -> Weight { - // Proof Size summary in bytes: - // Measured: `1287` - // Estimated: `3829` - // Minimum execution time: 47_000_000 picoseconds. - Weight::from_parts(50_000_000, 3829) - .saturating_add(RocksDbWeight::get().reads(4_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) - } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Bids` (r:1 w:1) - /// Proof: `PolimecFunding::Bids` (`max_values`: None, `max_size`: Some(418), added: 2893, mode: `MaxEncodedLen`) - /// Storage: `LinearRelease::Vesting` (r:1 w:1) - /// Proof: `LinearRelease::Vesting` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn start_bid_vesting_schedule_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `851` - // Estimated: `4316` - // Minimum execution time: 29_000_000 picoseconds. - Weight::from_parts(31_000_000, 4316) - .saturating_add(RocksDbWeight::get().reads(3_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) - } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Contributions` (r:1 w:1) - /// Proof: `PolimecFunding::Contributions` (`max_values`: None, `max_size`: Some(364), added: 2839, mode: `MaxEncodedLen`) - /// Storage: `LinearRelease::Vesting` (r:1 w:1) - /// Proof: `LinearRelease::Vesting` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn start_contribution_vesting_schedule_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `880` - // Estimated: `4345` - // Minimum execution time: 30_000_000 picoseconds. - Weight::from_parts(31_000_000, 4345) - .saturating_add(RocksDbWeight::get().reads(3_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) - } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Bids` (r:1 w:1) - /// Proof: `PolimecFunding::Bids` (`max_values`: None, `max_size`: Some(418), added: 2893, mode: `MaxEncodedLen`) - /// Storage: `StatemintAssets::Asset` (r:1 w:1) - /// Proof: `StatemintAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `StatemintAssets::Account` (r:2 w:2) - /// Proof: `StatemintAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn payout_bid_funds_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `1405` - // Estimated: `6208` - // Minimum execution time: 67_000_000 picoseconds. - Weight::from_parts(68_000_000, 6208) - .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(5_u64)) - } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Contributions` (r:1 w:1) - /// Proof: `PolimecFunding::Contributions` (`max_values`: None, `max_size`: Some(364), added: 2839, mode: `MaxEncodedLen`) - /// Storage: `StatemintAssets::Asset` (r:1 w:1) - /// Proof: `StatemintAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `StatemintAssets::Account` (r:2 w:2) - /// Proof: `StatemintAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn payout_contribution_funds_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `1434` - // Estimated: `6208` - // Minimum execution time: 66_000_000 picoseconds. - Weight::from_parts(69_000_000, 6208) - .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(5_u64)) - } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) /// Storage: `PolimecFunding::ProjectsToUpdate` (r:2980 w:1) /// Proof: `PolimecFunding::ProjectsToUpdate` (`max_values`: None, `max_size`: Some(27), added: 2502, mode: `MaxEncodedLen`) /// The range of component `x` is `[1, 99]`. @@ -1378,69 +907,23 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 3946).saturating_mul(x.into())) } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Bids` (r:1 w:1) - /// Proof: `PolimecFunding::Bids` (`max_values`: None, `max_size`: Some(418), added: 2893, mode: `MaxEncodedLen`) - /// Storage: `StatemintAssets::Asset` (r:1 w:1) - /// Proof: `StatemintAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `StatemintAssets::Account` (r:2 w:2) - /// Proof: `StatemintAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn release_bid_funds_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `1514` - // Estimated: `6208` - // Minimum execution time: 65_000_000 picoseconds. - Weight::from_parts(70_000_000, 6208) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) + fn settle_successful_evaluation() -> Weight { + Weight::from_parts(0, 0) } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Contributions` (r:1 w:1) - /// Proof: `PolimecFunding::Contributions` (`max_values`: None, `max_size`: Some(364), added: 2839, mode: `MaxEncodedLen`) - /// Storage: `StatemintAssets::Asset` (r:1 w:1) - /// Proof: `StatemintAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `StatemintAssets::Account` (r:2 w:2) - /// Proof: `StatemintAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn release_contribution_funds_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `1507` - // Estimated: `6208` - // Minimum execution time: 63_000_000 picoseconds. - Weight::from_parts(69_000_000, 6208) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) + fn settle_failed_evaluation() -> Weight { + Weight::from_parts(0, 0) } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Bids` (r:1 w:1) - /// Proof: `PolimecFunding::Bids` (`max_values`: None, `max_size`: Some(418), added: 2893, mode: `MaxEncodedLen`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(1149), added: 3624, mode: `MaxEncodedLen`) - fn bid_unbond_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `1519` - // Estimated: `4614` - // Minimum execution time: 54_000_000 picoseconds. - Weight::from_parts(56_000_000, 4614) - .saturating_add(RocksDbWeight::get().reads(3_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) + fn settle_successful_bid() -> Weight { + Weight::from_parts(0, 0) } - /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:0) - /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) - /// Storage: `PolimecFunding::Contributions` (r:1 w:1) - /// Proof: `PolimecFunding::Contributions` (`max_values`: None, `max_size`: Some(364), added: 2839, mode: `MaxEncodedLen`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(1149), added: 3624, mode: `MaxEncodedLen`) - fn contribution_unbond_for() -> Weight { - // Proof Size summary in bytes: - // Measured: `1474` - // Estimated: `4614` - // Minimum execution time: 54_000_000 picoseconds. - Weight::from_parts(57_000_000, 4614) - .saturating_add(RocksDbWeight::get().reads(3_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) + fn settle_failed_bid() -> Weight { + Weight::from_parts(0, 0) + } + fn settle_successful_contribution() -> Weight { + Weight::from_parts(0, 0) + } + fn settle_failed_contribution() -> Weight { + Weight::from_parts(0, 0) } /// Storage: `PolimecFunding::ProjectsDetails` (r:1 w:1) /// Proof: `PolimecFunding::ProjectsDetails` (`max_values`: None, `max_size`: Some(349), added: 2824, mode: `MaxEncodedLen`) diff --git a/pallets/polimec-receiver/src/lib.rs b/pallets/polimec-receiver/src/lib.rs index 11e431965..70c3daa8e 100644 --- a/pallets/polimec-receiver/src/lib.rs +++ b/pallets/polimec-receiver/src/lib.rs @@ -107,6 +107,7 @@ pub mod pallet { for migration @ Migration { origin: MigrationOrigin { user, id, participation_type }, info: MigrationInfo { contribution_token_amount, .. }, + .. } in migrations.clone().inner() { let already_executed = ExecutedMigrations::::get((user, participation_type, id)); diff --git a/polimec-common/common/Cargo.toml b/polimec-common/common/Cargo.toml index de60a9249..542e164ad 100644 --- a/polimec-common/common/Cargo.toml +++ b/polimec-common/common/Cargo.toml @@ -23,6 +23,7 @@ pallet-timestamp.workspace = true sp-std.workspace = true sp-runtime.workspace = true itertools.workspace = true +xcm.workspace = true [features] @@ -40,6 +41,7 @@ std = [ "serde/std", "sp-runtime/std", "sp-std/std", + "xcm/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/polimec-common/common/src/lib.rs b/polimec-common/common/src/lib.rs index 06b8705d7..7cabd2a2a 100644 --- a/polimec-common/common/src/lib.rs +++ b/polimec-common/common/src/lib.rs @@ -17,7 +17,6 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::{pallet_prelude::*, traits::tokens::fungible}; -use itertools::Itertools; use sp_runtime::RuntimeDebug; use sp_std::prelude::*; pub mod credentials; @@ -142,11 +141,20 @@ pub mod migration_types { } } + #[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, MaxEncodedLen)] + pub enum MigrationStatus { + NotStarted, + Sent(xcm::v3::QueryId), + Confirmed, + Failed, + } + #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct Migration { pub origin: MigrationOrigin, pub info: MigrationInfo, } + impl Migration { pub fn new(origin: MigrationOrigin, info: MigrationInfo) -> Self { Self { origin, info } @@ -178,29 +186,20 @@ pub mod migration_types { Self(migrations) } - pub fn origins(&self) -> Vec { - self.0.iter().map(|migration| migration.origin.clone()).collect() + pub fn contains(&self, migration: &Migration) -> bool { + self.0.contains(migration) } - pub fn infos(&self) -> Vec { - self.0.iter().map(|migration| migration.info.clone()).collect() + pub fn len(&self) -> usize { + self.0.len() } - pub fn group_by_user(self) -> Vec<([u8; 32], Vec)> { - let mut migrations = self.0; - migrations.sort_by(|a, b| a.origin.user.cmp(&b.origin.user)); - migrations - .into_iter() - .group_by(|migration| migration.origin.user) - .into_iter() - .map(|(user, migrations)| (user, migrations.collect::>())) - .collect::>() + pub fn origins(&self) -> Vec { + self.0.iter().map(|migration| migration.origin.clone()).collect() } - pub fn sort_by_ct_amount(self) -> Migrations { - let mut migrations = self.0; - migrations.sort_by(|a, b| a.info.contribution_token_amount.cmp(&b.info.contribution_token_amount)); - Migrations(migrations) + pub fn infos(&self) -> Vec { + self.0.iter().map(|migration| migration.info.clone()).collect() } pub fn total_ct_amount(&self) -> u128 { diff --git a/runtimes/politest/src/benchmarks/helpers.rs b/runtimes/politest/src/benchmarks/helpers.rs index 2c52320be..5dfee57b0 100644 --- a/runtimes/politest/src/benchmarks/helpers.rs +++ b/runtimes/politest/src/benchmarks/helpers.rs @@ -1,7 +1,7 @@ use crate::{FixedU128, Oracle, Runtime, RuntimeOrigin}; pub use frame_support::BoundedVec; pub use pallet_funding::{traits::SetPrices, AcceptedFundingAsset}; - +use scale_info::prelude::vec; pub struct SetOraclePrices; impl SetPrices for SetOraclePrices { fn set_prices() { diff --git a/runtimes/politest/src/lib.rs b/runtimes/politest/src/lib.rs index fd927756b..675f8269e 100644 --- a/runtimes/politest/src/lib.rs +++ b/runtimes/politest/src/lib.rs @@ -37,7 +37,7 @@ use frame_support::{ }; use frame_system::{EnsureNever, EnsureRoot, EnsureRootWithSuccess, EnsureSigned}; use pallet_democracy::GetElectorate; -use pallet_funding::{DaysToBlocks}; +use pallet_funding::DaysToBlocks; use parachains_common::{ message_queue::{NarrowOriginToSibling, ParaIdToSibling}, @@ -996,11 +996,11 @@ impl pallet_funding::Config for Runtime { type InvestorOrigin = EnsureInvestor; type ManualAcceptanceDuration = ManualAcceptanceDuration; type MaxBidsPerProject = ConstU32<1024>; - type MaxBidsPerUser = ConstU32<32>; + type MaxBidsPerUser = ConstU32<16>; type MaxCapacityThresholds = MaxCapacityThresholds; - type MaxContributionsPerUser = ConstU32<32>; + type MaxContributionsPerUser = ConstU32<16>; type MaxEvaluationsPerProject = ConstU32<1024>; - type MaxEvaluationsPerUser = ConstU32<32>; + type MaxEvaluationsPerUser = ConstU32<16>; type MaxMessageSizeThresholds = MaxMessageSizeThresholds; type MaxProjectsToUpdateInsertionAttempts = ConstU32<100>; type MaxProjectsToUpdatePerBlock = ConstU32<1>; diff --git a/runtimes/shared-configuration/src/currency.rs b/runtimes/shared-configuration/src/currency.rs index a305124df..190bbb0d7 100644 --- a/runtimes/shared-configuration/src/currency.rs +++ b/runtimes/shared-configuration/src/currency.rs @@ -16,10 +16,10 @@ use crate::Balance; use frame_support::parameter_types; -use parachains_common::AssetIdForTrustBackedAssets as AssetId; use pallet_funding::AcceptedFundingAsset; -use sp_runtime::{traits::Convert, FixedU128}; use pallet_oracle_ocw::types::AssetName; +use parachains_common::AssetIdForTrustBackedAssets as AssetId; +use sp_runtime::{traits::Convert, FixedU128}; /// One PLMC pub const PLMC: Balance = 10u128.pow(10);