From 2f5b7de3f5dfc7576f4f497857fcd3d74ff7792c Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Thu, 16 Jan 2025 11:34:31 +0100 Subject: [PATCH] Funding Rounds Simplification --- Cargo.toml | 2 +- integration-tests/src/constants.rs | 4 +- integration-tests/src/tests/ct_migration.rs | 52 +- integration-tests/src/tests/defaults.rs | 114 +- integration-tests/src/tests/e2e.rs | 230 +- .../src/tests/evaluator_slash_sideffects.rs | 1 - integration-tests/src/tests/oracle.rs | 18 +- integration-tests/src/tests/otm_edge_cases.rs | 74 +- integration-tests/src/tests/runtime_apis.rs | 16 +- justfile | 2 +- nodes/parachain/src/chain_spec/common.rs | 15 +- pallets/funding/src/benchmarking.rs | 688 +---- pallets/funding/src/functions/3_auction.rs | 56 +- .../funding/src/functions/4_contribution.rs | 164 -- .../funding/src/functions/5_funding_end.rs | 38 +- pallets/funding/src/functions/6_settlement.rs | 73 - pallets/funding/src/functions/misc.rs | 6 +- pallets/funding/src/functions/mod.rs | 4 +- .../funding/src/instantiator/calculations.rs | 375 +-- .../src/instantiator/chain_interactions.rs | 283 +- pallets/funding/src/instantiator/tests.rs | 56 +- pallets/funding/src/instantiator/types.rs | 70 +- pallets/funding/src/lib.rs | 102 +- pallets/funding/src/runtime_api.rs | 79 +- pallets/funding/src/storage_migrations.rs | 8 +- pallets/funding/src/tests/1_application.rs | 171 +- pallets/funding/src/tests/2_evaluation.rs | 25 +- pallets/funding/src/tests/3_auction.rs | 403 ++- pallets/funding/src/tests/4_contribution.rs | 2317 ----------------- pallets/funding/src/tests/5_funding_end.rs | 28 +- pallets/funding/src/tests/6_settlement.rs | 787 +----- pallets/funding/src/tests/7_ct_migration.rs | 69 +- pallets/funding/src/tests/misc.rs | 183 +- pallets/funding/src/tests/mod.rs | 466 +--- pallets/funding/src/tests/runtime_api.rs | 506 +--- pallets/funding/src/types.rs | 58 +- pallets/oracle-ocw/src/mock.rs | 2 +- polimec-common/common/src/credentials/mod.rs | 4 +- polimec-common/common/src/lib.rs | 1 - runtimes/polimec/src/benchmark_helpers.rs | 28 +- runtimes/polimec/src/lib.rs | 10 +- runtimes/polimec/src/xcm_config.rs | 26 +- runtimes/shared-configuration/src/funding.rs | 8 +- 43 files changed, 1150 insertions(+), 6472 deletions(-) delete mode 100644 pallets/funding/src/functions/4_contribution.rs delete mode 100644 pallets/funding/src/tests/4_contribution.rs diff --git a/Cargo.toml b/Cargo.toml index 2759b888b..6397deac5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -247,4 +247,4 @@ cumulus-pallet-session-benchmarking = { version = "16.0.0", default-features = f # Runtimes polimec-runtime = { path = "runtimes/polimec" } rococo-runtime-constants = { version = "14.0.0" } -rococo-runtime = { version = "14.0.0" } +rococo-runtime = { version = "14.0.0" } \ No newline at end of file diff --git a/integration-tests/src/constants.rs b/integration-tests/src/constants.rs index dd4f8d821..d9eadbd14 100644 --- a/integration-tests/src/constants.rs +++ b/integration-tests/src/constants.rs @@ -441,13 +441,13 @@ pub mod polimec { (dot_asset_id.clone(), alice_account.clone(), true, 100_000_000), (usdt_asset_id.clone(), alice_account.clone(), true, 70_000), (usdc_asset_id.clone(), alice_account.clone(), true, 70_000), - (weth_asset_id, alice_account.clone(), true, 0_000_041_000_000_000_000), + (weth_asset_id.clone(), alice_account.clone(), true, 0_000_041_000_000_000_000), ], metadata: vec![ (dot_asset_id, "Local DOT".as_bytes().to_vec(), "DOT".as_bytes().to_vec(), 10), (usdt_asset_id, "Local USDT".as_bytes().to_vec(), "USDT".as_bytes().to_vec(), 6), (usdc_asset_id.clone(), "Local USDC".as_bytes().to_vec(), "USDC".as_bytes().to_vec(), 6), - (usdc_asset_id, "Local WETH".as_bytes().to_vec(), "WETH".as_bytes().to_vec(), 18), + (weth_asset_id, "Local WETH".as_bytes().to_vec(), "WETH".as_bytes().to_vec(), 18), ], accounts: vec![], }, diff --git a/integration-tests/src/tests/ct_migration.rs b/integration-tests/src/tests/ct_migration.rs index 47acb4ac3..b5fac0c73 100644 --- a/integration-tests/src/tests/ct_migration.rs +++ b/integration-tests/src/tests/ct_migration.rs @@ -157,16 +157,12 @@ fn migrations_are_vested(project_id: u32, accounts: Vec) { fn create_settled_project() -> (ProjectId, Vec) { let mut inst = IntegrationInstantiator::new(None); + + let project_metadata = default_project_metadata(ISSUER.into()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 8); PolimecNet::execute_with(|| { - let project_id = inst.create_finished_project( - default_project_metadata(ISSUER.into()), - ISSUER.into(), - None, - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), - ); + let project_id = inst.create_finished_project(project_metadata, ISSUER.into(), None, evaluations, bids); assert_eq!( inst.go_to_next_state(project_id), pallet_funding::ProjectStatus::SettlementStarted(FundingOutcome::Success) @@ -214,15 +210,10 @@ fn full_pallet_migration_test() { fn create_project_with_unsettled_participation(participation_type: ParticipationType) -> (ProjectId, Vec) { let mut inst = IntegrationInstantiator::new(None); PolimecNet::execute_with(|| { - let project_id = inst.create_finished_project( - default_project_metadata(ISSUER.into()), - ISSUER.into(), - None, - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), - ); + let project_metadata = default_project_metadata(ISSUER.into()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 8); + let project_id = inst.create_finished_project(project_metadata, ISSUER.into(), None, evaluations, bids); assert_eq!( inst.go_to_next_state(project_id), @@ -231,14 +222,11 @@ fn create_project_with_unsettled_participation(participation_type: Participation let evaluations_to_settle = pallet_funding::Evaluations::::iter_prefix_values((project_id,)).collect_vec(); let bids_to_settle = pallet_funding::Bids::::iter_prefix_values((project_id,)).collect_vec(); - let contributions_to_settle = - pallet_funding::Contributions::::iter_prefix_values((project_id,)).collect_vec(); let mut participants: Vec = evaluations_to_settle .iter() .map(|eval| eval.evaluator.clone()) .chain(bids_to_settle.iter().map(|bid| bid.bidder.clone())) - .chain(contributions_to_settle.iter().map(|contribution| contribution.contributor.clone())) .collect(); participants.sort(); participants.dedup(); @@ -259,35 +247,16 @@ fn create_project_with_unsettled_participation(participation_type: Participation PolimecFunding::settle_bid(RuntimeOrigin::signed(alice()), project_id, bid.bidder.clone(), bid.id).unwrap() } - let start = if participation_type == ParticipationType::Contribution { 1 } else { 0 }; - for contribution in contributions_to_settle[start..].iter() { - PolimecFunding::settle_contribution( - RuntimeOrigin::signed(alice()), - project_id, - contribution.contributor.clone(), - contribution.id, - ) - .unwrap() - } - let evaluations = pallet_funding::Evaluations::::iter_prefix_values((project_id,)).collect_vec(); let bids = pallet_funding::Bids::::iter_prefix_values((project_id,)).collect_vec(); - let contributions = - pallet_funding::Contributions::::iter_prefix_values((project_id,)).collect_vec(); if participation_type == ParticipationType::Evaluation { assert_eq!(evaluations.len(), 1); assert_eq!(bids.len(), 0); - assert_eq!(contributions.len(), 0); } else if participation_type == ParticipationType::Bid { assert_eq!(evaluations.len(), 0); assert_eq!(bids.len(), 1); - assert_eq!(contributions.len(), 0); - } else { - assert_eq!(evaluations.len(), 0); - assert_eq!(bids.len(), 0); - assert_eq!(contributions.len(), 1); } (project_id, participants) @@ -300,9 +269,8 @@ fn cannot_start_pallet_migration_with_unsettled_participations() { let tup_1 = create_project_with_unsettled_participation(ParticipationType::Evaluation); let tup_2 = create_project_with_unsettled_participation(ParticipationType::Bid); - let tup_3 = create_project_with_unsettled_participation(ParticipationType::Contribution); - let tups = vec![tup_1, tup_2, tup_3]; + let tups = vec![tup_1, tup_2]; for (project_id, _participants) in tups.into_iter() { PolimecNet::execute_with(|| { diff --git a/integration-tests/src/tests/defaults.rs b/integration-tests/src/tests/defaults.rs index 6b2aed4af..3ad2b3037 100644 --- a/integration-tests/src/tests/defaults.rs +++ b/integration-tests/src/tests/defaults.rs @@ -15,18 +15,19 @@ // along with this program. If not, see . use crate::PolimecRuntime; use frame_support::BoundedVec; -pub use pallet_funding::instantiator::{BidParams, ContributionParams, EvaluationParams}; +pub use pallet_funding::instantiator::EvaluationParams; use pallet_funding::{ - BiddingTicketSizes, ContributingTicketSizes, CurrencyMetadata, ParticipantsAccountType, ParticipationMode, - PriceProviderOf, ProjectMetadata, ProjectMetadataOf, TicketSize, + BiddingTicketSizes, CurrencyMetadata, ParticipantsAccountType, PriceProviderOf, ProjectMetadata, ProjectMetadataOf, + TicketSize, }; -use sp_arithmetic::{FixedPointNumber, Percent}; use macros::generate_accounts; -use polimec_common::{assets::AcceptedFundingAsset, ProvideAssetPrice, USD_DECIMALS, USD_UNIT}; -use polimec_runtime::{AccountId, PLMC}; -use sp_runtime::{traits::ConstU32, Perquintill}; -use ParticipationMode::{Classic, OTM}; +use polimec_common::{ + assets::AcceptedFundingAsset::{DOT, USDC, USDT, WETH}, + ProvideAssetPrice, USD_DECIMALS, USD_UNIT, +}; +use polimec_runtime::AccountId; +use sp_runtime::traits::ConstU32; pub const IPFS_CID: &str = "QmeuJ24ffwLAZppQcgcggJs3n689bewednYkuc8Bx5Gngz"; pub const CT_DECIMALS: u8 = 18; @@ -52,22 +53,12 @@ pub fn bounded_symbol() -> BoundedVec> { pub fn ipfs_hash() -> BoundedVec> { BoundedVec::try_from(IPFS_CID.as_bytes().to_vec()).unwrap() } -pub fn default_weights() -> Vec { - vec![20u8, 15u8, 10u8, 25u8, 30u8] -} -pub fn default_bidder_modes() -> Vec { - vec![Classic(1u8), Classic(6u8), OTM, OTM, Classic(3u8)] -} -pub fn default_contributor_modes() -> Vec { - vec![Classic(1u8), Classic(1u8), OTM, OTM, Classic(3u8)] -} pub fn default_project_metadata(issuer: AccountId) -> ProjectMetadataOf { ProjectMetadata { token_information: CurrencyMetadata { name: bounded_name(), symbol: bounded_symbol(), decimals: CT_DECIMALS }, mainnet_token_max_supply: 8_000_000 * CT_UNIT, total_allocation_size: 1_000_000 * CT_UNIT, - auction_round_allocation_percentage: Percent::from_percent(50u8), minimum_price: PriceProviderOf::::calculate_decimals_aware_price( sp_runtime::FixedU128::from_float(10.0), USD_DECIMALS, @@ -77,95 +68,12 @@ pub fn default_project_metadata(issuer: AccountId) -> ProjectMetadataOf Vec> { - vec![ - EvaluationParams::from((EVAL_1.into(), 500_000 * PLMC)), - EvaluationParams::from((EVAL_2.into(), 250_000 * PLMC)), - EvaluationParams::from((EVAL_3.into(), 320_000 * PLMC)), - ] -} -pub fn default_bidders() -> Vec { - vec![BIDDER_1.into(), BIDDER_2.into(), BIDDER_3.into(), BIDDER_4.into(), BIDDER_5.into()] -} - -pub fn default_bids() -> Vec> { - let inst = IntegrationInstantiator::new(None); - let default_metadata = default_project_metadata(ISSUER.into()); - let auction_allocation = - default_metadata.auction_round_allocation_percentage * default_metadata.total_allocation_size; - let auction_90_percent = Perquintill::from_percent(90) * auction_allocation; - let auction_usd_funding = default_metadata.minimum_price.saturating_mul_int(auction_90_percent); - - inst.generate_bids_from_total_usd( - auction_usd_funding, - default_metadata.minimum_price, - default_weights(), - default_bidders(), - default_bidder_modes(), - ) -} - -pub fn default_community_contributions() -> Vec> { - let inst = IntegrationInstantiator::new(None); - - let default_metadata = default_project_metadata(ISSUER.into()); - - let auction_allocation = - default_metadata.auction_round_allocation_percentage * default_metadata.total_allocation_size; - let contribution_allocation = default_metadata.total_allocation_size - auction_allocation; - - let eighty_percent_funding_ct = Perquintill::from_percent(80) * contribution_allocation; - let eighty_percent_funding_usd = default_metadata.minimum_price.saturating_mul_int(eighty_percent_funding_ct); - - inst.generate_contributions_from_total_usd( - eighty_percent_funding_usd, - default_metadata.minimum_price, - default_weights(), - default_community_contributors(), - default_contributor_modes(), - ) -} - -pub fn default_remainder_contributions() -> Vec> { - let inst = IntegrationInstantiator::new(None); - - let default_metadata = default_project_metadata(ISSUER.into()); - - let auction_allocation = - default_metadata.auction_round_allocation_percentage * default_metadata.total_allocation_size; - let contribution_allocation = default_metadata.total_allocation_size - auction_allocation; - - let ten_percent_auction = Perquintill::from_percent(10) * auction_allocation; - let ten_percent_auction_usd = default_metadata.minimum_price.saturating_mul_int(ten_percent_auction); - let ten_percent_contribution = Perquintill::from_percent(10) * contribution_allocation; - let ten_percent_contribution_usd = default_metadata.minimum_price.saturating_mul_int(ten_percent_contribution); - - inst.generate_contributions_from_total_usd( - ten_percent_auction_usd + ten_percent_contribution_usd, - default_metadata.minimum_price, - vec![20u8, 15u8, 10u8, 25u8, 23u8, 7u8], - default_remainder_contributors(), - default_contributor_modes(), - ) -} -pub fn default_community_contributors() -> Vec { - vec![BUYER_1.into(), BUYER_2.into(), BUYER_3.into(), BUYER_4.into(), BUYER_5.into()] -} - -pub fn default_remainder_contributors() -> Vec { - vec![EVAL_4.into(), BUYER_6.into(), BIDDER_6.into(), EVAL_1.into(), BUYER_1.into(), BIDDER_1.into()] -} diff --git a/integration-tests/src/tests/e2e.rs b/integration-tests/src/tests/e2e.rs index 0c94f7891..b330b6193 100644 --- a/integration-tests/src/tests/e2e.rs +++ b/integration-tests/src/tests/e2e.rs @@ -37,7 +37,7 @@ use polimec_common::{ }; use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt, get_mock_jwt_with_cid}; use polimec_runtime::PLMC; -use sp_arithmetic::{FixedPointNumber, Percent, Perquintill}; +use sp_arithmetic::{FixedPointNumber, Perquintill}; use sp_runtime::FixedU128; use InvestorType::{Institutional, Professional, Retail}; use ParticipationMode::{Classic, OTM}; @@ -48,9 +48,6 @@ generate_accounts!( ALMA, ALEX, ADAM, ALAN, ABEL, AMOS, ANNA, ABBY, ARIA, // Users that only bid BROCK, BEN, BILL, BRAD, BLAIR, BOB, BRETT, BLAKE, BRIAN, BELLA, BRUCE, BRENT, - // Users that only contributed - CLINT, CARL, CODY, COLE, CORY, CHAD, CRUZ, CASEY, CECIL, CINDY, CLARA, CARLY, CADEN, CRAIG, CAIN, CALEB, CAMI, CARA, - CARY, CATHY, CESAR, CHEN, CHIP, CHLOE, CHONG, CONOR, CYRUS, CEDRIC, CHAIM, CHICO, // Users that evaluated and bid DOUG, DAVE, // Users that evaluated and contributed @@ -69,8 +66,7 @@ pub fn project_metadata() -> ProjectMetadataOf { token_information: CurrencyMetadata { name: bounded_name, symbol: bounded_symbol, decimals: CT_DECIMALS }, mainnet_token_max_supply: 10_000_000 * CT_UNIT, // Made up, not in the Sheet. // Total Allocation of Contribution Tokens Available for the Funding Round - total_allocation_size: 100_000 * CT_UNIT, - auction_round_allocation_percentage: Percent::from_percent(50u8), + total_allocation_size: 50_000 * CT_UNIT, // Minimum Price per Contribution Token (in USDT) minimum_price: PriceProviderOf::::calculate_decimals_aware_price( @@ -82,12 +78,7 @@ pub fn project_metadata() -> ProjectMetadataOf { bidding_ticket_sizes: BiddingTicketSizes { professional: TicketSize::new(5000 * USD_UNIT, None), institutional: TicketSize::new(5000 * USD_UNIT, None), - phantom: Default::default(), - }, - contributing_ticket_sizes: ContributingTicketSizes { - retail: TicketSize::new(USD_UNIT, None), - professional: TicketSize::new(USD_UNIT, None), - institutional: TicketSize::new(USD_UNIT, None), + retail: TicketSize::new(100 * USD_UNIT, None), phantom: Default::default(), }, participation_currencies: vec![ @@ -119,9 +110,9 @@ fn usdt_price() -> FixedU128 { fn evaluations() -> Vec<([u8; 32], InvestorType, u64, f64)> { // (User, Investor type, USD specified in extrinsic, PLMC bonded as a consequence) vec![ - (ALMA, Institutional, 93_754, 514_566.41), - (ALEX, Professional, 162, 889.13), - (ADAM, Retail, 7_454, 40_911.09), + (ALMA, Institutional, 40_000, 219_538.97), + (ALEX, Professional, 9_500, 52_140.50), + (ADAM, Retail, 1_000, 5_488.47), (ALAN, Retail, 8_192, 44_961.58), (ABEL, Professional, 11_131, 61_092.21), (AMOS, Professional, 4_765, 26_152.58), @@ -201,104 +192,46 @@ fn post_wap_bids() -> Vec<(u32, [u8; 32], ParticipationMode, InvestorType, u64, ] } -fn community_contributions( -) -> Vec<(u32, [u8; 32], ParticipationMode, InvestorType, u64, AcceptedFundingAsset, f64, f64)> { - // (contribution_id, User, Participation mode, Investor type, CTs specified in extrinsic, PLMC bonded as a consequence, Participation Currency, Final participation currency ticket) - vec![ - (0, CLINT, OTM, Professional, 692, USDT, 7_236.949209, 7_826.5634), - (1, CARL, Classic(1), Retail, 236, USDT, 2_431.618231, 13_345.8739), - (2, CODY, Classic(2), Retail, 24, USDT, 247.28321, 678.6038), - (3, COLE, OTM, Retail, 688, USDT, 7_195.117133, 7_781.3231), - (4, CORY, OTM, Retail, 33, DOT, 74.21819998, 373.2321), - (5, CHAD, Classic(1), Retail, 1_148, DOT, 2_543.73768, 64_919.7597), - (6, CLINT, Classic(1), Retail, 35, DOT, 77.55297804, 1_979.2610), - (7, CRUZ, Classic(4), Retail, 840, USDC, 8_650.587056, 11_875.5658), - (8, CASEY, Classic(1), Retail, 132, USDC, 1_359.377966, 7_464.6414), - (9, CECIL, OTM, Retail, 21, USDT, 219.6184009, 237.5113), - (10, CINDY, Classic(1), Retail, 59, USDT, 607.9045578, 3_336.4685), - (11, CHAD, Classic(3), Retail, 89, USDT, 917.0085703, 1_677.6593), - (12, CLARA, Classic(1), Retail, 332, DOT, 735.6453917, 18_774.7040), - (13, CARLY, OTM, Retail, 8_110, USDC, 84_772.14873, 91_724.6082), - (14, CADEN, Classic(1), Retail, 394, USDC, 4_057.537262, 22_280.8234), - (15, CRAIG, Classic(1), Professional, 840, USDC, 8_650.587056, 47_502.2632), - (16, CAIN, Classic(1), Professional, 352, USDC, 3_625.007909, 19_905.7103), - (17, CALEB, Classic(1), Retail, 640, DOT, 1_418.111598, 36_192.2005), - (18, CAMI, Classic(1), Professional, 792, DOT, 1_754.913103, 44_787.8481), - (19, CARA, OTM, Retail, 993, DOT, 2_233.293109, 11_230.0), - (20, CARY, Classic(2), Retail, 794, USDT, 8_180.952863, 22_450.4744), - (21, CATHY, Classic(1), Retail, 256, USDC, 2_636.369388, 14_476.8802), - (22, CESAR, Classic(1), Retail, 431, USDC, 4_438.575025, 24_373.1850), - (23, CHAD, Classic(1), Retail, 935, USDC, 9_628.927258, 52_874.5429), - (24, CHEN, OTM, Retail, 174, USDC, 1_818.785928, 1_967.9509), - (25, CHIP, OTM, Retail, 877, DOT, 1_972.40489, 9_918.9250), - (26, CHLOE, OTM, Retail, 961, USDT, 10_050.15634, 10_868.9702), - (27, CHONG, Classic(1), Retail, 394, DOT, 873.0249528, 22_280.8234), - ] -} - -fn remainder_contributions( -) -> Vec<(u32, [u8; 32], ParticipationMode, InvestorType, u64, AcceptedFundingAsset, f64, f64)> { - // (contribution_id User, Participation mode, Investor type, CTs specified in extrinsic, Participation Currency, Final participation currency ticket, PLMC bonded as a consequence, ) - vec![ - (28, CODY, Classic(1), Retail, 442, DOT, 979.3833227, 24_995.2385), - (29, CRUZ, Classic(1), Retail, 486, DOT, 1_076.878495, 27_483.4523), - (30, CONOR, Classic(1), Retail, 17, DOT, 37.66858933, 961.3553), - (31, CYRUS, Classic(25), Institutional, 9_424, DOT, 20_881.69329, 21_317.2061), - (32, CEDRIC, Classic(2), Retail, 1_400, USDT, 14_424.85392, 39_585.2193), - (33, CHAIM, OTM, Retail, 4_906, USDT, 51_307.04165, 55_487.1674), - (34, CHICO, Classic(17), Institutional, 68, USDT, 700.6357616, 226.2013), - (35, CRUZ, OTM, Institutional, 9_037, USDT, 94_509.1185, 102_209.0362), - (36, MASON, OTM, Retail, 442, USDT, 4_622.444437, 4_999.0477), - (37, MIKE, OTM, Retail, 400, USDT, 4_183.207635, 4_524.0251), - (38, GERALT, OTM, Professional, 68, USDT, 711.145298, 769.0843), - (39, GEORGE, Classic(1), Professional, 68, USDT, 700.6357616, 3_845.4213), - (40, GINO, OTM, Institutional, 98, USDT, 1_024.885871, 1_108.3861), - // These two use their evaluation bond - (41, STEVE, Classic(5), Professional, 500, USDT, 5_151.733541, 0.0), - (42, SAM, Classic(1), Professional, 422, USDT, 4_348.063109, 0.0), - ] -} - // Includes evaluation rewards, participation purchases, and protocol fees. fn cts_minted() -> f64 { - 108_938.93 + 55_000.00 } fn usd_raised() -> f64 { - 1_008_177.9575 + 502_791.8972 } #[allow(unused)] fn ct_fees() -> (f64, f64, f64) { // (LP, Evaluator Rewards, Long term holder bonus) - (4944.466414, 2966.679848, 1977.786566) + (2500.0, 1500.0, 1000.0) } fn issuer_payouts() -> (f64, f64, f64) { // (USDT, USDC, DOT) - (646_218.88, 165_339.74, 42_265.73) + (430_124.27, 36_981.51, 7_670.46) } fn evaluator_reward_pots() -> (f64, f64, f64, f64) { // (CTs All, CTs Early, USD All, USD Early) - (2373.343879, 593.3359697, 167_588.0, 100_000.0) + (1200.0, 300.0, 116_718.0, 50_000.0) } fn final_payouts() -> Vec<([u8; 32], f64, f64)> { // User, CT rewarded, PLMC Self Bonded (Classic mode) with mult > 1 vec![ - (ALMA, 1945.787276, 0.00), - (ALEX, 3.25541214, 0.00), - (ADAM, 141.6604459, 0.00), - (ALAN, 116.0132769, 0.00), - (ABEL, 157.6347394, 0.00), - (AMOS, 67.48086726, 0.00), - (ANNA, 58.34652111, 0.00), - (ABBY, 23.02704935, 0.00), - (ARIA, 56.59046077, 0.00), - (BROCK, 12_500.0, 86_595.93), - (BEN, 4_000.0, 0.00), - (BILL, 3_000.0, 54_884.74), + (ALMA, 696.1044569, 0.00), + (ALEX, 154.6713103, 0.00), + (ADAM, 13.28119056, 0.00), + (ALAN, 84.22351308, 0.00), + (ABEL, 114.4399321, 0.00), + (AMOS, 48.98987303, 0.00), + (ANNA, 42.35850511, 0.00), + (ABBY, 16.71721585, 0.00), + (ARIA, 41.08363749, 0.00), + (BROCK, 12500.0, 86_595.93), + (BEN, 4000.0, 0.00), + (BILL, 3000.0, 54_884.74), (BRAD, 700.0, 6_403.22), (BLAIR, 1_000.0, 6_860.59), (BOB, 800.0, 4_390.78), @@ -308,55 +241,25 @@ fn final_payouts() -> Vec<([u8; 32], f64, f64)> { (BELLA, 800.0, 0.00), (BRUCE, 3_000.0, 41_163.56), (BRENT, 8_000.0, 0.00), - (CLINT, 727.0, 0.00), - (CARL, 236.0, 0.00), - (CODY, 466.0, 678.60), - (COLE, 688.0, 0.00), - (CORY, 33.0, 0.00), - (CRUZ, 10_363.0, 11_875.57), - (CASEY, 132.0, 0.00), - (CECIL, 21.0, 0.00), - (CINDY, 59.0, 0.00), - (CHAD, 2_172.0, 1_677.66), - (CLARA, 332.0, 0.00), - (CARLY, 8_110.0, 0.00), - (CADEN, 394.0, 0.00), - (CRAIG, 840.0, 0.00), - (CAIN, 352.0, 0.00), - (CALEB, 640.0, 0.00), - (CAMI, 792.0, 0.00), - (CARA, 993.0, 0.00), - (CARY, 794.0, 22_450.47), - (CATHY, 256.0, 0.00), - (CESAR, 431.0, 0.00), - (CHEN, 174.0, 0.00), - (CHIP, 877.0, 0.00), - (CHLOE, 961.0, 0.00), - (CHONG, 394.0, 0.00), - (CONOR, 17.0, 0.00), - (CYRUS, 9_424.0, 21_317.21), - (CEDRIC, 1_400.0, 39_585.22), - (CHAIM, 4_906.0, 0.0), - (CHICO, 68.0, 226.20), - (DOUG, 135.9425899, 0.00), - (DAVE, 1_082.180792, 0.00), - (MASON, 490.7306745, 0.00), - (MIKE, 513.973981, 0.00), - (GERALT, 568.0, 1_885.01), - (GEORGE, 1_968.0, 5_372.28), - (GINO, 698.0, 1_357.21), - (STEVE, 1_523.636006, 5_655.03), - (SAM, 4_714.419756, 0.0), + (DOUG, 126.0936616, 0.00), + (DAVE, 1059.661749, 0.00), + (MASON, 35.37757672, 0.00), + (MIKE, 82.74302164, 0.00), + (GERALT, 500.0, 1_885.01), + (GEORGE, 1_900.0, 5_372.28), + (GINO, 600.0, 1_357.21), + (STEVE, 1017.159307, 0.0), + (SAM, 4267.09505, 0.0), ] } fn otm_fee_recipient_balances() -> (f64, f64, f64) { // USDT, USDC, DOT - (3908.966909, 1384.616511, 136.3713724) + (1233.208025, 104.9475262, 73.12137929) } fn otm_treasury_sub_account_plmc_held() -> f64 { - 544_177.11 + 233_150.38 } fn participate_with_checks( @@ -408,18 +311,9 @@ fn participate_with_checks( if participation_type == ParticipationType::Bid { PolimecFunding::bid(PolimecOrigin::signed(user.clone()), user_jwt, project_id, ct_amount, mode, funding_asset) .unwrap(); - assert!(Bids::::get((project_id, user.clone(), participation_id)).is_some()); - } else { - PolimecFunding::contribute( - PolimecOrigin::signed(user.clone()), - user_jwt, - project_id, - ct_amount, - mode, - funding_asset, - ) - .unwrap(); - assert!(Contributions::::get((project_id, user.clone(), participation_id)).is_some()); + let stored_bid = Bids::::get((project_id, user.clone(), participation_id)); + // dbg!(&stored_bid); + assert!(stored_bid.is_some()); } let post_participation_free_plmc = PolimecBalances::free_balance(user.clone()); @@ -516,7 +410,7 @@ fn e2e_test() { ); } - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful)); // Only Dave got a rejected bid, so they can settle it early to get refunded: let rejected_bidder = PolimecAccountId::from(DAVE); @@ -568,58 +462,11 @@ fn e2e_test() { ); assert_close_enough!(treasury_usdt_delta, expected_otm_fee, Perquintill::from_float(0.9999)); - for (contribution_id, user, mode, investor_type, ct_amount, funding_asset, funding_asset_ticket, plmc_bonded) in - community_contributions() - { - inst = participate_with_checks( - inst, - project_id, - ParticipationType::Contribution, - contribution_id, - user, - mode, - investor_type, - ct_amount, - funding_asset, - funding_asset_ticket, - plmc_bonded, - ); - } - - let remainder_block = - if let ProjectStatus::CommunityRound(remainder_block) = inst.get_project_details(project_id).status { - remainder_block - } else { - panic!("Expected CommunityRound"); - }; - - inst.jump_to_block(remainder_block); - - for (contribution_id, user, mode, investor_type, ct_amount, funding_asset, funding_asset_ticket, plmc_bonded) in - remainder_contributions() - { - inst = participate_with_checks( - inst, - project_id, - ParticipationType::Contribution, - contribution_id, - user, - mode, - investor_type, - ct_amount, - funding_asset, - funding_asset_ticket, - plmc_bonded, - ); - } - - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); // Used for checking CT migrations at the end let stored_evaluations = inst.get_evaluations(project_id); let stored_bids = inst.get_bids(project_id); - let stored_contributions = inst.get_contributions(project_id); inst.settle_project(project_id, true); @@ -788,7 +635,6 @@ fn e2e_test() { inst.assert_evaluations_migrations_created(project_id, stored_evaluations, true); inst.assert_bids_migrations_created(project_id, stored_bids, true); - inst.assert_contributions_migrations_created(project_id, stored_contributions, true); PolimecFunding::start_offchain_migration(PolimecOrigin::signed(issuer.clone()), issuer_jwt, project_id) .unwrap(); diff --git a/integration-tests/src/tests/evaluator_slash_sideffects.rs b/integration-tests/src/tests/evaluator_slash_sideffects.rs index 4f2e40c24..b26e0c96a 100644 --- a/integration-tests/src/tests/evaluator_slash_sideffects.rs +++ b/integration-tests/src/tests/evaluator_slash_sideffects.rs @@ -113,7 +113,6 @@ fn evaluator_slash_reduces_vesting_schedules() { let project_id = inst.create_evaluating_project(project_metadata, ISSUER.into(), None); assert_ok!(inst.evaluate_for_users(project_id, vec![alice_evaluation.clone(), bob_evaluation.clone()])); assert_eq!(ProjectStatus::AuctionRound, inst.go_to_next_state(project_id)); - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(_))); assert_eq!(ProjectStatus::FundingFailed, inst.go_to_next_state(project_id)); assert_eq!(ProjectStatus::SettlementStarted(FundingOutcome::Failure), inst.go_to_next_state(project_id)); assert_eq!(inst.current_block(), BlockNumberFor::::from(25u32)); diff --git a/integration-tests/src/tests/oracle.rs b/integration-tests/src/tests/oracle.rs index e7eb478cd..c4564bb37 100644 --- a/integration-tests/src/tests/oracle.rs +++ b/integration-tests/src/tests/oracle.rs @@ -15,10 +15,6 @@ // along with this program. If not, see . use crate::*; -/// Tests for the oracle pallet integration. -/// Alice, Bob, Charlie are members of the OracleProvidersMembers. -/// Only members should be able to feed data into the oracle. -use parity_scale_codec::alloc::collections::HashMap; use polimec_common::assets::AcceptedFundingAsset; use polimec_runtime::{Oracle, RuntimeOrigin}; use sp_runtime::{bounded_vec, BoundedVec, FixedU128}; @@ -26,6 +22,7 @@ use std::collections::BTreeMap; use tests::defaults::*; use AcceptedFundingAsset::{DOT, USDC, USDT, WETH}; + fn values( values: [f64; 5], ) -> BoundedVec<(Location, FixedU128), >::MaxFeedValues> { @@ -133,14 +130,9 @@ fn pallet_funding_works() { let charlie = PolimecNet::account_id_of(CHARLIE); assert_ok!(Oracle::feed_values(RuntimeOrigin::signed(charlie.clone()), values([4.84, 1.0, 1.0, 2500.0, 0.4]))); - let _project_id = inst.create_finished_project( - default_project_metadata(ISSUER.into()), - ISSUER.into(), - None, - default_evaluations(), - default_bids(), - default_community_contributions(), - vec![], - ); + let project_metadata = default_project_metadata(ISSUER.into()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 8); + let _project_id = inst.create_finished_project(project_metadata, ISSUER.into(), None, evaluations, bids); }); } diff --git a/integration-tests/src/tests/otm_edge_cases.rs b/integration-tests/src/tests/otm_edge_cases.rs index bc07c13dc..57766705a 100644 --- a/integration-tests/src/tests/otm_edge_cases.rs +++ b/integration-tests/src/tests/otm_edge_cases.rs @@ -1,6 +1,6 @@ use crate::{ constants::PricesBuilder, - tests::defaults::{default_evaluations, default_project_metadata, ipfs_hash, IntegrationInstantiator}, + tests::defaults::{default_project_metadata, ipfs_hash, IntegrationInstantiator}, *, }; use frame_support::traits::fungibles::Inspect; @@ -12,15 +12,13 @@ use polimec_common::{ use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt_with_cid}; use sp_arithmetic::{FixedPointNumber, FixedU128}; use sp_core::bounded_vec; -use sp_runtime::TokenError; generate_accounts!(ISSUER, BOBERT); #[test] -fn otm_fee_below_min_amount_reverts() { +fn otm_fee_below_min_amount_impossible() { let mut inst = IntegrationInstantiator::new(None); let issuer: PolimecAccountId = ISSUER.into(); - let bobert: PolimecAccountId = BOBERT.into(); let prices = PricesBuilder::new() .plmc(FixedU128::from_float(0.17f64)) @@ -50,26 +48,15 @@ fn otm_fee_below_min_amount_reverts() { ) .unwrap(); - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - issuer.clone(), - None, - default_evaluations(), - vec![], - ); - - let plmc_ed = inst.get_ed(); - - let min_usd_contribution = USD_UNIT; + let min_usd_bid = 10 * USD_UNIT; let otm_multiplier: MultiplierOf = ParticipationMode::OTM.multiplier().try_into().unwrap(); - let min_usd_bond = - otm_multiplier.calculate_usd_bonding_requirement::(min_usd_contribution).unwrap(); + let min_usd_bond = otm_multiplier.calculate_usd_bonding_requirement::(min_usd_bid).unwrap(); let min_plmc_bond = plmc_price.reciprocal().unwrap().saturating_mul_int(min_usd_bond); let min_usd_otm_fee = polimec_runtime::ProxyBonding::calculate_fee(min_plmc_bond, AcceptedFundingAsset::USDT.id()).unwrap(); - let mut min_usdt_contribution = usdt_price.reciprocal().unwrap().saturating_mul_int(min_usd_contribution); - while usdt_price.saturating_mul_int(min_usdt_contribution) < min_usd_contribution { + let mut min_usdt_contribution = usdt_price.reciprocal().unwrap().saturating_mul_int(min_usd_bid); + while usdt_price.saturating_mul_int(min_usdt_contribution) < min_usd_bid { min_usdt_contribution += 1; } @@ -77,41 +64,7 @@ fn otm_fee_below_min_amount_reverts() { let usdt_min_balance = inst.execute(|| PolimecForeignAssets::minimum_balance(AcceptedFundingAsset::USDT.id())); - assert!(min_usdt_contribution_otm_fee < usdt_min_balance); - - let ct_for_min_usdt_contribution = PolimecFunding::funding_asset_to_ct_amount_classic( - project_id, - AcceptedFundingAsset::USDT, - min_usdt_contribution, - ); - - let jwt = get_mock_jwt_with_cid( - bobert.clone(), - InvestorType::Retail, - generate_did_from_account(bobert.clone()), - ipfs_hash(), - ); - - inst.mint_plmc_to(vec![(bobert.clone(), plmc_ed).into()]); - inst.mint_funding_asset_to(vec![( - bobert.clone(), - min_usdt_contribution + min_usdt_contribution_otm_fee + 10_000, - AcceptedFundingAsset::USDT.id(), - ) - .into()]); - - // Assert noop checks that storage had no changes - assert_noop!( - PolimecFunding::contribute( - PolimecOrigin::signed(bobert.clone()), - jwt.clone(), - project_id, - ct_for_min_usdt_contribution, - ParticipationMode::OTM, - AcceptedFundingAsset::USDT - ), - TokenError::BelowMinimum - ); + assert!(min_usdt_contribution_otm_fee > usdt_min_balance); }); } @@ -124,14 +77,9 @@ fn after_otm_fee_user_goes_under_ed_reverts() { polimec::set_prices(PricesBuilder::default_prices()); PolimecNet::execute_with(|| { let project_metadata = default_project_metadata(issuer.clone()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - issuer.clone(), - None, - default_evaluations(), - vec![], - ); + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer.clone(), None, evaluations); let plmc_price = ::PriceProvider::get_decimals_aware_price( Location::here(), @@ -177,7 +125,7 @@ fn after_otm_fee_user_goes_under_ed_reverts() { .into()]); assert_noop!( - PolimecFunding::contribute( + PolimecFunding::bid( PolimecOrigin::signed(bobert.clone()), jwt.clone(), project_id, @@ -190,7 +138,7 @@ fn after_otm_fee_user_goes_under_ed_reverts() { inst.mint_funding_asset_to(vec![(bobert.clone(), usdt_ed, AcceptedFundingAsset::USDT.id()).into()]); - assert_ok!(PolimecFunding::contribute( + assert_ok!(PolimecFunding::bid( PolimecOrigin::signed(bobert.clone()), jwt.clone(), project_id, diff --git a/integration-tests/src/tests/runtime_apis.rs b/integration-tests/src/tests/runtime_apis.rs index fe06ba00d..5093ac46f 100644 --- a/integration-tests/src/tests/runtime_apis.rs +++ b/integration-tests/src/tests/runtime_apis.rs @@ -1,10 +1,6 @@ use crate::{constants::*, *}; use assets_common::runtime_api::runtime_decl_for_fungibles_api::FungiblesApiV2; -use frame_support::traits::{ - fungible::{Inspect, Mutate as FMutate}, - fungibles::Mutate, - tokens::ConversionToAssetBalance, -}; +use frame_support::traits::{fungible::Mutate as FMutate, fungibles::Mutate, tokens::ConversionToAssetBalance}; use polimec_common::assets::AcceptedFundingAsset; use polimec_runtime::PLMCToAssetBalance; use sp_arithmetic::FixedU128; @@ -108,13 +104,3 @@ mod fungibles_api { }); } } - -#[test] -fn sandbox() { - use super::*; - - PolimecNet::execute_with(|| { - let b = PLMCToAssetBalance::to_asset_balance(135_0_000_000_000, Location::here()); - dbg!(b); - }); -} diff --git a/justfile b/justfile index c5f0f567f..8ba10b791 100644 --- a/justfile +++ b/justfile @@ -27,7 +27,7 @@ dry-run-benchmarks mode="fast-mode" pallet="*" extrinsic="*" : # Build the project with each mode for mode in "${modes[@]}"; do \ echo -e "\033[34mBuilding runtime with mode: \033[92m$mode\033[34m\033[0m" - cargo build --features runtime-benchmarks,$mode --release + cargo build --features development-settings,runtime-benchmarks,$mode --release echo -e "\033[34mRunning benchmarks" diff --git a/nodes/parachain/src/chain_spec/common.rs b/nodes/parachain/src/chain_spec/common.rs index 599b7eef2..d31f247d2 100644 --- a/nodes/parachain/src/chain_spec/common.rs +++ b/nodes/parachain/src/chain_spec/common.rs @@ -110,6 +110,7 @@ pub fn genesis_config(genesis_config_params: GenesisConfigParams) -> serde_json: let usdt_id = AcceptedFundingAsset::USDT.id(); let usdc_id = AcceptedFundingAsset::USDC.id(); let dot_id = AcceptedFundingAsset::DOT.id(); + let weth_id = AcceptedFundingAsset::WETH.id(); serde_json::json!({ "balances": { @@ -136,18 +137,26 @@ pub fn genesis_config(genesis_config_params: GenesisConfigParams) -> serde_json: &AccountIdConversion::::into_account_truncating(&::PalletId::get()), true, 70000, - )], + ), + ( + AcceptedFundingAsset::WETH.id(), + &AccountIdConversion::::into_account_truncating(&::PalletId::get()), + true, + 70000, + ),], // (id, name, symbol, decimals) "metadata": vec![ (usdt_id.clone(), b"Local USDT", b"USDT", 6), (usdc_id.clone(), b"Local USDC", b"USDC", 6), - (dot_id.clone(), b"Local DOT ", b"DOT ", 10) + (dot_id.clone(), b"Local DOT ", b"DOT ", 10), + (weth_id.clone(), b"Local WETH", b"WETH", 18), ], // (id, account_id, amount) "accounts": vec![ (usdt_id, funding_assets_owner.clone(), 1000000000000u64), (usdc_id, funding_assets_owner.clone(), 1000000000000u64), - (dot_id, funding_assets_owner.clone(), 1000000000000u64) + (dot_id, funding_assets_owner.clone(), 1000000000000u64), + (weth_id, funding_assets_owner.clone(), 1000000000000u64), ], }, "parachainStaking": { diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs index d68985a00..728ce9d0d 100644 --- a/pallets/funding/src/benchmarking.rs +++ b/pallets/funding/src/benchmarking.rs @@ -35,7 +35,11 @@ use frame_support::{ }; use itertools::Itertools; use parity_scale_codec::{Decode, Encode}; -use polimec_common::{credentials::InvestorType, ProvideAssetPrice, ReleaseSchedule, USD_DECIMALS, USD_UNIT}; +use polimec_common::{ + assets::AcceptedFundingAsset::{DOT, USDC, USDT, WETH}, + credentials::InvestorType, + ProvideAssetPrice, USD_DECIMALS, USD_UNIT, +}; use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt_with_cid}; use sp_arithmetic::Percent; use sp_core::H256; @@ -63,23 +67,17 @@ where ProjectMetadata { token_information: CurrencyMetadata { name: bounded_name, symbol: bounded_symbol, decimals: CT_DECIMALS }, mainnet_token_max_supply: 1_000_000u128 * CT_UNIT, - total_allocation_size: 1_000_000u128 * CT_UNIT, - auction_round_allocation_percentage: Percent::from_percent(50u8), + total_allocation_size: 500_000u128 * CT_UNIT, minimum_price: PriceProviderOf::::calculate_decimals_aware_price(10u128.into(), USD_DECIMALS, CT_DECIMALS) .unwrap(), bidding_ticket_sizes: BiddingTicketSizes { - professional: TicketSize::new(5000u128 * USD_UNIT, None), - institutional: TicketSize::new(5000u128 * USD_UNIT, None), - phantom: Default::default(), - }, - contributing_ticket_sizes: ContributingTicketSizes { - retail: TicketSize::new(USD_UNIT, None), - professional: TicketSize::new(USD_UNIT, None), - institutional: TicketSize::new(USD_UNIT, None), + professional: TicketSize::new(10u128 * USD_UNIT, None), + institutional: TicketSize::new(10u128 * USD_UNIT, None), + retail: TicketSize::new(10u128 * USD_UNIT, None), phantom: Default::default(), }, - participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), + participation_currencies: vec![USDT, USDC, DOT, WETH].try_into().unwrap(), funding_destination_account: issuer, policy_ipfs_cid: Some(metadata_hash.into()), participants_account_type: ParticipantsAccountType::Polkadot, @@ -120,17 +118,14 @@ where T::Hash: From, { let default_project_metadata = default_project_metadata::(account::>("issuer", 0, 0)); - let auction_funding_target = default_project_metadata.minimum_price.saturating_mul_int( - default_project_metadata.auction_round_allocation_percentage * default_project_metadata.total_allocation_size, - ); + let auction_funding_target = + default_project_metadata.minimum_price.saturating_mul_int(default_project_metadata.total_allocation_size); let inst = BenchInstantiator::::new(None); inst.generate_bids_from_total_usd( + default_project_metadata.clone(), Percent::from_percent(95) * auction_funding_target, - default_project_metadata.minimum_price, - default_weights(), - default_bidders::(), - default_bidder_modes(), + 5, ) } @@ -142,65 +137,9 @@ where { let inst = BenchInstantiator::::new(None); let default_project = default_project_metadata::(account::>("issuer", 0, 0)); - let total_ct_for_bids = default_project.auction_round_allocation_percentage * default_project.total_allocation_size; + let total_ct_for_bids = default_project.total_allocation_size; let total_usd_for_bids = default_project.minimum_price.checked_mul_int(total_ct_for_bids).unwrap(); - inst.generate_bids_from_total_usd( - total_usd_for_bids, - default_project.minimum_price, - default_weights(), - default_bidders::(), - default_bidder_modes(), - ) -} - -pub fn default_community_contributions() -> Vec> -where - ::Price: From, - T::Hash: From, -{ - let inst = BenchInstantiator::::new(None); - let default_project_metadata = default_project_metadata::(account::>("issuer", 0, 0)); - - let funding_target = - default_project_metadata.minimum_price.saturating_mul_int(default_project_metadata.total_allocation_size); - let auction_funding_target = default_project_metadata.minimum_price.saturating_mul_int( - default_project_metadata.auction_round_allocation_percentage * default_project_metadata.total_allocation_size, - ); - - let contributing_funding_target = funding_target - auction_funding_target; - - inst.generate_contributions_from_total_usd( - Percent::from_percent(85) * contributing_funding_target, - default_project_metadata.minimum_price, - default_weights(), - default_community_contributors::(), - default_community_contributor_modes(), - ) -} - -pub fn default_remainder_contributions() -> Vec> -where - ::Price: From, - T::Hash: From, -{ - let inst = BenchInstantiator::::new(None); - let default_project_metadata = default_project_metadata::(account::>("issuer", 0, 0)); - - let funding_target = - default_project_metadata.minimum_price.saturating_mul_int(default_project_metadata.total_allocation_size); - let auction_funding_target = default_project_metadata.minimum_price.saturating_mul_int( - default_project_metadata.auction_round_allocation_percentage * default_project_metadata.total_allocation_size, - ); - - let contributing_funding_target = funding_target - auction_funding_target; - - inst.generate_contributions_from_total_usd( - Percent::from_percent(15) * contributing_funding_target, - 10u128.into(), - default_weights(), - default_remainder_contributors::(), - default_remainder_contributor_modes(), - ) + inst.generate_bids_from_total_usd(default_project.clone(), total_usd_for_bids, 5) } pub fn default_weights() -> Vec { @@ -226,35 +165,9 @@ pub fn default_bidders() -> Vec> { ] } -pub fn default_community_contributors() -> Vec> { - vec![ - account::>("contributor_1", 0, 0), - account::>("contributor_2", 0, 0), - account::>("contributor_3", 0, 0), - account::>("contributor_4", 0, 0), - account::>("contributor_5", 0, 0), - ] -} -pub fn default_remainder_contributors() -> Vec> { - vec![ - account::>("bidder_1", 0, 0), - account::>("bidder_2", 0, 0), - account::>("evaluator_1", 0, 0), - account::>("evaluator_2", 0, 0), - account::>("contributor_6", 0, 0), - ] -} - pub fn default_bidder_modes() -> Vec { vec![Classic(10u8), Classic(3u8), OTM, OTM, Classic(4u8)] } -pub fn default_community_contributor_modes() -> Vec { - vec![Classic(2u8), Classic(1u8), Classic(3u8), OTM, OTM] -} -pub fn default_remainder_contributor_modes() -> Vec { - vec![Classic(1u8), OTM, Classic(1u8), OTM, Classic(1u8)] -} - /// Grab an account, seeded by a name and index. pub fn string_account( name: scale_info::prelude::string::String, @@ -281,6 +194,7 @@ mod benchmarks { // This is actually used in the benchmarking setup, check one line below. #[allow(unused_imports)] use pallet::Pallet as PalletFunding; + use polimec_common::credentials::InvestorType::Institutional; impl_benchmark_test_suite!(PalletFunding, crate::mock::new_test_ext(), crate::mock::TestRuntime); @@ -390,8 +304,7 @@ mod benchmarks { decimals: CT_DECIMALS - 2, }, mainnet_token_max_supply: 200_000u128 * CT_UNIT, - total_allocation_size: 200_000u128 * CT_UNIT, - auction_round_allocation_percentage: Percent::from_percent(30u8), + total_allocation_size: 100_000u128 * CT_UNIT, minimum_price: PriceProviderOf::::calculate_decimals_aware_price( 11u128.into(), USD_DECIMALS, @@ -401,12 +314,7 @@ mod benchmarks { bidding_ticket_sizes: BiddingTicketSizes { professional: TicketSize::new(5000 * USD_UNIT, Some(10_000 * USD_UNIT)), institutional: TicketSize::new(5000 * USD_UNIT, Some(10_000 * USD_UNIT)), - phantom: Default::default(), - }, - contributing_ticket_sizes: ContributingTicketSizes { - retail: TicketSize::new(5000 * USD_UNIT, Some(10_000 * USD_UNIT)), - professional: TicketSize::new(5000 * USD_UNIT, Some(10_000 * USD_UNIT)), - institutional: TicketSize::new(5000 * USD_UNIT, Some(10_000 * USD_UNIT)), + retail: TicketSize::new(10 * USD_UNIT, Some(10_000 * USD_UNIT)), phantom: Default::default(), }, participation_currencies: vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC].try_into().unwrap(), @@ -662,16 +570,13 @@ mod benchmarks { ) .unwrap(); - let evaluations = inst.generate_successful_evaluations( - project_metadata.clone(), - default_evaluators::(), - default_weights(), - ); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); let existing_bid = BidParams::from(( bidder.clone(), + Institutional, (50 * CT_UNIT).into(), ParticipationMode::Classic(5u8), AcceptedFundingAsset::USDT, @@ -718,6 +623,7 @@ mod benchmarks { assert!(new_bidder.clone() != bidder.clone()); let bid_params = BidParams::from(( new_bidder.clone(), + Institutional, current_bucket.amount_left, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, @@ -740,14 +646,18 @@ mod benchmarks { inst.bid_for_users(project_id, vec![bid_params]).unwrap(); - let auction_allocation = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let auction_allocation = project_metadata.total_allocation_size; let bucket_size = Percent::from_percent(10) * auction_allocation; ct_amount = bucket_size * (y as u128); usdt_for_filler_bidder = usdt_for_new_bidder; } - let extrinsic_bid = - BidParams::from((bidder.clone(), ct_amount, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT)); + let extrinsic_bid = BidParams::from(( + bidder.clone(), + Institutional, + ct_amount, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + )); let original_extrinsic_bid = extrinsic_bid.clone(); let current_bucket = Buckets::::get(project_id).unwrap(); // we need to call this after bidding `x` amount of times, to get the latest bucket from storage @@ -828,14 +738,12 @@ mod benchmarks { } // Bucket Storage Check - let bucket_delta_amount = Percent::from_percent(10) * - project_metadata.auction_round_allocation_percentage * - project_metadata.total_allocation_size; + let bucket_delta_amount = Percent::from_percent(10) * project_metadata.total_allocation_size; let ten_percent_in_price: ::Price = PriceOf::::checked_from_rational(1, 10).unwrap() * project_metadata.minimum_price; let mut expected_bucket = Bucket::new( - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size, + project_metadata.total_allocation_size, project_metadata.minimum_price, ten_percent_in_price, bucket_delta_amount, @@ -886,284 +794,50 @@ mod benchmarks { } } - #[benchmark] - fn end_auction( - // Accepted Bids - x: Linear<10, { 25 }>, - // Failed Bids - y: Linear<0, { 8 }>, - ) { - // * setup * - let mut inst = BenchInstantiator::::new(None); - ::SetPrices::set_prices(); - // We can't see events at block 0 - inst.jump_to_block(1u32.into()); - - let issuer = account::>("issuer", 0, 0); - whitelist_account!(issuer); - - let mut project_metadata = default_project_metadata::(issuer.clone()); - project_metadata.mainnet_token_max_supply = 10_000_000 * CT_UNIT; - project_metadata.total_allocation_size = 10_000_000 * CT_UNIT; - project_metadata.auction_round_allocation_percentage = Percent::from_percent(100u8); - - let project_id = inst.create_auctioning_project( - project_metadata.clone(), - issuer.clone(), - None, - inst.generate_successful_evaluations( - project_metadata.clone(), - default_evaluators::(), - default_weights(), - ), - ); - let expected_remainder_round_block = inst.remainder_round_block() - One::one(); - - let mut all_bids = Vec::new(); - - let auction_allocation = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - let min_bid_amount = 500u128; - - // These bids will always be rejected, and will be made after the first bucket bid - let rejected_bids = (0..y.saturating_sub(1)) - .map(|i| { - BidParams::::from(( - account::>("bidder", 0, i), - (min_bid_amount * CT_UNIT).into(), - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )) - }) - .collect_vec(); - all_bids.extend(rejected_bids.clone()); - - let already_accepted_bids_count = if y > 0 { - // This one needs to fill the remaining with the bucket, so that all "accepted" bids will take the CT from a rejected one - let last_rejected_bid = BidParams::::from(( - account::>("bidder", 0, 420), - auction_allocation - (min_bid_amount * CT_UNIT * (y as u128 - 1u128)), - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )); - all_bids.push(last_rejected_bid.clone()); - - // We first need to invalidate all rejected bids. - // We do it by placing a bid of the whole auction allocation, i.e. 10 new bids - let allocation_bid = BidParams::::from(( - account::>("bidder", 0, y), - auction_allocation, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )); - all_bids.push(allocation_bid); - - 10 - } else { - 0 - }; - - let accepted_bids = (0..x.saturating_sub(already_accepted_bids_count)) - .map(|i| { - BidParams::::from(( - account::>("bidder", 0, i), - (min_bid_amount * CT_UNIT).into(), - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )) - }) - .collect_vec(); - all_bids.extend(accepted_bids.clone()); - - let plmc_needed_for_bids = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &all_bids, - project_metadata.clone(), - None, - ); - let funding_asset_needed_for_bids = inst - .calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &all_bids, - project_metadata.clone(), - None, - ); - inst.mint_plmc_ed_if_required(plmc_needed_for_bids.accounts()); - inst.mint_plmc_to(plmc_needed_for_bids); - inst.mint_funding_asset_ed_if_required(funding_asset_needed_for_bids.to_account_asset_map()); - inst.mint_funding_asset_to(funding_asset_needed_for_bids); - - inst.bid_for_users(project_id, all_bids).unwrap(); - - let auction_end = inst.get_project_details(project_id).round_duration.end().unwrap(); - inst.jump_to_block(auction_end); - - #[block] - { - Pallet::::do_end_auction(project_id).unwrap(); - } - - // * validity checks * - // Storage - let stored_details = ProjectsDetails::::get(project_id).unwrap(); - assert!(matches!(stored_details.status, ProjectStatus::CommunityRound(..))); - - let accepted_bids_count = Bids::::iter_prefix_values((project_id,)) - .filter(|b| matches!(b.status, BidStatus::Accepted | BidStatus::PartiallyAccepted(..))) - .count(); - let rejected_bids_count = - Bids::::iter_prefix_values((project_id,)).filter(|b| matches!(b.status, BidStatus::Rejected)).count(); - assert_eq!(accepted_bids_count, x as usize); - assert_eq!(rejected_bids_count, y as usize); - - // Events - frame_system::Pallet::::assert_last_event( - Event::::ProjectPhaseTransition { - project_id, - phase: ProjectStatus::CommunityRound(expected_remainder_round_block), - } - .into(), - ); - } - - // We check if the user has a winning bid regardless if its the community or remainder round, so both rounds should have - // the same weight with `x` being equal. - #[benchmark] - fn contribute( - // How many other contributions the user did for that same project - x: Linear<0, { T::MaxContributionsPerUser::get() - 1 }>, - ) { - // setup - let mut inst = BenchInstantiator::::new(None); - ::SetPrices::set_prices(); - - // We can't see events at block 0 - inst.jump_to_block(1u32.into()); - - let issuer = account::>("issuer", 0, 0); - let contributor = account::>("contributor", 0, 0); - whitelist_account!(contributor); - - let project_metadata = default_project_metadata::(issuer.clone()); - - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - issuer, - None, - default_evaluations::(), - full_bids::(), - ); - - let price = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - let contributions = vec![ - ContributionParams::from(( - contributor.clone(), - (50 * CT_UNIT).into(), - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT - )); - x as usize + 1 - ]; - - let plmc = inst.calculate_contributed_plmc_spent(contributions.clone(), price); - let usdt = inst.calculate_contributed_funding_asset_spent(contributions.clone(), price); - - let escrow_account = Pallet::::fund_account_id(project_id); - let prev_total_usdt_locked = - inst.get_free_funding_asset_balances_for(vec![(escrow_account.clone(), usdt_id())]); - - inst.mint_plmc_ed_if_required(plmc.accounts()); - inst.mint_plmc_to(plmc.clone()); - inst.mint_funding_asset_ed_if_required(usdt.to_account_asset_map()); - inst.mint_funding_asset_to(usdt.clone()); - - // do "x" contributions for this user - inst.contribute_for_users(project_id, contributions[1..].to_vec()).expect("All contributions are accepted"); - - let total_plmc_bonded = inst.sum_balance_mappings(vec![plmc.clone()]); - let total_usdt_locked = inst.sum_funding_asset_mappings(vec![prev_total_usdt_locked, usdt.clone()])[0].1; - - let total_free_plmc = inst.get_ed(); - let total_free_usdt = inst.get_funding_asset_ed(AcceptedFundingAsset::USDT.id()); - - let jwt = get_mock_jwt_with_cid( - contributor.clone(), - InvestorType::Retail, - generate_did_from_account(contributor.clone()), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - - #[extrinsic_call] - contribute( - RawOrigin::Signed(contributor.clone()), - jwt, - project_id, - contributions[0].amount, - contributions[0].mode, - contributions[0].asset, - ); - - // * validity checks * - // Storage - let stored_contributions = - Contributions::::iter_prefix_values((project_id, contributor.clone())).collect_vec(); - assert_eq!(stored_contributions.len(), x as usize + 1); - - // Balances - let bonded_plmc = inst.get_reserved_plmc_balance_for(contributor.clone(), HoldReason::Participation.into()); - assert_eq!(bonded_plmc, total_plmc_bonded); - - let free_plmc = inst.get_free_plmc_balance_for(contributor.clone()); - assert_eq!(free_plmc, total_free_plmc); - - let escrow_account = Pallet::::fund_account_id(project_id); - let locked_usdt = inst.get_free_funding_asset_balance_for(usdt_id(), escrow_account.clone()); - assert_eq!(locked_usdt, total_usdt_locked); - - let free_usdt = inst.get_free_funding_asset_balance_for(usdt_id(), contributor.clone()); - assert_eq!(free_usdt, total_free_usdt); - - // Events - frame_system::Pallet::::assert_last_event( - Event::Contribution { - project_id, - contributor, - id: x, - ct_amount: contributions[0].amount, - funding_asset: AcceptedFundingAsset::USDT, - funding_amount: usdt[0].asset_amount, - plmc_bond: plmc[0].plmc_amount, - mode: contributions[0].mode, - } - .into(), - ); - } - // end_funding has 2 logic paths: // 1 - Funding successful (most expensive, not by much) // 2 - Funding failed // They only differ in that 1- has to calculate the evaluator rewards. #[benchmark] fn end_funding_project_successful() { + // log something + // setup let mut inst = BenchInstantiator::::new(None); ::SetPrices::set_prices(); + let weth_price = PriceProviderOf::::get_price(WETH.id()).unwrap(); + log::info!("weth_price: {:?}", weth_price); + // We can't see events at block 0 - inst.jump_to_block(1u32.into()); + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); let project_metadata = default_project_metadata::(issuer.clone()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let project_id = inst.create_remainder_contributing_project( - project_metadata, - issuer.clone(), + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer.clone(), None, evaluations); + + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 10); + let accounts = bids.accounts(); + let necessary_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + &bids, + project_metadata.clone(), + None, + ); + let necessary_funding_asset = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( + &bids, + project_metadata.clone(), None, - default_evaluations::(), - default_bids::(), - default_community_contributions::(), ); + inst.mint_plmc_ed_if_required(accounts.clone()); + inst.mint_funding_asset_ed_if_required(necessary_funding_asset.clone().to_account_asset_map()); + inst.mint_plmc_to(necessary_plmc.clone()); + inst.mint_funding_asset_to(necessary_funding_asset.clone()); + inst.bid_for_users(project_id, bids).unwrap(); + let end_block = inst.get_project_details(project_id).round_duration.end().unwrap(); inst.jump_to_block(end_block); @@ -1188,7 +862,7 @@ mod benchmarks { ::SetPrices::set_prices(); // We can't see events at block 0 - inst.jump_to_block(1u32.into()); + inst.advance_time(1u32.into()); let anyone = account::>("anyone", 0, 0); let issuer = account::>("issuer", 0, 0); @@ -1200,8 +874,6 @@ mod benchmarks { None, default_evaluations::(), default_bids::(), - default_community_contributions::(), - vec![], ); #[extrinsic_call] @@ -1238,8 +910,6 @@ mod benchmarks { None, evaluations, default_bids::(), - default_community_contributions::(), - vec![], ); let evaluation_to_settle = @@ -1313,8 +983,6 @@ mod benchmarks { None, default_evaluations::(), bids.clone(), - default_community_contributions::(), - vec![], ); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); @@ -1354,83 +1022,6 @@ mod benchmarks { ); } - // We have 2 logic paths - // 1 - Project was successful, USDT is transferred to issuer, CT minted, PLMC locked for vesting - // 2 - Project failed, USDT is refunded to contributor, CT is not minted, PLMC is released - // Path 1 is the most expensive but not by far, so we only benchmark and charge for this weight - #[benchmark] - fn settle_contribution_project_successful() { - // setup - let mut inst = BenchInstantiator::::new(None); - ::SetPrices::set_prices(); - - // We can't see events at block 0 - inst.advance_time(1u32.into()); - - let issuer = account::>("issuer", 0, 0); - let contributions = default_community_contributions::(); - let contributor = contributions[0].contributor.clone(); - whitelist_account!(contributor); - - let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_finished_project( - project_metadata.clone(), - issuer, - None, - default_evaluations::(), - default_bids::(), - contributions, - vec![], - ); - - assert_ok!(>::do_start_settlement(project_id)); - - let contribution_to_settle = - inst.execute(|| Contributions::::iter_prefix_values((project_id, contributor.clone())).next().unwrap()); - - #[extrinsic_call] - settle_contribution( - RawOrigin::Signed(contributor.clone()), - project_id, - contributor.clone(), - contribution_to_settle.id, - ); - - // * validity checks * - // Storage - assert!(Contributions::::get((project_id, contributor.clone(), contribution_to_settle.id)).is_none()); - - // Balances - let ct_amount = inst.get_ct_asset_balances_for(project_id, vec![contributor.clone()])[0]; - assert_eq!(contribution_to_settle.ct_amount, ct_amount); - inst.assert_plmc_held_balance( - contributor.clone(), - contribution_to_settle.plmc_bond, - HoldReason::Participation.into(), - ); - assert_eq!( - VestingOf::::total_scheduled_amount(&contributor, HoldReason::Participation.into()), - Some(contribution_to_settle.plmc_bond) - ); - let funding_account = project_metadata.funding_destination_account; - inst.assert_funding_asset_free_balance( - funding_account, - AcceptedFundingAsset::USDT.id(), - contribution_to_settle.funding_asset_amount, - ); - - // Events - frame_system::Pallet::::assert_last_event( - Event::ContributionSettled { - project_id, - account: contributor.clone(), - id: contribution_to_settle.id, - ct_amount, - } - .into(), - ); - } - #[benchmark] fn mark_project_as_settled() { // setup @@ -1448,8 +1039,6 @@ mod benchmarks { None, default_evaluations::(), default_bids::(), - default_community_contributions::(), - vec![], false, ); @@ -1470,16 +1059,10 @@ mod benchmarks { let issuer = account::>("issuer", 0, 0); let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_settled_project( - project_metadata.clone(), - issuer.clone(), - None, - default_evaluations::(), - default_bids::(), - default_community_contributions::(), - vec![], - true, - ); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 1); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 1); + let project_id = + inst.create_settled_project(project_metadata.clone(), issuer.clone(), None, evaluations, bids, true); let jwt = get_mock_jwt_with_cid( issuer.clone(), @@ -1494,13 +1077,13 @@ mod benchmarks { // * validity checks * let project_details = inst.get_project_details(project_id); assert_eq!(project_details.status, ProjectStatus::CTMigrationStarted); - assert_eq!(UnmigratedCounter::::get(project_id), 13); + assert_eq!(UnmigratedCounter::::get(project_id), 2); } #[benchmark] fn confirm_offchain_migration( // Amount of migrations to confirm for a single user - x: Linear<1, { MaxParticipationsPerUser::::get() }>, + x: Linear<1, { <::MaxBidsPerUser>::get() }>, ) { // setup let mut inst = BenchInstantiator::::new(None); @@ -1509,50 +1092,32 @@ mod benchmarks { let issuer = account::>("issuer", 0, 0); let participant = account::>("test_participant", 0, 0); - let max_evaluations = (x / 3).min(::MaxEvaluationsPerUser::get()); - let max_bids = ((x - max_evaluations) / 2).min(::MaxBidsPerUser::get()); - let max_contributions = x - max_evaluations - max_bids; - - let participant_evaluations = (0..max_evaluations) - .map(|_| EvaluationParams::from((participant.clone(), (100 * USD_UNIT).into()))) - .collect_vec(); + let max_bids = x; let participant_bids = (0..max_bids) .map(|_| { BidParams::from(( participant.clone(), - (500 * CT_UNIT).into(), + Institutional, + (50 * CT_UNIT).into(), ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, )) }) .collect_vec(); - let participant_contributions = (0..max_contributions) - .map(|_| { - ContributionParams::::from(( - participant.clone(), - (10 * CT_UNIT).into(), - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - )) - }) - .collect_vec(); - let mut evaluations = default_evaluations::(); - evaluations.extend(participant_evaluations); + let project_metadata = default_project_metadata::(issuer.clone()); + + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 1); - let mut bids = default_bids::(); + let mut bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 1); bids.extend(participant_bids); - let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_settled_project( - project_metadata.clone(), - issuer.clone(), - None, - evaluations, - bids, - default_community_contributions::(), - participant_contributions, - true, + let project_id = + inst.create_settled_project(project_metadata.clone(), issuer.clone(), None, evaluations, bids, true); + + assert_eq!( + inst.get_project_details(project_id).status, + ProjectStatus::SettlementFinished(FundingOutcome::Success) ); let jwt = get_mock_jwt_with_cid( @@ -1565,7 +1130,8 @@ mod benchmarks { >::start_offchain_migration(RawOrigin::Signed(issuer.clone()).into(), jwt.clone(), project_id) .unwrap(); - let participant_migrations_len = UserMigrations::::get((project_id, participant.clone())).unwrap().1.len(); + let participant_migrations = UserMigrations::::get((project_id, participant.clone())); + let participant_migrations_len = participant_migrations.unwrap().1.len(); assert_eq!(participant_migrations_len as u32, x); #[extrinsic_call] @@ -1574,7 +1140,8 @@ mod benchmarks { // * validity checks * let project_details = inst.get_project_details(project_id); assert_eq!(project_details.status, ProjectStatus::CTMigrationStarted); - assert_eq!(UnmigratedCounter::::get(project_id), 13); + // Evaluations and Bids had 1 each where it was a different account that the one we just confirmed. + assert_eq!(UnmigratedCounter::::get(project_id), 2); } #[benchmark] @@ -1592,8 +1159,6 @@ mod benchmarks { None, default_evaluations::(), default_bids::(), - default_community_contributions::(), - vec![], true, ); @@ -1638,8 +1203,6 @@ mod benchmarks { None, default_evaluations::(), default_bids::(), - default_community_contributions::(), - vec![], true, ); @@ -1697,8 +1260,6 @@ mod benchmarks { None, default_evaluations::(), default_bids::(), - default_community_contributions::(), - vec![], true, ); @@ -1773,8 +1334,6 @@ mod benchmarks { None, default_evaluations::(), default_bids::(), - default_community_contributions::(), - vec![], true, ); @@ -1849,7 +1408,7 @@ mod benchmarks { #[benchmark] fn send_pallet_migration_for( // Amount of migrations to confirm for a single user - x: Linear<1, { MaxParticipationsPerUser::::get() }>, + x: Linear<1, { ::MaxBidsPerUser::get() }>, ) { // setup let mut inst = BenchInstantiator::::new(None); @@ -1858,51 +1417,27 @@ mod benchmarks { let issuer = account::>("issuer", 0, 0); let participant = account::>("test_participant", 0, 0); - let max_evaluations = (x / 3).min(::MaxEvaluationsPerUser::get()); - let max_bids = ((x - max_evaluations) / 2).min(::MaxBidsPerUser::get()); - let max_contributions = x - max_evaluations - max_bids; + let max_bids = x; - let participant_evaluations = (0..max_evaluations) - .map(|_| EvaluationParams::from((participant.clone(), (100 * USD_UNIT).into()))) - .collect_vec(); let participant_bids = (0..max_bids) .map(|_| { BidParams::from(( participant.clone(), + Institutional, (500 * CT_UNIT).into(), ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, )) }) .collect_vec(); - let participant_contributions = (0..max_contributions) - .map(|_| { - ContributionParams::::from(( - participant.clone(), - (10 * CT_UNIT).into(), - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - )) - }) - .collect_vec(); - - let mut evaluations = default_evaluations::(); - evaluations.extend(participant_evaluations); - let mut bids = default_bids::(); + let project_metadata = default_project_metadata::(issuer.clone()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let mut bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 1); bids.extend(participant_bids); - let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_settled_project( - project_metadata.clone(), - issuer.clone(), - None, - evaluations, - bids, - default_community_contributions::(), - participant_contributions, - true, - ); + let project_id = + inst.create_settled_project(project_metadata.clone(), issuer.clone(), None, evaluations, bids, true); let jwt = get_mock_jwt_with_cid( issuer.clone(), @@ -1947,7 +1482,7 @@ mod benchmarks { #[benchmark] fn confirm_pallet_migrations( // Amount of migrations to confirm for a single user - x: Linear<1, { MaxParticipationsPerUser::::get() }>, + x: Linear<1, { ::MaxBidsPerUser::get() }>, ) { // setup let mut inst = BenchInstantiator::::new(None); @@ -1956,51 +1491,28 @@ mod benchmarks { let issuer = account::>("issuer", 0, 0); let participant = account::>("test_participant", 0, 0); - let max_evaluations = (x / 3).min(::MaxEvaluationsPerUser::get()); - let max_bids = ((x - max_evaluations) / 2).min(::MaxBidsPerUser::get()); - let max_contributions = x - max_evaluations - max_bids; + let max_bids = x; - let participant_evaluations = (0..max_evaluations) - .map(|_| EvaluationParams::from((participant.clone(), (100 * USD_UNIT).into()))) - .collect_vec(); let participant_bids = (0..max_bids) .map(|_| { BidParams::from(( participant.clone(), + Institutional, (500 * CT_UNIT).into(), ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, )) }) .collect_vec(); - let participant_contributions = (0..max_contributions) - .map(|_| { - ContributionParams::::from(( - participant.clone(), - (10 * CT_UNIT).into(), - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - )) - }) - .collect_vec(); - let mut evaluations = default_evaluations::(); - evaluations.extend(participant_evaluations); + let project_metadata = default_project_metadata::(issuer.clone()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 1); - let mut bids = default_bids::(); + let mut bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 1); bids.extend(participant_bids); - let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_settled_project( - project_metadata.clone(), - issuer.clone(), - None, - evaluations, - bids, - default_community_contributions::(), - participant_contributions, - true, - ); + let project_id = + inst.create_settled_project(project_metadata.clone(), issuer.clone(), None, evaluations, bids, true); let jwt = get_mock_jwt_with_cid( issuer.clone(), @@ -2065,8 +1577,6 @@ mod benchmarks { None, default_evaluations::(), default_bids::(), - default_community_contributions::(), - vec![], true, ); @@ -2106,8 +1616,6 @@ mod benchmarks { None, default_evaluations::(), default_bids::(), - default_community_contributions::(), - vec![], true, ); @@ -2149,8 +1657,6 @@ mod benchmarks { None, default_evaluations::(), default_bids::(), - default_community_contributions::(), - vec![], true, ); diff --git a/pallets/funding/src/functions/3_auction.rs b/pallets/funding/src/functions/3_auction.rs index b348b2d81..67894453c 100644 --- a/pallets/funding/src/functions/3_auction.rs +++ b/pallets/funding/src/functions/3_auction.rs @@ -2,47 +2,6 @@ use super::*; impl Pallet { - #[transactional] - pub fn do_end_auction(project_id: ProjectId) -> DispatchResultWithPostInfo { - // * Get variables * - let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; - let bucket = Buckets::::get(project_id).ok_or(Error::::BucketNotFound)?; - - // * Calculate WAP * - let auction_allocation_size = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - let weighted_token_price = bucket.calculate_wap(auction_allocation_size); - - // * Update Storage * - let calculation_result = Self::decide_winning_bids( - project_id, - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size, - weighted_token_price, - ); - let updated_project_details = - ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - - match calculation_result { - Err(e) => return Err(DispatchErrorWithPostInfo { post_info: ().into(), error: e }), - Ok((accepted_bids_count, rejected_bids_count)) => { - let now = >::block_number(); - // * Transition Round * - Self::transition_project( - project_id, - updated_project_details, - ProjectStatus::AuctionRound, - ProjectStatus::CommunityRound(now.saturating_add(T::CommunityRoundDuration::get())), - Some(T::CommunityRoundDuration::get() + T::RemainderRoundDuration::get()), - false, - )?; - Ok(PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::end_auction(accepted_bids_count, rejected_bids_count)), - pays_fee: Pays::Yes, - }) - }, - } - } - #[transactional] pub fn do_bid(params: DoBidParams) -> DispatchResultWithPostInfo { // * Get variables * @@ -79,22 +38,16 @@ impl Pallet { let metadata_ticket_size_bounds = match investor_type { InvestorType::Institutional => project_metadata.bidding_ticket_sizes.institutional, InvestorType::Professional => project_metadata.bidding_ticket_sizes.professional, - _ => return Err(Error::::WrongInvestorType.into()), + InvestorType::Retail => project_metadata.bidding_ticket_sizes.retail, }; let max_multiplier = match investor_type { InvestorType::Professional => PROFESSIONAL_MAX_MULTIPLIER, InvestorType::Institutional => INSTITUTIONAL_MAX_MULTIPLIER, - // unreachable - _ => return Err(Error::::ImpossibleState.into()), + InvestorType::Retail => RETAIL_MAX_MULTIPLIER, }; // * Validity checks * ensure!(project_policy == whitelisted_policy, Error::::PolicyMismatch); - ensure!( - matches!(investor_type, InvestorType::Institutional | InvestorType::Professional), - DispatchError::from("Retail investors are not allowed to bid") - ); - ensure!(ct_amount > Zero::zero(), Error::::TooLow); ensure!(did != project_details.issuer_did, Error::::ParticipationToOwnProject); ensure!(matches!(project_details.status, ProjectStatus::AuctionRound), Error::::IncorrectRound); @@ -114,10 +67,7 @@ impl Pallet { ensure!(mode.multiplier() <= max_multiplier && mode.multiplier() > 0u8, Error::::ForbiddenMultiplier); // Note: We limit the CT Amount to the auction allocation size, to avoid long-running loops. - ensure!( - ct_amount <= project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size, - Error::::TooHigh - ); + ensure!(ct_amount <= project_metadata.total_allocation_size, Error::::TooHigh); ensure!(existing_bids.len() < T::MaxBidsPerUser::get() as usize, Error::::TooManyUserParticipations); ensure!( project_metadata.participants_account_type.junction_is_supported(&receiving_account), diff --git a/pallets/funding/src/functions/4_contribution.rs b/pallets/funding/src/functions/4_contribution.rs deleted file mode 100644 index d22fdcea6..000000000 --- a/pallets/funding/src/functions/4_contribution.rs +++ /dev/null @@ -1,164 +0,0 @@ -#[allow(clippy::wildcard_imports)] -use super::*; - -impl Pallet { - #[transactional] - pub fn do_contribute(params: DoContributeParams) -> DispatchResultWithPostInfo { - let DoContributeParams { - contributor, - project_id, - ct_amount: token_amount, - mode, - funding_asset, - investor_type, - did, - whitelisted_policy, - receiving_account, - } = params; - let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let did_has_winning_bid = DidWithWinningBids::::get(project_id, did.clone()); - - let remainder_start = match project_details.status { - ProjectStatus::CommunityRound(remainder_start) => remainder_start, - _ => return Err(Error::::IncorrectRound.into()), - }; - - let now = >::block_number(); - let remainder_started = now >= remainder_start; - ensure!(!did_has_winning_bid || remainder_started, Error::::UserHasWinningBid); - ensure!( - project_details.round_duration.started(now) && !project_details.round_duration.ended(now), - Error::::IncorrectRound - ); - - let buyable_tokens = token_amount.min(project_details.remaining_contribution_tokens); - if buyable_tokens.is_zero() { - return Err(Error::::ProjectSoldOut.into()); - } - project_details.remaining_contribution_tokens.saturating_reduce(buyable_tokens); - - let perform_params = DoPerformContributionParams { - contributor, - project_id, - project_details: &mut project_details, - buyable_tokens, - mode, - funding_asset, - investor_type, - did, - whitelisted_policy, - receiving_account, - }; - - Self::do_perform_contribution(perform_params) - } - - #[transactional] - fn do_perform_contribution(params: DoPerformContributionParams) -> DispatchResultWithPostInfo { - let DoPerformContributionParams { - contributor, - project_id, - project_details, - buyable_tokens, - mode, - funding_asset, - investor_type, - did, - whitelisted_policy, - receiving_account, - } = params; - - let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; - let caller_existing_contributions = - Contributions::::iter_prefix_values((project_id, contributor.clone())).collect::>(); - let total_usd_bought_by_did = ContributionBoughtUSD::::get((project_id, did.clone())); - let now = >::block_number(); - let ct_usd_price = project_details.weighted_average_price.ok_or(Error::::WapNotSet)?; - let project_policy = project_metadata.policy_ipfs_cid.ok_or(Error::::ImpossibleState)?; - - let ticket_size = ct_usd_price.checked_mul_int(buyable_tokens).ok_or(Error::::BadMath)?; - let contributor_ticket_size = match investor_type { - InvestorType::Institutional => project_metadata.contributing_ticket_sizes.institutional, - InvestorType::Professional => project_metadata.contributing_ticket_sizes.professional, - InvestorType::Retail => project_metadata.contributing_ticket_sizes.retail, - }; - let max_multiplier = match investor_type { - InvestorType::Retail => RETAIL_MAX_MULTIPLIER, - InvestorType::Professional => PROFESSIONAL_MAX_MULTIPLIER, - InvestorType::Institutional => INSTITUTIONAL_MAX_MULTIPLIER, - }; - let multiplier: MultiplierOf = mode.multiplier().try_into().map_err(|_| Error::::ForbiddenMultiplier)?; - - // * Validity checks * - ensure!(project_policy == whitelisted_policy, Error::::PolicyMismatch); - ensure!(multiplier.into() <= max_multiplier && multiplier.into() > 0u8, Error::::ForbiddenMultiplier); - ensure!( - project_metadata.participation_currencies.contains(&funding_asset), - Error::::FundingAssetNotAccepted - ); - ensure!(did.clone() != project_details.issuer_did, Error::::ParticipationToOwnProject); - ensure!( - caller_existing_contributions.len() < T::MaxContributionsPerUser::get() as usize, - Error::::TooManyUserParticipations - ); - ensure!( - contributor_ticket_size.usd_ticket_above_minimum_per_participation(ticket_size) || - project_details.remaining_contribution_tokens.is_zero(), - Error::::TooLow - ); - ensure!( - contributor_ticket_size.usd_ticket_below_maximum_per_did(total_usd_bought_by_did + ticket_size), - Error::::TooHigh - ); - ensure!( - project_metadata.participants_account_type.junction_is_supported(&receiving_account), - Error::::UnsupportedReceiverAccountJunction - ); - - let plmc_bond = Self::calculate_plmc_bond(ticket_size, multiplier)?; - let funding_asset_amount = Self::calculate_funding_asset_amount(ticket_size, funding_asset)?; - - let contribution_id = NextContributionId::::get(); - let new_contribution = ContributionInfoOf:: { - did: did.clone(), - id: contribution_id, - project_id, - contributor: contributor.clone(), - ct_amount: buyable_tokens, - usd_contribution_amount: ticket_size, - mode, - funding_asset, - funding_asset_amount, - plmc_bond, - when: now, - receiving_account, - }; - - // Try adding the new contribution to the system - Self::bond_plmc_with_mode(&contributor, project_id, plmc_bond, mode, funding_asset)?; - Self::try_funding_asset_hold(&contributor, project_id, funding_asset_amount, funding_asset.id())?; - - Contributions::::insert((project_id, contributor.clone(), contribution_id), &new_contribution); - NextContributionId::::set(contribution_id.saturating_add(One::one())); - ContributionBoughtUSD::::mutate((project_id, did), |amount| *amount = amount.saturating_add(ticket_size)); - - project_details.funding_amount_reached_usd.saturating_accrue(new_contribution.usd_contribution_amount); - ProjectsDetails::::insert(project_id, project_details); - - // * Emit events * - Self::deposit_event(Event::Contribution { - project_id, - contributor: contributor.clone(), - id: contribution_id, - ct_amount: buyable_tokens, - funding_asset, - funding_amount: funding_asset_amount, - plmc_bond, - mode, - }); - - // return correct weight function - let actual_weight = Some(WeightInfoOf::::contribute(caller_existing_contributions.len() as u32)); - Ok(PostDispatchInfo { actual_weight, pays_fee: Pays::No }) - } -} diff --git a/pallets/funding/src/functions/5_funding_end.rs b/pallets/funding/src/functions/5_funding_end.rs index f88d2fc46..6ff0cede1 100644 --- a/pallets/funding/src/functions/5_funding_end.rs +++ b/pallets/funding/src/functions/5_funding_end.rs @@ -5,7 +5,9 @@ impl Pallet { #[transactional] pub fn do_end_funding(project_id: ProjectId) -> DispatchResult { // * Get variables * - let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; + let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + let bucket = Buckets::::get(project_id).ok_or(Error::::BucketNotFound)?; let remaining_cts = project_details.remaining_contribution_tokens; let now = >::block_number(); let issuer_did = project_details.issuer_did.clone(); @@ -15,28 +17,46 @@ impl Pallet { // Can end due to running out of CTs remaining_cts == Zero::zero() || // or the last funding round ending - project_details.round_duration.ended(now) && matches!(project_details.status, ProjectStatus::CommunityRound(..)), + project_details.round_duration.ended(now) && matches!(project_details.status, ProjectStatus::AuctionRound), Error::::TooEarlyForRound ); - // * Calculate new variables * - let funding_target = project_details.fundraising_target_usd; - let funding_reached = project_details.funding_amount_reached_usd; - let funding_ratio = Perquintill::from_rational(funding_reached, funding_target); + // * Calculate WAP * + let auction_allocation_size = project_metadata.total_allocation_size; + let weighted_token_price = bucket.calculate_wap(auction_allocation_size); // * Update Storage * + let _calculation_result = + Self::decide_winning_bids(project_id, project_metadata.total_allocation_size, weighted_token_price)?; + let mut updated_project_details = + ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + DidWithActiveProjects::::set(issuer_did, None); + // * Calculate new variables * + let funding_target = updated_project_details.fundraising_target_usd; + let funding_reached = updated_project_details.funding_amount_reached_usd; + let funding_ratio = Perquintill::from_rational(funding_reached, funding_target); + + // * Update project status * let next_status = if funding_ratio < T::FundingSuccessThreshold::get() { - project_details.evaluation_round_info.evaluators_outcome = Some(EvaluatorsOutcome::Slashed); + updated_project_details.evaluation_round_info.evaluators_outcome = Some(EvaluatorsOutcome::Slashed); ProjectStatus::FundingFailed } else { let reward_info = Self::generate_evaluator_rewards_info(project_id)?; - project_details.evaluation_round_info.evaluators_outcome = Some(EvaluatorsOutcome::Rewarded(reward_info)); + updated_project_details.evaluation_round_info.evaluators_outcome = + Some(EvaluatorsOutcome::Rewarded(reward_info)); ProjectStatus::FundingSuccessful }; - Self::transition_project(project_id, project_details.clone(), project_details.status, next_status, None, true)?; + Self::transition_project( + project_id, + updated_project_details.clone(), + project_details.status, + next_status, + None, + true, + )?; Ok(()) } diff --git a/pallets/funding/src/functions/6_settlement.rs b/pallets/funding/src/functions/6_settlement.rs index 814f2c42e..e406f8d5a 100644 --- a/pallets/funding/src/functions/6_settlement.rs +++ b/pallets/funding/src/functions/6_settlement.rs @@ -255,79 +255,6 @@ impl Pallet { Ok(BidRefund:: { final_ct_usd_price, final_ct_amount, refunded_plmc, refunded_funding_asset_amount }) } - pub fn do_settle_contribution(contribution: ContributionInfoOf, project_id: ProjectId) -> DispatchResult { - let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; - let mut final_ct_amount = Zero::zero(); - - let ProjectStatus::SettlementStarted(outcome) = project_details.status else { - return Err(Error::::SettlementNotStarted.into()); - }; - let funding_end_block = project_details.funding_end_block.ok_or(Error::::ImpossibleState)?; - - if outcome == FundingOutcome::Failure { - Self::release_funding_asset( - project_id, - &contribution.contributor, - contribution.funding_asset_amount, - contribution.funding_asset, - )?; - - if contribution.mode == ParticipationMode::OTM { - >::refund_fee( - project_id, - &contribution.contributor, - contribution.plmc_bond, - contribution.funding_asset.id(), - )?; - } else { - Self::release_participation_bond_for(&contribution.contributor, contribution.plmc_bond)?; - } - } else { - let ct_vesting_duration = Self::set_plmc_bond_release_with_mode( - contribution.contributor.clone(), - contribution.plmc_bond, - contribution.mode, - funding_end_block, - )?; - - // Mint the contribution tokens - Self::mint_contribution_tokens(project_id, &contribution.contributor, contribution.ct_amount)?; - - // Payout the bid funding asset amount to the project account - Self::release_funding_asset( - project_id, - &project_metadata.funding_destination_account, - contribution.funding_asset_amount, - contribution.funding_asset, - )?; - - // Create Migration - Self::create_migration( - project_id, - &contribution.contributor, - contribution.id, - ParticipationType::Contribution, - contribution.ct_amount, - ct_vesting_duration, - contribution.receiving_account, - )?; - - final_ct_amount = contribution.ct_amount; - } - - Contributions::::remove((project_id, contribution.contributor.clone(), contribution.id)); - - Self::deposit_event(Event::ContributionSettled { - project_id, - account: contribution.contributor, - id: contribution.id, - ct_amount: final_ct_amount, - }); - - Ok(()) - } - pub fn do_mark_project_as_settled(project_id: ProjectId) -> DispatchResult { let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; let outcome = match project_details.status { diff --git a/pallets/funding/src/functions/misc.rs b/pallets/funding/src/functions/misc.rs index 8e477d897..dfebae732 100644 --- a/pallets/funding/src/functions/misc.rs +++ b/pallets/funding/src/functions/misc.rs @@ -24,7 +24,7 @@ impl Pallet { } pub fn create_bucket_from_metadata(metadata: &ProjectMetadataOf) -> Result, DispatchError> { - let auction_allocation_size = metadata.auction_round_allocation_percentage * metadata.total_allocation_size; + let auction_allocation_size = metadata.total_allocation_size; let bucket_delta_amount = Percent::from_percent(10) * auction_allocation_size; let ten_percent_in_price: ::Price = PriceOf::::checked_from_rational(1, 10).ok_or(Error::::BadMath)?; @@ -397,8 +397,8 @@ impl Pallet { pub(crate) fn transition_project( project_id: ProjectId, mut project_details: ProjectDetailsOf, - current_round: ProjectStatus>, - next_round: ProjectStatus>, + current_round: ProjectStatus, + next_round: ProjectStatus, maybe_round_duration: Option>, skip_end_check: bool, ) -> DispatchResult { diff --git a/pallets/funding/src/functions/mod.rs b/pallets/funding/src/functions/mod.rs index a7cd3d7fd..9c033b096 100644 --- a/pallets/funding/src/functions/mod.rs +++ b/pallets/funding/src/functions/mod.rs @@ -2,7 +2,7 @@ use super::{traits::*, *}; use core::ops::Not; use frame_support::{ - dispatch::{DispatchErrorWithPostInfo, DispatchResult, DispatchResultWithPostInfo, PostDispatchInfo}, + dispatch::{DispatchResult, DispatchResultWithPostInfo, PostDispatchInfo}, ensure, pallet_prelude::*, traits::{ @@ -31,8 +31,6 @@ const QUERY_RESPONSE_TIME_WINDOW_BLOCKS: u32 = 20u32; mod application; #[path = "3_auction.rs"] mod auction; -#[path = "4_contribution.rs"] -mod contribution; #[path = "7_ct_migration.rs"] mod ct_migration; #[path = "2_evaluation.rs"] diff --git a/pallets/funding/src/instantiator/calculations.rs b/pallets/funding/src/instantiator/calculations.rs index 373d9a70f..06135f82a 100644 --- a/pallets/funding/src/instantiator/calculations.rs +++ b/pallets/funding/src/instantiator/calculations.rs @@ -1,11 +1,17 @@ use super::*; use crate::{MultiplierOf, ParticipationMode}; use core::cmp::Ordering; -use itertools::GroupBy; +use itertools::{izip, GroupBy}; #[allow(clippy::wildcard_imports)] use polimec_common::assets::AcceptedFundingAsset; -use polimec_common::{ProvideAssetPrice, USD_DECIMALS}; -use sp_core::{ecdsa, hexdisplay::AsBytesRef, keccak_256, sr25519, Pair}; +use polimec_common::{ + assets::AcceptedFundingAsset::{DOT, USDC, USDT, WETH}, + ProvideAssetPrice, USD_DECIMALS, +}; +use sp_core::{blake2_256, ecdsa, hexdisplay::AsBytesRef, keccak_256, sr25519, Pair}; +use sp_runtime::traits::TrailingZeroInput; +use sp_std::ops::Div; +use InvestorType::{self, *}; impl< T: Config, @@ -68,7 +74,7 @@ impl< while !amount_to_bid.is_zero() { let bid_amount = if amount_to_bid <= bucket.amount_left { amount_to_bid } else { bucket.amount_left }; output.push(( - BidParams::from((bid.bidder.clone(), bid_amount, bid.mode, bid.asset)), + BidParams::from((bid.bidder.clone(), bid.investor_type.clone(), bid_amount, bid.mode, bid.asset)), bucket.current_price, )); bucket.update(bid_amount); @@ -134,8 +140,7 @@ impl< .collect(); grouped_by_price_bids.reverse(); - let mut remaining_cts = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let mut remaining_cts = project_metadata.total_allocation_size; for (price_charged, bids) in grouped_by_price_bids { for bid in bids { @@ -245,8 +250,7 @@ impl< .collect(); grouped_by_price_bids.reverse(); - let mut remaining_cts = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let mut remaining_cts = project_metadata.total_allocation_size; for (price_charged, bids) in grouped_by_price_bids { for bid in bids { @@ -314,97 +318,19 @@ impl< total_cts_left.saturating_reduce(bid.amount); filtered_bids.push(bid); } else if !total_cts_left.is_zero() { - filtered_bids.push(BidParams::from((bid.bidder.clone(), total_cts_left, bid.mode, bid.asset))); + filtered_bids.push(BidParams::from(( + bid.bidder.clone(), + bid.investor_type, + total_cts_left, + bid.mode, + bid.asset, + ))); total_cts_left = Zero::zero(); } } filtered_bids } - pub fn calculate_contributed_plmc_spent( - &mut self, - contributions: Vec>, - token_usd_price: PriceOf, - ) -> Vec> { - let mut output = Vec::new(); - for cont in contributions { - let mut plmc_bond = 0u128; - if let ParticipationMode::Classic(multiplier) = cont.mode { - let usd_ticket_size = token_usd_price.saturating_mul_int(cont.amount); - self.add_required_plmc_to(&mut plmc_bond, usd_ticket_size, multiplier); - } - - output.push(UserToPLMCBalance::new(cont.contributor, plmc_bond)); - } - output - } - - pub fn calculate_total_plmc_locked_from_evaluations_and_remainder_contributions( - &mut self, - evaluations: Vec>, - contributions: Vec>, - price: PriceOf, - slashed: bool, - with_ed: bool, - ) -> Vec> { - let evaluation_locked_plmc_amounts = self.calculate_evaluation_plmc_spent(evaluations); - // how much new plmc would be locked without considering evaluation bonds - let theoretical_contribution_locked_plmc_amounts = self.calculate_contributed_plmc_spent(contributions, price); - - let slash_percentage = ::EvaluatorSlash::get(); - let slashable_min_deposits = evaluation_locked_plmc_amounts - .iter() - .map(|UserToPLMCBalance { account, plmc_amount }| UserToPLMCBalance { - account: account.clone(), - plmc_amount: slash_percentage * *plmc_amount, - }) - .collect::>(); - let available_evaluation_locked_plmc_for_lock_transfer = self.generic_map_operation( - vec![evaluation_locked_plmc_amounts.clone(), slashable_min_deposits.clone()], - MergeOperation::Subtract, - ); - - // how much new plmc was actually locked, considering already evaluation bonds used - // first. - let actual_contribution_locked_plmc_amounts = self.generic_map_operation( - vec![theoretical_contribution_locked_plmc_amounts, available_evaluation_locked_plmc_for_lock_transfer], - MergeOperation::Subtract, - ); - let mut result = self.generic_map_operation( - vec![evaluation_locked_plmc_amounts, actual_contribution_locked_plmc_amounts], - MergeOperation::Add, - ); - - if slashed { - result = self.generic_map_operation(vec![result, slashable_min_deposits], MergeOperation::Subtract); - } - if with_ed { - for UserToPLMCBalance { account: _, plmc_amount } in result.iter_mut() { - *plmc_amount += self.get_ed(); - } - } - result - } - - pub fn calculate_contributed_funding_asset_spent( - &mut self, - contributions: Vec>, - token_usd_price: PriceOf, - ) -> Vec> { - let mut output = Vec::new(); - for cont in contributions { - let usd_ticket_size = token_usd_price.saturating_mul_int(cont.amount); - let mut funding_asset_spent = Balance::zero(); - self.add_required_funding_asset_to(&mut funding_asset_spent, usd_ticket_size, cont.asset); - if cont.mode == ParticipationMode::OTM { - self.add_otm_fee_to(&mut funding_asset_spent, usd_ticket_size, cont.asset); - } - - output.push(UserToFundingAsset::new(cont.contributor, funding_asset_spent, cont.asset.id())); - } - output - } - pub fn add_otm_fee_to( &mut self, balance: &mut Balance, @@ -528,102 +454,222 @@ impl< output } - pub fn generate_successful_evaluations( + pub fn generate_evaluations_from_total_usd( &self, - project_metadata: ProjectMetadataOf, - evaluators: Vec>, - weights: Vec, + usd_amount: Balance, + evaluations_count: u8, ) -> Vec> { - let funding_target = project_metadata.minimum_price.saturating_mul_int(project_metadata.total_allocation_size); - let evaluation_success_threshold = ::EvaluationSuccessThreshold::get(); // if we use just the threshold, then for big usd targets we lose the evaluation due to PLMC conversion errors in `evaluation_end` - let usd_threshold = evaluation_success_threshold * funding_target * 2u128; + // Even distribution of weights totaling 100% among bids. + let weights = { + if evaluations_count == 0 { + return vec![]; + } + let base = 100 / evaluations_count; + let remainder = 100 % evaluations_count; + let mut result = vec![base; evaluations_count as usize]; + for i in 0..remainder { + result[i as usize] += 1; + } + result + }; + let evaluators = (0..evaluations_count as u32).map(|i| self.account_from_u32(i, "EVALUATOR")).collect_vec(); zip(evaluators, weights) .map(|(evaluator, weight)| { - let ticket_size = Percent::from_percent(weight) * usd_threshold; + let ticket_size = Percent::from_percent(weight) * usd_amount; (evaluator, ticket_size).into() }) .collect() } - pub fn generate_bids_from_total_usd( + pub fn generate_successful_evaluations( &self, - usd_amount: Balance, - min_price: PriceOf, - weights: Vec, - bidders: Vec>, - modes: Vec, - ) -> Vec> { - assert_eq!(weights.len(), bidders.len(), "Should have enough weights for all the bidders"); - - zip(zip(weights, bidders), modes) - .map(|((weight, bidder), mode)| { - let ticket_size = Percent::from_percent(weight) * usd_amount; - let token_amount = min_price.reciprocal().unwrap().saturating_mul_int(ticket_size); + project_metadata: ProjectMetadataOf, + evaluations_count: u8, + ) -> Vec> { + let funding_target = project_metadata.minimum_price.saturating_mul_int(project_metadata.total_allocation_size); + // if we use just the threshold, then for big usd targets we lose the evaluation due to PLMC conversion errors in `evaluation_end` + let evaluation_success_threshold = 100; + let usd_threshold = Percent::from_percent(evaluation_success_threshold) * funding_target; - BidParams::from((bidder, token_amount, mode, AcceptedFundingAsset::USDT)) - }) - .collect() + self.generate_evaluations_from_total_usd(usd_threshold, evaluations_count) } - pub fn generate_bids_from_total_ct_percent( + pub fn generate_failing_evaluations( &self, project_metadata: ProjectMetadataOf, - percent_funding: u8, - weights: Vec, - bidders: Vec>, - modes: Vec, - ) -> Vec> { - let total_allocation_size = project_metadata.total_allocation_size; - let total_ct_bid = Percent::from_percent(percent_funding) * total_allocation_size; + evaluations_count: u8, + ) -> Vec> { + let funding_target = project_metadata.minimum_price.saturating_mul_int(project_metadata.total_allocation_size); + // if we use just the threshold, then for big usd targets we lose the evaluation due to PLMC conversion errors in `evaluation_end` + let evaluation_fail_percent = ::EvaluationSuccessThreshold::get().deconstruct() / 2; + + let usd_threshold = Percent::from_percent(evaluation_fail_percent) * funding_target; + + self.generate_evaluations_from_total_usd(usd_threshold, evaluations_count) + } + + pub fn generate_bids_from_total_ct_amount(&self, bids_count: u8, total_ct_bid: Balance) -> Vec> { + // This range should be allowed for all investor types. + let mut multipliers = (1u8..=5u8).cycle(); + + let modes = (0..bids_count) + .map(|i| { + if i % 2 == 0 { + ParticipationMode::Classic(multipliers.next().unwrap()) + } else { + ParticipationMode::OTM + } + }) + .collect_vec(); + + let investor_types = + vec![Retail, Professional, Institutional].into_iter().cycle().take(bids_count as usize).collect_vec(); + let funding_assets = + vec![USDT, USDC, DOT, WETH, USDT].into_iter().cycle().take(bids_count as usize).collect_vec(); + + // Even distribution of weights totaling 100% among bids. + let weights = { + if bids_count == 0 { + return vec![]; + } + let base = 100 / bids_count; + let remainder = 100 % bids_count; + let mut result = vec![base; bids_count as usize]; + for i in 0..remainder { + result[i as usize] += 1; + } + result + }; - assert_eq!(weights.len(), bidders.len(), "Should have enough weights for all the bidders"); + let bidders = (0..bids_count as u32).map(|i| self.account_from_u32(i, "BIDDER")).collect_vec(); - zip(zip(weights, bidders), modes) - .map(|((weight, bidder), mode)| { + izip!(weights, bidders, modes, investor_types, funding_assets) + .map(|(weight, bidder, mode, investor_type, funding_asset)| { let token_amount = Percent::from_percent(weight) * total_ct_bid; - BidParams::from((bidder, token_amount, mode, AcceptedFundingAsset::USDT)) + BidParams::from((bidder, investor_type, token_amount, mode, funding_asset)) }) .collect() } - pub fn generate_contributions_from_total_usd( + pub fn generate_bids_from_total_usd( &self, + project_metadata: ProjectMetadataOf, usd_amount: Balance, - final_price: PriceOf, - weights: Vec, - contributors: Vec>, - modes: Vec, - ) -> Vec> { - zip(zip(weights, contributors), modes) - .map(|((weight, bidder), mode)| { - let ticket_size = Percent::from_percent(weight) * usd_amount; - let token_amount = final_price.reciprocal().unwrap().saturating_mul_int(ticket_size); + bids_count: u8, + ) -> Vec> { + let min_price = project_metadata.minimum_price; + let total_allocation_size = project_metadata.total_allocation_size; + let total_ct_bid = min_price.reciprocal().unwrap().saturating_mul_int(usd_amount); + if total_ct_bid > total_allocation_size { + panic!("This function should be used for filling only the first bucket. usd_amount given was too high!") + } + self.generate_bids_from_total_ct_amount(bids_count, total_ct_bid) + } - ContributionParams::from((bidder, token_amount, mode, AcceptedFundingAsset::USDT)) - }) - .collect() + pub fn generate_bids_from_higher_usd_than_target( + &mut self, + project_metadata: ProjectMetadataOf, + usd_amount: Balance, + ) -> Vec> { + let min_price = project_metadata.minimum_price; + let total_allocation_size = project_metadata.total_allocation_size; + let first_bucket_usd_amount = min_price.saturating_mul_int(total_allocation_size); + + // Initial setup + let target_wap_multiplicator = PriceOf::::saturating_from_rational(usd_amount, first_bucket_usd_amount); + let mut target_wap: PriceOf = min_price * target_wap_multiplicator; + let mut bucket = self.find_bucket_for_wap(project_metadata.clone(), target_wap); + + let first_account = self.account_from_u32(0, "BIDDER"); + let next_account = |acc: AccountIdOf| -> AccountIdOf { + let acc_bytes = acc.encode(); + let account_string = String::from_utf8_lossy(&acc_bytes); + let entropy = (0, account_string).using_encoded(blake2_256); + Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed") + }; + + let evaluations = self.generate_successful_evaluations(project_metadata.clone(), 5); + + // Initial bid generation + let mut bids = self.generate_bids_that_take_price_to( + project_metadata.clone(), + target_wap, + first_account.clone(), + next_account, + ); + + // Get initial USD amount + let project_id = self.create_finished_project( + project_metadata.clone(), + self.account_from_u32(420, "blaze it"), + None, + evaluations.clone(), + bids.clone(), + ); + let mut usd_amount_raised = self.get_project_details(project_id).funding_amount_reached_usd; + + let mut step_divider = PriceOf::::saturating_from_rational(1, 1); + let mut previous_direction = usd_amount_raised < usd_amount; + let mut previous_wap = target_wap; + let mut loop_counter = 0; + + while usd_amount_raised != usd_amount { + loop_counter += 1; + if loop_counter > 100 { + return bids; + } + + let current_direction = usd_amount_raised < usd_amount; + + // If we changed direction, increase precision + if current_direction != previous_direction { + step_divider = step_divider * PriceOf::::saturating_from_rational(10, 1); + } + + let step_size = bucket.delta_price.div(step_divider); + target_wap = if current_direction { target_wap + step_size } else { target_wap - step_size }; + + // Check if WAP is the same as previous + if target_wap == previous_wap { + return bids; + } + previous_wap = target_wap; + + bucket = self.find_bucket_for_wap(project_metadata.clone(), target_wap); + bids = self.generate_bids_that_take_price_to( + project_metadata.clone(), + target_wap, + first_account.clone(), + next_account, + ); + + let project_id = self.create_finished_project( + project_metadata.clone(), + self.account_from_u32(420, "blaze it"), + None, + evaluations.clone(), + bids.clone(), + ); + usd_amount_raised = self.get_project_details(project_id).funding_amount_reached_usd; + + previous_direction = current_direction; + } + + bids } - pub fn generate_contributions_from_total_ct_percent( + pub fn generate_bids_from_total_ct_percent( &self, project_metadata: ProjectMetadataOf, percent_funding: u8, - weights: Vec, - contributors: Vec>, - modes: Vec, - ) -> Vec> { + bids_count: u8, + ) -> Vec> { let total_allocation_size = project_metadata.total_allocation_size; - let total_ct_bought = Percent::from_percent(percent_funding) * total_allocation_size; - - assert_eq!(weights.len(), contributors.len(), "Should have enough weights for all the bidders"); + let total_ct_bid = Percent::from_percent(percent_funding) * total_allocation_size; - zip(zip(weights, contributors), modes) - .map(|((weight, contributor), mode)| { - let token_amount = Percent::from_percent(weight) * total_ct_bought; - ContributionParams::from((contributor, token_amount, mode, AcceptedFundingAsset::USDT)) - }) - .collect() + self.generate_bids_from_total_ct_amount(bids_count, total_ct_bid) } pub fn slash_evaluator_balances(&self, mut balances: Vec>) -> Vec> { @@ -653,8 +699,7 @@ impl< pub fn find_bucket_for_wap(&self, project_metadata: ProjectMetadataOf, target_wap: PriceOf) -> BucketOf { let mut bucket = >::create_bucket_from_metadata(&project_metadata).unwrap(); - let auction_allocation = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let auction_allocation = project_metadata.total_allocation_size; if target_wap == bucket.initial_price { return bucket @@ -716,11 +761,10 @@ impl< if bucket.current_price == bucket.initial_price { return vec![] } - let auction_allocation = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let auction_allocation = project_metadata.total_allocation_size; let mut generate_bid = |ct_amount| -> BidParams { - let bid = (starting_account.clone(), ct_amount, funding_asset).into(); + let bid = (starting_account.clone(), Retail, ct_amount, funding_asset).into(); starting_account = increment_account(starting_account.clone()); bid }; @@ -781,8 +825,7 @@ impl< bucket.update(bid.amount); } - let auction_allocation = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let auction_allocation = project_metadata.total_allocation_size; bucket.calculate_wap(auction_allocation) } @@ -843,4 +886,10 @@ impl< let junction = Junction::AccountId32 { network: Some(Polkadot), id: sr_pair.public().to_raw() }; (junction, signature_bytes) } + + pub fn account_from_u32(&self, x: u32, seed: &str) -> AccountIdOf { + let entropy = (x, seed).using_encoded(blake2_256); + Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed") + } } diff --git a/pallets/funding/src/instantiator/chain_interactions.rs b/pallets/funding/src/instantiator/chain_interactions.rs index 1a83e3701..427bbfe12 100644 --- a/pallets/funding/src/instantiator/chain_interactions.rs +++ b/pallets/funding/src/instantiator/chain_interactions.rs @@ -398,7 +398,7 @@ impl< self.execute(|| ProjectsDetails::::get(project_id).expect("Project details exists")) } - pub fn go_to_next_state(&mut self, project_id: ProjectId) -> ProjectStatus> { + pub fn go_to_next_state(&mut self, project_id: ProjectId) -> ProjectStatus { let project_details = self.get_project_details(project_id); let issuer = project_details.issuer_account; let original_state = project_details.status; @@ -415,9 +415,6 @@ impl< self.execute(|| >::do_end_evaluation(project_id).unwrap()); }, ProjectStatus::AuctionRound => { - self.execute(|| >::do_end_auction(project_id).unwrap()); - }, - ProjectStatus::CommunityRound(..) => { self.execute(|| >::do_end_funding(project_id).unwrap()); }, ProjectStatus::FundingSuccessful | ProjectStatus::FundingFailed => { @@ -465,7 +462,7 @@ impl< mode: bid.mode, funding_asset: bid.asset, did, - investor_type: InvestorType::Institutional, + investor_type: bid.investor_type, whitelisted_policy: project_policy.clone(), receiving_account: bid.receiving_account, }; @@ -475,38 +472,6 @@ impl< Ok(().into()) } - pub fn contribute_for_users( - &mut self, - project_id: ProjectId, - contributions: Vec>, - ) -> DispatchResultWithPostInfo { - let project_policy = self.get_project_metadata(project_id).policy_ipfs_cid.unwrap(); - - match self.get_project_details(project_id).status { - ProjectStatus::CommunityRound(..) => - for cont in contributions { - let did = generate_did_from_account(cont.contributor.clone()); - // We use institutional to be able to test most multipliers. - let investor_type = InvestorType::Institutional; - let params = DoContributeParams:: { - contributor: cont.contributor.clone(), - project_id, - ct_amount: cont.amount, - mode: cont.mode, - funding_asset: cont.asset, - did, - investor_type, - whitelisted_policy: project_policy.clone(), - receiving_account: cont.receiving_account, - }; - self.execute(|| crate::Pallet::::do_contribute(params))?; - }, - _ => panic!("Project should be in Community or Remainder status"), - } - - Ok(().into()) - } - pub fn settle_project(&mut self, project_id: ProjectId, mark_as_settled: bool) { self.execute(|| { Evaluations::::iter_prefix((project_id,)) @@ -515,9 +480,6 @@ impl< Bids::::iter_prefix((project_id,)) .for_each(|(_, bid)| Pallet::::do_settle_bid(bid, project_id).unwrap()); - Contributions::::iter_prefix((project_id,)) - .for_each(|(_, contribution)| Pallet::::do_settle_contribution(contribution, project_id).unwrap()); - if mark_as_settled { crate::Pallet::::do_mark_project_as_settled(project_id).unwrap(); } @@ -532,17 +494,8 @@ impl< 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 all the USDT/USDC/DOT was paid to the issuer funding account - pub fn assert_total_funding_paid_out( - &mut self, - project_id: ProjectId, - bids: Vec>, - contributions: Vec>, - ) { + pub fn assert_total_funding_paid_out(&mut self, project_id: ProjectId, bids: Vec>) { let project_metadata = self.get_project_metadata(project_id); let mut total_expected_dot: Balance = Zero::zero(); let mut total_expected_usdt: Balance = Zero::zero(); @@ -558,15 +511,6 @@ impl< } } - for contribution in contributions { - match contribution.funding_asset { - AcceptedFundingAsset::DOT => total_expected_dot += contribution.funding_asset_amount, - AcceptedFundingAsset::USDT => total_expected_usdt += contribution.funding_asset_amount, - AcceptedFundingAsset::USDC => total_expected_usdc += contribution.funding_asset_amount, - AcceptedFundingAsset::WETH => total_expected_weth += contribution.funding_asset_amount, - } - } - let total_stored_dot = self.get_free_funding_asset_balance_for( AcceptedFundingAsset::DOT.id(), project_metadata.funding_destination_account.clone(), @@ -652,29 +596,6 @@ impl< } } - // 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, - ) { - for contribution in contributions { - let account = contribution.contributor.clone(); - assert_eq!(self.execute(|| { Bids::::iter_prefix_values((&project_id, &account)).count() }), 0); - let amount: Balance = if is_successful { contribution.ct_amount } else { 0u64.into() }; - self.assert_migration( - project_id, - account, - amount, - contribution.id, - ParticipationType::Contribution, - contribution.receiving_account, - is_successful, - ); - } - } - pub(crate) fn assert_migration( &mut self, project_id: ProjectId, @@ -784,7 +705,7 @@ impl< project_id } - pub fn create_community_contributing_project( + pub fn create_finished_project( &mut self, project_metadata: ProjectMetadataOf, issuer: AccountIdOf, @@ -794,10 +715,6 @@ impl< ) -> ProjectId { let project_id = self.create_auctioning_project(project_metadata.clone(), issuer, maybe_did, evaluations.clone()); - if bids.is_empty() { - assert!(matches!(self.go_to_next_state(project_id), ProjectStatus::CommunityRound(_))); - return project_id - } self.mint_plmc_ed_if_required(bids.accounts()); self.mint_funding_asset_ed_if_required(bids.to_account_asset_map()); @@ -836,154 +753,8 @@ impl< self.do_free_plmc_assertions(expected_free_plmc_balances); self.do_reserved_plmc_assertions(expected_held_plmc_balances, HoldReason::Participation.into()); self.do_free_funding_asset_assertions(prev_funding_asset_balances); - assert_eq!(self.get_plmc_total_supply(), expected_plmc_supply); - - assert!(matches!(self.go_to_next_state(project_id), ProjectStatus::CommunityRound(_))); - - project_id - } - - pub fn create_remainder_contributing_project( - &mut self, - project_metadata: ProjectMetadataOf, - issuer: AccountIdOf, - maybe_did: Option, - evaluations: Vec>, - bids: Vec>, - contributions: Vec>, - ) -> ProjectId { - let project_id = self.create_community_contributing_project( - project_metadata.clone(), - issuer, - maybe_did, - evaluations.clone(), - bids.clone(), - ); - - if !contributions.is_empty() { - let ct_price = self.get_project_details(project_id).weighted_average_price.unwrap(); - - self.mint_plmc_ed_if_required(contributions.accounts()); - self.mint_funding_asset_ed_if_required(contributions.to_account_asset_map()); - let prev_free_plmc_balances = self.get_free_plmc_balances_for(contributions.accounts()); - let prev_held_plmc_balances = - self.get_reserved_plmc_balances_for(contributions.accounts(), HoldReason::Participation.into()); - let prev_funding_asset_balances = - self.get_free_funding_asset_balances_for(contributions.to_account_asset_map()); - let prev_plmc_supply = self.get_plmc_total_supply(); - - let plmc_contribution_deposits = self.calculate_contributed_plmc_spent(contributions.clone(), ct_price); - let funding_asset_contribution_deposits = - self.calculate_contributed_funding_asset_spent(contributions.clone(), ct_price); - - let plmc_evaluation_deposits = self.calculate_evaluation_plmc_spent(evaluations.clone()); - let reducible_evaluator_balances = self.slash_evaluator_balances(plmc_evaluation_deposits.clone()); - let necessary_plmc_contribution_mint = self.generic_map_operation( - vec![plmc_contribution_deposits.clone(), reducible_evaluator_balances], - MergeOperation::Subtract, - ); - - let expected_free_plmc_balances = prev_free_plmc_balances; - - let expected_held_plmc_balances = self - .generic_map_operation(vec![prev_held_plmc_balances, plmc_contribution_deposits], MergeOperation::Add); - - let expected_plmc_supply = prev_plmc_supply + necessary_plmc_contribution_mint.total(); - - self.mint_plmc_to(necessary_plmc_contribution_mint.clone()); - self.mint_funding_asset_to(funding_asset_contribution_deposits.clone()); - - self.contribute_for_users(project_id, contributions).expect("Contributing should work"); - - self.do_free_plmc_assertions(expected_free_plmc_balances); - self.do_reserved_plmc_assertions(expected_held_plmc_balances, HoldReason::Participation.into()); - - self.do_free_funding_asset_assertions(prev_funding_asset_balances.merge_accounts(MergeOperation::Add)); - assert_eq!(self.get_plmc_total_supply(), expected_plmc_supply); - } - - let ProjectStatus::CommunityRound(remainder_block) = self.get_project_details(project_id).status else { - panic!("Project should be in CommunityRound status"); - }; - self.jump_to_block(remainder_block); - - project_id - } - - pub fn create_finished_project( - &mut self, - project_metadata: ProjectMetadataOf, - issuer: AccountIdOf, - maybe_did: Option, - evaluations: Vec>, - bids: Vec>, - community_contributions: Vec>, - remainder_contributions: Vec>, - ) -> ProjectId { - let project_id = self.create_remainder_contributing_project( - project_metadata.clone(), - issuer, - maybe_did, - evaluations.clone(), - bids.clone(), - community_contributions.clone(), - ); - - if !remainder_contributions.is_empty() { - let ct_price = self.get_project_details(project_id).weighted_average_price.unwrap(); - - self.mint_plmc_ed_if_required(remainder_contributions.accounts()); - self.mint_funding_asset_ed_if_required(remainder_contributions.to_account_asset_map()); - - let prev_free_plmc_balances = self.get_free_plmc_balances_for(remainder_contributions.accounts()); - let prev_held_plmc_balances = self - .get_reserved_plmc_balances_for(remainder_contributions.accounts(), HoldReason::Participation.into()); - let prev_funding_asset_balances = - self.get_free_funding_asset_balances_for(remainder_contributions.to_account_asset_map()); - let prev_supply = self.get_plmc_total_supply(); - - let plmc_evaluation_deposits = self.calculate_evaluation_plmc_spent(evaluations); - let plmc_bid_deposits = self.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &bids, - project_metadata.clone(), - None, - ); - let plmc_remainder_contribution_deposits = - self.calculate_contributed_plmc_spent(remainder_contributions.clone(), ct_price); - let reducible_evaluator_balances = self.slash_evaluator_balances(plmc_evaluation_deposits); - let remaining_reducible_evaluator_balances = self.generic_map_operation( - vec![reducible_evaluator_balances, plmc_bid_deposits.clone()], - MergeOperation::Subtract, - ); - - let necessary_plmc_contribution_mint = self.generic_map_operation( - vec![plmc_remainder_contribution_deposits.clone(), remaining_reducible_evaluator_balances], - MergeOperation::Subtract, - ); - - let funding_asset_deposits = - self.calculate_contributed_funding_asset_spent(remainder_contributions.clone(), ct_price); - - let expected_free_plmc_balances = prev_free_plmc_balances; - let expected_held_plmc_balances = self.generic_map_operation( - vec![prev_held_plmc_balances, plmc_remainder_contribution_deposits], - MergeOperation::Add, - ); - - let expected_supply = prev_supply + necessary_plmc_contribution_mint.total(); - - self.mint_plmc_to(necessary_plmc_contribution_mint.clone()); - self.mint_funding_asset_to(funding_asset_deposits.clone()); - - self.contribute_for_users(project_id, remainder_contributions.clone()) - .expect("Remainder Contributing should work"); - - self.do_free_plmc_assertions(expected_free_plmc_balances); - self.do_reserved_plmc_assertions(expected_held_plmc_balances, HoldReason::Participation.into()); - self.do_free_funding_asset_assertions(prev_funding_asset_balances); - assert_eq!(self.get_plmc_total_supply(), expected_supply); - } + assert_eq!(self.get_plmc_total_supply(), expected_plmc_supply); let status = self.go_to_next_state(project_id); @@ -995,18 +766,11 @@ impl< .iter() .map(|bid| bid.amount) .fold(Balance::zero(), |acc, item| item + acc) - .min(project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size); - let community_bought_tokens = - community_contributions.iter().map(|cont| cont.amount).fold(Balance::zero(), |acc, item| item + acc); - let remainder_bought_tokens = - remainder_contributions.iter().map(|cont| cont.amount).fold(Balance::zero(), |acc, item| item + acc); + .min(project_metadata.total_allocation_size); assert_eq!( project_details.remaining_contribution_tokens, - project_metadata.total_allocation_size - - auction_bought_tokens - - community_bought_tokens - - remainder_bought_tokens, + project_metadata.total_allocation_size - auction_bought_tokens, "Remaining CTs are incorrect" ); } else if status == ProjectStatus::FundingFailed { @@ -1025,8 +789,6 @@ impl< maybe_did: Option, evaluations: Vec>, bids: Vec>, - community_contributions: Vec>, - remainder_contributions: Vec>, mark_as_settled: bool, ) -> ProjectId { let project_id = self.create_finished_project( @@ -1035,8 +797,6 @@ impl< maybe_did, evaluations.clone(), bids.clone(), - community_contributions.clone(), - remainder_contributions.clone(), ); assert!(matches!(self.go_to_next_state(project_id), ProjectStatus::SettlementStarted(_))); @@ -1044,33 +804,4 @@ impl< self.settle_project(project_id, mark_as_settled); project_id } - - pub fn create_project_at( - &mut self, - status: ProjectStatus>, - project_metadata: ProjectMetadataOf, - issuer: AccountIdOf, - evaluations: Vec>, - bids: Vec>, - community_contributions: Vec>, - remainder_contributions: Vec>, - ) -> ProjectId { - match status { - ProjectStatus::FundingSuccessful => self.create_finished_project( - project_metadata, - issuer, - None, - evaluations, - bids, - community_contributions, - remainder_contributions, - ), - ProjectStatus::CommunityRound(..) => - self.create_community_contributing_project(project_metadata, issuer, None, evaluations, bids), - ProjectStatus::AuctionRound => self.create_auctioning_project(project_metadata, issuer, None, evaluations), - ProjectStatus::EvaluationRound => self.create_evaluating_project(project_metadata, issuer, None), - ProjectStatus::Application => self.create_new_project(project_metadata, issuer, None), - _ => panic!("unsupported project creation in that status"), - } - } } diff --git a/pallets/funding/src/instantiator/tests.rs b/pallets/funding/src/instantiator/tests.rs index d2fd1280a..592dfde9e 100644 --- a/pallets/funding/src/instantiator/tests.rs +++ b/pallets/funding/src/instantiator/tests.rs @@ -2,7 +2,7 @@ use crate::{ instantiator::{UserToFundingAsset, UserToPLMCBalance}, mock::{new_test_ext, TestRuntime, PLMC}, tests::{ - defaults::{bounded_name, bounded_symbol, default_evaluations, default_project_metadata, ipfs_hash}, + defaults::{bounded_name, bounded_symbol, default_project_metadata, ipfs_hash}, CT_DECIMALS, CT_UNIT, }, *, @@ -10,7 +10,7 @@ use crate::{ use core::cell::RefCell; use itertools::Itertools; use polimec_common::{assets::AcceptedFundingAsset, ProvideAssetPrice, USD_DECIMALS, USD_UNIT}; -use sp_arithmetic::Percent; +use sp_arithmetic::Perquintill; #[test] fn dry_run_wap() { @@ -36,17 +36,11 @@ fn dry_run_wap() { token_information: CurrencyMetadata { name: bounded_name, symbol: bounded_symbol, decimals: CT_DECIMALS }, mainnet_token_max_supply: 8_000_000 * CT_UNIT, total_allocation_size: 100_000 * CT_UNIT, - auction_round_allocation_percentage: Percent::from_percent(50u8), minimum_price: decimal_aware_price, bidding_ticket_sizes: BiddingTicketSizes { professional: TicketSize::new(5000 * USD_UNIT, None), institutional: TicketSize::new(5000 * USD_UNIT, None), - phantom: Default::default(), - }, - contributing_ticket_sizes: ContributingTicketSizes { - retail: TicketSize::new(USD_UNIT, None), - professional: TicketSize::new(USD_UNIT, None), - institutional: TicketSize::new(USD_UNIT, None), + retail: TicketSize::new(100 * USD_UNIT, None), phantom: Default::default(), }, participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), @@ -69,7 +63,8 @@ fn dry_run_wap() { inst.mint_plmc_to(plmc_fundings); inst.mint_funding_asset_to(usdt_fundings); - let project_id = inst.create_auctioning_project(project_metadata.clone(), 0, None, default_evaluations()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = inst.create_auctioning_project(project_metadata.clone(), 0, None, evaluations); let bids = vec![ (ADAM, 10_000 * CT_UNIT).into(), @@ -82,13 +77,12 @@ fn dry_run_wap() { inst.bid_for_users(project_id, bids).unwrap(); - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(_))); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful)); let project_details = inst.get_project_details(project_id); let wap = project_details.weighted_average_price.unwrap(); let bucket = inst.execute(|| Buckets::::get(project_id).unwrap()); - let dry_run_price = bucket - .calculate_wap(project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size); + let dry_run_price = bucket.calculate_wap(project_metadata.total_allocation_size); assert_eq!(dry_run_price, wap); } @@ -116,18 +110,12 @@ fn find_bucket_for_wap() { let project_metadata = ProjectMetadata { token_information: CurrencyMetadata { name: bounded_name, symbol: bounded_symbol, decimals: CT_DECIMALS }, mainnet_token_max_supply: 8_000_000 * CT_UNIT, - total_allocation_size: 100_000 * CT_UNIT, - auction_round_allocation_percentage: Percent::from_percent(50u8), + total_allocation_size: 50_000 * CT_UNIT, minimum_price: decimal_aware_price, bidding_ticket_sizes: BiddingTicketSizes { professional: TicketSize::new(5000 * USD_UNIT, None), institutional: TicketSize::new(5000 * USD_UNIT, None), - phantom: Default::default(), - }, - contributing_ticket_sizes: ContributingTicketSizes { - retail: TicketSize::new(USD_UNIT, None), - professional: TicketSize::new(USD_UNIT, None), - institutional: TicketSize::new(USD_UNIT, None), + retail: TicketSize::new(100 * USD_UNIT, None), phantom: Default::default(), }, participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), @@ -150,7 +138,8 @@ fn find_bucket_for_wap() { inst.mint_plmc_to(plmc_fundings); inst.mint_funding_asset_to(usdt_fundings); - let project_id = inst.create_auctioning_project(project_metadata.clone(), 0, None, default_evaluations()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = inst.create_auctioning_project(project_metadata.clone(), 0, None, evaluations); let bids = vec![ (ADAM, 10_000 * CT_UNIT).into(), @@ -163,7 +152,7 @@ fn find_bucket_for_wap() { inst.bid_for_users(project_id, bids).unwrap(); - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(_))); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful)); let project_details = inst.get_project_details(project_id); let wap = project_details.weighted_average_price.unwrap(); @@ -172,8 +161,7 @@ fn find_bucket_for_wap() { let bucket_found = inst.find_bucket_for_wap(project_metadata.clone(), wap); assert_eq!(bucket_found, bucket_stored); - let wap_found = bucket_found - .calculate_wap(project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size); + let wap_found = bucket_found.calculate_wap(project_metadata.total_allocation_size); assert_eq!(wap_found, wap); } @@ -195,9 +183,23 @@ fn generate_bids_from_bucket() { |x| x + 1, AcceptedFundingAsset::USDT, ); - let project_id = - inst.create_community_contributing_project(project_metadata.clone(), 0, None, default_evaluations(), bids); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = inst.create_finished_project(project_metadata.clone(), 0, None, evaluations, bids); let project_details = inst.get_project_details(project_id); let wap = project_details.weighted_average_price.unwrap(); assert_eq!(wap, desired_price_aware_wap); } + +#[test] +fn generate_bids_from_higher_usd_than_target() { + let mut inst = tests::MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let mut project_metadata = default_project_metadata(0); + project_metadata.total_allocation_size = 100_000 * CT_UNIT; + + const TARGET_USD: u128 = 1_500_000 * USD_UNIT; + let bids = inst.generate_bids_from_higher_usd_than_target(project_metadata.clone(), TARGET_USD); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = inst.create_finished_project(project_metadata, 0, None, evaluations, bids); + let project_details = inst.get_project_details(project_id); + assert_close_enough!(project_details.funding_amount_reached_usd, TARGET_USD, Perquintill::from_float(0.9999)); +} diff --git a/pallets/funding/src/instantiator/types.rs b/pallets/funding/src/instantiator/types.rs index befd7612f..bdd842ae9 100644 --- a/pallets/funding/src/instantiator/types.rs +++ b/pallets/funding/src/instantiator/types.rs @@ -15,7 +15,7 @@ impl Default for BoxToFunction { #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, Serialize, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields, bound(serialize = ""), bound(deserialize = ""))] pub struct TestProjectParams { - pub expected_state: ProjectStatus>, + pub expected_state: ProjectStatus, pub metadata: ProjectMetadataOf, pub issuer: AccountIdOf, pub evaluations: Vec>, @@ -302,6 +302,7 @@ impl Conversions for Vec> { #[serde(rename_all = "camelCase", deny_unknown_fields, bound(serialize = ""), bound(deserialize = ""))] pub struct BidParams { pub bidder: AccountIdOf, + pub investor_type: InvestorType, pub amount: Balance, pub mode: ParticipationMode, pub asset: AcceptedFundingAsset, @@ -310,18 +311,20 @@ pub struct BidParams { impl BidParams { pub fn new( bidder: AccountIdOf, + investor_type: InvestorType, amount: Balance, mode: ParticipationMode, asset: AcceptedFundingAsset, receiving_account: Junction, ) -> Self { - Self { bidder, amount, mode, asset, receiving_account } + Self { bidder, investor_type, amount, mode, asset, receiving_account } } } impl From<(AccountIdOf, Balance)> for BidParams { fn from((bidder, amount): (AccountIdOf, Balance)) -> Self { Self { bidder: bidder.clone(), + investor_type: InvestorType::Retail, amount, mode: ParticipationMode::Classic(1u8), asset: AcceptedFundingAsset::USDT, @@ -332,10 +335,26 @@ impl From<(AccountIdOf, Balance)> for BidParams { } } } -impl From<(AccountIdOf, Balance, ParticipationMode)> for BidParams { - fn from((bidder, amount, mode): (AccountIdOf, Balance, ParticipationMode)) -> Self { +impl From<(AccountIdOf, InvestorType, Balance)> for BidParams { + fn from((bidder, investor_type, amount): (AccountIdOf, InvestorType, Balance)) -> Self { Self { bidder: bidder.clone(), + investor_type, + amount, + mode: ParticipationMode::Classic(1u8), + asset: AcceptedFundingAsset::USDT, + receiving_account: Junction::AccountId32 { + network: Some(NetworkId::Polkadot), + id: T::AccountId32Conversion::convert(bidder.clone()), + }, + } + } +} +impl From<(AccountIdOf, InvestorType, Balance, ParticipationMode)> for BidParams { + fn from((bidder, investor_type, amount, mode): (AccountIdOf, InvestorType, Balance, ParticipationMode)) -> Self { + Self { + bidder: bidder.clone(), + investor_type, amount, mode, asset: AcceptedFundingAsset::USDT, @@ -346,10 +365,13 @@ impl From<(AccountIdOf, Balance, ParticipationMode)> for BidParams } } } -impl From<(AccountIdOf, Balance, AcceptedFundingAsset)> for BidParams { - fn from((bidder, amount, asset): (AccountIdOf, Balance, AcceptedFundingAsset)) -> Self { +impl From<(AccountIdOf, InvestorType, Balance, AcceptedFundingAsset)> for BidParams { + fn from( + (bidder, investor_type, amount, asset): (AccountIdOf, InvestorType, Balance, AcceptedFundingAsset), + ) -> Self { Self { bidder: bidder.clone(), + investor_type, amount, mode: ParticipationMode::Classic(1u8), asset, @@ -360,10 +382,21 @@ impl From<(AccountIdOf, Balance, AcceptedFundingAsset)> for BidPar } } } -impl From<(AccountIdOf, Balance, ParticipationMode, AcceptedFundingAsset)> for BidParams { - fn from((bidder, amount, mode, asset): (AccountIdOf, Balance, ParticipationMode, AcceptedFundingAsset)) -> Self { +impl From<(AccountIdOf, InvestorType, Balance, ParticipationMode, AcceptedFundingAsset)> + for BidParams +{ + fn from( + (bidder, investor_type, amount, mode, asset): ( + AccountIdOf, + InvestorType, + Balance, + ParticipationMode, + AcceptedFundingAsset, + ), + ) -> Self { Self { bidder: bidder.clone(), + investor_type, amount, mode, asset, @@ -374,24 +407,33 @@ impl From<(AccountIdOf, Balance, ParticipationMode, AcceptedFundin } } } -impl From<(AccountIdOf, Balance, AcceptedFundingAsset, Junction)> for BidParams { +impl From<(AccountIdOf, InvestorType, Balance, AcceptedFundingAsset, Junction)> for BidParams { fn from( - (bidder, amount, asset, receiving_account): (AccountIdOf, Balance, AcceptedFundingAsset, Junction), + (bidder, investor_type, amount, asset, receiving_account): ( + AccountIdOf, + InvestorType, + Balance, + AcceptedFundingAsset, + Junction, + ), ) -> Self { - Self { bidder, amount, mode: ParticipationMode::Classic(1u8), asset, receiving_account } + Self { bidder, investor_type, amount, mode: ParticipationMode::Classic(1u8), asset, receiving_account } } } -impl From<(AccountIdOf, Balance, ParticipationMode, AcceptedFundingAsset, Junction)> for BidParams { +impl From<(AccountIdOf, InvestorType, Balance, ParticipationMode, AcceptedFundingAsset, Junction)> + for BidParams +{ fn from( - (bidder, amount, mode, asset, receiving_account): ( + (bidder, investor_type, amount, mode, asset, receiving_account): ( AccountIdOf, + InvestorType, Balance, ParticipationMode, AcceptedFundingAsset, Junction, ), ) -> Self { - Self { bidder, amount, mode, asset, receiving_account } + Self { bidder, investor_type, amount, mode, asset, receiving_account } } } impl From> for (AccountIdOf, AssetIdOf) { diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 8fe01544e..586099a9a 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -470,7 +470,7 @@ pub mod pallet { pub struct MaxParticipationsPerUser(PhantomData); impl Get for MaxParticipationsPerUser { fn get() -> u32 { - T::MaxContributionsPerUser::get() + T::MaxBidsPerUser::get() + T::MaxEvaluationsPerUser::get() + T::MaxBidsPerUser::get() + T::MaxEvaluationsPerUser::get() } } @@ -495,7 +495,7 @@ pub mod pallet { /// The metadata of a project was modified. MetadataEdited { project_id: ProjectId, metadata: ProjectMetadataOf }, /// Project transitioned to a new phase. - ProjectPhaseTransition { project_id: ProjectId, phase: ProjectStatus> }, + ProjectPhaseTransition { project_id: ProjectId, phase: ProjectStatus }, /// A `bonder` bonded an `amount` of PLMC for `project_id`. Evaluation { project_id: ProjectId, evaluator: AccountIdOf, id: u32, plmc_amount: Balance }, /// A bid was made for a project @@ -880,90 +880,6 @@ pub mod pallet { Self::do_bid(params) } - #[pallet::call_index(8)] - #[pallet::weight(WeightInfoOf::::end_auction( - ::MaxBidsPerProject::get() / 2, - ::MaxBidsPerProject::get() / 2, - ) - .max(WeightInfoOf::::end_auction( - ::MaxBidsPerProject::get(), - 0u32, - )) - .max(WeightInfoOf::::end_auction( - 0u32, - ::MaxBidsPerProject::get(), - )))] - pub fn end_auction(origin: OriginFor, project_id: ProjectId) -> DispatchResultWithPostInfo { - ensure_signed(origin)?; - Self::do_end_auction(project_id) - } - - /// Buy tokens in the Community or Remainder round at the price set in the Auction Round - #[pallet::call_index(9)] - #[pallet::weight( - WeightInfoOf::::contribute(T::MaxContributionsPerUser::get()) - )] - pub fn contribute( - origin: OriginFor, - jwt: UntrustedToken, - project_id: ProjectId, - #[pallet::compact] ct_amount: Balance, - mode: ParticipationMode, - funding_asset: AcceptedFundingAsset, - ) -> DispatchResultWithPostInfo { - let (contributor, did, investor_type, whitelisted_policy) = - T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?; - let receiving_account = Junction::AccountId32 { - network: Some(NetworkId::Polkadot), - id: T::AccountId32Conversion::convert(contributor.clone()), - }; - let params = DoContributeParams:: { - contributor, - project_id, - ct_amount, - mode, - funding_asset, - did, - investor_type, - whitelisted_policy, - receiving_account, - }; - Self::do_contribute(params) - } - - #[pallet::call_index(90)] - #[pallet::weight( - WeightInfoOf::::contribute(T::MaxContributionsPerUser::get()) - )] - pub fn contribute_with_receiving_account( - origin: OriginFor, - jwt: UntrustedToken, - project_id: ProjectId, - #[pallet::compact] ct_amount: Balance, - mode: ParticipationMode, - funding_asset: AcceptedFundingAsset, - receiving_account: Junction, - signature_bytes: [u8; 65], - ) -> DispatchResultWithPostInfo { - let (contributor, did, investor_type, whitelisted_policy) = - T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?; - - Self::verify_receiving_account_signature(&contributor, project_id, &receiving_account, signature_bytes)?; - - let params = DoContributeParams:: { - contributor, - project_id, - ct_amount, - mode, - funding_asset, - did, - investor_type, - whitelisted_policy, - receiving_account, - }; - Self::do_contribute(params) - } - #[pallet::call_index(10)] #[pallet::weight(WeightInfoOf::::end_funding_project_successful())] pub fn end_funding(origin: OriginFor, project_id: ProjectId) -> DispatchResult { @@ -1005,20 +921,6 @@ pub mod pallet { Self::do_settle_bid(bid, project_id) } - #[pallet::call_index(17)] - #[pallet::weight(WeightInfoOf::::settle_contribution_project_successful())] - pub fn settle_contribution( - origin: OriginFor, - project_id: ProjectId, - contributor: AccountIdOf, - contribution_id: u32, - ) -> DispatchResult { - let _caller = ensure_signed(origin)?; - let bid = Contributions::::get((project_id, contributor, contribution_id)) - .ok_or(Error::::ParticipationNotFound)?; - Self::do_settle_contribution(bid, project_id) - } - #[pallet::call_index(18)] #[pallet::weight(WeightInfoOf::::mark_project_as_settled())] pub fn mark_project_as_settled(origin: OriginFor, project_id: ProjectId) -> DispatchResult { diff --git a/pallets/funding/src/runtime_api.rs b/pallets/funding/src/runtime_api.rs index 3412c7649..c3941b166 100644 --- a/pallets/funding/src/runtime_api.rs +++ b/pallets/funding/src/runtime_api.rs @@ -284,61 +284,38 @@ impl Pallet { funding_asset: AcceptedFundingAsset, investor_type: InvestorType, ) -> Option<(Balance, Balance)> { - let project_details = ProjectsDetails::::get(project_id)?; let project_metadata = ProjectsMetadata::::get(project_id)?; let funding_asset_price = Pallet::::get_decimals_aware_funding_asset_price(&funding_asset)?; - let (min_usd_ticket, maybe_max_usd_ticket, already_spent_usd, total_cts_usd_amount) = - match project_details.status { - ProjectStatus::AuctionRound => { - let ticket_sizes = match investor_type { - InvestorType::Institutional => project_metadata.bidding_ticket_sizes.institutional, - InvestorType::Professional => project_metadata.bidding_ticket_sizes.professional, - _ => return None, - }; - let already_spent_usd = AuctionBoughtUSD::::get((project_id, did)); - let mut max_contribution_tokens = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - - let mut total_cts_usd_amount = 0; - - let mut current_bucket = Buckets::::get(project_id)?; - while max_contribution_tokens > 0u128 { - let bucket_price = current_bucket.current_price; - let ct_to_buy = max_contribution_tokens.min(current_bucket.amount_left); - let usd_spent = bucket_price.saturating_mul_int(ct_to_buy); - - max_contribution_tokens -= ct_to_buy; - total_cts_usd_amount += usd_spent; - current_bucket.update(ct_to_buy); - } - - ( - ticket_sizes.usd_minimum_per_participation, - ticket_sizes.usd_maximum_per_did, - already_spent_usd, - total_cts_usd_amount, - ) - }, - ProjectStatus::CommunityRound(..) => { - let ticket_sizes = match investor_type { - InvestorType::Institutional => project_metadata.contributing_ticket_sizes.institutional, - InvestorType::Professional => project_metadata.contributing_ticket_sizes.professional, - InvestorType::Retail => project_metadata.contributing_ticket_sizes.retail, - }; - let already_spent_usd = ContributionBoughtUSD::::get((project_id, did)); - let max_contribution_tokens = project_details.remaining_contribution_tokens; - let price = project_details.weighted_average_price?; - let total_cts_usd_amount = price.saturating_mul_int(max_contribution_tokens); - ( - ticket_sizes.usd_minimum_per_participation, - ticket_sizes.usd_maximum_per_did, - already_spent_usd, - total_cts_usd_amount, - ) - }, - _ => return None, + let (min_usd_ticket, maybe_max_usd_ticket, already_spent_usd, total_cts_usd_amount) = { + let ticket_sizes = match investor_type { + InvestorType::Institutional => project_metadata.bidding_ticket_sizes.institutional, + InvestorType::Professional => project_metadata.bidding_ticket_sizes.professional, + InvestorType::Retail => project_metadata.bidding_ticket_sizes.retail, }; + let already_spent_usd = AuctionBoughtUSD::::get((project_id, did)); + let mut max_contribution_tokens = project_metadata.total_allocation_size; + + let mut total_cts_usd_amount = 0; + + let mut current_bucket = Buckets::::get(project_id)?; + while max_contribution_tokens > 0u128 { + let bucket_price = current_bucket.current_price; + let ct_to_buy = max_contribution_tokens.min(current_bucket.amount_left); + let usd_spent = bucket_price.saturating_mul_int(ct_to_buy); + + max_contribution_tokens -= ct_to_buy; + total_cts_usd_amount += usd_spent; + current_bucket.update(ct_to_buy); + } + + ( + ticket_sizes.usd_minimum_per_participation, + ticket_sizes.usd_maximum_per_did, + already_spent_usd, + total_cts_usd_amount, + ) + }; let max_usd_ticket = if let Some(issuer_set_max_usd_ticket) = maybe_max_usd_ticket { total_cts_usd_amount.min(issuer_set_max_usd_ticket.saturating_sub(already_spent_usd)) diff --git a/pallets/funding/src/storage_migrations.rs b/pallets/funding/src/storage_migrations.rs index 736a1081f..94b27ab2a 100644 --- a/pallets/funding/src/storage_migrations.rs +++ b/pallets/funding/src/storage_migrations.rs @@ -1,7 +1,7 @@ //! A module that is responsible for migration of storage. use crate::{ - AccountIdOf, BiddingTicketSizes, Config, ContributingTicketSizes, CurrencyMetadata, FixedPointNumber, - ParticipantsAccountType, PriceOf, ProjectMetadataOf, StringLimitOf, + AccountIdOf, BiddingTicketSizes, Config, CurrencyMetadata, FixedPointNumber, ParticipantsAccountType, PriceOf, + ProjectMetadataOf, StringLimitOf, }; use core::marker::PhantomData; use frame_support::traits::{StorageVersion, UncheckedOnRuntimeUpgrade}; @@ -36,8 +36,6 @@ pub mod v5_storage_items { pub minimum_price: Price, /// Maximum and minimum ticket sizes for auction round pub bidding_ticket_sizes: BiddingTicketSizes, - /// Maximum and minimum ticket sizes for community/remainder rounds - pub contributing_ticket_sizes: ContributingTicketSizes, /// Participation currencies (e.g stablecoin, DOT, KSM) /// e.g. https://github.com/paritytech/substrate/blob/427fd09bcb193c1e79dec85b1e207c718b686c35/frame/uniques/src/types.rs#L110 /// For now is easier to handle the case where only just one Currency is accepted @@ -100,10 +98,8 @@ pub mod v6 { token_information: item.token_information, mainnet_token_max_supply: item.mainnet_token_max_supply, total_allocation_size: item.total_allocation_size, - auction_round_allocation_percentage: item.auction_round_allocation_percentage, minimum_price: item.minimum_price, bidding_ticket_sizes: item.bidding_ticket_sizes, - contributing_ticket_sizes: item.contributing_ticket_sizes, participation_currencies: item.participation_currencies, funding_destination_account: item.funding_destination_account, policy_ipfs_cid: item.policy_ipfs_cid, diff --git a/pallets/funding/src/tests/1_application.rs b/pallets/funding/src/tests/1_application.rs index 04911a365..51c73e2f4 100644 --- a/pallets/funding/src/tests/1_application.rs +++ b/pallets/funding/src/tests/1_application.rs @@ -1,5 +1,8 @@ use super::*; -use polimec_common::{assets::AcceptedFundingAsset, credentials::InvestorType}; +use polimec_common::{ + assets::AcceptedFundingAsset, + credentials::InvestorType::{self}, +}; use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt_with_cid}; #[cfg(test)] @@ -29,7 +32,9 @@ mod create_project_extrinsic { #[cfg(test)] mod success { use super::*; + use polimec_common::assets::AcceptedFundingAsset::{DOT, USDC, USDT, WETH}; use polimec_common_test_utils::get_mock_jwt_with_cid; + use std::collections::BTreeSet; #[test] fn project_id_autoincrement_works() { @@ -125,10 +130,66 @@ mod create_project_extrinsic { project_metadata.clone().policy_ipfs_cid.unwrap(), ); - let failing_bids = vec![(BIDDER_1, 1000 * CT_UNIT).into(), (BIDDER_2, 1000 * CT_UNIT).into()]; - - inst.mint_plmc_to(default_plmc_balances()); - inst.mint_funding_asset_to(default_usdt_balances()); + let failing_bids = + vec![(BIDDER_1, Professional, 1000 * CT_UNIT).into(), (BIDDER_2, Retail, 1000 * CT_UNIT).into()]; + let successful_evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let successful_bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 90, 5); + + let accounts = vec![ + vec![ISSUER_1], + successful_evaluations.accounts(), + successful_bids.accounts(), + failing_bids.accounts(), + ] + .concat() + .into_iter() + .collect::>() + .into_iter() + .collect_vec(); + + inst.mint_plmc_to( + accounts.iter().map(|acc| UserToPLMCBalance { account: *acc, plmc_amount: 1_000_000 * PLMC }).collect(), + ); + inst.mint_funding_asset_to( + accounts + .iter() + .map(|acc| UserToFundingAsset { + account: *acc, + asset_amount: 1_000_000 * USD_UNIT, + asset_id: USDT.id(), + }) + .collect(), + ); + inst.mint_funding_asset_to( + accounts + .iter() + .map(|acc| UserToFundingAsset { + account: *acc, + asset_amount: 1_000_000 * USD_UNIT, + asset_id: USDC.id(), + }) + .collect(), + ); + inst.mint_funding_asset_to( + accounts + .iter() + .map(|acc| UserToFundingAsset { + account: *acc, + asset_amount: 1_000_000__000_000_000_0, + asset_id: DOT.id(), + }) + .collect(), + ); + inst.mint_funding_asset_to( + accounts + .iter() + .map(|acc| UserToFundingAsset { + account: *acc, + asset_amount: 1_000_000__000_000_000_000_000_000, + asset_id: WETH.id(), + }) + .collect(), + ); // Cannot create 2 projects consecutively inst.execute(|| { @@ -175,23 +236,12 @@ mod create_project_extrinsic { // A Project is "inactive" after the funding fails assert_eq!(inst.go_to_next_state(1), ProjectStatus::EvaluationRound); - inst.evaluate_for_users(1, default_evaluations()).unwrap(); + inst.evaluate_for_users(1, successful_evaluations.clone()).unwrap(); assert_eq!(inst.go_to_next_state(1), ProjectStatus::AuctionRound); inst.bid_for_users(1, failing_bids).unwrap(); - assert!(matches!(inst.go_to_next_state(1), ProjectStatus::CommunityRound(_))); - inst.execute(|| { - assert_noop!( - Pallet::::create_project( - RuntimeOrigin::signed(ISSUER_1), - jwt.clone(), - project_metadata.clone() - ), - Error::::HasActiveProject - ); - }); assert_eq!(inst.go_to_next_state(1), ProjectStatus::FundingFailed); inst.execute(|| { assert_ok!(Pallet::::create_project( @@ -203,19 +253,11 @@ mod create_project_extrinsic { // A project is "inactive" after the funding succeeds assert_eq!(inst.go_to_next_state(2), ProjectStatus::EvaluationRound); - inst.evaluate_for_users(2, default_evaluations()).unwrap(); + inst.evaluate_for_users(2, successful_evaluations).unwrap(); assert_eq!(inst.go_to_next_state(2), ProjectStatus::AuctionRound); - inst.bid_for_users(2, default_bids()).unwrap(); - - let ProjectStatus::CommunityRound(remainder_start) = inst.go_to_next_state(2) else { - panic!("Expected CommunityRound"); - }; - - inst.contribute_for_users(2, default_community_contributions()).unwrap(); - inst.jump_to_block(remainder_start); - inst.contribute_for_users(2, default_remainder_contributions()).unwrap(); + inst.bid_for_users(2, successful_bids).unwrap(); assert_eq!(inst.go_to_next_state(2), ProjectStatus::FundingSuccessful); @@ -391,63 +433,39 @@ mod create_project_extrinsic { fn invalid_ticket_sizes() { let correct_project = default_project_metadata(ISSUER_1); - // min in bidding below 5k + // min in bidding below 10 USD let mut wrong_project_1 = correct_project.clone(); - wrong_project_1.bidding_ticket_sizes.professional = TicketSize::new(4999 * USD_UNIT, None); + wrong_project_1.bidding_ticket_sizes.professional = TicketSize::new(9 * USD_UNIT, None); let mut wrong_project_2 = correct_project.clone(); - wrong_project_2.bidding_ticket_sizes.institutional = TicketSize::new(4999 * USD_UNIT, None); + wrong_project_2.bidding_ticket_sizes.institutional = TicketSize::new(9 * USD_UNIT, None); let mut wrong_project_3 = correct_project.clone(); - wrong_project_3.bidding_ticket_sizes.professional = TicketSize::new(3000 * USD_UNIT, None); - wrong_project_3.bidding_ticket_sizes.institutional = TicketSize::new(0 * USD_UNIT, None); + wrong_project_3.bidding_ticket_sizes.professional = TicketSize::new(9 * USD_UNIT, None); + wrong_project_3.bidding_ticket_sizes.institutional = TicketSize::new(9 * USD_UNIT, None); + wrong_project_3.bidding_ticket_sizes.retail = TicketSize::new(9 * USD_UNIT, None); let mut wrong_project_4 = correct_project.clone(); - wrong_project_4.bidding_ticket_sizes.professional = TicketSize::new(USD_UNIT, None); - wrong_project_4.bidding_ticket_sizes.institutional = TicketSize::new(USD_UNIT, None); - - // min in contributing below 1 USD - let mut wrong_project_5 = correct_project.clone(); - wrong_project_5.contributing_ticket_sizes.retail = TicketSize::new(USD_UNIT / 2, None); - - let mut wrong_project_6 = correct_project.clone(); - wrong_project_6.contributing_ticket_sizes.professional = TicketSize::new(USD_UNIT / 2, None); - - let mut wrong_project_7 = correct_project.clone(); - wrong_project_7.contributing_ticket_sizes.institutional = TicketSize::new(USD_UNIT / 2, None); + wrong_project_4.bidding_ticket_sizes.professional = TicketSize::new(0, None); + wrong_project_4.bidding_ticket_sizes.institutional = TicketSize::new(0, None); + wrong_project_4.bidding_ticket_sizes.retail = TicketSize::new(0, None); // min higher than max let mut wrong_project_8 = correct_project.clone(); - wrong_project_8.bidding_ticket_sizes.professional = TicketSize::new(5000 * USD_UNIT, Some(4990 * USD_UNIT)); + wrong_project_8.bidding_ticket_sizes.professional = TicketSize::new(100 * USD_UNIT, Some(50 * USD_UNIT)); + wrong_project_8.bidding_ticket_sizes.retail = TicketSize::new(100 * USD_UNIT, Some(50 * USD_UNIT)); let mut wrong_project_9 = correct_project.clone(); wrong_project_9.bidding_ticket_sizes.institutional = TicketSize::new(6000 * USD_UNIT, Some(5500 * USD_UNIT)); - let mut wrong_project_10 = correct_project.clone(); - wrong_project_10.contributing_ticket_sizes.retail = TicketSize::new(6000 * USD_UNIT, Some(5500 * USD_UNIT)); - - let mut wrong_project_11 = correct_project.clone(); - wrong_project_11.contributing_ticket_sizes.professional = - TicketSize::new(6000 * USD_UNIT, Some(5500 * USD_UNIT)); - - let mut wrong_project_12 = correct_project.clone(); - wrong_project_12.contributing_ticket_sizes.professional = - TicketSize::new(6000 * USD_UNIT, Some(5500 * USD_UNIT)); - let wrong_projects = vec![ wrong_project_1.clone(), wrong_project_2, wrong_project_3.clone(), wrong_project_4, - wrong_project_5, - wrong_project_6, - wrong_project_7, wrong_project_8, wrong_project_9, - wrong_project_10, - wrong_project_11, - wrong_project_12, ]; let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); @@ -542,27 +560,6 @@ mod create_project_extrinsic { }); } - #[test] - fn auction_round_percentage_zero() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let mut project_metadata = default_project_metadata(ISSUER_1); - project_metadata.auction_round_allocation_percentage = Percent::from_percent(0); - - inst.mint_plmc_to(default_plmc_balances()); - let jwt = get_mock_jwt_with_cid( - ISSUER_1, - InvestorType::Institutional, - generate_did_from_account(ISSUER_1), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - inst.execute(|| { - assert_noop!( - Pallet::::create_project(RuntimeOrigin::signed(ISSUER_1), jwt, project_metadata), - Error::::AuctionRoundPercentageError - ); - }); - } - #[test] fn target_funding_less_than_1000_usd() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); @@ -908,7 +905,6 @@ mod edit_project_extrinsic { }, mainnet_token_max_supply: 100_000_000 * CT_UNIT, total_allocation_size: 5_000_000 * CT_UNIT, - auction_round_allocation_percentage: Percent::from_percent(30u8), minimum_price: PriceProviderOf::::calculate_decimals_aware_price( PriceOf::::from_float(20.0), USD_DECIMALS, @@ -918,12 +914,7 @@ mod edit_project_extrinsic { bidding_ticket_sizes: BiddingTicketSizes { professional: TicketSize::new(10_000 * USD_UNIT, Some(20_000 * USD_UNIT)), institutional: TicketSize::new(20_000 * USD_UNIT, Some(30_000 * USD_UNIT)), - phantom: Default::default(), - }, - contributing_ticket_sizes: ContributingTicketSizes { - retail: TicketSize::new(1_000 * USD_UNIT, Some(2_000 * USD_UNIT)), - professional: TicketSize::new(2_000 * USD_UNIT, Some(3_000 * USD_UNIT)), - institutional: TicketSize::new(3_000 * USD_UNIT, Some(4_000 * USD_UNIT)), + retail: TicketSize::new(10 * USD_UNIT, None), phantom: Default::default(), }, participation_currencies: vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC] diff --git a/pallets/funding/src/tests/2_evaluation.rs b/pallets/funding/src/tests/2_evaluation.rs index 88b0c4137..561a39f2e 100644 --- a/pallets/funding/src/tests/2_evaluation.rs +++ b/pallets/funding/src/tests/2_evaluation.rs @@ -14,7 +14,7 @@ mod round_flow { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let issuer = ISSUER_1; let project_metadata = default_project_metadata(issuer); - let evaluations = default_evaluations(); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); inst.create_auctioning_project(project_metadata, issuer, None, evaluations); } @@ -26,7 +26,7 @@ mod round_flow { let project2 = default_project_metadata(ISSUER_2); let project3 = default_project_metadata(ISSUER_3); let project4 = default_project_metadata(ISSUER_4); - let evaluations = default_evaluations(); + let evaluations = inst.generate_successful_evaluations(project1.clone(), 5); inst.create_auctioning_project(project1, ISSUER_1, None, evaluations.clone()); inst.create_auctioning_project(project2, ISSUER_2, None, evaluations.clone()); @@ -209,8 +209,9 @@ mod round_flow { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let issuer = ISSUER_1; let project_metadata = default_project_metadata(issuer); - let evaluations = default_failing_evaluations(); - let plmc_eval_deposits: Vec> = inst.calculate_evaluation_plmc_spent(evaluations); + let evaluations = inst.generate_failing_evaluations(project_metadata.clone(), 5); + let plmc_eval_deposits: Vec> = + inst.calculate_evaluation_plmc_spent(evaluations.clone()); let plmc_existential_deposits = plmc_eval_deposits.accounts().existential_deposits(); let expected_evaluator_balances = inst.generic_map_operation( @@ -223,7 +224,7 @@ mod round_flow { let project_id = inst.create_evaluating_project(project_metadata, issuer, None); - inst.evaluate_for_users(project_id, default_failing_evaluations()).expect("Bonding should work"); + inst.evaluate_for_users(project_id, evaluations).expect("Bonding should work"); inst.do_free_plmc_assertions(plmc_existential_deposits); inst.do_reserved_plmc_assertions(plmc_eval_deposits, HoldReason::Evaluation.into()); @@ -631,15 +632,13 @@ mod evaluate_extrinsic { )); }); - let new_evaluations = default_evaluations(); + let new_evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let new_plmc_required = inst.calculate_evaluation_plmc_spent(new_evaluations.clone()); inst.mint_plmc_ed_if_required(new_plmc_required.accounts()); inst.mint_plmc_to(new_plmc_required.clone()); inst.evaluate_for_users(project_id, new_evaluations).unwrap(); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::AuctionRound); - - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(_))); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); let free_balance = inst.get_free_plmc_balance_for(EVALUATOR_4); @@ -764,8 +763,12 @@ mod evaluate_extrinsic { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let issuer = ISSUER_1; let project_metadata = default_project_metadata(issuer); - let project_id = - inst.create_auctioning_project(project_metadata.clone(), issuer, None, default_evaluations()); + let project_id = inst.create_auctioning_project( + project_metadata.clone(), + issuer, + None, + inst.generate_successful_evaluations(project_metadata.clone(), 5), + ); inst.execute(|| { assert_noop!( @@ -790,7 +793,7 @@ mod evaluate_extrinsic { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let issuer = ISSUER_1; let project_metadata = default_project_metadata(issuer); - let evaluations = default_evaluations(); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let insufficient_eval_deposits = inst .calculate_evaluation_plmc_spent(evaluations.clone()) .iter() diff --git a/pallets/funding/src/tests/3_auction.rs b/pallets/funding/src/tests/3_auction.rs index 84c2140bc..4db22179f 100644 --- a/pallets/funding/src/tests/3_auction.rs +++ b/pallets/funding/src/tests/3_auction.rs @@ -4,6 +4,7 @@ use polimec_common::assets::{AcceptedFundingAsset, AcceptedFundingAsset::USDT}; use sp_core::bounded_vec; use sp_runtime::traits::Convert; use std::collections::HashSet; +use InvestorType::{self}; #[cfg(test)] mod round_flow { @@ -17,10 +18,9 @@ mod round_flow { fn auction_round_completed() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_metadata = default_project_metadata(ISSUER_1); - let evaluations = default_evaluations(); - let bids = default_bids(); - let _project_id = - inst.create_community_contributing_project(project_metadata, ISSUER_1, None, evaluations, bids); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 60, 5); + let _project_id = inst.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids); } #[test] @@ -30,47 +30,36 @@ mod round_flow { let project2 = default_project_metadata(ISSUER_2); let project3 = default_project_metadata(ISSUER_3); let project4 = default_project_metadata(ISSUER_4); - let evaluations = default_evaluations(); - let bids = default_bids(); + let evaluations = inst.generate_successful_evaluations(project1.clone(), 5); + let bids = inst.generate_bids_from_total_ct_percent(project1.clone(), 60, 5); - inst.create_community_contributing_project(project1, ISSUER_1, None, evaluations.clone(), bids.clone()); - inst.create_community_contributing_project(project2, ISSUER_2, None, evaluations.clone(), bids.clone()); - inst.create_community_contributing_project(project3, ISSUER_3, None, evaluations.clone(), bids.clone()); - inst.create_community_contributing_project(project4, ISSUER_4, None, evaluations, bids); + inst.create_finished_project(project1, ISSUER_1, None, evaluations.clone(), bids.clone()); + inst.create_finished_project(project2, ISSUER_2, None, evaluations.clone(), bids.clone()); + inst.create_finished_project(project3, ISSUER_3, None, evaluations.clone(), bids.clone()); + inst.create_finished_project(project4, ISSUER_4, None, evaluations, bids); } #[test] fn auction_gets_percentage_of_ct_total_allocation() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_metadata = default_project_metadata(ISSUER_1); - let evaluations = default_evaluations(); - let auction_percentage = project_metadata.auction_round_allocation_percentage; + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let total_allocation = project_metadata.total_allocation_size; + let auction_allocation = total_allocation; - let auction_allocation = auction_percentage * total_allocation; - - let bids = vec![(BIDDER_1, auction_allocation).into()]; - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - evaluations.clone(), - bids, - ); + let bids = vec![(BIDDER_1, Retail, auction_allocation).into()]; + let project_id = + inst.create_finished_project(project_metadata.clone(), ISSUER_1, None, evaluations.clone(), bids); let mut bid_infos = Bids::::iter_prefix_values((project_id,)); let bid_info = inst.execute(|| bid_infos.next().unwrap()); assert!(inst.execute(|| bid_infos.next().is_none())); assert_eq!(bid_info.original_ct_amount, auction_allocation); let project_metadata = default_project_metadata(ISSUER_2); - let bids = vec![(BIDDER_1, auction_allocation).into(), (BIDDER_1, 1000 * CT_UNIT).into()]; - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - ISSUER_2, - None, - evaluations.clone(), - bids, - ); + let bids = + vec![(BIDDER_1, Retail, auction_allocation).into(), (BIDDER_1, Institutional, 1000 * CT_UNIT).into()]; + let project_id = + inst.create_finished_project(project_metadata.clone(), ISSUER_2, None, evaluations.clone(), bids); let project_details = inst.get_project_details(project_id); let bid_info_1 = inst.execute(|| Bids::::get((project_id, BIDDER_1, 1)).unwrap()); @@ -90,10 +79,10 @@ mod round_flow { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let issuer = ISSUER_1; let project_metadata = default_project_metadata(issuer); - let evaluations = default_evaluations(); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed)); assert_eq!( inst.get_project_details(project_id).weighted_average_price, @@ -166,15 +155,10 @@ mod round_flow { project_metadata.participation_currencies = bounded_vec!(funding_asset); let issuer: AccountIdOf = (10_000 + inst.get_new_nonce()).try_into().unwrap(); - let evaluations = inst.generate_successful_evaluations( - project_metadata.clone(), - default_evaluators(), - default_weights(), - ); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); - let auction_allocation_percentage = project_metadata.auction_round_allocation_percentage; - let auction_allocation_ct = auction_allocation_percentage * project_metadata.total_allocation_size; + let auction_allocation_ct = project_metadata.total_allocation_size; auction_allocations_ct.push(auction_allocation_ct); let auction_allocation_usd = project_metadata.minimum_price.saturating_mul_int(auction_allocation_ct); auction_allocations_usd.push(auction_allocation_usd); @@ -190,8 +174,8 @@ mod round_flow { let min_professional_bid_funding_asset = funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(min_professional_bid_usd); - // Every project should want to raise 5MM USD on the auction round regardless of CT decimals - assert_eq!(auction_allocation_usd, 5_000_000 * USD_UNIT); + // Every project should want to raise 10MM USD on the auction round regardless of CT decimals + assert_eq!(auction_allocation_usd, 10_000_000 * USD_UNIT); // A minimum bid goes through. This is a fixed USD value, but the extrinsic amount depends on CT decimals. inst.mint_plmc_ed_if_required(vec![BIDDER_1]); @@ -217,9 +201,9 @@ mod round_flow { funding_asset, ))); - // The bucket should have 50% of 1MM * 10^decimals CT minus what we just bid + // The bucket should have 1MM * 10^decimals CT minus what we just bid let bucket = inst.execute(|| Buckets::::get(project_id).unwrap()); - assert_eq!(bucket.amount_left, 500_000u128 * 10u128.pow(decimals as u32) - min_professional_bid_ct); + assert_eq!(bucket.amount_left, 1_000_000u128 * 10u128.pow(decimals as u32) - min_professional_bid_ct); }; for decimals in 6..=18 { @@ -250,21 +234,20 @@ mod round_flow { let total_allocation = 10_000_000 * CT_UNIT; let min_bid_ct = 500 * CT_UNIT; // 5k USD at 10USD/CT let max_bids_per_project: u32 = ::MaxBidsPerProject::get(); - let big_bid: BidParams = (BIDDER_1, total_allocation).into(); + let big_bid: BidParams = (BIDDER_1, Institutional, total_allocation).into(); let small_bids: Vec> = - (0..max_bids_per_project - 1).map(|i| (i as u64 + BIDDER_1, min_bid_ct).into()).collect(); + (0..max_bids_per_project - 1).map(|i| (i as u64 + BIDDER_1, Retail, min_bid_ct).into()).collect(); let all_bids = vec![vec![big_bid.clone()], small_bids.clone()].into_iter().flatten().collect_vec(); let mut project_metadata = default_project_metadata(ISSUER_1); project_metadata.mainnet_token_max_supply = total_allocation; project_metadata.total_allocation_size = total_allocation; - project_metadata.auction_round_allocation_percentage = Percent::from_percent(100); - let project_id = inst.create_community_contributing_project( + let project_id = inst.create_finished_project( project_metadata.clone(), ISSUER_1, None, - inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()), + inst.generate_successful_evaluations(project_metadata.clone(), 5), all_bids, ); @@ -280,8 +263,7 @@ mod round_flow { fn auction_oversubscription() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_metadata = default_project_metadata(ISSUER_1); - let auction_allocation = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let auction_allocation = project_metadata.total_allocation_size; let bucket_size = Percent::from_percent(10) * auction_allocation; let bids = vec![ (BIDDER_1, auction_allocation).into(), @@ -292,11 +274,11 @@ mod round_flow { (BIDDER_6, bucket_size).into(), ]; - let project_id = inst.create_community_contributing_project( + let project_id = inst.create_finished_project( project_metadata.clone(), ISSUER_1, None, - default_evaluations(), + inst.generate_successful_evaluations(project_metadata.clone(), 5), bids, ); @@ -304,36 +286,6 @@ mod round_flow { assert!(wap > project_metadata.minimum_price); } } - - #[cfg(test)] - mod failure { - use super::*; - - #[test] - fn contribute_does_not_work() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); - let did = generate_did_from_account(ISSUER_1); - let investor_type = InvestorType::Retail; - inst.execute(|| { - assert_noop!( - PolimecFunding::do_contribute(DoContributeParams:: { - contributor: BIDDER_1, - project_id, - ct_amount: 100, - mode: ParticipationMode::Classic(1u8), - funding_asset: AcceptedFundingAsset::USDT, - did, - investor_type, - whitelisted_policy: project_metadata.clone().policy_ipfs_cid.unwrap(), - receiving_account: polkadot_junction!(BIDDER_1) - }), - Error::::IncorrectRound - ); - }); - } - } } #[cfg(test)] @@ -350,11 +302,13 @@ mod bid_extrinsic { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let issuer = ISSUER_1; let project_metadata = default_project_metadata(issuer); - let mut evaluations = default_evaluations(); + let mut evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let evaluator_bidder = 69u64; let evaluation_amount = 420 * USD_UNIT; let evaluator_bid = BidParams::from(( evaluator_bidder, + Retail, 600 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, @@ -430,22 +384,25 @@ mod bid_extrinsic { let mut project_metadata_dot = default_project_metadata(ISSUER_4); project_metadata_dot.participation_currencies = vec![AcceptedFundingAsset::DOT].try_into().unwrap(); - let evaluations = default_evaluations(); + let evaluations = inst.generate_successful_evaluations(project_metadata_all.clone(), 5); let usdt_bid = BidParams::from(( BIDDER_1, + Retail, 10_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, )); let usdc_bid = BidParams::from(( BIDDER_1, + Retail, 10_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDC, )); let dot_bid = BidParams::from(( BIDDER_1, + Retail, 10_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::DOT, @@ -545,8 +502,7 @@ mod bid_extrinsic { fn multiplier_limits() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_metadata = default_project_metadata(ISSUER_1); - let evaluations = - inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); // Professional bids: 0x multiplier should fail assert_err!( @@ -594,33 +550,19 @@ mod bid_extrinsic { project_metadata.clone().token_information.decimals, ) .unwrap(); - project_metadata.auction_round_allocation_percentage = Percent::from_percent(50u8); - let evaluations = default_evaluations(); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); - // bid that fills 80% of the first bucket - let bid_40_percent = inst.generate_bids_from_total_ct_percent( - project_metadata.clone(), - 40u8, - vec![100], - vec![BIDDER_1], - vec![ParticipationMode::Classic(8u8)], - ); + // bid that fills 90% of the first bucket + let bid_90_percent = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 90u8, 1); - // Note: 5% of total CTs is one bucket, i.e 10% of the auction allocation // This bid fills last 20% of the first bucket, // and gets split into 3 more bids of 2 more full and one partially full buckets. - // 10% + 5% + 5% + 3% = 23% - let bid_23_percent = inst.generate_bids_from_total_ct_percent( - project_metadata.clone(), - 23u8, - vec![100], - vec![BIDDER_2], - vec![ParticipationMode::Classic(7u8)], - ); + // 10% + 10% + 10% + 3% = 33% + let bid_33_percent = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 33u8, 1); - let all_bids = vec![bid_40_percent[0].clone(), bid_23_percent[0].clone()]; + let all_bids = vec![bid_90_percent[0].clone(), bid_33_percent[0].clone()]; inst.mint_plmc_ed_if_required(all_bids.accounts()); inst.mint_funding_asset_ed_if_required(all_bids.to_account_asset_map()); @@ -638,14 +580,14 @@ mod bid_extrinsic { inst.mint_plmc_to(necessary_plmc.clone()); inst.mint_funding_asset_to(necessary_usdt.clone()); - inst.bid_for_users(project_id, bid_40_percent.clone()).unwrap(); + inst.bid_for_users(project_id, bid_90_percent.clone()).unwrap(); let stored_bids = inst.execute(|| Bids::::iter_prefix_values((project_id,)).collect_vec()); assert_eq!(stored_bids.len(), 1); - inst.bid_for_users(project_id, bid_23_percent.clone()).unwrap(); + inst.bid_for_users(project_id, bid_33_percent.clone()).unwrap(); let mut stored_bids = inst.execute(|| Bids::::iter_prefix_values((project_id,)).collect_vec()); stored_bids.sort_by(|a, b| a.id.cmp(&b.id)); - // 40% + 10% + 5% + 5% + 3% = 5 total bids + // 90% + 10% + 10% + 10% + 3% = 5 total bids assert_eq!(stored_bids.len(), 5); let normalize_price = |decimal_aware_price| { @@ -667,13 +609,13 @@ mod bid_extrinsic { ); assert_eq!( stored_bids[2].original_ct_amount, - Percent::from_percent(5) * project_metadata.total_allocation_size + Percent::from_percent(10) * project_metadata.total_allocation_size ); assert_eq!(normalize_price(stored_bids[3].original_ct_usd_price), PriceOf::::from_float(1.2)); assert_eq!( stored_bids[3].original_ct_amount, - Percent::from_percent(5) * project_metadata.total_allocation_size + Percent::from_percent(10) * project_metadata.total_allocation_size ); assert_eq!(normalize_price(stored_bids[4].original_ct_usd_price), PriceOf::::from_float(1.3)); @@ -683,7 +625,7 @@ mod bid_extrinsic { ); let current_bucket = inst.execute(|| Buckets::::get(project_id)).unwrap(); assert_eq!(normalize_price(current_bucket.current_price), PriceOf::::from_float(1.3)); - assert_eq!(current_bucket.amount_left, Percent::from_percent(2) * project_metadata.total_allocation_size); + assert_eq!(current_bucket.amount_left, Percent::from_percent(7) * project_metadata.total_allocation_size); assert_eq!(normalize_price(current_bucket.delta_price), PriceOf::::from_float(0.1)); } @@ -692,11 +634,16 @@ mod bid_extrinsic { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let issuer = ISSUER_1; let project_metadata = default_project_metadata(issuer); - let project_id = - inst.create_auctioning_project(project_metadata.clone(), issuer, None, default_evaluations()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); - let bid = - BidParams::from((BIDDER_4, 500 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT)); + let bid = BidParams::from(( + BIDDER_4, + Retail, + 500 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + )); let plmc_required = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( &vec![bid.clone()], project_metadata.clone(), @@ -742,7 +689,6 @@ mod bid_extrinsic { )); }); - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); @@ -773,11 +719,16 @@ mod bid_extrinsic { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let issuer = ISSUER_1; let project_metadata = default_project_metadata(issuer); - let project_id = - inst.create_auctioning_project(project_metadata.clone(), issuer, None, default_evaluations()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); - let bid = - BidParams::from((BIDDER_4, 500 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::USDT)); + let bid = BidParams::from(( + BIDDER_4, + Retail, + 300_000 * CT_UNIT, + ParticipationMode::Classic(5u8), + AcceptedFundingAsset::USDT, + )); let plmc_required = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( &vec![bid.clone()], project_metadata.clone(), @@ -823,25 +774,6 @@ mod bid_extrinsic { )); }); - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - let contributions = inst.generate_contributions_from_total_ct_percent( - project_metadata.clone(), - 90u8, - default_weights(), - default_community_contributors(), - default_modes(), - ); - let plmc_required = inst.calculate_contributed_plmc_spent(contributions.clone(), wap); - inst.mint_plmc_ed_if_required(contributions.accounts()); - inst.mint_plmc_to(plmc_required.clone()); - - let usdt_required = inst.calculate_contributed_funding_asset_spent(contributions.clone(), wap); - inst.mint_funding_asset_ed_if_required(contributions.to_account_asset_map()); - inst.mint_funding_asset_to(usdt_required.clone()); - inst.contribute_for_users(project_id, contributions).unwrap(); - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); @@ -900,8 +832,7 @@ mod bid_extrinsic { ) .unwrap(); - let evaluations = - inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); let otm_multiplier: MultiplierOf = @@ -995,7 +926,6 @@ mod bid_extrinsic { Perquintill::from_float(0.999) ); - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(_))); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); inst.settle_project(project_id, true); @@ -1058,8 +988,7 @@ mod bid_extrinsic { ) .unwrap(); - let evaluations = - inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); let otm_multiplier: MultiplierOf = @@ -1151,7 +1080,6 @@ mod bid_extrinsic { Perquintill::from_float(0.999) ); - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); inst.settle_project(project_id, true); @@ -1198,8 +1126,15 @@ mod bid_extrinsic { let mut project_metadata = default_project_metadata(ISSUER_1); project_metadata.participants_account_type = ParticipantsAccountType::Ethereum; + let mut eth_evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + for eval in &mut eth_evaluations { + let mut key = [0u8; 20]; + key[..8].copy_from_slice(&eval.account.to_le_bytes()); + eval.receiving_account = Junction::AccountKey20 { network: None, key }; + } + let project_id = - inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_eth_evaluations()); + inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, eth_evaluations.clone()); let jwt = get_mock_jwt_with_cid( BIDDER_1, InvestorType::Professional, @@ -1208,7 +1143,8 @@ mod bid_extrinsic { ); let (eth_acc, eth_sig) = inst.eth_key_and_sig_from("//BIDDER1", project_id, BIDDER_1); - let bid = BidParams::from((BIDDER_1, 500 * CT_UNIT, ParticipationMode::OTM, AcceptedFundingAsset::USDT)); + let bid = + BidParams::from((BIDDER_1, Retail, 500 * CT_UNIT, ParticipationMode::OTM, AcceptedFundingAsset::USDT)); let mint_amount = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( &vec![bid], project_metadata.clone(), @@ -1236,9 +1172,9 @@ mod bid_extrinsic { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_metadata = default_project_metadata(ISSUER_1); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let project_id = - inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); + let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); let jwt = get_mock_jwt_with_cid( BIDDER_1, InvestorType::Professional, @@ -1247,7 +1183,8 @@ mod bid_extrinsic { ); let (dot_acc, dot_sig) = inst.dot_key_and_sig_from("//BIDDER1", project_id, BIDDER_1); - let bid = BidParams::from((BIDDER_1, 500 * CT_UNIT, ParticipationMode::OTM, AcceptedFundingAsset::USDT)); + let bid = + BidParams::from((BIDDER_1, Retail, 500 * CT_UNIT, ParticipationMode::OTM, AcceptedFundingAsset::USDT)); let mint_amount = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( &vec![bid], project_metadata.clone(), @@ -1280,11 +1217,12 @@ mod bid_extrinsic { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let issuer = ISSUER_1; let project_metadata = default_project_metadata(issuer); - let mut evaluations = default_evaluations(); + let mut evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let evaluator_bidder = 69; let evaluation_amount = 420 * USD_UNIT; let evaluator_bid = BidParams::from(( evaluator_bidder, + Retail, 600 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, @@ -1312,13 +1250,14 @@ mod bid_extrinsic { let project_metadata_1 = default_project_metadata(ISSUER_1); let project_metadata_2 = default_project_metadata(ISSUER_2); - let mut evaluations_1 = default_evaluations(); - let evaluations_2 = default_evaluations(); + let mut evaluations_1 = inst.generate_successful_evaluations(project_metadata_1.clone(), 5); + let evaluations_2 = evaluations_1.clone(); let evaluator_bidder = 69; let evaluation_amount = 420 * USD_UNIT; let evaluator_bid = BidParams::from(( evaluator_bidder, + Retail, 600 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, @@ -1403,8 +1342,7 @@ mod bid_extrinsic { project_metadata.mainnet_token_max_supply = 1_000_000_000 * CT_UNIT; project_metadata.total_allocation_size = 100_000_000 * CT_UNIT; - let evaluations = - inst.generate_successful_evaluations(project_metadata.clone(), vec![EVALUATOR_1], vec![100u8]); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let max_bids_per_project: u32 = ::MaxBidsPerProject::get(); let bids = (0u32..max_bids_per_project - 1).map(|i| (i as u64 + 420, 5000 * CT_UNIT).into()).collect_vec(); @@ -1431,6 +1369,7 @@ mod bid_extrinsic { // This bid should be split in 2, but the second one should fail, making the whole extrinsic fail and roll back storage let failing_bid = BidParams::::from(( BIDDER_1, + Retail, remaining_ct + 5000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, @@ -1512,8 +1451,7 @@ mod bid_extrinsic { project_metadata.mainnet_token_max_supply = 1_000_000_000 * CT_UNIT; project_metadata.total_allocation_size = 100_000_000 * CT_UNIT; - let evaluations = - inst.generate_successful_evaluations(project_metadata.clone(), vec![EVALUATOR_1], vec![100u8]); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let max_bids_per_user: u32 = ::MaxBidsPerUser::get(); let bids = (0u32..max_bids_per_user - 1u32).map(|_| (BIDDER_1, 5000 * CT_UNIT).into()).collect_vec(); @@ -1540,6 +1478,7 @@ mod bid_extrinsic { // This bid should be split in 2, but the second one should fail, making the whole extrinsic fail and roll back storage let failing_bid = BidParams::::from(( BIDDER_1, + Retail, remaining_ct + 5000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, @@ -1620,10 +1559,11 @@ mod bid_extrinsic { project_metadata.bidding_ticket_sizes = BiddingTicketSizes { professional: TicketSize::new(8_000 * USD_UNIT, None), institutional: TicketSize::new(20_000 * USD_UNIT, None), + retail: TicketSize::new(100 * USD_UNIT, None), phantom: Default::default(), }; - let evaluations = default_evaluations(); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations.clone()); @@ -1675,10 +1615,11 @@ mod bid_extrinsic { fn ticket_size_minimums_use_current_bucket_price() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let mut project_metadata = default_project_metadata(ISSUER_1); - project_metadata.total_allocation_size = 100_000 * CT_UNIT; + project_metadata.total_allocation_size = 50_000 * CT_UNIT; project_metadata.bidding_ticket_sizes = BiddingTicketSizes { professional: TicketSize::new(8_000 * USD_UNIT, None), institutional: TicketSize::new(20_000 * USD_UNIT, None), + retail: TicketSize::new(100 * USD_UNIT, None), phantom: Default::default(), }; project_metadata.minimum_price = PriceProviderOf::::calculate_decimals_aware_price( @@ -1688,7 +1629,7 @@ mod bid_extrinsic { ) .unwrap(); - let evaluations = default_evaluations(); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations.clone()); @@ -1721,7 +1662,9 @@ mod bid_extrinsic { .checked_mul_int(8000 * USD_UNIT) // add 1 because result could be .99999 of what we expect .unwrap() + 1; + assert!(smallest_ct_amount_at_8k_usd < 8000 * CT_UNIT); + inst.execute(|| { assert_ok!(Pallet::::do_bid(DoBidParams:: { bidder: BIDDER_2, @@ -1735,6 +1678,7 @@ mod bid_extrinsic { receiving_account: polkadot_junction!(BIDDER_2) })); }); + let smallest_ct_amount_at_20k_usd = bucket_increase_price .reciprocal() .unwrap() @@ -1764,15 +1708,10 @@ mod bid_extrinsic { project_metadata.bidding_ticket_sizes = BiddingTicketSizes { professional: TicketSize::new(8_000 * USD_UNIT, Some(100_000 * USD_UNIT)), institutional: TicketSize::new(20_000 * USD_UNIT, Some(500_000 * USD_UNIT)), + retail: TicketSize::new(100 * USD_UNIT, None), phantom: Default::default(), }; - project_metadata.contributing_ticket_sizes = ContributingTicketSizes { - retail: TicketSize::new(USD_UNIT, Some(100_000 * USD_UNIT)), - professional: TicketSize::new(USD_UNIT, Some(20_000 * USD_UNIT)), - institutional: TicketSize::new(USD_UNIT, Some(50_000 * USD_UNIT)), - phantom: Default::default(), - }; - let evaluations = default_evaluations(); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations.clone()); @@ -1892,8 +1831,8 @@ mod bid_extrinsic { fn issuer_cannot_bid_his_project() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_metadata = default_project_metadata(ISSUER_1); - let project_id = - inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); assert_err!( inst.execute(|| crate::Pallet::::do_bid(DoBidParams:: { bidder: ISSUER_1, @@ -1913,11 +1852,14 @@ mod bid_extrinsic { #[test] fn bid_with_asset_not_accepted() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = - inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.participation_currencies = bounded_vec![USDT]; + + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); let bids = [BidParams::::from(( BIDDER_1, + Retail, 10_000, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDC, @@ -1946,8 +1888,8 @@ mod bid_extrinsic { fn wrong_policy_on_jwt() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_metadata = default_project_metadata(ISSUER_1); - let project_id = - inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); inst.execute(|| { assert_noop!( @@ -1973,8 +1915,8 @@ mod bid_extrinsic { fn bid_after_end_block_before_transitioning_project() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_metadata = default_project_metadata(ISSUER_1); - let project_id = - inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); let end_block = inst.get_project_details(project_id).round_duration.end.unwrap(); inst.jump_to_block(end_block + 1); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionRound); @@ -2039,18 +1981,12 @@ mod end_auction_extrinsic { decimals: CT_DECIMALS, }, mainnet_token_max_supply: 8_000_000 * CT_UNIT, - total_allocation_size: 100_000 * CT_UNIT, - auction_round_allocation_percentage: Percent::from_percent(50u8), + total_allocation_size: 50_000 * CT_UNIT, minimum_price: decimal_aware_price, bidding_ticket_sizes: BiddingTicketSizes { - professional: TicketSize::new(5000 * USD_UNIT, None), - institutional: TicketSize::new(5000 * USD_UNIT, None), - phantom: Default::default(), - }, - contributing_ticket_sizes: ContributingTicketSizes { - retail: TicketSize::new(USD_UNIT, None), - professional: TicketSize::new(USD_UNIT, None), - institutional: TicketSize::new(USD_UNIT, None), + professional: TicketSize::new(100 * USD_UNIT, None), + institutional: TicketSize::new(100 * USD_UNIT, None), + retail: TicketSize::new(100 * USD_UNIT, None), phantom: Default::default(), }, participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), @@ -2075,8 +2011,8 @@ mod end_auction_extrinsic { inst.mint_plmc_to(plmc_fundings); inst.mint_funding_asset_to(usdt_fundings); - let project_id = - inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); let bids = vec![ (ADAM, 10_000 * CT_UNIT).into(), @@ -2089,7 +2025,9 @@ mod end_auction_extrinsic { inst.bid_for_users(project_id, bids).unwrap(); - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); + let next_state = inst.go_to_next_state(project_id); + + assert!(matches!(next_state, ProjectStatus::FundingSuccessful)); let token_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); let normalized_wap = @@ -2112,9 +2050,8 @@ mod end_auction_extrinsic { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let issuer = ISSUER_1; let mut project_metadata = default_project_metadata(issuer); - project_metadata.total_allocation_size = 100_000 * CT_UNIT; + project_metadata.total_allocation_size = 50_000 * CT_UNIT; project_metadata.mainnet_token_max_supply = project_metadata.total_allocation_size; - project_metadata.auction_round_allocation_percentage = Percent::from_percent(50); project_metadata.minimum_price = ConstPriceProvider::calculate_decimals_aware_price( FixedU128::from_float(10.0f64), USD_DECIMALS, @@ -2124,36 +2061,45 @@ mod end_auction_extrinsic { project_metadata.participation_currencies = bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT]; - let evaluations = default_evaluations(); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); // We use multiplier > 1 so after settlement, only the refunds defined above are done. The rest will be done // through the linear release pallet let bid_1 = BidParams::from(( BIDDER_1, + Retail, 5000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::USDT, )); let bid_2 = BidParams::from(( BIDDER_2, + Institutional, 40_000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::USDC, )); let bid_3 = BidParams::from(( BIDDER_1, + Professional, 10_000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::DOT, )); let bid_4 = BidParams::from(( BIDDER_3, + Retail, 6000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::USDT, )); - let bid_5 = - BidParams::from((BIDDER_4, 2000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::DOT)); + let bid_5 = BidParams::from(( + BIDDER_4, + Retail, + 2000 * CT_UNIT, + ParticipationMode::Classic(5u8), + AcceptedFundingAsset::DOT, + )); // post bucketing, the bids look like this: // (BIDDER_1, 5k) - (BIDDER_2, 40k) - (BIDDER_1, 5k) - (BIDDER_1, 5k) - (BIDDER_3 - 5k) - (BIDDER_3 - 1k) - (BIDDER_4 - 2k) // | -------------------- 10USD ----------------------|---- 11 USD ---|---- 12 USD ----|----------- 13 USD -------------| @@ -2192,7 +2138,7 @@ mod end_auction_extrinsic { ]); inst.do_reserved_plmc_assertions(plmc_amounts.clone(), HoldReason::Participation.into()); - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(_))); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful)); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); let returned_auction_plmc = @@ -2244,7 +2190,6 @@ mod end_auction_extrinsic { inst.execute(|| Bids::::get((project_id, BIDDER_2, 1)).unwrap()).status, BidStatus::PartiallyAccepted(32_000 * CT_UNIT) ); - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); inst.settle_project(project_id, true); @@ -2273,7 +2218,7 @@ mod end_auction_extrinsic { let accounts = [ADAM, TOM, SOFIA, FRED, ANNA, DAMIAN]; let mut project_metadata = default_project_metadata(ISSUER_1); - project_metadata.total_allocation_size = 100_000 * CT_UNIT; + project_metadata.total_allocation_size = 50_000 * CT_UNIT; project_metadata.participation_currencies = bounded_vec![ AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, @@ -2310,20 +2255,22 @@ mod end_auction_extrinsic { inst.mint_plmc_to(plmc_fundings); inst.mint_funding_asset_to(funding_asset_mints); - let project_id = inst.create_auctioning_project(project_metadata, ISSUER_1, None, default_evaluations()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = inst.create_auctioning_project(project_metadata, ISSUER_1, None, evaluations); let bids = vec![ - (ADAM, 10_000 * CT_UNIT, ParticipationMode::Classic(1), AcceptedFundingAsset::USDT).into(), - (TOM, 20_000 * CT_UNIT, ParticipationMode::Classic(1), AcceptedFundingAsset::USDC).into(), - (SOFIA, 20_000 * CT_UNIT, ParticipationMode::Classic(1), AcceptedFundingAsset::DOT).into(), - (FRED, 10_000 * CT_UNIT, ParticipationMode::Classic(1), AcceptedFundingAsset::WETH).into(), - (ANNA, 5_000 * CT_UNIT, ParticipationMode::Classic(1), AcceptedFundingAsset::USDT).into(), - (DAMIAN, 5_000 * CT_UNIT, ParticipationMode::Classic(1), AcceptedFundingAsset::USDC).into(), + (ADAM, Retail, 10_000 * CT_UNIT, ParticipationMode::Classic(1), AcceptedFundingAsset::USDT).into(), + (TOM, Professional, 20_000 * CT_UNIT, ParticipationMode::Classic(1), AcceptedFundingAsset::USDC).into(), + (SOFIA, Retail, 20_000 * CT_UNIT, ParticipationMode::Classic(1), AcceptedFundingAsset::DOT).into(), + (FRED, Institutional, 10_000 * CT_UNIT, ParticipationMode::Classic(1), AcceptedFundingAsset::WETH) + .into(), + (ANNA, Retail, 5_000 * CT_UNIT, ParticipationMode::Classic(1), AcceptedFundingAsset::USDT).into(), + (DAMIAN, Retail, 5_000 * CT_UNIT, ParticipationMode::Classic(1), AcceptedFundingAsset::USDC).into(), ]; inst.bid_for_users(project_id, bids).unwrap(); - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful)); let token_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); let normalized_wap = @@ -2339,48 +2286,4 @@ mod end_auction_extrinsic { ); } } - - #[cfg(test)] - mod failure { - use super::*; - - #[test] - fn cannot_be_called_early() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = - inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); - - let project_details = inst.get_project_details(project_id); - let now = inst.current_block(); - assert!(now < project_details.round_duration.end().unwrap()); - - inst.execute(|| { - assert_noop!( - PolimecFunding::end_auction(RuntimeOrigin::signed(420), project_id,), - Error::::TooEarlyForRound - ); - }); - } - - #[test] - fn cannot_be_called_twice() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = - inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); - - let project_details = inst.get_project_details(project_id); - - inst.jump_to_block(project_details.round_duration.end().unwrap()); - - inst.execute(|| { - assert_ok!(PolimecFunding::end_auction(RuntimeOrigin::signed(420), project_id,)); - assert_noop!( - PolimecFunding::end_auction(RuntimeOrigin::signed(420), project_id,), - Error::::IncorrectRound - ); - }); - } - } } diff --git a/pallets/funding/src/tests/4_contribution.rs b/pallets/funding/src/tests/4_contribution.rs deleted file mode 100644 index 4aa980bde..000000000 --- a/pallets/funding/src/tests/4_contribution.rs +++ /dev/null @@ -1,2317 +0,0 @@ -use super::*; -use polimec_common::assets::AcceptedFundingAsset::{self, DOT, USDC, USDT}; - -#[cfg(test)] -mod round_flow { - use super::*; - - #[cfg(test)] - mod success { - use super::*; - use sp_runtime::bounded_vec; - use std::collections::HashSet; - - #[test] - fn contribution_round_completed() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let _ = inst.create_finished_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), - ); - } - - #[test] - fn multiple_contribution_projects_completed() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project1 = default_project_metadata(ISSUER_1); - let project2 = default_project_metadata(ISSUER_2); - let project3 = default_project_metadata(ISSUER_3); - let project4 = default_project_metadata(ISSUER_4); - let evaluations = default_evaluations(); - let bids = default_bids(); - let community_buys = default_community_contributions(); - - inst.create_remainder_contributing_project( - project1, - ISSUER_1, - None, - evaluations.clone(), - bids.clone(), - community_buys.clone(), - ); - inst.create_remainder_contributing_project( - project2, - ISSUER_2, - None, - evaluations.clone(), - bids.clone(), - community_buys.clone(), - ); - inst.create_remainder_contributing_project( - project3, - ISSUER_3, - None, - evaluations.clone(), - bids.clone(), - community_buys.clone(), - ); - inst.create_remainder_contributing_project(project4, ISSUER_4, None, evaluations, bids, community_buys); - } - - #[test] - fn contribution_round_ends_on_all_ct_sold_exact() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let bids = - vec![BidParams::from((BIDDER_1, 40_000 * CT_UNIT)), BidParams::from((BIDDER_2, 10_000 * CT_UNIT))]; - let project_id = inst.create_community_contributing_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - default_evaluations(), - bids, - ); - const BOB: AccountId = 808; - - let remaining_ct = inst.get_project_details(project_id).remaining_contribution_tokens; - let ct_price = inst.get_project_details(project_id).weighted_average_price.expect("CT Price should exist"); - - let contributions = vec![ContributionParams::from(( - BOB, - remaining_ct, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - ))]; - let plmc_fundings = inst.calculate_contributed_plmc_spent(contributions.clone(), ct_price); - let foreign_asset_fundings = - inst.calculate_contributed_funding_asset_spent(contributions.clone(), ct_price); - - inst.mint_plmc_ed_if_required(contributions.accounts()); - inst.mint_funding_asset_ed_if_required(contributions.to_account_asset_map()); - - let prev_free_funding_asset_balances = - inst.get_free_funding_asset_balances_for(contributions.to_account_asset_map()); - - inst.mint_plmc_to(plmc_fundings.clone()); - inst.mint_funding_asset_to(foreign_asset_fundings.clone()); - - // Buy remaining CTs - inst.contribute_for_users(project_id, contributions) - .expect("The Buyer should be able to buy the exact amount of remaining CTs"); - - // Check remaining CTs is 0 - assert_eq!( - inst.get_project_details(project_id).remaining_contribution_tokens, - 0, - "There are still remaining CTs" - ); - - // Check project is in FundingEnded state - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); - - // inst.do_free_plmc_assertions(plmc_existential_deposits); - inst.do_free_funding_asset_assertions(prev_free_funding_asset_balances); - inst.do_reserved_plmc_assertions(plmc_fundings, HoldReason::Participation.into()); - } - - #[test] - fn round_has_total_ct_allocation_minus_auction_sold() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - ); - let project_details = inst.get_project_details(project_id); - let bid_ct_sold: Balance = inst.execute(|| { - Bids::::iter_prefix_values((project_id,)).fold(Zero::zero(), |acc, bid| { - assert_eq!(bid.status, BidStatus::Accepted); - acc + bid.original_ct_amount - }) - }); - assert_eq!( - project_details.remaining_contribution_tokens, - project_metadata.total_allocation_size - bid_ct_sold - ); - - let contributions = vec![(BUYER_1, project_details.remaining_contribution_tokens).into()]; - - let plmc_contribution_funding = inst.calculate_contributed_plmc_spent( - contributions.clone(), - project_details.weighted_average_price.unwrap(), - ); - - inst.mint_plmc_ed_if_required(contributions.accounts()); - inst.mint_plmc_to(plmc_contribution_funding.clone()); - - let foreign_asset_contribution_funding = inst.calculate_contributed_funding_asset_spent( - contributions.clone(), - project_details.weighted_average_price.unwrap(), - ); - inst.mint_funding_asset_ed_if_required(contributions.to_account_asset_map()); - inst.mint_funding_asset_to(foreign_asset_contribution_funding.clone()); - - inst.contribute_for_users(project_id, contributions).unwrap(); - - assert_eq!(inst.get_project_details(project_id).remaining_contribution_tokens, 0); - } - - #[test] - fn different_decimals_ct_works_as_expected() { - // Setup some base values to compare different decimals - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let ed = inst.get_ed(); - let default_project_metadata = default_project_metadata(ISSUER_1); - let original_decimal_aware_price = default_project_metadata.minimum_price; - let original_price = ::PriceProvider::convert_back_to_normal_price( - original_decimal_aware_price, - USD_DECIMALS, - default_project_metadata.token_information.decimals, - ) - .unwrap(); - let usable_plmc_price = inst.execute(|| { - ::PriceProvider::get_decimals_aware_price( - Location::here(), - USD_DECIMALS, - PLMC_DECIMALS, - ) - .unwrap() - }); - let usdt_price = inst.execute(|| { - PolimecFunding::get_decimals_aware_funding_asset_price(&AcceptedFundingAsset::USDT).unwrap() - }); - let usdc_price = inst.execute(|| { - PolimecFunding::get_decimals_aware_funding_asset_price(&AcceptedFundingAsset::USDC).unwrap() - }); - let dot_price = inst.execute(|| { - PolimecFunding::get_decimals_aware_funding_asset_price(&AcceptedFundingAsset::DOT).unwrap() - }); - - let mut funding_assets_cycle = - vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT] - .into_iter() - .cycle(); - - let mut total_fundings_ct = Vec::new(); - let mut total_fundings_usd = Vec::new(); - let mut total_fundings_plmc = Vec::new(); - - let mut decimal_test = |decimals: u8| { - let funding_asset = funding_assets_cycle.next().unwrap(); - let funding_asset_usd_price = match funding_asset { - AcceptedFundingAsset::USDT => usdt_price, - AcceptedFundingAsset::USDC => usdc_price, - AcceptedFundingAsset::DOT => dot_price, - AcceptedFundingAsset::WETH => todo!(), - }; - - let mut project_metadata = default_project_metadata.clone(); - project_metadata.token_information.decimals = decimals; - project_metadata.minimum_price = - ::PriceProvider::calculate_decimals_aware_price( - original_price, - USD_DECIMALS, - decimals, - ) - .unwrap(); - - project_metadata.total_allocation_size = 1_000_000 * 10u128.pow(decimals as u32); - project_metadata.mainnet_token_max_supply = project_metadata.total_allocation_size; - project_metadata.participation_currencies = bounded_vec!(funding_asset); - - let issuer: AccountIdOf = (10_000 + inst.get_new_nonce()).try_into().unwrap(); - let evaluations = inst.generate_successful_evaluations( - project_metadata.clone(), - default_evaluators(), - default_weights(), - ); - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - issuer, - None, - evaluations, - vec![], - ); - - let total_funding_ct = project_metadata.total_allocation_size; - let total_funding_usd = project_metadata.minimum_price.saturating_mul_int(total_funding_ct); - let total_funding_plmc = usable_plmc_price.reciprocal().unwrap().saturating_mul_int(total_funding_usd); - let total_funding_funding_asset = - funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(total_funding_usd); - - total_fundings_ct.push(total_funding_ct); - total_fundings_usd.push(total_funding_usd); - total_fundings_plmc.push(total_funding_plmc); - - // Every project should want to raise 10MM USD - assert_eq!(total_funding_usd, 10_000_000 * USD_UNIT); - - // Every project should produce the same PLMC bond when having the full funding at multiplier 1. - assert_close_enough!(total_funding_plmc, 1_190_476 * PLMC, Perquintill::from_float(0.999)); - - // Every project should have a different amount of CTs to raise, depending on their decimals - assert_eq!(total_funding_ct, 1_000_000 * 10u128.pow(decimals as u32)); - - // Buying all the remaining tokens. This is a fixed USD value, but the extrinsic amount depends on CT decimals. - inst.mint_plmc_ed_if_required(vec![BUYER_1]); - inst.mint_funding_asset_ed_if_required(vec![(BUYER_1, funding_asset.id())]); - inst.mint_plmc_to(vec![UserToPLMCBalance::new(BUYER_1, total_funding_plmc + ed)]); - inst.mint_funding_asset_to(vec![UserToFundingAsset::new( - BUYER_1, - total_funding_funding_asset, - funding_asset.id(), - )]); - - assert_ok!(inst.execute(|| PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_1), - get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Retail, - generate_did_from_account(BUYER_1), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - total_funding_ct, - ParticipationMode::Classic(1), - funding_asset, - ))); - - // the remaining tokens should be zero - assert_eq!(inst.get_project_details(project_id).remaining_contribution_tokens, 0); - - // We can successfully finish the project - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); - }; - - for decimals in 6..=18 { - decimal_test(decimals); - } - - // Since we use the same original price and allocation size and adjust for decimals, - // the USD and PLMC amounts should be the same - assert!(total_fundings_usd.iter().all(|x| *x == total_fundings_usd[0])); - assert!(total_fundings_plmc.iter().all(|x| *x == total_fundings_plmc[0])); - - // CT amounts however should be different from each other - let mut hash_set_1 = HashSet::new(); - for amount in total_fundings_ct { - assert!(!hash_set_1.contains(&amount)); - hash_set_1.insert(amount); - } - } - } -} - -#[cfg(test)] -mod contribute_extrinsic { - use super::*; - - #[cfg(test)] - mod success { - use super::*; - use frame_support::{dispatch::DispatchResultWithPostInfo, traits::fungible::InspectFreeze}; - - #[test] - fn evaluation_bond_counts_towards_contribution() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - - const BOB: AccountId = 42069; - const CARL: AccountId = 420691; - let mut evaluations = default_evaluations(); - let bob_evaluation: EvaluationParams = (BOB, 1337 * USD_UNIT).into(); - let carl_evaluation: EvaluationParams = (CARL, 1337 * USD_UNIT).into(); - evaluations.push(bob_evaluation.clone()); - evaluations.push(carl_evaluation.clone()); - - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - evaluations, - default_bids(), - ); - let ct_price = inst.get_project_details(project_id).weighted_average_price.unwrap(); - let plmc_price = ::PriceProvider::get_decimals_aware_price( - Location::here(), - USD_DECIMALS, - PLMC_DECIMALS, - ) - .unwrap(); - - let evaluation_plmc_bond = inst.execute(|| Balances::balance_on_hold(&HoldReason::Evaluation.into(), &BOB)); - let slashable_plmc = ::EvaluatorSlash::get() * evaluation_plmc_bond; - let usable_plmc = evaluation_plmc_bond - slashable_plmc; - - let usable_usd = plmc_price.checked_mul_int(usable_plmc).unwrap(); - let slashable_usd = plmc_price.checked_mul_int(slashable_plmc).unwrap(); - - let usable_ct = ct_price.reciprocal().unwrap().saturating_mul_int(usable_usd); - let slashable_ct = ct_price.reciprocal().unwrap().saturating_mul_int(slashable_usd); - - // Can't contribute with only the evaluation bond - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BOB), - get_mock_jwt_with_cid( - BOB, - InvestorType::Retail, - generate_did_from_account(BOB), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - usable_ct + slashable_ct, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - ), - Error::::ParticipantNotEnoughFunds - ); - }); - - // Can partially use the usable evaluation bond (half in this case) - let contribution_usdt = - inst.calculate_contributed_funding_asset_spent(vec![(BOB, usable_ct / 2).into()], ct_price); - inst.mint_funding_asset_ed_if_required(vec![(BOB, AcceptedFundingAsset::USDT.id())]); - inst.mint_funding_asset_to(contribution_usdt.clone()); - inst.execute(|| { - assert_ok!(Pallet::::contribute( - RuntimeOrigin::signed(BOB), - get_mock_jwt_with_cid( - BOB, - InvestorType::Retail, - generate_did_from_account(BOB), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - usable_ct / 2, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - )); - }); - - // Can use the full evaluation bond - let contribution_usdt = - inst.calculate_contributed_funding_asset_spent(vec![(CARL, usable_ct).into()], ct_price); - inst.mint_funding_asset_ed_if_required(vec![(CARL, AcceptedFundingAsset::USDT.id())]); - inst.mint_funding_asset_to(contribution_usdt.clone()); - inst.execute(|| { - assert_ok!(Pallet::::contribute( - RuntimeOrigin::signed(CARL), - get_mock_jwt_with_cid( - CARL, - InvestorType::Retail, - generate_did_from_account(CARL), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - usable_ct, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - )); - }); - } - - #[test] - fn evaluation_bond_used_on_failed_bid_can_be_reused_on_contribution() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let bob = 42069; - let project_metadata = default_project_metadata(ISSUER_1); - - // An evaluator that did a bid but it was not accepted at the end of the auction, can use that PLMC for contributing - let mut evaluations = default_evaluations(); - let bob_evaluation = (bob, 10_000 * USD_UNIT).into(); - evaluations.push(bob_evaluation); - - let plmc_price = ::PriceProvider::get_decimals_aware_price( - Location::here(), - USD_DECIMALS, - PLMC_DECIMALS, - ) - .unwrap(); - - let project_id = - inst.create_auctioning_project(default_project_metadata(ISSUER_2), ISSUER_2, None, evaluations); - let bucket = inst.execute(|| Buckets::::get(project_id).unwrap()); - let first_bucket = bucket.amount_left; - - // Failed bids can only happen on oversubscription. We want Bob's bid as the last one of the first bucket - let bob_plmc_bond = inst.execute(|| Balances::balance_on_hold(&HoldReason::Evaluation.into(), &bob)); - let usable_bond = bob_plmc_bond - ::EvaluatorSlash::get() * bob_plmc_bond; - let usable_usd = plmc_price.saturating_mul_int(usable_bond); - let usable_bob_ct = bucket.current_price.reciprocal().unwrap().saturating_mul_int(usable_usd); - - let bids = vec![ - (BIDDER_1, first_bucket - usable_bob_ct).into(), - (bob, usable_bob_ct).into(), - (BIDDER_2, usable_bob_ct).into(), - ]; - - let mut bids_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &bids, - project_metadata.clone(), - None, - ); - // We don't want bob to get any PLMC - bids_plmc.remove(2); - - inst.mint_plmc_ed_if_required(bids.accounts()); - inst.mint_plmc_to(bids_plmc.clone()); - assert_eq!(inst.execute(|| Balances::free_balance(bob)), inst.get_ed()); - - let bids_funding_assets = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &bids, - project_metadata.clone(), - None, - ); - inst.mint_funding_asset_ed_if_required(bids.to_account_asset_map()); - inst.mint_funding_asset_to(bids_funding_assets.clone()); - - inst.bid_for_users(project_id, bids).unwrap(); - - assert_eq!(inst.execute(|| Balances::free_balance(bob)), inst.get_ed()); - - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); - - // Free up the plmc and usdt from the failed bid: - inst.execute(|| { - PolimecFunding::settle_bid(RuntimeOrigin::signed(bob), project_id, bob, 1).unwrap(); - }); - let bob_plmc = inst.execute(|| Balances::free_balance(bob)); - assert_close_enough!(bob_plmc, inst.get_ed() + usable_bond, Perquintill::from_float(0.9999)); - - // Calculate how much CTs can bob buy with his evaluation PLMC bond - let usable_bob_plmc = bob_plmc - inst.get_ed(); - let usable_bob_usd = plmc_price.saturating_mul_int(usable_bob_plmc); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - let usable_bob_ct = wap.reciprocal().unwrap().saturating_mul_int(usable_bob_usd); - - let bob_contribution = (bob, usable_bob_ct).into(); - let contribution_usdt = inst.calculate_contributed_funding_asset_spent(vec![bob_contribution], wap); - inst.mint_funding_asset_to(contribution_usdt.clone()); - - inst.execute(|| { - assert_ok!(Pallet::::contribute( - RuntimeOrigin::signed(bob), - get_mock_jwt_with_cid( - bob, - InvestorType::Retail, - generate_did_from_account(bob), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - usable_bob_ct, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - )); - }); - - // Check he had no free PLMC - assert_close_enough!( - inst.execute(|| Balances::free_balance(bob)), - inst.get_ed(), - Perquintill::from_float(0.999) - ); - } - - #[test] - fn contribute_with_multiple_currencies() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let mut project_metadata_all = default_project_metadata(ISSUER_1); - project_metadata_all.participation_currencies = - vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT] - .try_into() - .unwrap(); - - let project_id = inst.create_community_contributing_project( - project_metadata_all.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - ); - let usdt_contribution = ContributionParams::from(( - BUYER_1, - 10_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )); - let usdc_contribution = ContributionParams::from(( - BUYER_2, - 10_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDC, - )); - let dot_contribution = ContributionParams::from(( - BUYER_3, - 10_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::DOT, - )); - - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - let plmc_fundings = inst.calculate_contributed_plmc_spent( - vec![usdt_contribution.clone(), usdc_contribution.clone(), dot_contribution.clone()], - wap, - ); - let plmc_existential_deposits = plmc_fundings.accounts().existential_deposits(); - - let plmc_all_mints = - inst.generic_map_operation(vec![plmc_fundings, plmc_existential_deposits], MergeOperation::Add); - inst.mint_plmc_ed_if_required(vec![BUYER_1, BUYER_2, BUYER_3]); - inst.mint_plmc_to(plmc_all_mints.clone()); - - let asset_hub_fundings = inst.calculate_contributed_funding_asset_spent( - vec![usdt_contribution.clone(), usdc_contribution.clone(), dot_contribution.clone()], - wap, - ); - inst.mint_funding_asset_ed_if_required(vec![ - (BUYER_1, USDT.id()), - (BUYER_2, USDC.id()), - (BUYER_3, DOT.id()), - ]); - inst.mint_funding_asset_to(asset_hub_fundings.clone()); - - assert_ok!(inst.contribute_for_users( - project_id, - vec![usdt_contribution.clone(), usdc_contribution.clone(), dot_contribution.clone()] - )); - } - - fn test_contribution_setup( - inst: &mut MockInstantiator, - project_id: ProjectId, - contributor: AccountIdOf, - investor_type: InvestorType, - u8_multiplier: u8, - ) -> DispatchResultWithPostInfo { - let project_policy = inst.get_project_metadata(project_id).policy_ipfs_cid.unwrap(); - let jwt = get_mock_jwt_with_cid( - contributor, - investor_type, - generate_did_from_account(contributor), - project_policy, - ); - let amount = 1000 * CT_UNIT; - - if u8_multiplier > 0 { - // We can't calculate exactly the amounts needed because the multipliers can be invalid - let necessary_plmc = vec![(contributor, 1_000_000 * PLMC).into()]; - let necessary_usdt = vec![(contributor, 1_000_000 * USDT_UNIT).into()]; - - inst.mint_plmc_to(necessary_plmc.clone()); - inst.mint_funding_asset_to(necessary_usdt.clone()); - } - inst.execute(|| { - Pallet::::contribute( - RuntimeOrigin::signed(contributor), - jwt, - project_id, - amount, - ParticipationMode::Classic(u8_multiplier), - AcceptedFundingAsset::USDT, - ) - }) - } - - #[test] - fn multiplier_limits() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let mut project_metadata = default_project_metadata(ISSUER_1); - project_metadata.mainnet_token_max_supply = 80_000_000 * CT_UNIT; - project_metadata.total_allocation_size = 10_000_000 * CT_UNIT; - project_metadata.bidding_ticket_sizes = BiddingTicketSizes { - professional: TicketSize::new(5000 * USD_UNIT, None), - institutional: TicketSize::new(5000 * USD_UNIT, None), - phantom: Default::default(), - }; - project_metadata.contributing_ticket_sizes = ContributingTicketSizes { - retail: TicketSize::new(USD_UNIT, None), - professional: TicketSize::new(USD_UNIT, None), - institutional: TicketSize::new(USD_UNIT, None), - phantom: Default::default(), - }; - let evaluations = - inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); - let bids = inst.generate_bids_from_total_ct_percent( - project_metadata.clone(), - 50, - default_weights(), - default_bidders(), - default_modes(), - ); - let project_id = - inst.create_community_contributing_project(project_metadata.clone(), ISSUER_1, None, evaluations, bids); - - // Retail contributions: 0x multiplier should fail - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Retail, 0), - Error::::ForbiddenMultiplier - ); - // Retail contributions: 1 - 5x multiplier should work - for multiplier in 1..=5u8 { - assert_ok!(test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Retail, multiplier)); - } - // Retail contributions: >= 6 multiplier should fail - for multiplier in 6..=30u8 { - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Retail, multiplier), - Error::::ForbiddenMultiplier - ); - } - - // Professional contributions: 0x multiplier should fail - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Professional, 0), - Error::::ForbiddenMultiplier - ); - // Professional contributions: 1 - 10x multiplier should work - for multiplier in 1..=10u8 { - assert_ok!(test_contribution_setup( - &mut inst, - project_id, - BUYER_1, - InvestorType::Professional, - multiplier - )); - } - // Professional contributions: >=11x multiplier should fail - for multiplier in 11..=50u8 { - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_1, InvestorType::Professional, multiplier), - Error::::ForbiddenMultiplier - ); - } - - // Institutional contributions: 0x multiplier should fail - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_2, InvestorType::Institutional, 0), - Error::::ForbiddenMultiplier - ); - // Institutional contributions: 1 - 25x multiplier should work - for multiplier in 1..=25u8 { - assert_ok!(test_contribution_setup( - &mut inst, - project_id, - BUYER_2, - InvestorType::Institutional, - multiplier - )); - } - // Institutional contributions: >=26x multiplier should fail - for multiplier in 26..=50u8 { - assert_err!( - test_contribution_setup(&mut inst, project_id, BUYER_2, InvestorType::Institutional, multiplier), - Error::::ForbiddenMultiplier - ); - } - } - - #[test] - fn did_with_losing_bid_can_contribute() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let mut evaluations = default_evaluations(); - evaluations.push((BIDDER_4, 1337 * USD_UNIT).into()); - - let successful_bids = vec![ - BidParams::from(( - BIDDER_1, - 400_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - BidParams::from(( - BIDDER_2, - 100_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - ]; - - // This bids should fill the first bucket. - let failing_bids_sold_out = vec![(BIDDER_6, 250_000 * CT_UNIT).into()]; - - let all_bids = failing_bids_sold_out.iter().chain(successful_bids.iter()).cloned().collect_vec(); - - let project_id = - inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); - - let plmc_fundings = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &all_bids.clone(), - project_metadata.clone(), - None, - ); - inst.mint_plmc_ed_if_required(plmc_fundings.accounts()); - inst.mint_plmc_to(plmc_fundings.clone()); - - let foreign_funding = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &all_bids.clone(), - project_metadata.clone(), - None, - ); - inst.mint_funding_asset_ed_if_required(all_bids.to_account_asset_map()); - inst.mint_funding_asset_to(foreign_funding.clone()); - - inst.bid_for_users(project_id, failing_bids_sold_out).unwrap(); - inst.bid_for_users(project_id, successful_bids).unwrap(); - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); - - // Some low amount of plmc and usdt to cover a purchase of 10CTs. - let plmc_mints = vec![ - (BIDDER_3, 42069 * PLMC).into(), - (BIDDER_4, 42069 * PLMC).into(), - (BIDDER_5, 42069 * PLMC).into(), - (BIDDER_6, 42069 * PLMC).into(), - (BUYER_3, 42069 * PLMC).into(), - (BUYER_4, 42069 * PLMC).into(), - (BUYER_5, 42069 * PLMC).into(), - (BUYER_6, 42069 * PLMC).into(), - ]; - inst.mint_plmc_to(plmc_mints); - let usdt_mints = vec![ - (BIDDER_3, 42069 * CT_UNIT).into(), - (BIDDER_4, 42069 * CT_UNIT).into(), - (BIDDER_5, 42069 * CT_UNIT).into(), - (BIDDER_6, 42069 * CT_UNIT).into(), - (BUYER_3, 42069 * CT_UNIT).into(), - (BUYER_4, 42069 * CT_UNIT).into(), - (BUYER_5, 42069 * CT_UNIT).into(), - (BUYER_6, 42069 * CT_UNIT).into(), - ]; - inst.mint_funding_asset_to(usdt_mints); - - let mut bid_should_succeed = |account, investor_type, did_acc| { - inst.execute(|| { - assert_ok!(Pallet::::contribute( - RuntimeOrigin::signed(account), - get_mock_jwt_with_cid( - account, - investor_type, - generate_did_from_account(did_acc), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - 10 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - )); - }); - }; - - // Bidder has a losing bid due to CTs being sold out at his price point. - // Their did should be able to contribute regardless of what investor type or account he uses to sign the transaction - bid_should_succeed(BIDDER_5, InvestorType::Institutional, BIDDER_5); - bid_should_succeed(BUYER_5, InvestorType::Institutional, BIDDER_5); - bid_should_succeed(BIDDER_5, InvestorType::Professional, BIDDER_5); - bid_should_succeed(BUYER_5, InvestorType::Professional, BIDDER_5); - bid_should_succeed(BIDDER_5, InvestorType::Retail, BIDDER_5); - bid_should_succeed(BUYER_5, InvestorType::Retail, BIDDER_5); - } - - #[test] - fn can_contribute_with_frozen_tokens_funding_failed() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(issuer); - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - issuer, - None, - default_evaluations(), - vec![], - ); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - let contribution = ContributionParams::from(( - BUYER_4, - 500 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )); - let plmc_required = inst.calculate_contributed_plmc_spent(vec![contribution.clone()], wap); - let frozen_amount = plmc_required[0].plmc_amount; - - inst.mint_plmc_ed_if_required(vec![BUYER_4]); - inst.mint_plmc_to(plmc_required.clone()); - - inst.execute(|| { - mock::Balances::set_freeze(&(), &BUYER_4, plmc_required[0].plmc_amount).unwrap(); - }); - - let usdt_required = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); - inst.mint_funding_asset_ed_if_required(vec![(BUYER_4, AcceptedFundingAsset::USDT.id())]); - inst.mint_funding_asset_to(usdt_required); - - inst.execute(|| { - assert_noop!( - Balances::transfer_allow_death(RuntimeOrigin::signed(BUYER_4), ISSUER_1, frozen_amount,), - TokenError::Frozen - ); - }); - - inst.execute(|| { - assert_ok!(PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_4), - get_mock_jwt_with_cid( - BUYER_4, - InvestorType::Institutional, - generate_did_from_account(BUYER_4), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - contribution.amount, - contribution.mode, - contribution.asset - )); - }); - - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); - - let free_balance = inst.get_free_plmc_balance_for(BUYER_4); - let bid_held_balance = inst.get_reserved_plmc_balance_for(BUYER_4, HoldReason::Participation.into()); - let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BUYER_4)); - - assert_eq!(free_balance, inst.get_ed()); - assert_eq!(bid_held_balance, frozen_amount); - assert_eq!(frozen_balance, frozen_amount); - - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); - - inst.execute(|| { - PolimecFunding::settle_contribution(RuntimeOrigin::signed(BUYER_4), project_id, BUYER_4, 0).unwrap(); - }); - - let free_balance = inst.get_free_plmc_balance_for(BUYER_4); - let bid_held_balance = inst.get_reserved_plmc_balance_for(BUYER_4, HoldReason::Evaluation.into()); - let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BUYER_4)); - - assert_eq!(free_balance, inst.get_ed() + frozen_amount); - assert_eq!(bid_held_balance, Zero::zero()); - assert_eq!(frozen_balance, frozen_amount); - } - - #[test] - fn can_contribute_with_frozen_tokens_funding_success() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let project_metadata = default_project_metadata(issuer); - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - issuer, - None, - default_evaluations(), - default_bids(), - ); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - let contribution = ContributionParams::from(( - BUYER_4, - 500 * CT_UNIT, - ParticipationMode::Classic(5u8), - AcceptedFundingAsset::USDT, - )); - let plmc_required = inst.calculate_contributed_plmc_spent(vec![contribution.clone()], wap); - let frozen_amount = plmc_required[0].plmc_amount; - - inst.mint_plmc_ed_if_required(vec![BUYER_4]); - inst.mint_plmc_to(plmc_required.clone()); - - inst.execute(|| { - mock::Balances::set_freeze(&(), &BUYER_4, plmc_required[0].plmc_amount).unwrap(); - }); - - let usdt_required = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); - inst.mint_funding_asset_ed_if_required(vec![(BUYER_4, AcceptedFundingAsset::USDT.id())]); - inst.mint_funding_asset_to(usdt_required); - - inst.execute(|| { - assert_noop!( - Balances::transfer_allow_death(RuntimeOrigin::signed(BUYER_4), ISSUER_1, frozen_amount,), - TokenError::Frozen - ); - }); - - inst.execute(|| { - assert_ok!(PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_4), - get_mock_jwt_with_cid( - BUYER_4, - InvestorType::Institutional, - generate_did_from_account(BUYER_4), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - contribution.amount, - contribution.mode, - contribution.asset - )); - }); - - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); - - let free_balance = inst.get_free_plmc_balance_for(BUYER_4); - let bid_held_balance = inst.get_reserved_plmc_balance_for(BUYER_4, HoldReason::Participation.into()); - let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BUYER_4)); - - assert_eq!(free_balance, inst.get_ed()); - assert_eq!(bid_held_balance, frozen_amount); - assert_eq!(frozen_balance, frozen_amount); - - inst.execute(|| { - PolimecFunding::settle_contribution(RuntimeOrigin::signed(BUYER_4), project_id, BUYER_4, 0).unwrap(); - }); - - let free_balance = inst.get_free_plmc_balance_for(BUYER_4); - let bid_held_balance = inst.get_reserved_plmc_balance_for(BUYER_4, HoldReason::Participation.into()); - let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BUYER_4)); - - assert_eq!(free_balance, inst.get_ed()); - assert_eq!(bid_held_balance, frozen_amount); - assert_eq!(frozen_balance, frozen_amount); - - let vest_duration = - MultiplierOf::::try_from(5u8).unwrap().calculate_vesting_duration::(); - let now = inst.current_block(); - inst.jump_to_block(now + vest_duration + 1u64); - inst.execute(|| { - assert_ok!(mock::LinearRelease::vest(RuntimeOrigin::signed(BUYER_4), HoldReason::Participation.into())); - }); - - let free_balance = inst.get_free_plmc_balance_for(BUYER_4); - let bid_held_balance = inst.get_reserved_plmc_balance_for(BUYER_4, HoldReason::Participation.into()); - let frozen_balance = inst.execute(|| mock::Balances::balance_frozen(&(), &BUYER_4)); - - assert_eq!(free_balance, inst.get_ed() + frozen_amount); - assert_eq!(bid_held_balance, Zero::zero()); - assert_eq!(frozen_balance, frozen_amount); - } - - #[test] - fn participant_was_evaluator_and_bidder() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - let participant = 42069; - let project_metadata = default_project_metadata(issuer); - let mut evaluations = default_evaluations(); - evaluations.push((participant, 100 * USD_UNIT).into()); - let mut bids = default_bids(); - bids.push(BidParams::from(( - participant, - 1000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - ))); - let community_contributions = default_community_contributions(); - let mut remainder_contributions = default_remainder_contributions(); - remainder_contributions.push(ContributionParams::from(( - participant, - 10 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - ))); - - let _project_id = inst.create_finished_project( - project_metadata.clone(), - issuer, - None, - evaluations, - bids, - community_contributions, - remainder_contributions, - ); - } - - #[test] - fn one_token_mode_contribution_funding_success() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - - let mut project_metadata = default_project_metadata(issuer); - project_metadata.mainnet_token_max_supply = 50_000 * CT_UNIT; - project_metadata.total_allocation_size = 5_000 * CT_UNIT; - project_metadata.minimum_price = ::PriceProvider::calculate_decimals_aware_price( - PriceOf::::from_float(1.0), - USD_DECIMALS, - CT_DECIMALS, - ) - .unwrap(); - - let evaluations = - inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); - - let project_id = - inst.create_community_contributing_project(project_metadata.clone(), issuer, None, evaluations, vec![]); - let otm_multiplier: MultiplierOf = - ParticipationMode::OTM.multiplier().try_into().ok().unwrap(); - let otm_duration = otm_multiplier.calculate_vesting_duration::(); - - let usdt_id = AcceptedFundingAsset::USDT.id(); - const USDT_PARTICIPATION: u128 = 3000 * USDT_UNIT; - - let otm_usdt_fee: u128 = (FeePercentage::get() / ParticipationMode::OTM.multiplier()) * USDT_PARTICIPATION; - let usdt_ed = inst.get_funding_asset_ed(AcceptedFundingAsset::USDT.id()); - let required_usdt = - UserToFundingAsset::new(BUYER_1, USDT_PARTICIPATION + otm_usdt_fee + usdt_ed, usdt_id.clone()); - inst.mint_funding_asset_to(vec![required_usdt.clone()]); - - let ct_participation = inst.execute(|| { - >::funding_asset_to_ct_amount_classic( - project_id, - AcceptedFundingAsset::USDT, - USDT_PARTICIPATION, - ) - }); - // USDT has the same decimals and price as our baseline USD - let expected_plmc_bond = - >::calculate_plmc_bond(USDT_PARTICIPATION, otm_multiplier).unwrap(); - - let otm_escrow_account = - ::RootId::get().into_sub_account_truncating(project_id); - let otm_treasury_account = ::Treasury::get(); - let otm_fee_recipient_account = ::FeeRecipient::get(); - let funding_project_escrow = PolimecFunding::fund_account_id(project_id); - - assert!(funding_project_escrow != otm_escrow_account); - - let pre_participation_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); - let pre_participation_otm_escrow_held_plmc = - inst.get_reserved_plmc_balance_for(otm_escrow_account, HoldReason::Participation.into()); - let pre_participation_otm_escrow_usdt = - inst.get_free_funding_asset_balance_for(usdt_id.clone(), otm_escrow_account); - let pre_participation_otm_fee_recipient_usdt = - inst.get_free_funding_asset_balance_for(usdt_id.clone(), otm_fee_recipient_account); - let pre_participation_buyer_usdt = inst.get_free_funding_asset_balance_for(usdt_id.clone(), BUYER_1); - - inst.execute(|| { - assert_ok!(PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_1), - get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Retail, - generate_did_from_account(BUYER_1), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - ct_participation, - ParticipationMode::OTM, - AcceptedFundingAsset::USDT - )); - }); - - let post_participation_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); - let post_participation_otm_escrow_held_plmc = - inst.get_reserved_plmc_balance_for(otm_escrow_account, HoldReason::Participation.into()); - let post_participation_otm_escrow_usdt = - inst.get_free_funding_asset_balance_for(usdt_id.clone(), otm_escrow_account); - let post_participation_otm_fee_recipient_usdt = - inst.get_free_funding_asset_balance_for(usdt_id.clone(), otm_fee_recipient_account); - let post_participation_buyer_usdt = inst.get_free_funding_asset_balance_for(usdt_id.clone(), BUYER_1); - - assert_eq!( - post_participation_treasury_free_plmc, - pre_participation_treasury_free_plmc - expected_plmc_bond - inst.get_ed() - ); - assert_eq!( - post_participation_otm_escrow_held_plmc, - pre_participation_otm_escrow_held_plmc + expected_plmc_bond - ); - assert_close_enough!( - post_participation_otm_escrow_usdt, - pre_participation_otm_escrow_usdt + otm_usdt_fee, - Perquintill::from_float(0.999) - ); - assert_close_enough!( - post_participation_otm_fee_recipient_usdt, - pre_participation_otm_fee_recipient_usdt, - Perquintill::from_float(0.999) - ); - assert_close_enough!( - post_participation_buyer_usdt, - pre_participation_buyer_usdt - USDT_PARTICIPATION - otm_usdt_fee, - Perquintill::from_float(0.999) - ); - - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); - inst.settle_project(project_id, true); - - inst.execute(|| { - assert_ok!(>::transfer_fees_to_recipient( - RuntimeOrigin::signed(BUYER_1), - project_id, - HoldReason::Participation.into(), - usdt_id.clone() - )); - assert_noop!( - >::transfer_bonds_back_to_treasury( - RuntimeOrigin::signed(BUYER_1), - project_id, - HoldReason::Participation.into() - ), - pallet_proxy_bonding::Error::::TooEarlyToUnlock - ); - }); - let now = inst.current_block(); - inst.jump_to_block(otm_duration + now); - inst.execute(|| { - assert_ok!(>::transfer_bonds_back_to_treasury( - RuntimeOrigin::signed(BUYER_1), - project_id, - HoldReason::Participation.into() - )); - }); - - let post_settlement_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); - let post_settlement_otm_escrow_held_plmc = inst.get_free_plmc_balance_for(otm_escrow_account); - let post_settlement_otm_escrow_usdt = - inst.get_free_funding_asset_balance_for(usdt_id.clone(), otm_escrow_account); - let post_settlement_otm_fee_recipient_usdt = - inst.get_free_funding_asset_balance_for(usdt_id.clone(), otm_fee_recipient_account); - let post_settlement_buyer_usdt = inst.get_free_funding_asset_balance_for(usdt_id.clone(), BUYER_1); - let issuer_funding_account = inst.get_free_funding_asset_balance_for(usdt_id, issuer); - - assert_eq!(post_settlement_treasury_free_plmc, post_participation_treasury_free_plmc + expected_plmc_bond); - assert_eq!(post_settlement_otm_escrow_held_plmc, inst.get_ed()); - assert_eq!(post_settlement_otm_escrow_usdt, Zero::zero()); - assert_close_enough!(post_settlement_otm_fee_recipient_usdt, otm_usdt_fee, Perquintill::from_float(0.999)); - assert_close_enough!(post_settlement_buyer_usdt, usdt_ed, Perquintill::from_float(0.999)); - assert_eq!(issuer_funding_account, USDT_PARTICIPATION); - } - - #[test] - fn one_token_mode_contribution_funding_failed() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let issuer = ISSUER_1; - - let mut project_metadata = default_project_metadata(issuer); - project_metadata.mainnet_token_max_supply = 50_000 * CT_UNIT; - project_metadata.total_allocation_size = 20_000 * CT_UNIT; - project_metadata.minimum_price = ::PriceProvider::calculate_decimals_aware_price( - PriceOf::::from_float(1.0), - USD_DECIMALS, - CT_DECIMALS, - ) - .unwrap(); - - let evaluations = - inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); - - let project_id = - inst.create_community_contributing_project(project_metadata.clone(), issuer, None, evaluations, vec![]); - let otm_multiplier: MultiplierOf = - ParticipationMode::OTM.multiplier().try_into().ok().unwrap(); - - let usdt_id = AcceptedFundingAsset::USDT.id(); - const USDT_PARTICIPATION: u128 = 3000 * USDT_UNIT; - - let otm_usdt_fee: u128 = (FeePercentage::get() / ParticipationMode::OTM.multiplier()) * USDT_PARTICIPATION; - let usdt_ed = inst.get_funding_asset_ed(AcceptedFundingAsset::USDT.id()); - let required_usdt = - UserToFundingAsset::new(BUYER_1, USDT_PARTICIPATION + otm_usdt_fee + usdt_ed, usdt_id.clone()); - inst.mint_funding_asset_to(vec![required_usdt.clone()]); - - let ct_participation = inst.execute(|| { - >::funding_asset_to_ct_amount_classic( - project_id, - AcceptedFundingAsset::USDT, - USDT_PARTICIPATION, - ) - }); - // USDT has the same decimals and price as our baseline USD - let expected_plmc_bond = - >::calculate_plmc_bond(USDT_PARTICIPATION, otm_multiplier).unwrap(); - - let otm_escrow_account = - ::RootId::get().into_sub_account_truncating(project_id); - let otm_treasury_account = ::Treasury::get(); - let otm_fee_recipient_account = ::FeeRecipient::get(); - let funding_project_escrow = PolimecFunding::fund_account_id(project_id); - - assert!(funding_project_escrow != otm_escrow_account); - - let pre_participation_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); - let pre_participation_otm_escrow_held_plmc = - inst.get_reserved_plmc_balance_for(otm_escrow_account, HoldReason::Participation.into()); - let pre_participation_otm_escrow_usdt = - inst.get_free_funding_asset_balance_for(usdt_id.clone(), otm_escrow_account); - let pre_participation_otm_fee_recipient_usdt = - inst.get_free_funding_asset_balance_for(usdt_id.clone(), otm_fee_recipient_account); - let pre_participation_buyer_usdt = inst.get_free_funding_asset_balance_for(usdt_id.clone(), BUYER_1); - - inst.execute(|| { - assert_ok!(PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_1), - get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Retail, - generate_did_from_account(BUYER_1), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - ct_participation, - ParticipationMode::OTM, - AcceptedFundingAsset::USDT - )); - }); - - let post_participation_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); - let post_participation_otm_escrow_held_plmc = - inst.get_reserved_plmc_balance_for(otm_escrow_account, HoldReason::Participation.into()); - let post_participation_otm_escrow_usdt = - inst.get_free_funding_asset_balance_for(usdt_id.clone(), otm_escrow_account); - let post_participation_otm_fee_recipient_usdt = - inst.get_free_funding_asset_balance_for(usdt_id.clone(), otm_fee_recipient_account); - let post_participation_buyer_usdt = inst.get_free_funding_asset_balance_for(usdt_id.clone(), BUYER_1); - - assert_eq!( - post_participation_treasury_free_plmc, - pre_participation_treasury_free_plmc - expected_plmc_bond - inst.get_ed() - ); - assert_eq!( - post_participation_otm_escrow_held_plmc, - pre_participation_otm_escrow_held_plmc + expected_plmc_bond - ); - assert_close_enough!( - post_participation_otm_escrow_usdt, - pre_participation_otm_escrow_usdt + otm_usdt_fee, - Perquintill::from_float(0.999) - ); - assert_close_enough!( - post_participation_otm_fee_recipient_usdt, - pre_participation_otm_fee_recipient_usdt, - Perquintill::from_float(0.999) - ); - assert_close_enough!( - post_participation_buyer_usdt, - pre_participation_buyer_usdt - USDT_PARTICIPATION - otm_usdt_fee, - Perquintill::from_float(0.999) - ); - - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); - inst.settle_project(project_id, true); - - inst.execute(|| { - assert_noop!( - >::transfer_fees_to_recipient( - RuntimeOrigin::signed(BUYER_1), - project_id, - HoldReason::Participation.into(), - usdt_id.clone() - ), - pallet_proxy_bonding::Error::::FeeToRecipientDisallowed - ); - - assert_ok!(>::transfer_bonds_back_to_treasury( - RuntimeOrigin::signed(BUYER_1), - project_id, - HoldReason::Participation.into() - )); - }); - - let post_settlement_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); - let post_settlement_otm_escrow_held_plmc = inst.get_free_plmc_balance_for(otm_escrow_account); - let post_settlement_otm_escrow_usdt = - inst.get_free_funding_asset_balance_for(usdt_id.clone(), otm_escrow_account); - let post_settlement_otm_fee_recipient_usdt = - inst.get_free_funding_asset_balance_for(usdt_id.clone(), otm_fee_recipient_account); - let post_settlement_buyer_usdt = inst.get_free_funding_asset_balance_for(usdt_id.clone(), BUYER_1); - let issuer_funding_account = inst.get_free_funding_asset_balance_for(usdt_id, issuer); - - assert_eq!(post_settlement_treasury_free_plmc, post_participation_treasury_free_plmc + expected_plmc_bond); - assert_eq!(post_settlement_otm_escrow_held_plmc, inst.get_ed()); - assert_eq!(post_settlement_otm_escrow_usdt, Zero::zero()); - assert_eq!(post_settlement_otm_fee_recipient_usdt, Zero::zero()); - assert_eq!(post_settlement_buyer_usdt, usdt_ed + USDT_PARTICIPATION + otm_usdt_fee); - assert_eq!(issuer_funding_account, Zero::zero()); - } - - #[test] - fn contribute_on_ethereum_project() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - - let mut project_metadata = default_project_metadata(ISSUER_1); - project_metadata.participants_account_type = ParticipantsAccountType::Ethereum; - - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_eth_evaluations(), - vec![], - ); - let jwt = get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Professional, - generate_did_from_account(BUYER_1), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - - let (eth_acc, eth_sig) = inst.eth_key_and_sig_from("//BUYER1", project_id, BUYER_1); - let contribution = - ContributionParams::from((BUYER_1, 500 * CT_UNIT, ParticipationMode::OTM, AcceptedFundingAsset::USDT)); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - let mint_amount = inst.calculate_contributed_funding_asset_spent(vec![contribution], wap); - inst.mint_funding_asset_ed_if_required(mint_amount.to_account_asset_map()); - inst.mint_funding_asset_to(mint_amount.clone()); - - assert_ok!(inst.execute(|| { - PolimecFunding::contribute_with_receiving_account( - RuntimeOrigin::signed(BUYER_1), - jwt, - project_id, - 500 * CT_UNIT, - ParticipationMode::OTM, - AcceptedFundingAsset::USDT, - eth_acc, - eth_sig, - ) - })); - } - - #[test] - fn contribute_with_different_receiver_polkadot_account() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - - let project_metadata = default_project_metadata(ISSUER_1); - - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - vec![], - ); - let jwt = get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Professional, - generate_did_from_account(BUYER_1), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - - let (dot_acc, dot_sig) = inst.dot_key_and_sig_from("//BUYER1", project_id, BUYER_1); - let bid = - ContributionParams::from((BUYER_1, 500 * CT_UNIT, ParticipationMode::OTM, AcceptedFundingAsset::USDT)); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - let mint_amount = inst.calculate_contributed_funding_asset_spent(vec![bid], wap); - inst.mint_funding_asset_ed_if_required(mint_amount.to_account_asset_map()); - inst.mint_funding_asset_to(mint_amount.clone()); - - assert_ok!(inst.execute(|| { - PolimecFunding::contribute_with_receiving_account( - RuntimeOrigin::signed(BUYER_1), - jwt, - project_id, - 500 * CT_UNIT, - ParticipationMode::OTM, - AcceptedFundingAsset::USDT, - dot_acc, - dot_sig, - ) - })); - } - } - - #[cfg(test)] - mod failure { - use super::*; - use frame_support::traits::{ - fungible::Mutate as MutateFungible, - fungibles::Mutate as MutateFungibles, - tokens::{Fortitude, Precision, Preservation}, - }; - use sp_runtime::bounded_vec; - - #[test] - fn contribution_errors_if_user_limit_is_reached() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_community_contributing_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - ); - const CONTRIBUTOR: AccountIdOf = 420; - - let project_details = inst.get_project_details(project_id); - let token_price = project_details.weighted_average_price.unwrap(); - - // Create a contribution vector that will reach the limit of contributions for a user-project - let token_amount: Balance = CT_UNIT; - let range = 0..::MaxContributionsPerUser::get(); - let contributions: Vec> = range - .map(|_| { - ContributionParams::from(( - CONTRIBUTOR, - token_amount, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )) - }) - .collect(); - - let plmc_funding = inst.calculate_contributed_plmc_spent(contributions.clone(), token_price); - let foreign_funding = inst.calculate_contributed_funding_asset_spent(contributions.clone(), token_price); - - inst.mint_plmc_ed_if_required(contributions.accounts()); - inst.mint_plmc_to(plmc_funding.clone()); - - inst.mint_funding_asset_ed_if_required(contributions.to_account_asset_map()); - inst.mint_funding_asset_to(foreign_funding.clone()); - - // Reach up to the limit of contributions for a user-project - assert!(inst.contribute_for_users(project_id, contributions).is_ok()); - - // Try to contribute again, but it should fail because the limit of contributions for a user-project was reached. - let over_limit_contribution = ContributionParams::from(( - CONTRIBUTOR, - token_amount, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )); - assert!(inst.contribute_for_users(project_id, vec![over_limit_contribution]).is_err()); - - // Check that the right amount of PLMC is bonded, and funding currency is transferred - let contributor_post_buy_plmc_balance = - inst.execute(|| ::NativeCurrency::balance(&CONTRIBUTOR)); - let contributor_post_buy_foreign_asset_balance = inst.execute(|| { - ::FundingCurrency::balance(AcceptedFundingAsset::USDT.id(), CONTRIBUTOR) - }); - - assert_eq!(contributor_post_buy_plmc_balance, inst.get_ed()); - assert_eq!( - contributor_post_buy_foreign_asset_balance, - inst.get_funding_asset_ed(AcceptedFundingAsset::USDT.id()) - ); - - let plmc_bond_stored = inst.execute(|| { - ::NativeCurrency::balance_on_hold( - &HoldReason::Participation.into(), - &CONTRIBUTOR, - ) - }); - let foreign_asset_contributions_stored = inst.execute(|| { - Contributions::::iter_prefix_values((project_id, CONTRIBUTOR)) - .map(|c| c.funding_asset_amount) - .sum::() - }); - - assert_eq!(plmc_bond_stored, inst.sum_balance_mappings(vec![plmc_funding.clone()])); - assert_eq!( - foreign_asset_contributions_stored, - inst.sum_funding_asset_mappings(vec![foreign_funding.clone()])[0].1 - ); - } - - #[test] - fn issuer_cannot_contribute_his_project() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - ); - assert_err!( - inst.execute(|| crate::Pallet::::do_contribute(DoContributeParams:: { - contributor: ISSUER_1, - project_id, - ct_amount: 500 * CT_UNIT, - mode: ParticipationMode::Classic(1), - funding_asset: AcceptedFundingAsset::USDT, - did: generate_did_from_account(ISSUER_1), - investor_type: InvestorType::Institutional, - whitelisted_policy: project_metadata.policy_ipfs_cid.unwrap(), - receiving_account: polkadot_junction!(ISSUER_1) - })), - Error::::ParticipationToOwnProject - ); - } - - #[test] - fn did_with_winning_bid_cannot_contribute() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let mut evaluations = default_evaluations(); - evaluations.push((BIDDER_2, 1337 * USD_UNIT).into()); - let bids = vec![ - BidParams::from(( - BIDDER_1, - 400_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - BidParams::from(( - BIDDER_2, - 50_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - // Partially accepted bid. Only the 50k of the second bid will be accepted. - BidParams::from(( - BIDDER_3, - 100_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - ]; - - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - bids, - ); - - let mut bid_should_fail = |account, investor_type, did_acc| { - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(account), - get_mock_jwt_with_cid( - account, - investor_type, - generate_did_from_account(did_acc), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - 10 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - ), - Error::::UserHasWinningBid - ); - }); - }; - - // Bidder 1 has a winning bid, his did should not be able to contribute regardless of what investor type - // or account he uses to sign the transaction - bid_should_fail(BIDDER_1, InvestorType::Institutional, BIDDER_1); - bid_should_fail(BUYER_1, InvestorType::Institutional, BIDDER_1); - bid_should_fail(BIDDER_1, InvestorType::Professional, BIDDER_1); - bid_should_fail(BUYER_1, InvestorType::Professional, BIDDER_1); - bid_should_fail(BIDDER_1, InvestorType::Retail, BIDDER_1); - bid_should_fail(BUYER_1, InvestorType::Retail, BIDDER_1); - - // Bidder 2 has a winning bid, and he was also an evaluator. Same conditions as before should apply. - bid_should_fail(BIDDER_2, InvestorType::Institutional, BIDDER_2); - bid_should_fail(BUYER_2, InvestorType::Institutional, BIDDER_2); - bid_should_fail(BIDDER_2, InvestorType::Professional, BIDDER_2); - bid_should_fail(BUYER_2, InvestorType::Professional, BIDDER_2); - bid_should_fail(BIDDER_2, InvestorType::Retail, BIDDER_2); - bid_should_fail(BUYER_2, InvestorType::Retail, BIDDER_2); - - // Bidder 3 has a partial winning bid. Same conditions as before should apply. - bid_should_fail(BIDDER_3, InvestorType::Institutional, BIDDER_3); - bid_should_fail(BUYER_3, InvestorType::Institutional, BIDDER_3); - bid_should_fail(BIDDER_3, InvestorType::Professional, BIDDER_3); - bid_should_fail(BUYER_3, InvestorType::Professional, BIDDER_3); - bid_should_fail(BIDDER_3, InvestorType::Retail, BIDDER_3); - bid_should_fail(BUYER_3, InvestorType::Retail, BIDDER_3); - } - - #[test] - fn per_credential_type_ticket_size_minimums() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let mut project_metadata = default_project_metadata(ISSUER_1); - project_metadata.contributing_ticket_sizes = ContributingTicketSizes { - retail: TicketSize::new(10 * USD_UNIT, None), - professional: TicketSize::new(100_000 * USD_UNIT, None), - institutional: TicketSize::new(200_000 * USD_UNIT, None), - phantom: Default::default(), - }; - - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - ); - - inst.mint_plmc_to(vec![ - (BUYER_1, 50_000 * CT_UNIT).into(), - (BUYER_2, 50_000 * CT_UNIT).into(), - (BUYER_3, 50_000 * CT_UNIT).into(), - ]); - - inst.mint_funding_asset_to(vec![ - (BUYER_1, 50_000 * USD_UNIT).into(), - (BUYER_2, 50_000 * USD_UNIT).into(), - (BUYER_3, 50_000 * USD_UNIT).into(), - ]); - - // contribution below 1 CT (10 USD) should fail for retail - let jwt = get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Retail, - generate_did_from_account(BUYER_1), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BUYER_1), - jwt, - project_id, - CT_UNIT / 2, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - ), - Error::::TooLow - ); - }); - // contribution below 10_000 CT (100k USD) should fail for professionals - let jwt = get_mock_jwt_with_cid( - BUYER_2, - InvestorType::Professional, - generate_did_from_account(BUYER_2), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BUYER_2), - jwt, - project_id, - 9_999, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - ), - Error::::TooLow - ); - }); - - // contribution below 20_000 CT (200k USD) should fail for institutionals - let jwt = get_mock_jwt_with_cid( - BUYER_3, - InvestorType::Professional, - generate_did_from_account(BUYER_3), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BUYER_3), - jwt, - project_id, - 19_999, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - ), - Error::::TooLow - ); - }); - } - - #[test] - fn per_credential_type_ticket_size_maximums() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let mut project_metadata = default_project_metadata(ISSUER_1); - project_metadata.contributing_ticket_sizes = ContributingTicketSizes { - retail: TicketSize::new(USD_UNIT, Some(100_000 * USD_UNIT)), - professional: TicketSize::new(USD_UNIT, Some(20_000 * USD_UNIT)), - institutional: TicketSize::new(USD_UNIT, Some(50_000 * USD_UNIT)), - phantom: Default::default(), - }; - - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - ); - - inst.mint_plmc_to(vec![ - (BUYER_1, 500_000 * CT_UNIT).into(), - (BUYER_2, 500_000 * CT_UNIT).into(), - (BUYER_3, 500_000 * CT_UNIT).into(), - (BUYER_4, 500_000 * CT_UNIT).into(), - (BUYER_5, 500_000 * CT_UNIT).into(), - (BUYER_6, 500_000 * CT_UNIT).into(), - ]); - - inst.mint_funding_asset_to(vec![ - (BUYER_1, 500_000 * USD_UNIT).into(), - (BUYER_2, 500_000 * USD_UNIT).into(), - (BUYER_3, 500_000 * USD_UNIT).into(), - (BUYER_4, 500_000 * USD_UNIT).into(), - (BUYER_5, 500_000 * USD_UNIT).into(), - (BUYER_6, 500_000 * USD_UNIT).into(), - ]); - - let buyer_1_jwt = get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Retail, - generate_did_from_account(BUYER_1), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - let buyer_2_jwt_same_did = get_mock_jwt_with_cid( - BUYER_2, - InvestorType::Retail, - generate_did_from_account(BUYER_1), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - // total contributions with same DID above 10k CT (100k USD) should fail for retail - inst.execute(|| { - assert_ok!(Pallet::::contribute( - RuntimeOrigin::signed(BUYER_1), - buyer_1_jwt, - project_id, - 9000 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - )); - }); - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BUYER_2), - buyer_2_jwt_same_did.clone(), - project_id, - 1001 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - ), - Error::::TooHigh - ); - }); - // bidding 2k total works - inst.execute(|| { - assert_ok!(Pallet::::contribute( - RuntimeOrigin::signed(BUYER_2), - buyer_2_jwt_same_did, - project_id, - 1000 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - )); - }); - - let buyer_3_jwt = get_mock_jwt_with_cid( - BUYER_3, - InvestorType::Professional, - generate_did_from_account(BUYER_3), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - let buyer_4_jwt_same_did = get_mock_jwt_with_cid( - BUYER_4, - InvestorType::Professional, - generate_did_from_account(BUYER_3), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - // total contributions with same DID above 2k CT (20k USD) should fail for professionals - inst.execute(|| { - assert_ok!(Pallet::::contribute( - RuntimeOrigin::signed(BUYER_3), - buyer_3_jwt, - project_id, - 1800 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - )); - }); - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BUYER_4), - buyer_4_jwt_same_did.clone(), - project_id, - 201 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - ), - Error::::TooHigh - ); - }); - // bidding 2k total works - inst.execute(|| { - assert_ok!(Pallet::::contribute( - RuntimeOrigin::signed(BUYER_4), - buyer_4_jwt_same_did, - project_id, - 200 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - )); - }); - - let buyer_5_jwt = get_mock_jwt_with_cid( - BUYER_5, - InvestorType::Institutional, - generate_did_from_account(BUYER_5), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - let buyer_6_jwt_same_did = get_mock_jwt_with_cid( - BUYER_6, - InvestorType::Institutional, - generate_did_from_account(BUYER_5), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - // total contributions with same DID above 5k CT (50 USD) should fail for institutionals - inst.execute(|| { - assert_ok!(Pallet::::contribute( - RuntimeOrigin::signed(BUYER_5), - buyer_5_jwt, - project_id, - 4690 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - )); - }); - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BUYER_6), - buyer_6_jwt_same_did.clone(), - project_id, - 311 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - ), - Error::::TooHigh - ); - }); - // bidding 5k total works - inst.execute(|| { - assert_ok!(Pallet::::contribute( - RuntimeOrigin::signed(BUYER_6), - buyer_6_jwt_same_did, - project_id, - 310 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - )); - }); - } - - #[test] - fn insufficient_funds() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - ); - - let jwt = get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Retail, - generate_did_from_account(BUYER_1), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ); - let contribution = ContributionParams::from(( - BUYER_1, - 1_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - // 1 unit less native asset than needed - let plmc_funding = inst.calculate_contributed_plmc_spent(vec![contribution.clone()], wap); - let plmc_existential_deposits = plmc_funding.accounts().existential_deposits(); - inst.mint_plmc_to(plmc_funding.clone()); - inst.mint_plmc_to(plmc_existential_deposits.clone()); - inst.execute(|| { - Balances::burn_from(&BUYER_1, 1, Preservation::Expendable, Precision::BestEffort, Fortitude::Force) - }) - .unwrap(); - - let foreign_funding = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); - inst.mint_funding_asset_to(foreign_funding.clone()); - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BUYER_1), - jwt.clone(), - project_id, - 1_000 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - ), - Error::::ParticipantNotEnoughFunds - ); - }); - - // 1 unit less funding asset than needed - let plmc_funding = inst.calculate_contributed_plmc_spent(vec![contribution.clone()], wap); - let plmc_existential_deposits = plmc_funding.accounts().existential_deposits(); - inst.mint_plmc_to(plmc_funding.clone()); - inst.mint_plmc_to(plmc_existential_deposits.clone()); - let foreign_funding = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); - - inst.execute(|| ForeignAssets::set_balance(AcceptedFundingAsset::USDT.id(), &BUYER_1, 0)); - inst.mint_funding_asset_to(foreign_funding.clone()); - - inst.execute(|| { - ForeignAssets::burn_from( - AcceptedFundingAsset::USDT.id(), - &BUYER_1, - 100, - Preservation::Expendable, - Precision::BestEffort, - Fortitude::Force, - ) - }) - .unwrap(); - - inst.execute(|| { - assert_noop!( - Pallet::::contribute( - RuntimeOrigin::signed(BUYER_1), - jwt, - project_id, - 1_000 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - ), - Error::::ParticipantNotEnoughFunds - ); - }); - } - - #[test] - fn called_outside_community_round() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let created_project = inst.create_new_project(default_project_metadata(ISSUER_1), ISSUER_1, None); - let evaluating_project = inst.create_evaluating_project(default_project_metadata(ISSUER_2), ISSUER_2, None); - let auctioning_project = inst.create_auctioning_project( - default_project_metadata(ISSUER_3), - ISSUER_3, - None, - default_evaluations(), - ); - let finished_project = inst.create_finished_project( - default_project_metadata(ISSUER_5), - ISSUER_5, - None, - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), - ); - - let projects = vec![created_project, evaluating_project, auctioning_project, finished_project]; - for project in projects { - let project_policy = inst.get_project_metadata(project).policy_ipfs_cid.unwrap(); - inst.execute(|| { - assert_noop!( - PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_1), - get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Retail, - generate_did_from_account(BUYER_1), - project_policy - ), - project, - 1000 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT - ), - Error::::IncorrectRound - ); - }); - } - } - - #[test] - fn contribute_with_unaccepted_currencies() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - - let mut project_metadata_usdt = default_project_metadata(ISSUER_2); - project_metadata_usdt.participation_currencies = bounded_vec![AcceptedFundingAsset::USDT]; - - let mut project_metadata_usdc = default_project_metadata(ISSUER_3); - project_metadata_usdc.participation_currencies = bounded_vec![AcceptedFundingAsset::USDC]; - - let evaluations = default_evaluations(); - - let usdt_bids = default_bids() - .into_iter() - .map(|mut b| { - b.asset = AcceptedFundingAsset::USDT; - b - }) - .collect::>(); - - let usdc_bids = default_bids() - .into_iter() - .map(|mut b| { - b.asset = AcceptedFundingAsset::USDC; - b - }) - .collect::>(); - - let usdt_contribution = ContributionParams::from(( - BUYER_1, - 10_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )); - let usdc_contribution = ContributionParams::from(( - BUYER_2, - 10_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDC, - )); - let dot_contribution = ContributionParams::from(( - BUYER_3, - 10_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::DOT, - )); - - let project_id_usdc = inst.create_community_contributing_project( - project_metadata_usdc, - ISSUER_2, - None, - evaluations.clone(), - usdc_bids.clone(), - ); - assert_err!( - inst.contribute_for_users(project_id_usdc, vec![usdt_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - - let project_id_usdt = inst.create_community_contributing_project( - project_metadata_usdt, - ISSUER_3, - None, - evaluations.clone(), - usdt_bids.clone(), - ); - assert_err!( - inst.contribute_for_users(project_id_usdt, vec![usdc_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - assert_err!( - inst.contribute_for_users(project_id_usdt, vec![dot_contribution.clone()]), - Error::::FundingAssetNotAccepted - ); - } - - #[test] - fn cannot_use_evaluation_bond_on_another_project_contribution() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata_1 = default_project_metadata(ISSUER_1); - let project_metadata_2 = default_project_metadata(ISSUER_2); - - let mut evaluations_1 = default_evaluations(); - let evaluations_2 = default_evaluations(); - - let evaluator_contributor = 69; - let evaluation_amount = 420 * USD_UNIT; - let evaluator_contribution = ContributionParams::from(( - evaluator_contributor, - 600 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )); - evaluations_1.push((evaluator_contributor, evaluation_amount).into()); - - let _project_id_1 = inst.create_community_contributing_project( - project_metadata_1.clone(), - ISSUER_1, - None, - evaluations_1, - default_bids(), - ); - let project_id_2 = inst.create_community_contributing_project( - project_metadata_2.clone(), - ISSUER_2, - None, - evaluations_2, - default_bids(), - ); - - let wap = inst.get_project_details(project_id_2).weighted_average_price.unwrap(); - - // Necessary Mints - let already_bonded_plmc = - inst.calculate_evaluation_plmc_spent(vec![(evaluator_contributor, evaluation_amount).into()])[0] - .plmc_amount; - let usable_evaluation_plmc = - already_bonded_plmc - ::EvaluatorSlash::get() * already_bonded_plmc; - let necessary_plmc_for_contribution = - inst.calculate_contributed_plmc_spent(vec![evaluator_contribution.clone()], wap)[0].plmc_amount; - let necessary_usdt_for_contribution = - inst.calculate_contributed_funding_asset_spent(vec![evaluator_contribution.clone()], wap); - inst.mint_plmc_to(vec![UserToPLMCBalance::new( - evaluator_contributor, - necessary_plmc_for_contribution - usable_evaluation_plmc, - )]); - inst.mint_funding_asset_to(necessary_usdt_for_contribution); - - inst.execute(|| { - assert_noop!( - PolimecFunding::contribute( - RuntimeOrigin::signed(evaluator_contributor), - get_mock_jwt_with_cid( - evaluator_contributor, - InvestorType::Retail, - generate_did_from_account(evaluator_contributor), - project_metadata_2.clone().policy_ipfs_cid.unwrap() - ), - project_id_2, - evaluator_contribution.amount, - evaluator_contribution.mode, - evaluator_contribution.asset - ), - Error::::ParticipantNotEnoughFunds - ); - }); - } - - #[test] - fn wrong_policy_on_jwt() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - ); - - inst.execute(|| { - assert_noop!( - PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_1), - get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Retail, - generate_did_from_account(BUYER_1), - "wrong_cid".as_bytes().to_vec().try_into().unwrap() - ), - project_id, - 5000 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT - ), - Error::::PolicyMismatch - ); - }); - } - - #[test] - fn ct_sold_out() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - ); - let project_details = inst.get_project_details(project_id); - let remaining_cts = project_details.remaining_contribution_tokens; - let glutton_contribution = ContributionParams::from(( - BUYER_1, - remaining_cts, - ParticipationMode::Classic(4u8), - AcceptedFundingAsset::USDT, - )); - let wap = project_details.weighted_average_price.unwrap(); - let plmc_mint = inst.calculate_contributed_plmc_spent(vec![glutton_contribution.clone()], wap); - let funding_asset_mint = - inst.calculate_contributed_funding_asset_spent(vec![glutton_contribution.clone()], wap); - inst.mint_plmc_ed_if_required(vec![BUYER_1]); - inst.mint_plmc_to(plmc_mint); - inst.mint_funding_asset_ed_if_required(vec![(BUYER_1, AcceptedFundingAsset::USDT.id())]); - inst.mint_funding_asset_to(funding_asset_mint); - inst.contribute_for_users(project_id, vec![glutton_contribution.clone()]).unwrap(); - - let failing_contribution = ContributionParams::::from(( - BUYER_2, - 1000 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - )); - let plmc_mint = inst.calculate_contributed_plmc_spent(vec![glutton_contribution.clone()], wap); - let funding_asset_mint = - inst.calculate_contributed_funding_asset_spent(vec![glutton_contribution.clone()], wap); - - inst.mint_plmc_ed_if_required(vec![BUYER_2]); - inst.mint_plmc_to(plmc_mint); - inst.mint_funding_asset_ed_if_required(vec![(BUYER_2, AcceptedFundingAsset::USDT.id())]); - inst.mint_funding_asset_to(funding_asset_mint); - inst.execute(|| { - assert_noop!( - PolimecFunding::contribute( - RuntimeOrigin::signed(failing_contribution.contributor), - get_mock_jwt_with_cid( - failing_contribution.contributor, - InvestorType::Retail, - generate_did_from_account(failing_contribution.contributor), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - failing_contribution.amount, - failing_contribution.mode, - failing_contribution.asset - ), - Error::::ProjectSoldOut - ); - }); - } - - #[test] - fn contributed_after_end_block_before_transitioning_project() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - vec![], - ); - let end_block = inst.get_project_details(project_id).round_duration.end.unwrap(); - inst.jump_to_block(end_block + 1); - assert!(matches!(inst.get_project_details(project_id).status, ProjectStatus::CommunityRound(..))); - - inst.execute(|| { - assert_noop!( - PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_1), - get_mock_jwt_with_cid( - BUYER_1, - InvestorType::Professional, - generate_did_from_account(BUYER_1), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - 5000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT - ), - Error::::IncorrectRound - ); - }); - } - } -} diff --git a/pallets/funding/src/tests/5_funding_end.rs b/pallets/funding/src/tests/5_funding_end.rs index 1d6dc8e9a..d7a90f4b3 100644 --- a/pallets/funding/src/tests/5_funding_end.rs +++ b/pallets/funding/src/tests/5_funding_end.rs @@ -13,8 +13,7 @@ mod round_flow { fn auction_oversubscription() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_metadata = default_project_metadata(ISSUER_1); - let auction_allocation = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let auction_allocation = project_metadata.total_allocation_size; let bucket_size = Percent::from_percent(10) * auction_allocation; let bids = vec![ (BIDDER_1, auction_allocation).into(), @@ -29,10 +28,8 @@ mod round_flow { project_metadata.clone(), ISSUER_1, None, - default_evaluations(), + inst.generate_successful_evaluations(project_metadata.clone(), 5), bids, - default_community_contributions(), - default_remainder_contributions(), ); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); @@ -80,7 +77,8 @@ mod end_funding_extrinsic { let normal_evaluator_reward = Perquintill::from_percent(80u64) * total_evaluator_reward; const EARLY_EVALUATOR_TOTAL_USD_BONDED: u128 = 1_000_000 * USD_UNIT; - const NORMAL_EVALUATOR_TOTAL_USD_BONDED: u128 = 1_070_000 * USD_UNIT; + // The function that generates the successful evaluation does the full usd target amount as evaluation + const NORMAL_EVALUATOR_TOTAL_USD_BONDED: u128 = 10_000_000 * USD_UNIT; let expected_reward_info = RewardInfo { early_evaluator_reward_pot: early_evaluator_reward, @@ -136,13 +134,7 @@ mod end_funding_extrinsic { ); assert_eq!( project_details.funding_end_block, - Some( - EvaluationRoundDuration::get() + - AuctionRoundDuration::get() + - CommunityRoundDuration::get() + - RemainderRoundDuration::get() + - 1 - ) + Some(EvaluationRoundDuration::get() + AuctionRoundDuration::get() + 1) ); } } @@ -154,12 +146,12 @@ mod end_funding_extrinsic { #[test] fn called_too_early() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_community_contributing_project( - default_project_metadata(ISSUER_1), + let project_metadata = default_project_metadata(ISSUER_1); + let project_id = inst.create_auctioning_project( + project_metadata.clone(), ISSUER_1, None, - default_evaluations(), - vec![], + inst.generate_successful_evaluations(project_metadata.clone(), 5), ); inst.execute(|| { assert_noop!( @@ -187,7 +179,7 @@ mod end_funding_extrinsic { let funding_threshold: u128 = funding_threshold.deconstruct() as u128 * 100u128 / Perquintill::ACCURACY as u128; - let (mut inst, project_id) = create_project_with_funding_percentage(funding_threshold as u64 - 1, true); + let (mut inst, project_id) = create_project_with_funding_percentage(funding_threshold as u8 - 1, true); assert_eq!( inst.get_project_details(project_id).status, ProjectStatus::SettlementStarted(FundingOutcome::Failure) diff --git a/pallets/funding/src/tests/6_settlement.rs b/pallets/funding/src/tests/6_settlement.rs index b97068cac..871f3c192 100644 --- a/pallets/funding/src/tests/6_settlement.rs +++ b/pallets/funding/src/tests/6_settlement.rs @@ -2,7 +2,6 @@ use super::*; use frame_support::traits::fungibles::Inspect; use polimec_common::assets::AcceptedFundingAsset; use sp_runtime::bounded_vec; - #[cfg(test)] mod round_flow { use super::*; @@ -13,33 +12,29 @@ mod round_flow { #[test] fn can_fully_settle_accepted_project() { - let percentage = 100u64; + let percentage = 100u8; let (mut inst, project_id) = create_project_with_funding_percentage(percentage, true); let evaluations = inst.get_evaluations(project_id); let bids = inst.get_bids(project_id); - let contributions = inst.get_contributions(project_id); inst.settle_project(project_id, true); - inst.assert_total_funding_paid_out(project_id, bids.clone(), contributions.clone()); + inst.assert_total_funding_paid_out(project_id, bids.clone()); inst.assert_evaluations_migrations_created(project_id, evaluations, true); inst.assert_bids_migrations_created(project_id, bids, true); - inst.assert_contributions_migrations_created(project_id, contributions, true); } #[test] fn can_fully_settle_failed_project() { - let percentage = 32u64; + let percentage = 32u8; let (mut inst, project_id) = create_project_with_funding_percentage(percentage, true); let evaluations = inst.get_evaluations(project_id); let bids = inst.get_bids(project_id); - let contributions = inst.get_contributions(project_id); inst.settle_project(project_id, true); inst.assert_evaluations_migrations_created(project_id, evaluations, false); inst.assert_bids_migrations_created(project_id, bids, false); - inst.assert_contributions_migrations_created(project_id, contributions, false); } #[test] @@ -68,6 +63,7 @@ mod round_flow { let bids = vec![ BidParams::from(( BIDDER_1, + Retail, 120_000 * CT_UNIT, ParticipationMode::Classic(3u8), AcceptedFundingAsset::USDT, @@ -75,66 +71,19 @@ mod round_flow { )), BidParams::from(( BIDDER_2, + Retail, 420_000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::USDT, Junction::AccountKey20 { network: Some(Ethereum { chain_id: 1 }), key: [4u8; 20] }, )), ]; - let community_contributions = vec![ - ContributionParams::from(( - BUYER_1, - 20_000 * CT_UNIT, - ParticipationMode::OTM, - AcceptedFundingAsset::USDT, - Junction::AccountKey20 { network: Some(Ethereum { chain_id: 1 }), key: [5u8; 20] }, - )), - ContributionParams::from(( - BUYER_2, - 5_000 * CT_UNIT, - ParticipationMode::OTM, - AcceptedFundingAsset::USDT, - Junction::AccountKey20 { network: Some(Ethereum { chain_id: 1 }), key: [6u8; 20] }, - )), - ]; - let remainder_contributions = vec![ - ContributionParams::from(( - EVALUATOR_2, - 10_000 * CT_UNIT, - ParticipationMode::OTM, - AcceptedFundingAsset::USDT, - Junction::AccountKey20 { network: Some(Ethereum { chain_id: 1 }), key: [7u8; 20] }, - )), - ContributionParams::from(( - BIDDER_1, - 5_000 * CT_UNIT, - ParticipationMode::OTM, - AcceptedFundingAsset::USDT, - Junction::AccountKey20 { network: Some(Ethereum { chain_id: 1 }), key: [8u8; 20] }, - )), - ContributionParams::from(( - BUYER_1, - 100 * CT_UNIT, - ParticipationMode::OTM, - AcceptedFundingAsset::USDT, - Junction::AccountKey20 { network: Some(Ethereum { chain_id: 1 }), key: [9u8; 20] }, - )), - ]; - let project_id = inst.create_settled_project( - project_metadata.clone(), - ISSUER_1, - None, - evaluations, - bids, - community_contributions, - remainder_contributions, - false, - ); + let project_id = + inst.create_settled_project(project_metadata.clone(), ISSUER_1, None, evaluations, bids, false); let evaluations = inst.get_evaluations(project_id); let bids = inst.get_bids(project_id); - let contributions = inst.get_contributions(project_id); inst.settle_project(project_id, true); @@ -145,7 +94,6 @@ mod round_flow { inst.assert_evaluations_migrations_created(project_id, evaluations, true); inst.assert_bids_migrations_created(project_id, bids, true); - inst.assert_contributions_migrations_created(project_id, contributions, true); let _user_migrations = inst.execute(|| UserMigrations::::iter_prefix((project_id,)).collect_vec()); @@ -164,6 +112,7 @@ mod round_flow { let bids = vec![ BidParams::from(( BIDDER_1, + Retail, 120_000 * CT_UNIT, ParticipationMode::Classic(3u8), AcceptedFundingAsset::USDT, @@ -171,62 +120,15 @@ mod round_flow { )), BidParams::from(( BIDDER_2, + Retail, 420_000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::USDT, polkadot_junction!([4u8; 32]), )), ]; - let community_contributions = vec![ - ContributionParams::from(( - BUYER_1, - 20_000 * CT_UNIT, - ParticipationMode::OTM, - AcceptedFundingAsset::USDT, - polkadot_junction!([5u8; 32]), - )), - ContributionParams::from(( - BUYER_2, - 5_000 * CT_UNIT, - ParticipationMode::OTM, - AcceptedFundingAsset::USDT, - polkadot_junction!([6u8; 32]), - )), - ]; - let remainder_contributions = vec![ - ContributionParams::from(( - EVALUATOR_2, - 10_000 * CT_UNIT, - ParticipationMode::OTM, - AcceptedFundingAsset::USDT, - polkadot_junction!([7u8; 32]), - )), - ContributionParams::from(( - BIDDER_1, - 5_000 * CT_UNIT, - ParticipationMode::OTM, - AcceptedFundingAsset::USDT, - polkadot_junction!([8u8; 32]), - )), - ContributionParams::from(( - BUYER_1, - 100 * CT_UNIT, - ParticipationMode::OTM, - AcceptedFundingAsset::USDT, - polkadot_junction!([9u8; 32]), - )), - ]; - - let project_id = inst.create_settled_project( - project_metadata.clone(), - ISSUER_1, - None, - evaluations, - bids, - community_contributions, - remainder_contributions, - true, - ); + let project_id = + inst.create_settled_project(project_metadata.clone(), ISSUER_1, None, evaluations, bids, true); assert_eq!( inst.get_project_details(project_id).status, ProjectStatus::SettlementFinished(FundingOutcome::Success) @@ -300,14 +202,10 @@ mod start_settlement_extrinsic { #[test] fn called_too_early() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_remainder_contributing_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - default_evaluations(), - vec![], - vec![], - ); + let project_metadata = default_project_metadata(ISSUER_1); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = + inst.create_auctioning_project(default_project_metadata(ISSUER_1), ISSUER_1, None, evaluations); inst.execute(|| { assert_noop!( PolimecFunding::end_funding(RuntimeOrigin::signed(42), project_id), @@ -340,7 +238,9 @@ mod settle_evaluation_extrinsic { #[test] fn evaluation_rewarded() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.total_allocation_size = 1_000_000 * CT_UNIT; + let project_id = inst.create_finished_project( project_metadata.clone(), ISSUER_1, @@ -350,21 +250,7 @@ mod settle_evaluation_extrinsic { EvaluationParams::from((EVALUATOR_2, 250_000 * USD_UNIT)), EvaluationParams::from((EVALUATOR_3, 320_000 * USD_UNIT)), ], - inst.generate_bids_from_total_ct_percent( - project_metadata.clone(), - 50, - default_weights(), - default_bidders(), - default_modes(), - ), - inst.generate_contributions_from_total_ct_percent( - project_metadata.clone(), - 50, - default_weights(), - default_community_contributors(), - default_community_contributor_modes(), - ), - vec![], + inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 100, 5), ); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); @@ -459,7 +345,7 @@ mod settle_evaluation_extrinsic { #[test] fn evaluation_slashed() { - let percentage = 20u64; + let percentage = 20u8; let (mut inst, project_id) = create_project_with_funding_percentage(percentage, true); let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); @@ -524,7 +410,7 @@ mod settle_evaluation_extrinsic { #[test] fn cannot_settle_twice() { - let percentage = 100u64; + let percentage = 100u8; let (mut inst, project_id) = create_project_with_funding_percentage(percentage, true); let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); @@ -550,7 +436,7 @@ mod settle_evaluation_extrinsic { #[test] fn cannot_be_called_before_settlement_started() { - let percentage = 100u64; + let percentage = 100u8; let (mut inst, project_id) = create_project_with_funding_percentage(percentage, false); let first_evaluation = inst.get_evaluations(project_id).into_iter().next().unwrap(); @@ -588,27 +474,24 @@ mod settle_bid_extrinsic { let mut project_metadata = default_project_metadata(ISSUER_1); project_metadata.participation_currencies = bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; - let auction_allocation = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let auction_allocation = project_metadata.total_allocation_size; let partial_amount_bid_params = BidParams::from(( BIDDER_1, + Retail, auction_allocation, ParticipationMode::Classic(3u8), AcceptedFundingAsset::USDT, )); - let lower_price_bid_params = - BidParams::from((BIDDER_2, 2000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::DOT)); + let lower_price_bid_params = BidParams::from(( + BIDDER_2, + Retail, + 2000 * CT_UNIT, + ParticipationMode::Classic(5u8), + AcceptedFundingAsset::DOT, + )); let bids = vec![partial_amount_bid_params.clone(), lower_price_bid_params.clone()]; - - let project_id = inst.create_finished_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - bids, - default_community_contributions(), - vec![], - ); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = inst.create_finished_project(project_metadata.clone(), ISSUER_1, None, evaluations, bids); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); @@ -690,7 +573,7 @@ mod settle_bid_extrinsic { ); inst.execute(|| { - assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BUYER_1), project_id, BIDDER_2, 1)); + assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_2, 1)); }); let post_issuer_dot_balance = inst.get_free_funding_asset_balance_for( @@ -747,23 +630,21 @@ mod settle_bid_extrinsic { let mut project_metadata = default_project_metadata(ISSUER_1); project_metadata.participation_currencies = bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; - let auction_allocation = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let auction_allocation = project_metadata.total_allocation_size; let no_refund_bid_params = BidParams::from(( BIDDER_1, + Institutional, auction_allocation / 2, ParticipationMode::Classic(16u8), AcceptedFundingAsset::USDT, )); - + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let project_id = inst.create_finished_project( project_metadata.clone(), ISSUER_1, None, - default_evaluations(), + evaluations, vec![no_refund_bid_params.clone()], - default_community_contributions(), - vec![], ); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); @@ -818,131 +699,6 @@ mod settle_bid_extrinsic { inst.assert_plmc_free_balance(BIDDER_1, no_refund_bid_stored.plmc_bond + ed); } - #[test] - fn accepted_bid_with_refund_on_project_failure() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let ed = inst.get_ed(); - let usdt_ed = inst.get_funding_asset_ed(AcceptedFundingAsset::USDT.id()); - let dot_ed = inst.get_funding_asset_ed(AcceptedFundingAsset::DOT.id()); - - let mut project_metadata = default_project_metadata(ISSUER_1); - project_metadata.participation_currencies = - bounded_vec![AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT]; - project_metadata.auction_round_allocation_percentage = Percent::from_percent(10); - let auction_allocation = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - let partial_amount_bid_params = BidParams::from(( - BIDDER_1, - auction_allocation, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDC, - )); - let lower_price_bid_params = - BidParams::from((BIDDER_2, 2000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::DOT)); - let bids = vec![partial_amount_bid_params.clone(), lower_price_bid_params.clone()]; - - let project_id = inst.create_finished_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - bids, - vec![], - vec![], - ); - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); - let hold_reason: RuntimeHoldReason = HoldReason::Participation.into(); - - // Partial amount bid assertions - let partial_amount_bid_stored = - inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); - - let pre_issuer_usdc_balance = inst.get_free_funding_asset_balance_for( - AcceptedFundingAsset::USDC.id(), - project_metadata.funding_destination_account, - ); - - inst.execute(|| { - assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 0)); - }); - - let post_issuer_usdc_balance = inst.get_free_funding_asset_balance_for( - AcceptedFundingAsset::USDC.id(), - project_metadata.funding_destination_account, - ); - - inst.assert_funding_asset_free_balance( - BIDDER_1, - AcceptedFundingAsset::USDC.id(), - partial_amount_bid_stored.funding_asset_amount_locked + usdt_ed, - ); - assert_eq!(post_issuer_usdc_balance, pre_issuer_usdc_balance); - - inst.assert_plmc_free_balance(BIDDER_1, partial_amount_bid_stored.plmc_bond + ed); - inst.assert_ct_balance(project_id, BIDDER_1, Zero::zero()); - - inst.assert_migration( - project_id, - BIDDER_1, - Zero::zero(), - 0, - ParticipationType::Bid, - polkadot_junction!(BIDDER_1), - false, - ); - - inst.execute(|| { - assert_noop!( - LinearRelease::vest(RuntimeOrigin::signed(BIDDER_1), hold_reason), - pallet_linear_release::Error::::NotVesting - ); - }); - - // Price > wap bid assertions - let lower_price_bid_stored = inst.execute(|| Bids::::get((project_id, BIDDER_2, 1)).unwrap()); - - let pre_issuer_dot_balance = inst.get_free_funding_asset_balance_for( - AcceptedFundingAsset::DOT.id(), - project_metadata.funding_destination_account, - ); - - inst.execute(|| { - assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BUYER_1), project_id, BIDDER_2, 1)); - }); - - let post_issuer_dot_balance = inst.get_free_funding_asset_balance_for( - AcceptedFundingAsset::DOT.id(), - project_metadata.funding_destination_account, - ); - - inst.assert_funding_asset_free_balance( - BIDDER_2, - AcceptedFundingAsset::DOT.id(), - lower_price_bid_stored.funding_asset_amount_locked + dot_ed, - ); - assert_eq!(post_issuer_dot_balance, pre_issuer_dot_balance); - - inst.assert_plmc_free_balance(BIDDER_2, lower_price_bid_stored.plmc_bond + ed); - inst.assert_ct_balance(project_id, BIDDER_2, Zero::zero()); - - inst.assert_migration( - project_id, - BIDDER_2, - Zero::zero(), - 1, - ParticipationType::Bid, - polkadot_junction!(BIDDER_2), - false, - ); - - inst.execute(|| { - assert_noop!( - LinearRelease::vest(RuntimeOrigin::signed(BIDDER_2), hold_reason), - pallet_linear_release::Error::::NotVesting - ); - }); - } - #[test] fn accepted_bid_without_refund_on_project_failure() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); @@ -953,19 +709,19 @@ mod settle_bid_extrinsic { bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; let no_refund_bid_params = BidParams::from(( BIDDER_1, + Institutional, 500 * CT_UNIT, ParticipationMode::Classic(16u8), AcceptedFundingAsset::USDT, )); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let project_id = inst.create_finished_project( project_metadata.clone(), ISSUER_1, None, - default_evaluations(), + evaluations, vec![no_refund_bid_params.clone()], - vec![], - vec![], ); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); @@ -1005,74 +761,6 @@ mod settle_bid_extrinsic { }); } - #[test] - fn rejected_bid_on_community_round() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let ed = inst.get_ed(); - let usdt_ed = inst.get_funding_asset_ed(AcceptedFundingAsset::USDT.id()); - let mut project_metadata = default_project_metadata(ISSUER_1); - project_metadata.participation_currencies = - bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; - let auction_allocation = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - let rejected_bid_params = BidParams::from(( - BIDDER_1, - auction_allocation, - ParticipationMode::Classic(4u8), - AcceptedFundingAsset::USDT, - )); - let accepted_bid_params = BidParams::from(( - BIDDER_2, - auction_allocation, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::DOT, - )); - - let bids = vec![rejected_bid_params.clone(), accepted_bid_params.clone()]; - let project_id = inst.create_community_contributing_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - bids, - ); - - let rejected_bid_stored = inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); - assert_eq!(rejected_bid_stored.status, BidStatus::Rejected); - - let pre_issuer_usdt_balance = inst.get_free_funding_asset_balance_for( - AcceptedFundingAsset::USDT.id(), - project_metadata.funding_destination_account, - ); - - inst.execute(|| { - assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 0)); - }); - - let post_issuer_usdt_balance = inst.get_free_funding_asset_balance_for( - AcceptedFundingAsset::USDT.id(), - project_metadata.funding_destination_account, - ); - - inst.assert_funding_asset_free_balance( - BIDDER_1, - AcceptedFundingAsset::USDT.id(), - rejected_bid_stored.funding_asset_amount_locked + usdt_ed, - ); - assert_eq!(post_issuer_usdt_balance, pre_issuer_usdt_balance); - - inst.assert_plmc_free_balance(BIDDER_1, rejected_bid_stored.plmc_bond + ed); - inst.assert_ct_balance(project_id, BIDDER_1, Zero::zero()); - - let hold_reason = HoldReason::Participation.into(); - inst.execute(|| { - assert_noop!( - LinearRelease::vest(RuntimeOrigin::signed(BIDDER_2), hold_reason), - pallet_linear_release::Error::::NotVesting - ); - }); - } - #[test] fn rejected_bid_on_project_success() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); @@ -1081,31 +769,25 @@ mod settle_bid_extrinsic { let mut project_metadata = default_project_metadata(ISSUER_1); project_metadata.participation_currencies = bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; - let auction_allocation = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + let auction_allocation = project_metadata.total_allocation_size; let rejected_bid_params = BidParams::from(( BIDDER_1, + Retail, auction_allocation, ParticipationMode::Classic(4u8), AcceptedFundingAsset::USDT, )); let accepted_bid_params = BidParams::from(( BIDDER_2, + Retail, auction_allocation, ParticipationMode::Classic(1u8), AcceptedFundingAsset::DOT, )); let bids = vec![rejected_bid_params.clone(), accepted_bid_params.clone()]; - let project_id = inst.create_finished_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - bids, - default_community_contributions(), - vec![], - ); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = inst.create_finished_project(project_metadata.clone(), ISSUER_1, None, evaluations, bids); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); let rejected_bid_stored = inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); @@ -1143,78 +825,6 @@ mod settle_bid_extrinsic { ); }); } - - #[test] - fn rejected_bid_on_project_failure() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let ed = inst.get_ed(); - let usdt_ed = inst.get_funding_asset_ed(AcceptedFundingAsset::USDT.id()); - let mut project_metadata = default_project_metadata(ISSUER_1); - project_metadata.participation_currencies = - bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; - project_metadata.auction_round_allocation_percentage = Percent::from_percent(10); - let auction_allocation = - project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - let rejected_bid_params = BidParams::from(( - BIDDER_1, - auction_allocation, - ParticipationMode::Classic(4u8), - AcceptedFundingAsset::USDT, - )); - let accepted_bid_params = BidParams::from(( - BIDDER_2, - auction_allocation, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::DOT, - )); - - let bids = vec![rejected_bid_params.clone(), accepted_bid_params.clone()]; - let project_id = inst.create_finished_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - bids, - vec![], - vec![], - ); - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); - - let rejected_bid_stored = inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); - assert_eq!(rejected_bid_stored.status, BidStatus::Rejected); - - let pre_issuer_usdt_balance = inst.get_free_funding_asset_balance_for( - AcceptedFundingAsset::USDT.id(), - project_metadata.funding_destination_account, - ); - - inst.execute(|| { - assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 0)); - }); - - let post_issuer_usdt_balance = inst.get_free_funding_asset_balance_for( - AcceptedFundingAsset::USDT.id(), - project_metadata.funding_destination_account, - ); - - inst.assert_funding_asset_free_balance( - BIDDER_1, - AcceptedFundingAsset::USDT.id(), - rejected_bid_stored.funding_asset_amount_locked + usdt_ed, - ); - assert_eq!(post_issuer_usdt_balance, pre_issuer_usdt_balance); - - inst.assert_plmc_free_balance(BIDDER_1, rejected_bid_stored.plmc_bond + ed); - inst.assert_ct_balance(project_id, BIDDER_1, Zero::zero()); - - let hold_reason = HoldReason::Participation.into(); - inst.execute(|| { - assert_noop!( - LinearRelease::vest(RuntimeOrigin::signed(BIDDER_2), hold_reason), - pallet_linear_release::Error::::NotVesting - ); - }); - } } #[cfg(test)] @@ -1223,7 +833,7 @@ mod settle_bid_extrinsic { #[test] fn cannot_settle_twice() { - let percentage = 100u64; + let percentage = 100u8; let (mut inst, project_id) = create_project_with_funding_percentage(percentage, true); let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); @@ -1249,7 +859,7 @@ mod settle_bid_extrinsic { #[test] fn cannot_be_called_before_settlement_started() { - let percentage = 100u64; + let percentage = 100u8; let (mut inst, project_id) = create_project_with_funding_percentage(percentage, false); let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); @@ -1269,307 +879,6 @@ mod settle_bid_extrinsic { } } -#[cfg(test)] -mod settle_contribution_extrinsic { - use super::*; - - #[cfg(test)] - mod success { - use super::*; - - #[test] - fn contribution_on_successful_project() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let ed = inst.get_ed(); - let usdt_ed = inst.get_funding_asset_ed(AcceptedFundingAsset::USDT.id()); - let project_metadata = default_project_metadata(ISSUER_1); - - let contribution = ContributionParams::::from(( - BUYER_1, - 1000 * CT_UNIT, - ParticipationMode::Classic(2), - AcceptedFundingAsset::USDT, - )); - - let project_id = inst.create_finished_project( - project_metadata.clone(), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - vec![contribution.clone()], - vec![], - ); - - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); - - // First contribution assertions - let stored_contribution = - inst.execute(|| Contributions::::get((project_id, BUYER_1, 0)).unwrap()); - let hold_reason: RuntimeHoldReason = HoldReason::Participation.into(); - - inst.assert_plmc_free_balance(BUYER_1, ed); - inst.assert_plmc_held_balance(BUYER_1, stored_contribution.plmc_bond, hold_reason); - inst.assert_ct_balance(project_id, BUYER_1, Zero::zero()); - inst.assert_funding_asset_free_balance(BUYER_1, AcceptedFundingAsset::USDT.id(), usdt_ed); - inst.assert_funding_asset_free_balance( - project_metadata.funding_destination_account, - AcceptedFundingAsset::USDT.id(), - Zero::zero(), - ); - - inst.execute(|| { - assert_ok!(PolimecFunding::settle_contribution(RuntimeOrigin::signed(BUYER_1), project_id, BUYER_1, 0)); - }); - - inst.assert_plmc_free_balance(BUYER_1, ed); - inst.assert_plmc_held_balance(BUYER_1, stored_contribution.plmc_bond, hold_reason); - inst.assert_ct_balance(project_id, BUYER_1, stored_contribution.ct_amount); - inst.assert_funding_asset_free_balance(BUYER_1, AcceptedFundingAsset::USDT.id(), usdt_ed); - inst.assert_funding_asset_free_balance( - project_metadata.funding_destination_account, - AcceptedFundingAsset::USDT.id(), - stored_contribution.funding_asset_amount, - ); - inst.assert_migration( - project_id, - BUYER_1, - stored_contribution.ct_amount, - 0, - ParticipationType::Contribution, - polkadot_junction!(BUYER_1), - true, - ); - - let multiplier: MultiplierOf = contribution.mode.multiplier().try_into().ok().unwrap(); - let vesting_time = multiplier.calculate_vesting_duration::(); - let current_block = inst.current_block(); - inst.jump_to_block(current_block + vesting_time + 1); - inst.execute(|| LinearRelease::vest(RuntimeOrigin::signed(BUYER_1), hold_reason).expect("Vesting failed")); - inst.assert_plmc_free_balance(BUYER_1, ed + stored_contribution.plmc_bond); - inst.assert_plmc_held_balance(BUYER_1, Zero::zero(), hold_reason); - } - - #[test] - fn contribution_on_failed_project() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let issuer = ISSUER_1; - let evaluations = - inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); - let bids = inst.generate_bids_from_total_ct_percent( - project_metadata.clone(), - 10, - default_weights(), - default_bidders(), - default_modes(), - ); - let mut community_contributions = inst.generate_contributions_from_total_ct_percent( - project_metadata.clone(), - 10, - default_weights(), - default_community_contributors(), - default_community_contributor_modes(), - ); - - let contribution_mul_1 = ContributionParams::::from(( - BUYER_6, - 1000 * CT_UNIT, - ParticipationMode::Classic(1), - AcceptedFundingAsset::USDT, - )); - let contribution_mul_2 = ContributionParams::::from(( - BUYER_7, - 1000 * CT_UNIT, - ParticipationMode::Classic(2), - AcceptedFundingAsset::USDT, - )); - - community_contributions.push(contribution_mul_1); - - let project_id = inst.create_remainder_contributing_project( - project_metadata.clone(), - issuer, - None, - evaluations, - bids, - community_contributions, - ); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - let plmc_required = inst.calculate_contributed_plmc_spent(vec![contribution_mul_2.clone()], wap); - inst.mint_plmc_ed_if_required(plmc_required.accounts()); - inst.mint_plmc_to(plmc_required.clone()); - inst.mint_funding_asset_ed_if_required(vec![(BUYER_7, AcceptedFundingAsset::USDT.id())]); - let usdt_required = inst.calculate_contributed_funding_asset_spent(vec![contribution_mul_2.clone()], wap); - inst.mint_funding_asset_to(usdt_required.clone()); - - inst.execute(|| { - assert_ok!(PolimecFunding::contribute( - RuntimeOrigin::signed(BUYER_7), - get_mock_jwt_with_cid( - BUYER_7, - InvestorType::Professional, - generate_did_from_account(BUYER_7), - project_metadata.clone().policy_ipfs_cid.unwrap(), - ), - project_id, - contribution_mul_2.amount, - contribution_mul_2.mode, - contribution_mul_2.asset - )); - }); - - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); - - // First contribution assertions - let stored_contribution = - inst.execute(|| Contributions::::get((project_id, BUYER_6, 5)).unwrap()); - let plmc_free_amount = inst.get_free_plmc_balance_for(BUYER_6); - let plmc_held_amount = inst.get_reserved_plmc_balance_for(BUYER_6, HoldReason::Participation.into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BUYER_6); - let issuer_usdt_balance = - inst.get_free_funding_asset_balance_for(stored_contribution.funding_asset.id(), issuer); - let unvested_amount = inst.execute(|| { - VestingOf::::total_scheduled_amount(&BUYER_6, HoldReason::Participation.into()) - }); - - assert_eq!(plmc_free_amount, inst.get_ed()); - assert_eq!(plmc_held_amount, stored_contribution.plmc_bond); - assert_eq!(ct_amount, 0u128); - assert_eq!(issuer_usdt_balance, 0u128); - assert!(unvested_amount.is_none()); - - inst.execute(|| { - assert_ok!(PolimecFunding::settle_contribution(RuntimeOrigin::signed(BUYER_6), project_id, BUYER_6, 5)); - }); - - assert!(inst.execute(|| Contributions::::get((project_id, BUYER_6, 6)).is_none())); - let plmc_free_amount = inst.get_free_plmc_balance_for(BUYER_6); - let plmc_held_amount = inst.get_reserved_plmc_balance_for(BUYER_6, HoldReason::Participation.into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BUYER_6); - let issuer_usdt_balance = - inst.get_free_funding_asset_balance_for(stored_contribution.funding_asset.id(), issuer); - let unvested_amount = inst.execute(|| { - VestingOf::::total_scheduled_amount(&BUYER_6, HoldReason::Participation.into()) - }); - - assert_eq!(plmc_free_amount, inst.get_ed() + stored_contribution.plmc_bond); - assert_eq!(plmc_held_amount, 0u128); - assert_eq!(ct_amount, Zero::zero()); - assert_eq!(issuer_usdt_balance, Zero::zero()); - assert!(unvested_amount.is_none()); - inst.assert_migration( - project_id, - BUYER_6, - stored_contribution.ct_amount, - 5, - ParticipationType::Contribution, - polkadot_junction!(BUYER_6), - false, - ); - - // Second contribution assertions - let stored_contribution = - inst.execute(|| Contributions::::get((project_id, BUYER_7, 6)).unwrap()); - let plmc_free_amount = inst.get_free_plmc_balance_for(BUYER_7); - let plmc_held_amount = inst.get_reserved_plmc_balance_for(BUYER_7, HoldReason::Participation.into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BUYER_7); - let issuer_usdt_balance_2 = - inst.get_free_funding_asset_balance_for(stored_contribution.funding_asset.id(), issuer); - let unvested_amount = inst.execute(|| { - VestingOf::::total_scheduled_amount(&BUYER_7, HoldReason::Participation.into()) - }); - assert_eq!(plmc_free_amount, inst.get_ed()); - assert_eq!(plmc_held_amount, stored_contribution.plmc_bond); - assert_eq!(ct_amount, 0u128); - assert_eq!(issuer_usdt_balance_2, issuer_usdt_balance); - assert!(unvested_amount.is_none()); - - inst.execute(|| { - assert_ok!(PolimecFunding::settle_contribution(RuntimeOrigin::signed(BUYER_7), project_id, BUYER_7, 6)); - }); - - assert!(inst.execute(|| Contributions::::get((project_id, BUYER_7, 7)).is_none())); - let plmc_free_amount = inst.get_free_plmc_balance_for(BUYER_7); - let plmc_held_amount = inst.get_reserved_plmc_balance_for(BUYER_7, HoldReason::Participation.into()); - let ct_amount = inst.get_ct_asset_balance_for(project_id, BUYER_7); - let issuer_usdt_balance_2 = - inst.get_free_funding_asset_balance_for(stored_contribution.funding_asset.id(), issuer); - let unvested_amount = inst.execute(|| { - VestingOf::::total_scheduled_amount(&BUYER_7, HoldReason::Participation.into()) - }); - - assert_eq!(plmc_free_amount, inst.get_ed() + stored_contribution.plmc_bond); - assert_eq!(plmc_held_amount, 0u128); - assert_eq!(ct_amount, Zero::zero()); - assert_eq!(issuer_usdt_balance_2, Zero::zero()); - assert!(unvested_amount.is_none()); - - inst.assert_migration( - project_id, - BUYER_7, - stored_contribution.ct_amount, - 6, - ParticipationType::Contribution, - polkadot_junction!(BUYER_7), - false, - ); - } - } - - #[cfg(test)] - mod failure { - use super::*; - - #[test] - fn cannot_settle_twice() { - let percentage = 100u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, true); - - let first_contribution = inst.get_contributions(project_id).into_iter().next().unwrap(); - inst.execute(|| { - let contributor = first_contribution.contributor; - assert_ok!(crate::Pallet::::settle_contribution( - RuntimeOrigin::signed(contributor), - project_id, - contributor, - first_contribution.id - )); - assert_noop!( - crate::Pallet::::settle_contribution( - RuntimeOrigin::signed(contributor), - project_id, - contributor, - first_contribution.id - ), - Error::::ParticipationNotFound - ); - }); - } - - #[test] - fn cannot_be_called_before_settlement_started() { - let percentage = 100u64; - let (mut inst, project_id) = create_project_with_funding_percentage(percentage, false); - let first_contribution = inst.get_contributions(project_id).into_iter().next().unwrap(); - let contributor = first_contribution.contributor; - inst.execute(|| { - assert_noop!( - crate::Pallet::::settle_contribution( - RuntimeOrigin::signed(contributor), - project_id, - contributor, - first_contribution.id - ), - Error::::SettlementNotStarted - ); - }); - } - } -} - #[cfg(test)] mod mark_project_as_settled_extrinsic { use super::*; diff --git a/pallets/funding/src/tests/7_ct_migration.rs b/pallets/funding/src/tests/7_ct_migration.rs index 8ef21bd28..c98bdb39e 100644 --- a/pallets/funding/src/tests/7_ct_migration.rs +++ b/pallets/funding/src/tests/7_ct_migration.rs @@ -9,15 +9,10 @@ mod pallet_migration { #[test] fn start_pallet_migration() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_finished_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), - ); + let project_metadata = default_project_metadata(ISSUER_1); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 90, 5); + let project_id = inst.create_finished_project(project_metadata.clone(), ISSUER_1, None, evaluations, bids); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); inst.settle_project(project_id, true); @@ -34,10 +29,6 @@ mod pallet_migration { crate::Pallet::::do_start_pallet_migration(&BIDDER_1, project_id, ParaId::from(2006u32),), Error::::NotIssuer ); - assert_err!( - crate::Pallet::::do_start_pallet_migration(&BUYER_1, project_id, ParaId::from(2006u32),), - Error::::NotIssuer - ); assert_ok!(crate::Pallet::::do_start_pallet_migration( &ISSUER_1, project_id, @@ -62,15 +53,10 @@ mod pallet_migration { } fn create_pallet_migration_project(mut inst: MockInstantiator) -> (ProjectId, MockInstantiator) { - let project_id = inst.create_finished_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), - ); + let evaluations = inst.generate_successful_evaluations(default_project_metadata(ISSUER_1), 5); + let bids = inst.generate_bids_from_total_ct_percent(default_project_metadata(ISSUER_1), 90, 5); + let project_id = + inst.create_finished_project(default_project_metadata(ISSUER_1), ISSUER_1, None, evaluations, bids); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); inst.settle_project(project_id, true); inst.execute(|| { @@ -187,28 +173,21 @@ mod offchain_migration { #[test] fn start_offchain_migration() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let evaluations = inst.generate_successful_evaluations(default_project_metadata(ISSUER_1), 5); + let bids = inst.generate_bids_from_total_ct_percent(default_project_metadata(ISSUER_1), 90, 5); // Create migrations for 2 projects, to check the `remaining_participants` is unaffected by other projects let project_id = inst.create_finished_project( default_project_metadata(ISSUER_1), ISSUER_1, None, - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), + evaluations.clone(), + bids.clone(), ); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); inst.settle_project(project_id, true); - let project_id = inst.create_finished_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), - ); + let project_id = + inst.create_finished_project(default_project_metadata(ISSUER_1), ISSUER_1, None, evaluations, bids); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); inst.settle_project(project_id, true); @@ -227,15 +206,10 @@ mod offchain_migration { } fn create_offchain_migration_project(mut inst: MockInstantiator) -> (ProjectId, MockInstantiator) { - let project_id = inst.create_finished_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), - ); + let project_metadata = default_project_metadata(ISSUER_1); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 90, 5); + let project_id = inst.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); inst.settle_project(project_id, true); inst.execute(|| { @@ -248,15 +222,16 @@ mod offchain_migration { fn confirm_offchain_migration() { let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let (project_id, mut inst) = create_offchain_migration_project(inst); + let bidder_1 = inst.account_from_u32(0, "BIDDER"); - let bidder_1_migrations = inst.execute(|| UserMigrations::::get((project_id, BIDDER_1))).unwrap(); + let bidder_1_migrations = inst.execute(|| UserMigrations::::get((project_id, bidder_1))).unwrap(); assert_eq!(bidder_1_migrations.0, MigrationStatus::NotStarted); inst.execute(|| { - assert_ok!(crate::Pallet::::do_confirm_offchain_migration(project_id, ISSUER_1, BIDDER_1)); + assert_ok!(crate::Pallet::::do_confirm_offchain_migration(project_id, ISSUER_1, bidder_1)); }); - let bidder_1_migrations = inst.execute(|| UserMigrations::::get((project_id, BIDDER_1))).unwrap(); + let bidder_1_migrations = inst.execute(|| UserMigrations::::get((project_id, bidder_1))).unwrap(); assert_eq!(bidder_1_migrations.0, MigrationStatus::Confirmed); } diff --git a/pallets/funding/src/tests/misc.rs b/pallets/funding/src/tests/misc.rs index 016da01b3..373e27d9f 100644 --- a/pallets/funding/src/tests/misc.rs +++ b/pallets/funding/src/tests/misc.rs @@ -140,16 +140,41 @@ mod helper_functions { const CT_AMOUNT_4: u128 = 6000 * CT_UNIT; const CT_AMOUNT_5: u128 = 2000 * CT_UNIT; - let bid_1 = - BidParams::from((BIDDER_1, CT_AMOUNT_1, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT)); - let bid_2 = - BidParams::from((BIDDER_2, CT_AMOUNT_2, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT)); - let bid_3 = - BidParams::from((BIDDER_1, CT_AMOUNT_3, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT)); - let bid_4 = - BidParams::from((BIDDER_3, CT_AMOUNT_4, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT)); - let bid_5 = - BidParams::from((BIDDER_4, CT_AMOUNT_5, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT)); + let bid_1 = BidParams::from(( + BIDDER_1, + Retail, + CT_AMOUNT_1, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + )); + let bid_2 = BidParams::from(( + BIDDER_2, + Retail, + CT_AMOUNT_2, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + )); + let bid_3 = BidParams::from(( + BIDDER_1, + Retail, + CT_AMOUNT_3, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + )); + let bid_4 = BidParams::from(( + BIDDER_3, + Retail, + CT_AMOUNT_4, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + )); + let bid_5 = BidParams::from(( + BIDDER_4, + Retail, + CT_AMOUNT_5, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + )); // post bucketing, the bids look like this: // (BIDDER_1, 5k) - (BIDDER_2, 40k) - (BIDDER_1, 5k) - (BIDDER_1, 5k) - (BIDDER_3 - 5k) - (BIDDER_3 - 1k) - (BIDDER_4 - 2k) @@ -173,8 +198,7 @@ mod helper_functions { let project_metadata = ProjectMetadata { token_information: default_token_information(), mainnet_token_max_supply: 8_000_000 * CT_UNIT, - total_allocation_size: 100_000 * CT_UNIT, - auction_round_allocation_percentage: Percent::from_percent(50u8), + total_allocation_size: 50_000 * CT_UNIT, minimum_price: PriceProviderOf::::calculate_decimals_aware_price( PriceOf::::from_float(10.0), USD_DECIMALS, @@ -184,12 +208,7 @@ mod helper_functions { bidding_ticket_sizes: BiddingTicketSizes { professional: TicketSize::new(5000 * USD_UNIT, None), institutional: TicketSize::new(5000 * USD_UNIT, None), - phantom: Default::default(), - }, - contributing_ticket_sizes: ContributingTicketSizes { - retail: TicketSize::new(USD_UNIT, None), - professional: TicketSize::new(USD_UNIT, None), - institutional: TicketSize::new(USD_UNIT, None), + retail: TicketSize::new(100 * USD_UNIT, None), phantom: Default::default(), }, participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), @@ -198,11 +217,13 @@ mod helper_functions { participants_account_type: ParticipantsAccountType::Polkadot, }; - let project_id = inst.create_community_contributing_project( + let successful_evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + + let project_id = inst.create_finished_project( project_metadata.clone(), ISSUER_1, None, - default_evaluations(), + successful_evaluations, bids.clone(), ); @@ -246,116 +267,6 @@ mod helper_functions { let diff = if wap > expected { wap - expected } else { expected - wap }; assert!(diff <= FixedU128::from_float(0.001)); } - - #[test] - fn calculate_contributed_plmc_spent() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - const PLMC_PRICE: f64 = 8.4f64; - const CT_PRICE: f64 = 16.32f64; - - const CONTRIBUTOR_1: AccountIdOf = 1; - const TOKEN_AMOUNT_1: u128 = 120 * CT_UNIT; - const MULTIPLIER_1: u8 = 1u8; - const _TICKET_SIZE_USD_1: u128 = 1_958_4_000_000_000_u128; - const EXPECTED_PLMC_AMOUNT_1: f64 = 233.1_428_571_428f64; - - const CONTRIBUTOR_2: AccountIdOf = 2; - const TOKEN_AMOUNT_2: u128 = 5023 * CT_UNIT; - const MULTIPLIER_2: u8 = 2u8; - const _TICKET_SIZE_USD_2: u128 = 81_975_3_600_000_000_u128; - const EXPECTED_PLMC_AMOUNT_2: f64 = 4_879.4_857_142_857f64; - - const CONTRIBUTOR_3: AccountIdOf = 3; - const TOKEN_AMOUNT_3: u128 = 20_000 * CT_UNIT; - const MULTIPLIER_3: u8 = 17u8; - const _TICKET_SIZE_USD_3: u128 = 326_400_0_000_000_000_u128; - const EXPECTED_PLMC_AMOUNT_3: f64 = 2_285.7_142_857_142f64; - - const CONTRIBUTOR_4: AccountIdOf = 4; - const TOKEN_AMOUNT_4: u128 = 1_000_000 * CT_UNIT; - const MULTIPLIER_4: u8 = 25u8; - const _TICKET_SIZE_4: u128 = 16_320_000_0_000_000_000_u128; - const EXPECTED_PLMC_AMOUNT_4: f64 = 77_714.2_857_142_857f64; - - const CONTRIBUTOR_5: AccountIdOf = 5; - // 0.1233 CTs - const TOKEN_AMOUNT_5: u128 = 1_233 * CT_UNIT / 10_000; - const MULTIPLIER_5: u8 = 10u8; - const _TICKET_SIZE_5: u128 = 2_0_122_562_000_u128; - const EXPECTED_PLMC_AMOUNT_5: f64 = 0.0_239_554_285f64; - - assert_eq!( - ::PriceProvider::get_price(Location::here()).unwrap(), - PriceOf::::from_float(PLMC_PRICE) - ); - - let contributions = vec![ - ContributionParams::from(( - CONTRIBUTOR_1, - TOKEN_AMOUNT_1, - ParticipationMode::Classic(MULTIPLIER_1), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from(( - CONTRIBUTOR_2, - TOKEN_AMOUNT_2, - ParticipationMode::Classic(MULTIPLIER_2), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from(( - CONTRIBUTOR_3, - TOKEN_AMOUNT_3, - ParticipationMode::Classic(MULTIPLIER_3), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from(( - CONTRIBUTOR_4, - TOKEN_AMOUNT_4, - ParticipationMode::Classic(MULTIPLIER_4), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from(( - CONTRIBUTOR_5, - TOKEN_AMOUNT_5, - ParticipationMode::Classic(MULTIPLIER_5), - AcceptedFundingAsset::USDT, - )), - ]; - - let expected_plmc_spent = vec![ - (CONTRIBUTOR_1, EXPECTED_PLMC_AMOUNT_1), - (CONTRIBUTOR_2, EXPECTED_PLMC_AMOUNT_2), - (CONTRIBUTOR_3, EXPECTED_PLMC_AMOUNT_3), - (CONTRIBUTOR_4, EXPECTED_PLMC_AMOUNT_4), - (CONTRIBUTOR_5, EXPECTED_PLMC_AMOUNT_5), - ]; - - let calculated_plmc_spent = inst - .calculate_contributed_plmc_spent( - contributions, - PriceProviderOf::::calculate_decimals_aware_price( - PriceOf::::from_float(CT_PRICE), - USD_DECIMALS, - CT_DECIMALS, - ) - .unwrap(), - ) - .into_iter() - .sorted_by(|a, b| a.account.cmp(&b.account)) - .map(|map| map.plmc_amount) - .collect_vec(); - let expected_plmc_spent = expected_plmc_spent - .into_iter() - .sorted_by(|a, b| a.0.cmp(&b.0)) - .map(|map| { - let fixed_amount = FixedU128::from_float(map.1); - fixed_amount.checked_mul_int(PLMC).unwrap() - }) - .collect_vec(); - for (expected, calculated) in zip(expected_plmc_spent, calculated_plmc_spent) { - assert_close_enough!(expected, calculated, Perquintill::from_float(0.999)); - } - } } // logic of small functions that extrinsics use to process data or interact with storage @@ -401,14 +312,13 @@ mod inner_functions { #[test] fn project_state_transition_event() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let project_metadata = default_project_metadata(ISSUER_1); let project_id = inst.create_settled_project( - default_project_metadata(ISSUER_1), + project_metadata.clone(), ISSUER_1, None, - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), + inst.generate_successful_evaluations(project_metadata.clone(), 5), + inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 80, 5), true, ); @@ -427,9 +337,6 @@ fn project_state_transition_event() { let mut desired_transitions = vec![ ProjectStatus::EvaluationRound, ProjectStatus::AuctionRound, - ProjectStatus::CommunityRound( - EvaluationRoundDuration::get() + AuctionRoundDuration::get() + CommunityRoundDuration::get() + 1u64, - ), ProjectStatus::FundingSuccessful, ProjectStatus::SettlementStarted(FundingOutcome::Success), ProjectStatus::SettlementFinished(FundingOutcome::Success), diff --git a/pallets/funding/src/tests/mod.rs b/pallets/funding/src/tests/mod.rs index 5f25156c4..5a1402bf1 100644 --- a/pallets/funding/src/tests/mod.rs +++ b/pallets/funding/src/tests/mod.rs @@ -6,26 +6,24 @@ use defaults::*; use frame_support::{ assert_err, assert_noop, assert_ok, traits::{ - fungible::{Inspect as FungibleInspect, InspectHold as FungibleInspectHold, MutateFreeze, MutateHold}, + fungible::{MutateFreeze, MutateHold}, Get, }, }; use itertools::Itertools; use parachains_common::DAYS; -use polimec_common::{ProvideAssetPrice, ReleaseSchedule, USD_DECIMALS, USD_UNIT}; +use polimec_common::{ProvideAssetPrice, USD_DECIMALS, USD_UNIT}; use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt_with_cid}; use sp_arithmetic::{traits::Zero, Percent, Perquintill}; use sp_runtime::{traits::Convert, TokenError}; use sp_std::cell::RefCell; use std::iter::zip; -use ParticipationMode::{Classic, OTM}; +use InvestorType::{self, *}; #[path = "1_application.rs"] mod application; #[path = "3_auction.rs"] mod auction; -#[path = "4_contribution.rs"] -mod community; #[path = "7_ct_migration.rs"] mod ct_migration; #[path = "2_evaluation.rs"] @@ -62,17 +60,33 @@ const BIDDER_3: AccountId = 33; const BIDDER_4: AccountId = 34; const BIDDER_5: AccountId = 35; const BIDDER_6: AccountId = 36; -const BUYER_1: AccountId = 41; -const BUYER_2: AccountId = 42; -const BUYER_3: AccountId = 43; -const BUYER_4: AccountId = 44; -const BUYER_5: AccountId = 45; -const BUYER_6: AccountId = 46; -const BUYER_7: AccountId = 47; + +const fn default_accounts() -> [AccountId; 18] { + [ + ISSUER_1, + ISSUER_2, + ISSUER_3, + ISSUER_4, + ISSUER_5, + ISSUER_6, + ISSUER_7, + EVALUATOR_1, + EVALUATOR_2, + EVALUATOR_3, + EVALUATOR_4, + EVALUATOR_5, + BIDDER_1, + BIDDER_2, + BIDDER_3, + BIDDER_4, + BIDDER_5, + BIDDER_6, + ] +} pub mod defaults { use super::*; - use polimec_common::assets::AcceptedFundingAsset; + use polimec_common::assets::AcceptedFundingAsset::{DOT, USDC, USDT, WETH}; pub fn default_token_information() -> CurrencyMetadata>> { CurrencyMetadata { name: bounded_name(), symbol: bounded_symbol(), decimals: CT_DECIMALS } @@ -91,247 +105,21 @@ pub mod defaults { ProjectMetadata { token_information: CurrencyMetadata { name: bounded_name, symbol: bounded_symbol, decimals: CT_DECIMALS }, mainnet_token_max_supply: 8_000_000 * CT_UNIT, - total_allocation_size: 1_000_000 * CT_UNIT, - auction_round_allocation_percentage: Percent::from_percent(50u8), + total_allocation_size: 500_000 * CT_UNIT, minimum_price: decimal_aware_price, bidding_ticket_sizes: BiddingTicketSizes { - professional: TicketSize::new(5000 * USD_UNIT, None), - institutional: TicketSize::new(5000 * USD_UNIT, None), - phantom: Default::default(), - }, - contributing_ticket_sizes: ContributingTicketSizes { - retail: TicketSize::new(USD_UNIT, None), - professional: TicketSize::new(USD_UNIT, None), - institutional: TicketSize::new(USD_UNIT, None), + professional: TicketSize::new(10 * USD_UNIT, None), + institutional: TicketSize::new(10 * USD_UNIT, None), + retail: TicketSize::new(10 * USD_UNIT, None), phantom: Default::default(), }, - participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), + participation_currencies: vec![USDT, USDC, DOT, WETH].try_into().unwrap(), funding_destination_account: issuer, policy_ipfs_cid: Some(metadata_hash), participants_account_type: ParticipantsAccountType::Polkadot, } } - pub fn knowledge_hub_project() -> ProjectMetadataOf { - let bounded_name = bounded_name(); - let bounded_symbol = bounded_symbol(); - let metadata_hash = ipfs_hash(); - let base_price = PriceOf::::from_float(10.0); - let decimal_aware_price = ::PriceProvider::calculate_decimals_aware_price( - base_price, - USD_DECIMALS, - CT_DECIMALS, - ) - .unwrap(); - - ProjectMetadataOf:: { - token_information: CurrencyMetadata { name: bounded_name, symbol: bounded_symbol, decimals: CT_DECIMALS }, - mainnet_token_max_supply: 8_000_000 * CT_UNIT, - total_allocation_size: 100_000 * CT_UNIT, - auction_round_allocation_percentage: Percent::from_percent(50u8), - minimum_price: decimal_aware_price, - bidding_ticket_sizes: BiddingTicketSizes { - professional: TicketSize::new(5000 * USD_UNIT, None), - institutional: TicketSize::new(5000 * USD_UNIT, None), - phantom: Default::default(), - }, - contributing_ticket_sizes: ContributingTicketSizes { - retail: TicketSize::new(USD_UNIT, None), - professional: TicketSize::new(USD_UNIT, None), - institutional: TicketSize::new(USD_UNIT, None), - phantom: Default::default(), - }, - participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), - funding_destination_account: ISSUER_1, - policy_ipfs_cid: Some(metadata_hash), - participants_account_type: ParticipantsAccountType::Polkadot, - } - } - - pub fn default_plmc_balances() -> Vec> { - vec![ - UserToPLMCBalance::new(ISSUER_1, 10_000_000 * PLMC), - UserToPLMCBalance::new(EVALUATOR_1, 10_000_000 * PLMC), - UserToPLMCBalance::new(EVALUATOR_2, 10_000_000 * PLMC), - UserToPLMCBalance::new(EVALUATOR_3, 10_000_000 * PLMC), - UserToPLMCBalance::new(BIDDER_1, 10_000_000 * PLMC), - UserToPLMCBalance::new(BIDDER_2, 10_000_000 * PLMC), - UserToPLMCBalance::new(BUYER_1, 10_000_000 * PLMC), - UserToPLMCBalance::new(BUYER_2, 10_000_000 * PLMC), - UserToPLMCBalance::new(BUYER_3, 10_000_000 * PLMC), - UserToPLMCBalance::new(BUYER_4, 10_000_000 * PLMC), - UserToPLMCBalance::new(BUYER_5, 10_000_000 * PLMC), - ] - } - - pub fn default_usdt_balances() -> Vec> { - vec![ - (ISSUER_1, 10_000_000 * USDT_UNIT).into(), - (EVALUATOR_1, 10_000_000 * USDT_UNIT).into(), - (EVALUATOR_2, 10_000_000 * USDT_UNIT).into(), - (EVALUATOR_3, 10_000_000 * USDT_UNIT).into(), - (BIDDER_1, 10_000_000 * USDT_UNIT).into(), - (BIDDER_2, 10_000_000 * USDT_UNIT).into(), - (BUYER_1, 10_000_000 * USDT_UNIT).into(), - (BUYER_2, 10_000_000 * USDT_UNIT).into(), - (BUYER_3, 10_000_000 * USDT_UNIT).into(), - (BUYER_4, 10_000_000 * USDT_UNIT).into(), - (BUYER_5, 10_000_000 * USDT_UNIT).into(), - ] - } - - pub fn default_evaluations() -> Vec> { - vec![ - EvaluationParams::from((EVALUATOR_1, 500_000 * USD_UNIT)), - EvaluationParams::from((EVALUATOR_2, 250_000 * USD_UNIT)), - EvaluationParams::from((EVALUATOR_3, 320_000 * USD_UNIT)), - ] - } - - pub fn default_eth_evaluations() -> Vec> { - vec![ - EvaluationParams::new( - EVALUATOR_1, - 500_000 * USD_UNIT, - Junction::AccountKey20 { network: Some(NetworkId::Ethereum { chain_id: 1 }), key: [0u8; 20] }, - ), - EvaluationParams::new( - EVALUATOR_2, - 250_000 * USD_UNIT, - Junction::AccountKey20 { network: Some(NetworkId::Ethereum { chain_id: 1 }), key: [1u8; 20] }, - ), - EvaluationParams::new( - EVALUATOR_3, - 320_000 * USD_UNIT, - Junction::AccountKey20 { network: Some(NetworkId::Ethereum { chain_id: 1 }), key: [2u8; 20] }, - ), - ] - } - - pub fn knowledge_hub_evaluations() -> Vec> { - vec![ - EvaluationParams::from((EVALUATOR_1, 75_000 * USDT_UNIT)), - EvaluationParams::from((EVALUATOR_2, 65_000 * USDT_UNIT)), - EvaluationParams::from((EVALUATOR_3, 60_000 * USDT_UNIT)), - ] - } - - pub fn default_failing_evaluations() -> Vec> { - vec![ - EvaluationParams::from((EVALUATOR_1, 3_000 * USD_UNIT)), - EvaluationParams::from((EVALUATOR_2, 1_000 * USD_UNIT)), - ] - } - - pub fn default_bids() -> Vec> { - vec![ - BidParams::from((BIDDER_1, 400_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT)), - BidParams::from((BIDDER_2, 50_000 * CT_UNIT, ParticipationMode::OTM, AcceptedFundingAsset::USDT)), - ] - } - - pub fn knowledge_hub_bids() -> Vec> { - // This should reflect the bidding currency, which currently is USDT - vec![ - BidParams::from((BIDDER_1, 10_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT)), - BidParams::from((BIDDER_2, 20_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT)), - BidParams::from((BIDDER_3, 20_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT)), - BidParams::from((BIDDER_4, 10_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT)), - BidParams::from((BIDDER_5, 5_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT)), - BidParams::from((BIDDER_6, 5_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT)), - ] - } - - pub fn default_community_contributions() -> Vec> { - vec![ - ContributionParams::from(( - BUYER_1, - 50_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from(( - BUYER_2, - 130_000 * CT_UNIT, - ParticipationMode::Classic(5u8), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from((BUYER_3, 30_000 * CT_UNIT, ParticipationMode::OTM, AcceptedFundingAsset::USDT)), - ContributionParams::from(( - BUYER_4, - 210_000 * CT_UNIT, - ParticipationMode::Classic(3u8), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from((BUYER_5, 10_000 * CT_UNIT, ParticipationMode::OTM, AcceptedFundingAsset::USDT)), - ] - } - - pub fn default_remainder_contributions() -> Vec> { - vec![ - ContributionParams::from(( - EVALUATOR_2, - 20_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from(( - BUYER_2, - 5_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from((BIDDER_1, 30_000 * CT_UNIT, ParticipationMode::OTM, AcceptedFundingAsset::USDT)), - ] - } - - pub fn knowledge_hub_buys() -> Vec> { - vec![ - ContributionParams::from(( - BUYER_1, - 4_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from(( - BUYER_2, - 2_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from(( - BUYER_3, - 2_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from(( - BUYER_4, - 5_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from(( - BUYER_5, - 30_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from(( - BUYER_6, - 5_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from(( - BUYER_7, - 2_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - ] - } - pub fn bounded_name() -> BoundedVec> { BoundedVec::try_from("Contribution Token TEST".as_bytes().to_vec()).unwrap() } @@ -342,139 +130,45 @@ pub mod defaults { BoundedVec::try_from(IPFS_CID.as_bytes().to_vec()).unwrap() } - pub fn default_weights() -> Vec { - vec![20u8, 15u8, 10u8, 25u8, 30u8] - } - - pub fn default_evaluators() -> Vec { - vec![EVALUATOR_1, EVALUATOR_2, EVALUATOR_3, EVALUATOR_4, EVALUATOR_5] - } - pub fn default_bidders() -> Vec { - vec![BIDDER_1, BIDDER_2, BIDDER_3, BIDDER_4, BIDDER_5] - } - pub fn default_modes() -> Vec { - vec![Classic(1u8), Classic(1u8), Classic(1u8), OTM, OTM] - } - pub fn default_bidder_modes() -> Vec { - vec![Classic(10u8), OTM, Classic(8u8), OTM, Classic(4u8)] - } - pub fn default_community_contributor_modes() -> Vec { - vec![Classic(1u8), Classic(1u8), OTM, Classic(1u8), OTM] - } - pub fn default_remainder_contributor_modes() -> Vec { - vec![Classic(1u8), Classic(1u8), Classic(1u8), OTM, Classic(1u8)] - } - - pub fn default_community_contributors() -> Vec { - vec![BUYER_1, BUYER_2, BUYER_3, BUYER_4, BUYER_5] - } - - pub fn default_remainder_contributors() -> Vec { - vec![EVALUATOR_1, BIDDER_3, BUYER_4, BUYER_6, BIDDER_6] + pub fn default_plmc_balances() -> Vec> { + let accounts = default_accounts().to_vec(); + accounts.iter().map(|acc| UserToPLMCBalance { account: *acc, plmc_amount: PLMC * 1_000_000 }).collect() } - 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(); + pub fn default_usdt_balances() -> Vec> { + let accounts = default_accounts().to_vec(); accounts + .iter() + .map(|acc| UserToFundingAsset { account: *acc, asset_amount: 1_000_000 * USD_UNIT, asset_id: USDT.id() }) + .collect() } pub fn project_from_funding_reached(instantiator: &mut MockInstantiator, percent: u64) -> ProjectId { let project_metadata = default_project_metadata(ISSUER_1); - let min_price = project_metadata.minimum_price; let usd_to_reach = Perquintill::from_percent(percent) * (project_metadata.minimum_price.checked_mul_int(project_metadata.total_allocation_size).unwrap()); - let evaluations = default_evaluations(); - let bids = instantiator.generate_bids_from_total_usd( - Percent::from_percent(50u8) * usd_to_reach, - min_price, - default_weights(), - default_bidders(), - default_modes(), - ); - let contributions = instantiator.generate_contributions_from_total_usd( - Percent::from_percent(50u8) * usd_to_reach, - min_price, - default_weights(), - default_community_contributors(), - default_modes(), - ); - instantiator.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids, contributions, vec![]) - } - - pub fn default_bids_from_ct_percent(percent: u8) -> Vec> { - // Used only to generate values, not for chain interactions - let inst = MockInstantiator::new(None); - let project_metadata = default_project_metadata(ISSUER_1); - inst.generate_bids_from_total_ct_percent( - project_metadata, - percent, - default_weights(), - default_bidders(), - default_bidder_modes(), - ) - } + let evaluations = instantiator.generate_successful_evaluations(project_metadata.clone(), 5); + let bids = instantiator.generate_bids_from_total_usd(project_metadata.clone(), usd_to_reach, 5); - pub fn default_community_contributions_from_ct_percent(percent: u8) -> Vec> { - // Used only to generate values, not for chain interactions - let inst = MockInstantiator::new(None); - let project_metadata = default_project_metadata(ISSUER_1); - inst.generate_contributions_from_total_ct_percent( - project_metadata, - percent, - default_weights(), - default_community_contributors(), - default_community_contributor_modes(), - ) + instantiator.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids) } - pub fn default_remainder_contributions_from_ct_percent(percent: u8) -> Vec> { + pub fn default_bids_from_ct_percent(percent: u8) -> Vec> { // Used only to generate values, not for chain interactions let inst = MockInstantiator::new(None); let project_metadata = default_project_metadata(ISSUER_1); - inst.generate_contributions_from_total_ct_percent( - project_metadata, - percent, - default_weights(), - default_remainder_contributors(), - default_remainder_contributor_modes(), - ) + inst.generate_bids_from_total_ct_percent(project_metadata, percent, 5) } } -pub fn create_project_with_funding_percentage( - percentage: u64, - start_settlement: bool, -) -> (MockInstantiator, ProjectId) { +pub fn create_project_with_funding_percentage(percentage: u8, start_settlement: bool) -> (MockInstantiator, ProjectId) { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(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 = inst.generate_bids_from_total_usd( - Percent::from_percent(50u8) * percentage_funded_usd, - min_price, - default_weights(), - default_bidders(), - default_modes(), - ); - let contributions = inst.generate_contributions_from_total_usd( - Percent::from_percent(50u8) * percentage_funded_usd, - min_price, - default_weights(), - default_community_contributors(), - default_modes(), - ); - let project_id = - inst.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids, contributions, vec![]); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.total_allocation_size = 1_000_000 * CT_UNIT; + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), percentage, 5); + + let project_id = inst.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids); if start_settlement { assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(_))); @@ -484,7 +178,7 @@ pub fn create_project_with_funding_percentage( let project_details = inst.get_project_details(project_id); let percent_reached = Perquintill::from_rational(project_details.funding_amount_reached_usd, project_details.fundraising_target_usd); - assert_eq!(percent_reached, Perquintill::from_percent(percentage)); + assert_eq!(percent_reached, Perquintill::from_percent(percentage as u64)); (inst, project_id) } @@ -494,59 +188,27 @@ pub fn create_finished_project_with_usd_raised( usd_raised: Balance, usd_target: Balance, ) -> (MockInstantiator, ProjectId) { + let mut test_inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let issuer = inst.get_new_nonce(); let mut project_metadata = default_project_metadata(issuer); project_metadata.total_allocation_size = project_metadata.minimum_price.reciprocal().unwrap().saturating_mul_int(usd_target); - project_metadata.auction_round_allocation_percentage = Percent::from_percent(50u8); - - let required_price = if usd_raised <= usd_target { - project_metadata.minimum_price - } else { - // It's hard to know how much usd was raised on the auction to take the price to `x`. So we calculate - // the price needed to get the project from 0 to `usd_target` buying 50% of the supply in the contribution round. - // Later we adjust the exact amount of tokens based on the amount raised in the auction. - // This means we will never have 100% CTs sold. - let price_increase_percentage = FixedU128::from_rational(usd_raised, usd_target); - let required_price = price_increase_percentage * project_metadata.minimum_price; - - // Since we want to reach the usd target with half the tokens, and the usd target is first calculated based on - // selling all the CTs, we need the price to be double - FixedU128::from_rational(2, 1) * required_price - }; - - let evaluations = default_evaluations(); - - let bids = inst.generate_bids_that_take_price_to(project_metadata.clone(), required_price, 420, |acc| acc + 1); - let project_id = inst.create_community_contributing_project(project_metadata, issuer, None, evaluations, bids); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let project_details = inst.get_project_details(project_id); - let wap = project_details.weighted_average_price.unwrap(); - - let usd_raised_so_far = project_details.funding_amount_reached_usd; - let usd_remaining = usd_raised - usd_raised_so_far; - - let community_contributions = inst.generate_contributions_from_total_usd( - usd_remaining, - wap, - default_weights(), - default_community_contributors(), - default_modes(), - ); - let plmc_required = inst.calculate_contributed_plmc_spent(community_contributions.clone(), required_price); - let usdt_required = inst.calculate_contributed_funding_asset_spent(community_contributions.clone(), required_price); - inst.mint_plmc_ed_if_required(plmc_required.accounts()); - inst.mint_plmc_to(plmc_required); - inst.mint_funding_asset_ed_if_required(usdt_required.to_account_asset_map()); - inst.mint_funding_asset_to(usdt_required); - inst.contribute_for_users(project_id, community_contributions).unwrap(); + let bids; + if usd_raised <= usd_target { + bids = inst.generate_bids_from_total_usd(project_metadata.clone(), usd_raised, 5); + } else { + // This function generates new projects, so we need to use the test instance + bids = test_inst.generate_bids_from_higher_usd_than_target(project_metadata.clone(), usd_raised); + } - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); + let project_id = inst.create_finished_project(project_metadata, issuer, None, evaluations, bids); let project_details = inst.get_project_details(project_id); - // We are happy if the amount raised is 99.999 of what we wanted + // We are happy if the amount raised is 99.999% of what we wanted assert_close_enough!(project_details.funding_amount_reached_usd, usd_raised, Perquintill::from_float(0.999)); assert_eq!(project_details.fundraising_target_usd, usd_target); diff --git a/pallets/funding/src/tests/runtime_api.rs b/pallets/funding/src/tests/runtime_api.rs index 9c625776e..330331304 100644 --- a/pallets/funding/src/tests/runtime_api.rs +++ b/pallets/funding/src/tests/runtime_api.rs @@ -3,6 +3,7 @@ use crate::runtime_api::{ExtrinsicHelpers, Leaderboards, ProjectInformation, Use use frame_support::traits::fungibles::{metadata::Inspect, Mutate}; use polimec_common::assets::AcceptedFundingAsset; use sp_runtime::bounded_vec; +use InvestorType::{self}; #[test] fn top_evaluations() { @@ -48,13 +49,9 @@ fn top_bids() { (BIDDER_4, 10400 * CT_UNIT).into(), (BIDDER_1, 500 * CT_UNIT).into(), ]; - let project_id = inst.create_community_contributing_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - default_evaluations(), - bids, - ); + let project_metadata = default_project_metadata(ISSUER_1); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = inst.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids); inst.execute(|| { let block_hash = System::block_hash(System::block_number()); @@ -78,44 +75,6 @@ fn top_bids() { }); } -#[test] -fn top_contributions() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let community_contributors = - vec![(BUYER_1, 8000 * CT_UNIT).into(), (BUYER_2, 501 * CT_UNIT).into(), (BUYER_3, 1200 * CT_UNIT).into()]; - let remainder_contributors = vec![(BUYER_4, 10400 * CT_UNIT).into(), (BUYER_1, 500 * CT_UNIT).into()]; - let project_id = inst.create_finished_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - community_contributors, - remainder_contributors, - ); - - inst.execute(|| { - let block_hash = System::block_hash(System::block_number()); - let top_1 = TestRuntime::top_contributions(&TestRuntime, block_hash, project_id, 1).unwrap(); - let contributor_4_evaluation = Contributions::::get((project_id, BUYER_4, 3)).unwrap(); - assert!(top_1.len() == 1 && top_1[0] == contributor_4_evaluation); - - let top_4_contributors = TestRuntime::top_contributions(&TestRuntime, block_hash, project_id, 4) - .unwrap() - .into_iter() - .map(|evaluation| evaluation.contributor) - .collect_vec(); - assert_eq!(top_4_contributors, vec![BUYER_4, BUYER_1, BUYER_3, BUYER_2]); - - let top_6_contributors = TestRuntime::top_contributions(&TestRuntime, block_hash, project_id, 6) - .unwrap() - .into_iter() - .map(|evaluation| evaluation.contributor) - .collect_vec(); - assert_eq!(top_6_contributors, vec![BUYER_4, BUYER_1, BUYER_3, BUYER_2, BUYER_1]); - }); -} - #[test] fn top_projects_by_usd_raised() { let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); @@ -211,104 +170,55 @@ fn top_projects_by_usd_target_percent_reached() { #[test] fn contribution_tokens() { - let bob = 420; - let mut contributions_with_bob_1 = default_community_contributions(); - let bob_amount_1 = 10_000 * CT_UNIT; - contributions_with_bob_1.last_mut().unwrap().contributor = bob; - contributions_with_bob_1.last_mut().unwrap().amount = bob_amount_1; - - let mut contributions_with_bob_2 = default_community_contributions(); - let bob_amount_2 = 25_000 * CT_UNIT; - contributions_with_bob_2.last_mut().unwrap().contributor = bob; - contributions_with_bob_2.last_mut().unwrap().amount = bob_amount_2; - - let mut contributions_with_bob_3 = default_community_contributions(); - let bob_amount_3 = 5_020 * CT_UNIT; - contributions_with_bob_3.last_mut().unwrap().contributor = bob; - contributions_with_bob_3.last_mut().unwrap().amount = bob_amount_3; - - let mut contributions_with_bob_4 = default_community_contributions(); - let bob_amount_4 = 420 * CT_UNIT; - contributions_with_bob_4.last_mut().unwrap().contributor = bob; - contributions_with_bob_4.last_mut().unwrap().amount = bob_amount_4; - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id_1 = inst.create_settled_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - default_evaluations(), - default_bids(), - contributions_with_bob_1, - default_remainder_contributions(), - true, - ); - let _project_id_2 = inst.create_settled_project( + + let bob_amount_1 = 450_000 * CT_UNIT; + let bids_with_bob_1 = inst.generate_bids_from_total_ct_amount(1, bob_amount_1); + let bob = bids_with_bob_1[0].bidder; + + let bob_amount_2 = 500_000 * CT_UNIT; + let bids_with_bob_2 = inst.generate_bids_from_total_ct_amount(1, bob_amount_2); + + let bob_amount_3 = 300_020 * CT_UNIT; + let bids_with_bob_3 = inst.generate_bids_from_total_ct_amount(1, bob_amount_3); + + let bob_amount_4 = 250_100 * CT_UNIT; + let bids_with_bob_4 = inst.generate_bids_from_total_ct_amount(1, bob_amount_4); + + let project_metadata = default_project_metadata(ISSUER_1); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id_1 = + inst.create_settled_project(project_metadata, ISSUER_1, None, evaluations.clone(), bids_with_bob_1, true); + let project_id_2 = inst.create_settled_project( default_project_metadata(ISSUER_2), ISSUER_2, None, - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), + evaluations.clone(), + bids_with_bob_2, true, ); - let _project_id_3 = inst.create_settled_project( + let project_id_3 = inst.create_settled_project( default_project_metadata(ISSUER_3), ISSUER_3, None, - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), + evaluations.clone(), + bids_with_bob_3, true, ); let project_id_4 = inst.create_settled_project( default_project_metadata(ISSUER_4), ISSUER_4, None, - default_evaluations(), - default_bids(), - contributions_with_bob_2, - default_remainder_contributions(), - true, - ); - let _project_id_5 = inst.create_settled_project( - default_project_metadata(ISSUER_5), - ISSUER_5, - None, - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), - true, - ); - let project_id_6 = inst.create_settled_project( - default_project_metadata(ISSUER_6), - ISSUER_6, - None, - default_evaluations(), - default_bids(), - contributions_with_bob_3, - default_remainder_contributions(), - true, - ); - let project_id_7 = inst.create_settled_project( - default_project_metadata(ISSUER_7), - ISSUER_7, - None, - default_evaluations(), - default_bids(), - contributions_with_bob_4, - default_remainder_contributions(), + evaluations.clone(), + bids_with_bob_4, true, ); let expected_items = vec![ - (project_id_4, bob_amount_2), + (project_id_2, bob_amount_2), (project_id_1, bob_amount_1), - (project_id_6, bob_amount_3), - (project_id_7, bob_amount_4), + (project_id_3, bob_amount_3), + (project_id_4, bob_amount_4), ]; inst.execute(|| { @@ -334,13 +244,9 @@ fn funding_asset_to_ct_amount_classic() { // Easy case, wap is already calculated, we want to know how many tokens at wap we can buy with `x` USDT let project_metadata_1 = default_project_metadata(ISSUER_1); - let project_id_1 = inst.create_community_contributing_project( - project_metadata_1.clone(), - ISSUER_1, - None, - default_evaluations(), - vec![], - ); + let evaluations = inst.generate_successful_evaluations(project_metadata_1.clone(), 5); + let project_id_1 = + inst.create_finished_project(project_metadata_1.clone(), ISSUER_1, None, evaluations.clone(), vec![]); let wap = project_metadata_1.minimum_price; assert_eq!(inst.get_project_details(project_id_1).weighted_average_price.unwrap(), wap); @@ -367,13 +273,8 @@ fn funding_asset_to_ct_amount_classic() { let bids = inst.generate_bids_that_take_price_to(project_metadata_2.clone(), decimal_aware_price, 420, |acc| acc + 1); - let project_id_2 = inst.create_community_contributing_project( - project_metadata_2.clone(), - ISSUER_2, - None, - default_evaluations(), - bids, - ); + let project_id_2 = + inst.create_finished_project(project_metadata_2.clone(), ISSUER_2, None, evaluations.clone(), bids); // Sanity check let project_details = inst.get_project_details(project_id_2); assert_eq!(project_details.weighted_average_price.unwrap(), decimal_aware_price); @@ -395,8 +296,7 @@ fn funding_asset_to_ct_amount_classic() { // Medium case, a bid goes over part of a bucket (bucket after the first one) let project_metadata_3 = default_project_metadata(ISSUER_3); - let project_id_3 = - inst.create_auctioning_project(project_metadata_3.clone(), ISSUER_3, None, default_evaluations()); + let project_id_3 = inst.create_auctioning_project(project_metadata_3.clone(), ISSUER_3, None, evaluations.clone()); let mut bucket = inst.execute(|| Buckets::::get(project_id_3)).unwrap(); // We want a full bucket after filling 6 buckets. (first bucket has full allocation and initial price) @@ -485,13 +385,9 @@ fn funding_asset_to_ct_amount_otm() { // Easy case, wap is already calculated, we want to know how many tokens at wap we can buy with `x` USDT let project_metadata_1 = default_project_metadata(ISSUER_1); - let project_id_1 = inst.create_community_contributing_project( - project_metadata_1.clone(), - ISSUER_1, - None, - default_evaluations(), - vec![], - ); + let evaluations = inst.generate_successful_evaluations(project_metadata_1.clone(), 5); + let project_id_1 = + inst.create_finished_project(project_metadata_1.clone(), ISSUER_1, None, evaluations.clone(), vec![]); let wap = project_metadata_1.minimum_price; assert_eq!(inst.get_project_details(project_id_1).weighted_average_price.unwrap(), wap); @@ -519,13 +415,8 @@ fn funding_asset_to_ct_amount_otm() { let bids = inst.generate_bids_that_take_price_to(project_metadata_2.clone(), decimal_aware_price, 420, |acc| acc + 1); - let project_id_2 = inst.create_community_contributing_project( - project_metadata_2.clone(), - ISSUER_2, - None, - default_evaluations(), - bids, - ); + let project_id_2 = + inst.create_finished_project(project_metadata_2.clone(), ISSUER_2, None, evaluations.clone(), bids); // Sanity check let project_details = inst.get_project_details(project_id_2); assert_eq!(project_details.weighted_average_price.unwrap(), decimal_aware_price); @@ -548,8 +439,7 @@ fn funding_asset_to_ct_amount_otm() { // Medium case, a bid goes over part of a bucket (bucket after the first one) let project_metadata_3 = default_project_metadata(ISSUER_3); - let project_id_3 = - inst.create_auctioning_project(project_metadata_3.clone(), ISSUER_3, None, default_evaluations()); + let project_id_3 = inst.create_auctioning_project(project_metadata_3.clone(), ISSUER_3, None, evaluations.clone()); let mut bucket = inst.execute(|| Buckets::::get(project_id_3)).unwrap(); // We want a full bucket after filling 6 buckets. (first bucket has full allocation and initial price) @@ -633,13 +523,13 @@ fn get_message_to_sign_by_receiving_account() { let project_id_2 = inst.create_new_project(default_project_metadata(ISSUER_3), ISSUER_3, None); let block_hash = inst.execute(|| System::block_hash(System::block_number())); let message = inst.execute(|| { - TestRuntime::get_message_to_sign_by_receiving_account(&TestRuntime, block_hash, project_id_2, BUYER_1) + TestRuntime::get_message_to_sign_by_receiving_account(&TestRuntime, block_hash, project_id_2, BIDDER_1) .unwrap() .unwrap() }); const EXPECTED_MESSAGE: &str = - "Polimec account: 57CoZYedYQwJMnCivQ7FnjCr4dfF912XuvgjUaKUzWSvvEF5 - project id: 2 - nonce: 0"; + "Polimec account: 56yh5mops2XZvxVUjHTvFWjxoaJ5QjfAo9xhNCP1aMG4sY9X - project id: 2 - nonce: 0"; assert_eq!(&message, EXPECTED_MESSAGE); } @@ -653,27 +543,32 @@ fn get_next_vesting_schedule_merge_candidates() { EvaluationParams::from((BIDDER_1, 320_000 * USD_UNIT)), ]; let bids = vec![ - BidParams::from((BIDDER_1, 50_000 * CT_UNIT, ParticipationMode::Classic(10u8), AcceptedFundingAsset::USDT)), - BidParams::from((BIDDER_1, 400_000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::USDT)), - BidParams::from((BIDDER_2, 50_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT)), - ]; - let remaining_contributions = vec![ - ContributionParams::from(( + BidParams::from(( BIDDER_1, - 1_000 * CT_UNIT, - ParticipationMode::Classic(5u8), + Professional, + 50_000 * CT_UNIT, + ParticipationMode::Classic(4u8), AcceptedFundingAsset::USDT, )), - ContributionParams::from(( + BidParams::from(( BIDDER_1, - 15_000 * CT_UNIT, - ParticipationMode::Classic(10u8), + Institutional, + 50_000 * CT_UNIT, + ParticipationMode::Classic(3u8), AcceptedFundingAsset::USDT, )), - ContributionParams::from(( + BidParams::from(( BIDDER_1, - 100 * CT_UNIT, - ParticipationMode::Classic(1u8), + Retail, + 50_000 * CT_UNIT, + ParticipationMode::Classic(2u8), + AcceptedFundingAsset::USDT, + )), + BidParams::from(( + BIDDER_2, + Retail, + 350_000 * CT_UNIT, + ParticipationMode::Classic(2u8), AcceptedFundingAsset::USDT, )), ]; @@ -684,24 +579,14 @@ fn get_next_vesting_schedule_merge_candidates() { None, evaluations.clone(), bids.clone(), - default_community_contributions(), - remaining_contributions.clone(), ); assert_eq!(ProjectStatus::SettlementStarted(FundingOutcome::Success), inst.go_to_next_state(project_id)); - inst.execute(|| { - PolimecFunding::settle_evaluation(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 2).unwrap(); - PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 0).unwrap(); - PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 1).unwrap(); - PolimecFunding::settle_contribution(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 5).unwrap(); - PolimecFunding::settle_contribution(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 6).unwrap(); - PolimecFunding::settle_contribution(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 7).unwrap(); - }); + inst.settle_project(project_id, true); let hold_reason: mock::RuntimeHoldReason = HoldReason::Participation.into(); let bidder_1_schedules = inst.execute(|| pallet_linear_release::Vesting::::get(BIDDER_1, hold_reason).unwrap().to_vec()); - // Evaluations didn't get a vesting schedule - assert_eq!(bidder_1_schedules.len(), 4); + assert_eq!(bidder_1_schedules.len(), 3); inst.execute(|| { let block_hash = System::block_hash(System::block_number()); @@ -710,8 +595,8 @@ fn get_next_vesting_schedule_merge_candidates() { block_hash, BIDDER_1, HoldReason::Participation.into(), - // within 100 blocks - 100u128, + // around 4 weeks of blocks + 210_000, ) .unwrap() .unwrap(); @@ -725,8 +610,8 @@ fn get_next_vesting_schedule_merge_candidates() { block_hash, BIDDER_1, HoldReason::Participation.into(), - // within 100 blocks - 100u128, + // around 4 weeks of blocks + 210_000, ) .unwrap() .unwrap(); @@ -753,7 +638,8 @@ fn calculate_otm_fee() { .unwrap() }); - let project_id = inst.create_auctioning_project(project_metadata, ISSUER_1, None, default_evaluations()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let project_id = inst.create_auctioning_project(project_metadata, ISSUER_1, None, evaluations); let ct_amount = inst .execute(|| { @@ -797,17 +683,11 @@ fn calculate_otm_fee() { #[test] fn get_funding_asset_min_max_amounts() { ConstPriceProvider::set_price(AcceptedFundingAsset::USDT.id(), PriceOf::::from_float(1.0f64)); - ConstPriceProvider::set_price(AcceptedFundingAsset::USDC.id(), PriceOf::::from_float(1.0f64)); ConstPriceProvider::set_price(AcceptedFundingAsset::DOT.id(), PriceOf::::from_float(10.0f64)); + ConstPriceProvider::set_price(AcceptedFundingAsset::WETH.id(), PriceOf::::from_float(100.0f64)); ConstPriceProvider::set_price(Location::here(), PriceOf::::from_float(0.5f64)); const DOT_UNIT: u128 = 10u128.pow(10u32); - - // We test the following cases: - // Bidder Professional where max is the ct max because it's lower than the ticket max. DOT - // Bidder Institutional where max is the ticket max (first bid). USDT - // Contributor Retail where the max is the ticket max (first contribution). DOT - // Contributor Institutional where max is the ct max because there is no ticket max. USDT - // Contributor Professional where max is the ticket max (4500 USD already contributed). USDC + const WETH_UNIT: u128 = 10u128.pow(18u32); let mut project_metadata = default_project_metadata(ISSUER_1); let min_price = PriceProviderOf::::calculate_decimals_aware_price( @@ -817,55 +697,30 @@ fn get_funding_asset_min_max_amounts() { ) .unwrap(); project_metadata.minimum_price = min_price; - project_metadata.total_allocation_size = 5_000_000 * CT_UNIT; + project_metadata.total_allocation_size = 2_500_000 * CT_UNIT; project_metadata.bidding_ticket_sizes = BiddingTicketSizes { - professional: TicketSize { - usd_minimum_per_participation: 5_000 * USD_UNIT, - usd_maximum_per_did: Some(10_000_000 * USD_UNIT), - }, - institutional: TicketSize { - usd_minimum_per_participation: 10_000 * USD_UNIT, - usd_maximum_per_did: Some(1_000_000 * USD_UNIT), - }, - phantom: Default::default(), - }; - project_metadata.contributing_ticket_sizes = ContributingTicketSizes { - retail: TicketSize { - usd_minimum_per_participation: 50 * USD_UNIT, - usd_maximum_per_did: Some(10_000 * USD_UNIT), - }, - professional: TicketSize { - usd_minimum_per_participation: 100 * USD_UNIT, - usd_maximum_per_did: Some(100_000 * USD_UNIT), - }, - institutional: TicketSize { usd_minimum_per_participation: 5000 * USD_UNIT, usd_maximum_per_did: None }, + professional: TicketSize::new(5_000 * USD_UNIT, Some(500_000 * USD_UNIT)), + institutional: TicketSize::new(10_000 * USD_UNIT, None), + retail: TicketSize::new(100 * USD_UNIT, Some(25_000 * USD_UNIT)), phantom: Default::default(), }; + project_metadata.participation_currencies = bounded_vec![AcceptedFundingAsset::DOT, AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC]; - const BIDDING_USD_MAX: u128 = 2_500_000; - const CONTRIBUTING_USD_MAX: u128 = 5_000_000; - const BIDDER_PROFESSIONAL_DOT_MIN: u128 = 500 * DOT_UNIT; - const BIDDER_PROFESSIONAL_DOT_MAX: u128 = (BIDDING_USD_MAX / 10) * DOT_UNIT; + const BIDDER_PROFESSIONAL_DOT_MAX: u128 = 50_000 * DOT_UNIT; const BIDDER_INSTITUTIONAL_USDT_MIN: u128 = 10_000 * USD_UNIT; - const BIDDER_INSTITUTIONAL_USDT_MAX: u128 = 1_000_000 * USD_UNIT; - - const CONTRIBUTOR_RETAIL_DOT_MIN: u128 = 5 * DOT_UNIT; - const CONTRIBUTOR_RETAIL_DOT_MAX: u128 = 1_000 * DOT_UNIT; + // This max is set by the total allocation size instead of the unlimited institutional max. + const BIDDER_INSTITUTIONAL_USDT_MAX: u128 = 2_500_000 * USD_UNIT; - const CONTRIBUTOR_INSTITUTIONAL_USDT_MIN: u128 = 5000 * USD_UNIT; - const CONTRIBUTOR_INSTITUTIONAL_USDT_MAX: u128 = CONTRIBUTING_USD_MAX * USD_UNIT; - - const CONTRIBUTOR_PROFESSIONAL_USDC_MIN: u128 = 100 * USD_UNIT; - const CONTRIBUTOR_PROFESSIONAL_USDC_MAX: u128 = (100_000 - 6000) * USD_UNIT; + const BIDDER_RETAIL_WETH_MIN: u128 = 1 * WETH_UNIT; + const BIDDER_RETAIL_WETH_MAX: u128 = 250 * WETH_UNIT; let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let evaluations = - inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let project_id = inst.create_auctioning_project(project_metadata, ISSUER_1, None, evaluations); let block_hash = inst.execute(|| System::block_hash(System::block_number())); @@ -902,74 +757,21 @@ fn get_funding_asset_min_max_amounts() { assert_eq!(min, BIDDER_INSTITUTIONAL_USDT_MIN); assert_eq!(max, BIDDER_INSTITUTIONAL_USDT_MAX); - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); - let (min, max) = inst .execute(|| { TestRuntime::get_funding_asset_min_max_amounts( &TestRuntime, block_hash, project_id, - generate_did_from_account(BUYER_1), - AcceptedFundingAsset::DOT, + generate_did_from_account(BIDDER_1), + AcceptedFundingAsset::WETH, InvestorType::Retail, ) }) .unwrap() .unwrap(); - assert_eq!(min, CONTRIBUTOR_RETAIL_DOT_MIN); - assert_eq!(max, CONTRIBUTOR_RETAIL_DOT_MAX); - - let (min, max) = inst - .execute(|| { - TestRuntime::get_funding_asset_min_max_amounts( - &TestRuntime, - block_hash, - project_id, - generate_did_from_account(BUYER_1), - AcceptedFundingAsset::USDT, - InvestorType::Institutional, - ) - }) - .unwrap() - .unwrap(); - assert_eq!(min, CONTRIBUTOR_INSTITUTIONAL_USDT_MIN); - assert_eq!(max, CONTRIBUTOR_INSTITUTIONAL_USDT_MAX); - - // This test requires the buyer to have contributed 4500 USD before calling the API - let required_ct = inst - .execute(|| { - TestRuntime::funding_asset_to_ct_amount_classic( - &TestRuntime, - block_hash, - project_id, - AcceptedFundingAsset::USDC, - 6000 * USD_UNIT, - ) - }) - .unwrap(); - let contribution = - ContributionParams::from((BUYER_1, required_ct, ParticipationMode::OTM, AcceptedFundingAsset::USDC)); - let usdc_to_mint = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], min_price); - inst.mint_funding_asset_ed_if_required(usdc_to_mint.to_account_asset_map()); - inst.mint_funding_asset_to(usdc_to_mint); - inst.contribute_for_users(project_id, vec![contribution]).unwrap(); - - let (min, max) = inst - .execute(|| { - TestRuntime::get_funding_asset_min_max_amounts( - &TestRuntime, - block_hash, - project_id, - generate_did_from_account(BUYER_1), - AcceptedFundingAsset::USDC, - InvestorType::Professional, - ) - }) - .unwrap() - .unwrap(); - assert_eq!(min, CONTRIBUTOR_PROFESSIONAL_USDC_MIN); - assert_eq!(max, CONTRIBUTOR_PROFESSIONAL_USDC_MAX); + assert_eq!(min, BIDDER_RETAIL_WETH_MIN); + assert_eq!(max, BIDDER_RETAIL_WETH_MAX); } #[test] @@ -987,57 +789,17 @@ fn all_project_participations_by_did() { EvaluationParams::from((EVALUATOR_3, 320_000 * USD_UNIT)), ]; let bids = vec![ - BidParams::from((BIDDER_1, 400_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT)), - BidParams::from((BIDDER_2, 50_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT)), - ]; - let community_contributions = vec![ - ContributionParams::from(( - BUYER_1, - 50_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from(( - BUYER_2, - 130_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from(( - BUYER_3, - 30_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from(( - BUYER_4, - 210_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from(( - BUYER_5, - 10_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - ]; - let remainder_contributions = vec![ - ContributionParams::from(( - EVALUATOR_2, - 20_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )), - ContributionParams::from(( - BUYER_2, - 5_000 * CT_UNIT, + BidParams::from(( + BIDDER_1, + Retail, + 400_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, )), - ContributionParams::from(( - BIDDER_1, - 30_000 * CT_UNIT, + BidParams::from(( + BIDDER_2, + Retail, + 50_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, )), @@ -1046,14 +808,8 @@ fn all_project_participations_by_did() { let evaluations_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone()); let bids_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket(&bids, project_metadata.clone(), None); - let community_contributions_plmc = - inst.calculate_contributed_plmc_spent(community_contributions.clone(), project_metadata.minimum_price); - let remainder_contributions_plmc = - inst.calculate_contributed_plmc_spent(remainder_contributions.clone(), project_metadata.minimum_price); - let all_plmc = inst.generic_map_operation( - vec![evaluations_plmc, bids_plmc, community_contributions_plmc, remainder_contributions_plmc], - MergeOperation::Add, - ); + + let all_plmc = inst.generic_map_operation(vec![evaluations_plmc, bids_plmc], MergeOperation::Add); inst.mint_plmc_ed_if_required(all_plmc.accounts()); inst.mint_plmc_to(all_plmc); @@ -1062,14 +818,8 @@ fn all_project_participations_by_did() { project_metadata.clone(), None, ); - let community_contributions_usdt = - inst.calculate_contributed_funding_asset_spent(community_contributions.clone(), project_metadata.minimum_price); - let remainder_contributions_usdt = - inst.calculate_contributed_funding_asset_spent(remainder_contributions.clone(), project_metadata.minimum_price); - let all_usdt = inst.generic_map_operation( - vec![bids_usdt, community_contributions_usdt, remainder_contributions_usdt], - MergeOperation::Add, - ); + + let all_usdt = inst.generic_map_operation(vec![bids_usdt], MergeOperation::Add); inst.mint_funding_asset_ed_if_required(all_usdt.to_account_asset_map()); inst.mint_funding_asset_to(all_usdt); @@ -1092,30 +842,6 @@ fn all_project_participations_by_did() { .unwrap(); }); } - - let ProjectStatus::CommunityRound(remainder_start) = inst.go_to_next_state(project_id) else { - panic!("Expected CommunityRound") - }; - inst.contribute_for_users(project_id, community_contributions).unwrap(); - - inst.jump_to_block(remainder_start); - - for contribution in remainder_contributions { - let jwt = - get_mock_jwt_with_cid(contribution.contributor, InvestorType::Professional, did_user.clone(), cid.clone()); - inst.execute(|| { - PolimecFunding::contribute( - RuntimeOrigin::signed(contribution.contributor), - jwt, - project_id, - contribution.amount, - contribution.mode, - contribution.asset, - ) - .unwrap(); - }); - } - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); } @@ -1170,14 +896,14 @@ fn projects_by_did() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let did_user = generate_did_from_account(420); + let evaluations = inst.generate_successful_evaluations(default_project_metadata(ISSUER_1), 5); + let bids = inst.generate_bids_from_total_ct_percent(default_project_metadata(ISSUER_1), 80, 7); let project_id_1 = inst.create_settled_project( default_project_metadata(ISSUER_1), ISSUER_1, Some(did_user.clone()), - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), + evaluations.clone(), + bids.clone(), true, ); @@ -1185,10 +911,8 @@ fn projects_by_did() { default_project_metadata(ISSUER_1), ISSUER_1, None, - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), + evaluations.clone(), + bids.clone(), true, ); @@ -1196,10 +920,8 @@ fn projects_by_did() { default_project_metadata(ISSUER_2), ISSUER_2, Some(did_user.clone()), - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), + evaluations.clone(), + bids.clone(), true, ); @@ -1207,10 +929,8 @@ fn projects_by_did() { default_project_metadata(ISSUER_3), ISSUER_3, None, - default_evaluations(), - default_bids(), - default_community_contributions(), - default_remainder_contributions(), + evaluations.clone(), + bids.clone(), true, ); diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index ffa0b1e3f..ef7ba8c7f 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -28,7 +28,7 @@ use parachains_common::DAYS; use polimec_common::USD_DECIMALS; use polkadot_parachain_primitives::primitives::Id as ParaId; use serde::{Deserialize, Serialize}; -use sp_arithmetic::{traits::Saturating, FixedPointNumber, FixedU128, Percent}; +use sp_arithmetic::{traits::Saturating, FixedPointNumber, FixedU128}; use sp_runtime::traits::{Convert, One}; use sp_std::{cmp::Eq, prelude::*}; pub use storage::*; @@ -172,14 +172,10 @@ pub mod storage { pub mainnet_token_max_supply: Balance, /// Total allocation of Contribution Tokens available for the Funding Round. pub total_allocation_size: Balance, - /// Percentage of the total allocation of Contribution Tokens available for the Auction Round - pub auction_round_allocation_percentage: Percent, /// The minimum price per token in USD, decimal-aware. See [`calculate_decimals_aware_price()`](crate::traits::ProvideAssetPrice::calculate_decimals_aware_price) for more information. pub minimum_price: Price, /// Maximum and minimum ticket sizes for auction round pub bidding_ticket_sizes: BiddingTicketSizes, - /// Maximum and minimum ticket sizes for community/remainder rounds - pub contributing_ticket_sizes: ContributingTicketSizes, /// Participation currencies (e.g stablecoin, DOT, KSM) /// e.g. https://github.com/paritytech/substrate/blob/427fd09bcb193c1e79dec85b1e207c718b686c35/frame/uniques/src/types.rs#L110 /// For now is easier to handle the case where only just one Currency is accepted @@ -203,17 +199,11 @@ pub mod storage { let usd_unit = sp_arithmetic::traits::checked_pow(10u128, USD_DECIMALS as usize) .ok_or(MetadataError::BadTokenomics)?; - let min_bidder_bound_usd: Balance = usd_unit.checked_mul(5000u128).ok_or(MetadataError::BadTokenomics)?; + let min_bound_usd: Balance = usd_unit.checked_mul(10u128).ok_or(MetadataError::BadTokenomics)?; self.bidding_ticket_sizes.is_valid(vec![ - InvestorTypeUSDBounds::Professional((min_bidder_bound_usd, None).into()), - InvestorTypeUSDBounds::Institutional((min_bidder_bound_usd, None).into()), - ])?; - - let min_contributor_bound_usd: Balance = usd_unit.checked_mul(1u128).ok_or(MetadataError::BadTokenomics)?; - self.contributing_ticket_sizes.is_valid(vec![ - InvestorTypeUSDBounds::Institutional((min_contributor_bound_usd, None).into()), - InvestorTypeUSDBounds::Professional((min_contributor_bound_usd, None).into()), - InvestorTypeUSDBounds::Retail((min_contributor_bound_usd, None).into()), + InvestorTypeUSDBounds::Professional((min_bound_usd, None).into()), + InvestorTypeUSDBounds::Institutional((min_bound_usd, None).into()), + InvestorTypeUSDBounds::Retail((min_bound_usd, None).into()), ])?; if self.total_allocation_size == 0u64.into() || @@ -223,10 +213,6 @@ pub mod storage { return Err(MetadataError::AllocationSizeError); } - if self.auction_round_allocation_percentage <= Percent::from_percent(0) { - return Err(MetadataError::AuctionRoundPercentageError); - } - let mut deduped = self.participation_currencies.clone().to_vec(); deduped.sort(); deduped.dedup(); @@ -308,7 +294,7 @@ pub mod storage { /// The price in USD per token decided after the Auction Round pub weighted_average_price: Option, /// The current status of the project - pub status: ProjectStatus, + pub status: ProjectStatus, /// When the different project phases start and end pub round_duration: BlockNumberPair, /// Fundraising target amount in USD (6 decimals) @@ -583,37 +569,10 @@ pub mod inner { pub struct BiddingTicketSizes { pub professional: TicketSize, pub institutional: TicketSize, - pub phantom: PhantomData<(Price, Balance)>, - } - impl BiddingTicketSizes { - pub fn is_valid(&self, usd_bounds: Vec) -> Result<(), MetadataError> { - for bound in usd_bounds { - match bound { - InvestorTypeUSDBounds::Professional(bound) => - if !self.professional.check_valid(bound) { - return Err(MetadataError::TicketSizeError); - }, - InvestorTypeUSDBounds::Institutional(bound) => - if !self.institutional.check_valid(bound) { - return Err(MetadataError::TicketSizeError); - }, - _ => {}, - } - } - Ok(()) - } - } - - #[derive( - Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo, Serialize, Deserialize, - )] - pub struct ContributingTicketSizes { pub retail: TicketSize, - pub professional: TicketSize, - pub institutional: TicketSize, pub phantom: PhantomData<(Price, Balance)>, } - impl ContributingTicketSizes { + impl BiddingTicketSizes { pub fn is_valid(&self, usd_bounds: Vec) -> Result<(), MetadataError> { for bound in usd_bounds { match bound { @@ -638,12 +597,11 @@ pub mod inner { #[derive( Default, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen, Serialize, Deserialize, )] - pub enum ProjectStatus { + pub enum ProjectStatus { #[default] Application, EvaluationRound, AuctionRound, - CommunityRound(BlockNumber), FundingFailed, FundingSuccessful, SettlementStarted(FundingOutcome), diff --git a/pallets/oracle-ocw/src/mock.rs b/pallets/oracle-ocw/src/mock.rs index 9d8f5a88e..2db0829b1 100644 --- a/pallets/oracle-ocw/src/mock.rs +++ b/pallets/oracle-ocw/src/mock.rs @@ -272,7 +272,7 @@ pub(crate) const KRAKEN_RESPONSES: &[(&str, &[u8])] = &[ ("USDTZUSD", KRAKEN_USDT_CORRECT), ("USDCUSD", KRAKEN_USDC_CORRECT), ("DOTUSD", KRAKEN_DOT_CORRECT), - ("XETHZUSD", KRAKEN_WETH_CORRECT) + ("XETHZUSD", KRAKEN_WETH_CORRECT), ]; const KRAKEN_USDT_CORRECT: &[u8] = br#"{"error":[],"result":{"USDTZUSD":[[1701877920,"1.00009","1.00011","1.00008","1.00009","1.00010","58759.32214931",36],[1701877980,"1.00009","1.00011","1.00009","1.00010","1.00010","17156.51835679",18],[1701878040,"1.00011","1.00011","1.00010","1.00010","1.00010","231514.66903930",13],[1701878100,"1.00010","1.00015","1.00010","1.00014","1.00012","10577.17236868",27],[1701878160,"1.00015","1.00020","1.00015","1.00019","1.00017","1026827.06857105",67],[1701878220,"1.00019","1.00019","1.00018","1.00019","1.00018","44228.73461655",28],[1701878280,"1.00018","1.00018","1.00015","1.00015","1.00016","41144.63245059",23],[1701878340,"1.00014","1.00015","1.00013","1.00013","1.00013","252283.11050904",67],[1701878400,"1.00014","1.00014","1.00012","1.00014","1.00012","34519.85524461",23],[1701878460,"1.00013","1.00013","1.00008","1.00009","1.00010","49702.48469208",40],[1701878520,"1.00009","1.00016","1.00009","1.00016","1.00012","83532.48937609",43],[1701878580,"1.00016","1.00018","1.00015","1.00018","1.00017","340329.29664927",27],[1701878640,"1.00018","1.00018","1.00015","1.00015","1.00016","125875.61559451",33],[1701878700,"1.00015","1.00015","1.00010","1.00011","1.00012","63925.70403795",32],[1701878760,"1.00010","1.00010","1.00008","1.00008","1.00009","53316.20999461",26]],"last":1699977300}}"#; const KRAKEN_USDC_CORRECT: &[u8] = br#"{"error":[],"result":{"USDCUSD":[[1701878040,"1.0001","1.0001","1.0000","1.0000","1.0000","2210.00000000",2],[1701878100,"1.0002","1.0002","1.0002","1.0002","1.0002","999.00000000",1],[1701878160,"1.0001","1.0002","1.0001","1.0002","1.0001","7201.85053234",9],[1701878220,"1.0001","1.0001","1.0001","1.0001","1.0001","15.71930681",1],[1701878280,"1.0000","1.0001","1.0000","1.0001","1.0000","102108.24129487",5],[1701878340,"1.0001","1.0001","1.0001","1.0001","0.0000","0.00000000",0],[1701878400,"1.0001","1.0001","1.0001","1.0001","1.0001","1451.37880000",1],[1701878460,"1.0001","1.0001","1.0000","1.0000","1.0000","11005.00000000",2],[1701878520,"1.0001","1.0001","1.0000","1.0000","1.0000","6760.93865300",3],[1701878580,"1.0000","1.0000","1.0000","1.0000","0.0000","0.00000000",0],[1701878640,"1.0000","1.0001","1.0000","1.0001","1.0000","1290.84392400",4],[1701878700,"1.0000","1.0001","1.0000","1.0001","1.0000","53.03306930",2],[1701878760,"1.0000","1.0000","1.0000","1.0000","1.0000","16711.33870874",7],[1701878820,"1.0000","1.0000","1.0000","1.0000","1.0000","10007.53328427",2],[1701878880,"0.9999","0.9999","0.9999","0.9999","0.9999","1000.00000000",1]],"last":1699977300}}"#; diff --git a/polimec-common/common/src/credentials/mod.rs b/polimec-common/common/src/credentials/mod.rs index 73569cb04..741a91759 100644 --- a/polimec-common/common/src/credentials/mod.rs +++ b/polimec-common/common/src/credentials/mod.rs @@ -27,7 +27,9 @@ pub use jwt_compact::{ }; use serde::Deserializer; -#[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, Deserialize, Serialize)] +#[derive( + Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, Deserialize, Serialize, MaxEncodedLen, +)] #[serde(rename_all = "lowercase")] pub enum InvestorType { Retail, diff --git a/polimec-common/common/src/lib.rs b/polimec-common/common/src/lib.rs index 36e3a5eb2..6df43fd0c 100644 --- a/polimec-common/common/src/lib.rs +++ b/polimec-common/common/src/lib.rs @@ -135,7 +135,6 @@ pub mod migration_types { pub enum ParticipationType { Evaluation, Bid, - Contribution, } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] diff --git a/runtimes/polimec/src/benchmark_helpers.rs b/runtimes/polimec/src/benchmark_helpers.rs index 32cd49849..29839522b 100644 --- a/runtimes/polimec/src/benchmark_helpers.rs +++ b/runtimes/polimec/src/benchmark_helpers.rs @@ -2,6 +2,7 @@ extern crate alloc; use crate::{Oracle, Runtime, RuntimeOrigin}; use alloc::vec; +use frame_support::instances::Instance1; use pallet_funding::traits::SetPrices; use polimec_common::assets::AcceptedFundingAsset; use sp_runtime::{BoundedVec, FixedU128}; @@ -13,27 +14,18 @@ impl SetPrices for SetOraclePrices { let dot = (AcceptedFundingAsset::DOT.id(), FixedU128::from_rational(69, 1)); let usdc = (AcceptedFundingAsset::USDC.id(), FixedU128::from_rational(1, 1)); let usdt = (AcceptedFundingAsset::USDT.id(), FixedU128::from_rational(1, 1)); + let weth = (AcceptedFundingAsset::WETH.id(), FixedU128::from_rational(20_000, 1)); let plmc = (Location::here(), FixedU128::from_rational(840, 100)); let values: BoundedVec<(Location, FixedU128), ::MaxFeedValues> = - vec![dot, usdc, usdt, plmc].try_into().expect("benchmarks can panic"); - let alice: [u8; 32] = [ - 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, - 227, 154, 86, 132, 231, 165, 109, 162, 125, - ]; - let bob: [u8; 32] = [ - 142, 175, 4, 21, 22, 135, 115, 99, 38, 201, 254, 161, 126, 37, 252, 82, 135, 97, 54, 147, 201, 18, 144, - 156, 178, 38, 170, 71, 148, 242, 106, 72, - ]; - let charlie: [u8; 32] = [ - 144, 181, 171, 32, 92, 105, 116, 201, 234, 132, 27, 230, 136, 134, 70, 51, 220, 156, 168, 163, 87, 132, 62, - 234, 207, 35, 20, 100, 153, 101, 254, 34, - ]; + vec![dot, usdc, usdt, plmc, weth].try_into().expect("benchmarks can panic"); - frame_support::assert_ok!(Oracle::feed_values(RuntimeOrigin::signed(alice.clone().into()), values.clone())); - - frame_support::assert_ok!(Oracle::feed_values(RuntimeOrigin::signed(bob.clone().into()), values.clone())); - - frame_support::assert_ok!(Oracle::feed_values(RuntimeOrigin::signed(charlie.clone().into()), values.clone())); + let oracle_members = pallet_membership::Members::::get().to_vec(); + for member in oracle_members { + frame_support::assert_ok!(Oracle::feed_values( + RuntimeOrigin::signed(member.clone().into()), + values.clone() + )); + } } } diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index 178b4055c..220905bb1 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -98,12 +98,9 @@ use alloc::string::String; use sp_core::crypto::Ss58Codec; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; -use xcm::{ - v4::{Location}, - VersionedAssetId, -}; #[cfg(feature = "runtime-benchmarks")] -use xcm::{{v4::{ParentThen, Parent, Junction::Parachain}}}; +use xcm::v4::{Junction::Parachain, ParentThen}; +use xcm::{v4::Location, VersionedAssetId}; #[cfg(feature = "runtime-benchmarks")] mod benchmark_helpers; @@ -265,13 +262,10 @@ impl Contains for BaseCallFilter { pallet_funding::Call::evaluate { .. } | pallet_funding::Call::end_evaluation { .. } | pallet_funding::Call::bid { .. } | - pallet_funding::Call::end_auction { .. } | - pallet_funding::Call::contribute { .. } | pallet_funding::Call::end_funding { .. } | pallet_funding::Call::start_settlement { .. } | pallet_funding::Call::settle_evaluation { .. } | pallet_funding::Call::settle_bid { .. } | - pallet_funding::Call::settle_contribution { .. } | pallet_funding::Call::mark_project_as_settled { .. } | pallet_funding::Call::start_offchain_migration { .. } | pallet_funding::Call::confirm_offchain_migration { .. } | diff --git a/runtimes/polimec/src/xcm_config.rs b/runtimes/polimec/src/xcm_config.rs index 6302cfc92..c32a00c1e 100644 --- a/runtimes/polimec/src/xcm_config.rs +++ b/runtimes/polimec/src/xcm_config.rs @@ -18,7 +18,7 @@ extern crate alloc; use super::{ AccountId, AllPalletsWithSystem, Balance, Balances, ContributionTokens, EnsureRoot, ForeignAssets, Funding, PLMCToAssetBalance, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, - ToTreasury, TreasuryAccount, Vec, WeightToFee, + TreasuryAccount, Vec, WeightToFee, }; use core::marker::PhantomData; use cumulus_primitives_core::ParaId; @@ -27,7 +27,7 @@ use frame_support::{ pallet_prelude::*, parameter_types, traits::{ - ConstU32, Contains, ContainsPair, Everything, Nothing, OnUnbalanced as OnUnbalancedT, ProcessMessageError, + tokens::ConversionToAssetBalance, ConstU32, Contains, ContainsPair, Everything, Nothing, ProcessMessageError, }, weights::{Weight, WeightToFee as WeightToFeeT}, }; @@ -42,18 +42,16 @@ use xcm::v4::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CreateMatcher, DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, - FixedRateOfFungible, FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, IsConcrete, - MatchXcm, MatchedConvertedConcreteId, MintLocation, NoChecking, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeRevenue, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, + FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, IsConcrete, MatchXcm, + MatchedConvertedConcreteId, MintLocation, NoChecking, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, + StartsWith, StartsWithExplicitGlobalConsensus, TakeRevenue, TakeWeightCredit, TrailingSetTopicAsId, + WithComputedOrigin, }; use xcm_executor::{ traits::{JustTry, Properties, ShouldExecute, WeightTrader}, AssetsInHolding, XcmExecutor, }; -use frame_support::traits::tokens::ConversionToAssetBalance; - // DOT from Polkadot Asset Hub const DOT_PER_SECOND_EXECUTION: u128 = 0_2_000_000_000; // 0.2 DOT per second of execution time @@ -319,9 +317,7 @@ impl xcm_executor::Config for XcmConfig { // Do not allow any Transact instructions to be executed on our chain. type SafeCallFilter = Nothing; type SubscriptionService = PolkadotXcm; - type Trader = ( - AssetTrader, - ); + type Trader = (AssetTrader,); type TransactionalProcessor = FrameTransactionalProcessor; type UniversalAliases = Nothing; type UniversalLocation = UniversalLocation; @@ -525,7 +521,7 @@ impl WeightTrader for AssetTrader { ensure!(acceptable_assets.contains(&asset_id.0), XcmError::FeesNotMet); // If the trader was used already in this xcm execution, make sure we continue trading with the same asset - let old_amount = if let Some(asset) =&self.asset_spent { + let old_amount = if let Some(asset) = &self.asset_spent { ensure!(asset.id == *asset_id, XcmError::FeesNotMet); if let Fungibility::Fungible(amount) = asset.fun { amount @@ -536,8 +532,8 @@ impl WeightTrader for AssetTrader { Zero::zero() }; - let required_asset_amount = - PLMCToAssetBalance::to_asset_balance(native_amount, asset_id.0.clone()).map_err(|_| XcmError::FeesNotMet)?; + let required_asset_amount = PLMCToAssetBalance::to_asset_balance(native_amount, asset_id.0.clone()) + .map_err(|_| XcmError::FeesNotMet)?; ensure!(*asset_amount >= required_asset_amount, XcmError::FeesNotMet); let required = (AssetId(asset_id.0.clone()), required_asset_amount).into(); diff --git a/runtimes/shared-configuration/src/funding.rs b/runtimes/shared-configuration/src/funding.rs index 58713141f..0ed075955 100644 --- a/runtimes/shared-configuration/src/funding.rs +++ b/runtimes/shared-configuration/src/funding.rs @@ -25,16 +25,16 @@ use xcm::v4::Location; #[cfg(feature = "instant-mode")] pub const EVALUATION_ROUND_DURATION: BlockNumber = 7; #[cfg(feature = "fast-mode")] -pub const EVALUATION_ROUND_DURATION: BlockNumber = 10 * crate::MINUTES; +pub const EVALUATION_ROUND_DURATION: BlockNumber = 7 * crate::MINUTES; #[cfg(not(any(feature = "fast-mode", feature = "instant-mode")))] pub const EVALUATION_ROUND_DURATION: BlockNumber = 7 * crate::DAYS; #[cfg(feature = "instant-mode")] -pub const AUCTION_ROUND_DURATION: BlockNumber = 7; +pub const AUCTION_ROUND_DURATION: BlockNumber = 14; #[cfg(feature = "fast-mode")] -pub const AUCTION_ROUND_DURATION: BlockNumber = 30 * crate::MINUTES; +pub const AUCTION_ROUND_DURATION: BlockNumber = 14 * crate::MINUTES; #[cfg(not(any(feature = "fast-mode", feature = "instant-mode")))] -pub const AUCTION_ROUND_DURATION: BlockNumber = 7 * crate::DAYS; +pub const AUCTION_ROUND_DURATION: BlockNumber = 14 * crate::DAYS; #[cfg(feature = "instant-mode")] pub const COMMUNITY_ROUND_DURATION: BlockNumber = 5;