From 0e94e4ad26a628b91cbbfc6b1c4812147d259b0f Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios Date: Mon, 8 Jul 2024 16:49:00 +0200 Subject: [PATCH] Runtime APIs --- Cargo.lock | 1 + integration-tests/Cargo.toml | 1 + integration-tests/penpal/Cargo.toml | 18 +- integration-tests/src/tests/ct_migration.rs | 2 + integration-tests/src/tests/e2e.rs | 9 +- integration-tests/src/tests/oracle.rs | 1 + pallets/funding/Cargo.toml | 2 + pallets/funding/src/benchmarking.rs | 59 +- .../funding/src/instantiator/calculations.rs | 163 ++++ .../src/instantiator/chain_interactions.rs | 92 ++- pallets/funding/src/instantiator/mod.rs | 3 + pallets/funding/src/instantiator/tests.rs | 222 ++++++ pallets/funding/src/instantiator/types.rs | 10 + pallets/funding/src/lib.rs | 11 +- pallets/funding/src/mock.rs | 58 +- pallets/funding/src/runtime_api.rs | 230 ++++++ pallets/funding/src/tests/1_application.rs | 38 +- pallets/funding/src/tests/2_evaluation.rs | 61 +- pallets/funding/src/tests/3_auction.rs | 137 ++-- pallets/funding/src/tests/4_community.rs | 61 +- pallets/funding/src/tests/5_remainder.rs | 37 +- pallets/funding/src/tests/6_funding_end.rs | 56 +- pallets/funding/src/tests/7_settlement.rs | 13 +- pallets/funding/src/tests/8_ct_migration.rs | 5 + pallets/funding/src/tests/misc.rs | 1 + pallets/funding/src/tests/mod.rs | 82 +- pallets/funding/src/tests/runtime_api.rs | 707 ++++++++++++++++++ pallets/linear-release/src/tests.rs | 18 +- runtimes/polimec/src/lib.rs | 58 +- 29 files changed, 1976 insertions(+), 180 deletions(-) create mode 100644 pallets/funding/src/instantiator/tests.rs create mode 100644 pallets/funding/src/runtime_api.rs create mode 100644 pallets/funding/src/tests/runtime_api.rs diff --git a/Cargo.lock b/Cargo.lock index 7e2b0c6b6..2faa3e1d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6990,6 +6990,7 @@ dependencies = [ "polkadot-parachain-primitives", "scale-info", "serde", + "sp-api", "sp-arithmetic", "sp-core", "sp-io", diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 70ba1662b..78e5a199e 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -116,6 +116,7 @@ std = [ "polimec-common/std", "polimec-receiver/std", "polimec-runtime/std", + "polimec-xcm-executor/std", "polkadot-core-primitives/std", "polkadot-parachain-primitives/std", "polkadot-primitives/std", diff --git a/integration-tests/penpal/Cargo.toml b/integration-tests/penpal/Cargo.toml index e16fea408..f676cfbf0 100644 --- a/integration-tests/penpal/Cargo.toml +++ b/integration-tests/penpal/Cargo.toml @@ -87,10 +87,9 @@ assets-common = { version = "0.7.0", default-features = false } snowbridge-rococo-common = { version = "0.1.0", default-features = false } [features] -default = ["std"] +default = [ "std" ] std = [ "assets-common/std", - "parity-scale-codec/std", "cumulus-pallet-aura-ext/std", "cumulus-pallet-dmp-queue/std", "cumulus-pallet-parachain-system/std", @@ -119,12 +118,17 @@ std = [ "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", "pallet-transaction-payment/std", + "pallet-vesting/std", "pallet-xcm/std", "parachain-info/std", "parachains-common/std", + "parity-scale-codec/std", + "polimec-common/std", + "polimec-receiver/std", "polkadot-parachain-primitives/std", "polkadot-primitives/std", "polkadot-runtime-common/std", + "polkadot-runtime-parachains/std", "scale-info/std", "snowbridge-rococo-common/std", "sp-api/std", @@ -166,11 +170,15 @@ runtime-benchmarks = [ "pallet-message-queue/runtime-benchmarks", "pallet-sudo/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", + "pallet-vesting/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "parachains-common/runtime-benchmarks", + "polimec-common/runtime-benchmarks", + "polimec-receiver/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", + "polkadot-runtime-parachains/runtime-benchmarks", "snowbridge-rococo-common/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", @@ -198,10 +206,14 @@ try-runtime = [ "pallet-sudo/try-runtime", "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", + "pallet-vesting/try-runtime", "pallet-xcm/try-runtime", "parachain-info/try-runtime", + "polimec-common/try-runtime", + "polimec-receiver/try-runtime", "polkadot-runtime-common/try-runtime", + "polkadot-runtime-parachains/try-runtime", "sp-runtime/try-runtime", ] -experimental = ["pallet-aura/experimental"] +experimental = [ "pallet-aura/experimental" ] diff --git a/integration-tests/src/tests/ct_migration.rs b/integration-tests/src/tests/ct_migration.rs index 4f3857819..829fef786 100644 --- a/integration-tests/src/tests/ct_migration.rs +++ b/integration-tests/src/tests/ct_migration.rs @@ -168,6 +168,7 @@ fn create_settled_project() -> (ProjectId, Vec) { let project_id = inst.create_finished_project( default_project_metadata(ISSUER.into()), ISSUER.into(), + None, default_evaluations(), default_bids(), default_community_contributions(), @@ -221,6 +222,7 @@ fn create_project_with_unsettled_participation(participation_type: Participation let project_id = inst.create_finished_project( default_project_metadata(ISSUER.into()), ISSUER.into(), + None, default_evaluations(), default_bids(), default_community_contributions(), diff --git a/integration-tests/src/tests/e2e.rs b/integration-tests/src/tests/e2e.rs index c3db8eae7..a08ce9010 100644 --- a/integration-tests/src/tests/e2e.rs +++ b/integration-tests/src/tests/e2e.rs @@ -280,7 +280,7 @@ fn evaluation_round_completed() { let evaluations = excel_evaluators(); PolimecNet::execute_with(|| { - inst.create_auctioning_project(project, issuer, evaluations); + inst.create_auctioning_project(project, issuer, None, evaluations); }); } @@ -296,7 +296,7 @@ fn auction_round_completed() { let bids = excel_bidders(); PolimecNet::execute_with(|| { - let project_id = inst.create_community_contributing_project(project, issuer, evaluations, bids); + let project_id = inst.create_community_contributing_project(project, issuer, None, evaluations, bids); let excel_wap_fixed = FixedU128::from_float(10.202357561f64); let excel_wap_usd = excel_wap_fixed.saturating_mul_int(USD_UNIT); @@ -334,6 +334,7 @@ fn community_round_completed() { let _ = inst.create_remainder_contributing_project( excel_project(), ISSUER.into(), + None, excel_evaluators(), excel_bidders(), excel_contributions(), @@ -360,6 +361,7 @@ fn remainder_round_completed() { inst.create_finished_project( excel_project(), ISSUER.into(), + None, excel_evaluators(), excel_bidders(), excel_contributions(), @@ -393,6 +395,7 @@ fn funds_raised() { let project_id = inst.create_finished_project( excel_project(), ISSUER.into(), + None, excel_evaluators(), excel_bidders(), excel_contributions(), @@ -424,6 +427,7 @@ fn ct_minted() { let project_id = inst.create_finished_project( excel_project(), ISSUER.into(), + None, excel_evaluators(), excel_bidders(), excel_contributions(), @@ -452,6 +456,7 @@ fn ct_migrated() { let project_id = inst.create_finished_project( excel_project(), ISSUER.into(), + None, excel_evaluators(), excel_bidders(), excel_contributions(), diff --git a/integration-tests/src/tests/oracle.rs b/integration-tests/src/tests/oracle.rs index 8d46b38f1..9202d6a47 100644 --- a/integration-tests/src/tests/oracle.rs +++ b/integration-tests/src/tests/oracle.rs @@ -127,6 +127,7 @@ fn pallet_funding_works() { let _project_id = inst.create_finished_project( default_project_metadata(ISSUER.into()), ISSUER.into(), + None, default_evaluations(), default_bids(), default_community_contributions(), diff --git a/pallets/funding/Cargo.toml b/pallets/funding/Cargo.toml index 818c0aab0..ad77bdab3 100644 --- a/pallets/funding/Cargo.toml +++ b/pallets/funding/Cargo.toml @@ -37,6 +37,7 @@ pallet-xcm.workspace = true polkadot-parachain-primitives.workspace = true polimec-common-test-utils = { workspace = true, optional = true } frame-benchmarking = { workspace = true, optional = true } +sp-api.workspace = true # Used in the instantiator. itertools.workspace = true @@ -76,6 +77,7 @@ std = [ "polkadot-parachain-primitives/std", "scale-info/std", "serde/std", + "sp-api/std", "sp-arithmetic/std", "sp-core/std", "sp-io/std", diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs index 88f808207..8c50baf3f 100644 --- a/pallets/funding/src/benchmarking.rs +++ b/pallets/funding/src/benchmarking.rs @@ -393,7 +393,7 @@ mod benchmarks { whitelist_account!(issuer); let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_new_project(project_metadata.clone(), issuer.clone()); + let project_id = inst.create_new_project(project_metadata.clone(), issuer.clone(), None); let jwt = get_mock_jwt_with_cid( issuer.clone(), InvestorType::Institutional, @@ -427,7 +427,7 @@ mod benchmarks { whitelist_account!(issuer); let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_new_project(project_metadata.clone(), issuer.clone()); + let project_id = inst.create_new_project(project_metadata.clone(), issuer.clone(), None); let project_metadata = ProjectMetadataOf:: { token_information: CurrencyMetadata { @@ -530,7 +530,7 @@ mod benchmarks { whitelist_account!(issuer); let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_new_project(project_metadata.clone(), issuer.clone()); + let project_id = inst.create_new_project(project_metadata.clone(), issuer.clone(), None); // start_evaluation fn will try to add an automatic transition 1 block after the last evaluation block let block_number: BlockNumberFor = inst.current_block() + T::EvaluationDuration::get() + One::one(); @@ -584,7 +584,7 @@ mod benchmarks { whitelist_account!(issuer); let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer.clone()); + let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer.clone(), None); let evaluations = default_evaluations(); let plmc_for_evaluating = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); @@ -643,7 +643,7 @@ mod benchmarks { whitelist_account!(test_evaluator); let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer); + let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer, None); let existing_evaluation = UserToUSDBalance::new(test_evaluator.clone(), (200 * USD_UNIT).into()); let extrinsic_evaluation = UserToUSDBalance::new(test_evaluator.clone(), (1_000 * USD_UNIT).into()); @@ -773,7 +773,7 @@ mod benchmarks { default_weights(), ); - let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, evaluations); + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); let existing_bid = BidParams::new(bidder.clone(), (50 * CT_UNIT).into(), 5u8, AcceptedFundingAsset::USDT); @@ -1096,6 +1096,7 @@ mod benchmarks { let project_id = inst.create_community_contributing_project( project_metadata.clone(), issuer, + None, default_evaluations::(), full_bids::(), ); @@ -1406,6 +1407,7 @@ mod benchmarks { let project_id = inst.create_finished_project( project_metadata.clone(), issuer.clone(), + None, evaluations, bids, contributions, @@ -1457,6 +1459,7 @@ mod benchmarks { let project_id = inst.create_finished_project( default_project_metadata::(issuer.clone()), issuer, + None, evaluations, default_bids::(), default_community_contributions::(), @@ -1538,7 +1541,7 @@ mod benchmarks { ); let project_id = - inst.create_finished_project(project_metadata, issuer, evaluations, bids, contributions, vec![]); + inst.create_finished_project(project_metadata, issuer, None, evaluations, bids, contributions, vec![]); inst.advance_time(One::one()).unwrap(); assert_eq!( @@ -1605,6 +1608,7 @@ mod benchmarks { let project_id = inst.create_finished_project( default_project_metadata::(issuer.clone()), issuer, + None, default_evaluations::(), bids, default_community_contributions::(), @@ -1671,8 +1675,15 @@ mod benchmarks { default_community_contributor_multipliers(), ); - let project_id = - inst.create_finished_project(project_metadata, issuer.clone(), evaluations, bids, contributions, vec![]); + let project_id = inst.create_finished_project( + project_metadata, + issuer.clone(), + None, + evaluations, + bids, + contributions, + vec![], + ); inst.advance_time(One::one()).unwrap(); assert_eq!( @@ -1718,6 +1729,7 @@ mod benchmarks { let project_id = inst.create_finished_project( default_project_metadata::(issuer.clone()), issuer, + None, default_evaluations::(), default_bids::(), contributions, @@ -1796,7 +1808,7 @@ mod benchmarks { whitelist_account!(contributor); let project_id = - inst.create_finished_project(project_metadata, issuer, evaluations, bids, contributions, vec![]); + inst.create_finished_project(project_metadata, issuer, None, evaluations, bids, contributions, vec![]); inst.advance_time(One::one()).unwrap(); assert_eq!( @@ -1855,7 +1867,7 @@ mod benchmarks { whitelist_account!(issuer); let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_evaluating_project(project_metadata, issuer.clone()); + let project_id = inst.create_evaluating_project(project_metadata, issuer.clone(), None); let evaluations = default_evaluations(); let plmc_for_evaluating = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); @@ -1901,7 +1913,7 @@ mod benchmarks { whitelist_account!(issuer); let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_evaluating_project(project_metadata, issuer.clone()); + let project_id = inst.create_evaluating_project(project_metadata, issuer.clone(), None); let project_details = inst.get_project_details(project_id); let evaluation_usd_target = @@ -1963,7 +1975,7 @@ mod benchmarks { whitelist_account!(issuer); let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_auctioning_project(project_metadata, issuer.clone(), default_evaluations()); + let project_id = inst.create_auctioning_project(project_metadata, issuer.clone(), None, default_evaluations()); let opening_end_block = inst.get_project_details(project_id).phase_transition_points.auction_opening.end().unwrap(); @@ -2018,6 +2030,7 @@ mod benchmarks { let project_id = inst.create_auctioning_project( project_metadata.clone(), issuer.clone(), + None, inst.generate_successful_evaluations( project_metadata.clone(), default_evaluators::(), @@ -2151,6 +2164,7 @@ mod benchmarks { let project_id = inst.create_auctioning_project( project_metadata.clone(), issuer.clone(), + None, inst.generate_successful_evaluations( project_metadata.clone(), default_evaluators::(), @@ -2272,6 +2286,7 @@ mod benchmarks { let project_id = inst.create_community_contributing_project( project_metadata, issuer.clone(), + None, default_evaluations(), default_bids(), ); @@ -2340,6 +2355,7 @@ mod benchmarks { let project_id = inst.create_remainder_contributing_project( project_metadata, issuer.clone(), + None, default_evaluations::(), bids, contributions, @@ -2398,6 +2414,7 @@ mod benchmarks { let project_id = inst.create_remainder_contributing_project( project_metadata, issuer.clone(), + None, default_evaluations::(), bids, contributions, @@ -2457,6 +2474,7 @@ mod benchmarks { let project_id = inst.create_remainder_contributing_project( project_metadata, issuer.clone(), + None, default_evaluations::(), bids, contributions, @@ -2534,6 +2552,7 @@ mod benchmarks { let project_id = inst.create_remainder_contributing_project( project_metadata, issuer.clone(), + None, evaluations, bids, contributions, @@ -2590,6 +2609,7 @@ mod benchmarks { let project_id = inst.create_finished_project( project_metadata, issuer.clone(), + None, default_evaluations::(), bids, contributions, @@ -2621,6 +2641,7 @@ mod benchmarks { let project_id = inst.create_finished_project( project_metadata, issuer.clone(), + None, default_evaluations::(), default_bids::(), default_community_contributions::(), @@ -2668,6 +2689,7 @@ mod benchmarks { let project_id = inst.create_finished_project( project_metadata, issuer.clone(), + None, default_evaluations::(), bids, contributions, @@ -2698,6 +2720,7 @@ mod benchmarks { let project_id = inst.create_finished_project( project_metadata.clone(), issuer.clone(), + None, default_evaluations::(), default_bids::(), default_community_contributions::(), @@ -2747,6 +2770,7 @@ mod benchmarks { let project_id = inst.create_finished_project( project_metadata.clone(), issuer.clone(), + None, default_evaluations::(), default_bids::(), default_community_contributions::(), @@ -2817,6 +2841,7 @@ mod benchmarks { let project_id = inst.create_finished_project( project_metadata.clone(), issuer.clone(), + None, evaluations, bids, default_community_contributions::(), @@ -2862,6 +2887,7 @@ mod benchmarks { let project_id = inst.create_finished_project( project_metadata.clone(), issuer.clone(), + None, default_evaluations::(), default_bids::(), default_community_contributions::(), @@ -2920,6 +2946,7 @@ mod benchmarks { let project_id = inst.create_finished_project( project_metadata.clone(), issuer.clone(), + None, default_evaluations::(), default_bids::(), default_community_contributions::(), @@ -2982,6 +3009,7 @@ mod benchmarks { let project_id = inst.create_finished_project( project_metadata.clone(), issuer.clone(), + None, default_evaluations::(), default_bids::(), default_community_contributions::(), @@ -3062,6 +3090,7 @@ mod benchmarks { let project_id = inst.create_finished_project( project_metadata.clone(), issuer.clone(), + None, default_evaluations::(), default_bids::(), default_community_contributions::(), @@ -3183,6 +3212,7 @@ mod benchmarks { let project_id = inst.create_finished_project( project_metadata.clone(), issuer.clone(), + None, evaluations, bids, default_community_contributions::(), @@ -3277,6 +3307,7 @@ mod benchmarks { let project_id = inst.create_finished_project( project_metadata.clone(), issuer.clone(), + None, evaluations, bids, default_community_contributions::(), @@ -3352,6 +3383,7 @@ mod benchmarks { let project_id = inst.create_finished_project( project_metadata.clone(), issuer.clone(), + None, default_evaluations::(), default_bids::(), default_community_contributions::(), @@ -3401,6 +3433,7 @@ mod benchmarks { let project_id = inst.create_finished_project( project_metadata.clone(), issuer.clone(), + None, default_evaluations::(), default_bids::(), default_community_contributions::(), diff --git a/pallets/funding/src/instantiator/calculations.rs b/pallets/funding/src/instantiator/calculations.rs index 2b664d69b..ea5bac920 100644 --- a/pallets/funding/src/instantiator/calculations.rs +++ b/pallets/funding/src/instantiator/calculations.rs @@ -642,4 +642,167 @@ impl< early_evaluators_rewards.saturating_add(normal_evaluators_rewards) } + + // Assuming all purchases done before random end + pub fn dry_run_wap(&self, mut bucket: BucketOf, token_allocation: BalanceOf) -> PriceOf { + let mut accounted_tokens: BalanceOf = Zero::zero(); + let mut tickets = Vec::new(); + + if bucket.current_price == bucket.initial_price { + return bucket.initial_price + } + + // Do a reverse iteration over the bucket increments to see which tickets are accepted + while accounted_tokens < token_allocation { + let tokens_sold = if bucket.initial_price == bucket.current_price { + bucket.delta_amount * 10u32.into() + } else { + bucket.delta_amount - bucket.amount_left + }; + let price = bucket.current_price; + let remaining_tokens: BalanceOf = token_allocation - accounted_tokens; + let accepted_tokens = remaining_tokens.min(tokens_sold); + tickets.push((accepted_tokens, price)); + accounted_tokens += accepted_tokens; + + if price > bucket.initial_price { + bucket.amount_left = Zero::zero(); + bucket.current_price = bucket.current_price - bucket.delta_price; + } + } + + // Use the accepted tickets to calculate the WAP + let total_usd = tickets + .clone() + .into_iter() + .map(|(tokens, price)| price.saturating_mul_int(tokens.into())) + .reduce(|a, b| a + b) + .unwrap(); + let partial_waps = tickets + .into_iter() + .map(|(tokens, price)| { + let weight = PriceOf::::saturating_from_rational(price.saturating_mul_int(tokens.into()), total_usd); + let partial_wap = weight * price; + partial_wap + }) + .collect::>>(); + let wap = partial_waps.into_iter().reduce(|a, b| a + b).unwrap(); + + wap + } + + 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; + + if target_wap == bucket.initial_price { + return bucket + } + + // Fill first bucket + bucket.update(bucket.delta_amount * 10u32.into()); + + // Fill remaining buckets till we pass by the wap + loop { + dbg!(&bucket.current_price); + let wap = self.dry_run_wap(bucket.clone(), auction_allocation); + + if wap < target_wap { + bucket.update(bucket.delta_amount); + } else { + break + } + } + + // Go back one bucket + bucket.amount_left = bucket.delta_amount; + bucket.current_price = bucket.current_price - bucket.delta_price; + + // Do a binary search on the amount to reach the desired wap + let mut lower_bound: BalanceOf = Zero::zero(); + let mut upper_bound: BalanceOf = bucket.delta_amount; + let mid_point = |l: BalanceOf, u: BalanceOf| -> BalanceOf { (l.clone() + u.clone()) / 2u32.into() }; + bucket.amount_left = mid_point(lower_bound, upper_bound); + let mut new_wap = self.dry_run_wap(bucket.clone(), auction_allocation); + while new_wap != target_wap { + if new_wap > target_wap { + lower_bound = mid_point(lower_bound, upper_bound); + bucket.amount_left = mid_point(lower_bound, upper_bound); + } else { + upper_bound = mid_point(lower_bound, upper_bound); + bucket.amount_left = mid_point(lower_bound, upper_bound); + } + + if lower_bound == upper_bound { + break + } + + new_wap = self.dry_run_wap(bucket.clone(), auction_allocation); + } + dbg!(&bucket); + bucket + } + + // We assume a single bid can cover the whole first bucket. Make sure the ticket sizes allow this. + pub fn generate_bids_from_bucket( + &self, + project_metadata: ProjectMetadataOf, + bucket: BucketOf, + mut starting_account: AccountIdOf, + increment_account: fn(AccountIdOf) -> AccountIdOf, + funding_asset: AcceptedFundingAsset, + ) -> Vec> { + if bucket.current_price == bucket.initial_price { + return vec![] + } + let auction_allocation = + project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + + let mut generate_bid = |ct_amount| -> BidParams { + let bid = (starting_account.clone(), ct_amount, funding_asset).into(); + starting_account = increment_account(starting_account.clone()); + bid + }; + + let step_amounts = ((bucket.current_price - bucket.initial_price) / bucket.delta_price).saturating_mul_int(1u8); + let last_bid_amount = bucket.delta_amount - bucket.amount_left; + + let mut bids = Vec::new(); + + let first_bid = generate_bid(auction_allocation); + bids.push(first_bid); + + for _i in 0u8..step_amounts - 1u8 { + let full_bucket_bid = generate_bid(bucket.delta_amount); + bids.push(full_bucket_bid); + } + + // A CT amount can be so low that the PLMC required is less than the minimum mintable amount. We estimate all bids + // should be at least 1% of a bucket. + let min_bid_amount = Percent::from_percent(1) * bucket.delta_amount; + if last_bid_amount > min_bid_amount { + let last_bid = generate_bid(last_bid_amount); + bids.push(last_bid); + } + + bids + } + + pub fn generate_bids_that_take_price_to( + &self, + project_metadata: ProjectMetadataOf, + desired_price: PriceOf, + bidder_account: AccountIdOf, + next_bidder_account: fn(AccountIdOf) -> AccountIdOf, + ) -> Vec> { + let necessary_bucket = self.find_bucket_for_wap(project_metadata.clone(), desired_price); + self.generate_bids_from_bucket( + project_metadata, + necessary_bucket, + bidder_account, + next_bidder_account, + AcceptedFundingAsset::USDT, + ) + } } diff --git a/pallets/funding/src/instantiator/chain_interactions.rs b/pallets/funding/src/instantiator/chain_interactions.rs index 7a9df9ada..610506bee 100644 --- a/pallets/funding/src/instantiator/chain_interactions.rs +++ b/pallets/funding/src/instantiator/chain_interactions.rs @@ -286,14 +286,17 @@ impl< pub fn creation_assertions( &mut self, project_id: ProjectId, + maybe_did: Option, expected_metadata: ProjectMetadataOf, creation_start_block: BlockNumberFor, ) { let metadata = self.get_project_metadata(project_id); let details = self.get_project_details(project_id); + let issuer_did = + if let Some(did) = maybe_did { did } else { generate_did_from_account(self.get_issuer(project_id)) }; let expected_details = ProjectDetailsOf:: { issuer_account: self.get_issuer(project_id), - issuer_did: generate_did_from_account(self.get_issuer(project_id)), + issuer_did, is_frozen: false, weighted_average_price: None, status: ProjectStatus::Application, @@ -396,24 +399,26 @@ impl< }) } - pub fn create_new_project(&mut self, project_metadata: ProjectMetadataOf, issuer: AccountIdOf) -> ProjectId { + pub fn create_new_project( + &mut self, + project_metadata: ProjectMetadataOf, + issuer: AccountIdOf, + maybe_did: Option, + ) -> ProjectId { let now = self.current_block(); // one ED for the issuer, one ED for the escrow account self.mint_plmc_to(vec![UserToPLMCBalance::new(issuer.clone(), self.get_ed() * 2u64.into())]); + let did = if let Some(did) = maybe_did.clone() { did } else { generate_did_from_account(issuer.clone()) }; + self.execute(|| { - crate::Pallet::::do_create_project( - &issuer, - project_metadata.clone(), - generate_did_from_account(issuer.clone()), - ) - .unwrap(); + crate::Pallet::::do_create_project(&issuer, project_metadata.clone(), did).unwrap(); let last_project_metadata = ProjectsMetadata::::iter().last().unwrap(); log::trace!("Last project metadata: {:?}", last_project_metadata); }); let created_project_id = self.execute(|| NextProjectId::::get().saturating_sub(One::one())); - self.creation_assertions(created_project_id, project_metadata, now); + self.creation_assertions(created_project_id, maybe_did, project_metadata, now); created_project_id } @@ -429,8 +434,9 @@ impl< &mut self, project_metadata: ProjectMetadataOf, issuer: AccountIdOf, + maybe_did: Option, ) -> ProjectId { - let project_id = self.create_new_project(project_metadata, issuer.clone()); + let project_id = self.create_new_project(project_metadata, issuer.clone(), maybe_did); self.start_evaluation(project_id, issuer).unwrap(); project_id } @@ -478,9 +484,10 @@ impl< &mut self, project_metadata: ProjectMetadataOf, issuer: AccountIdOf, + maybe_did: Option, evaluations: Vec>, ) -> ProjectId { - let project_id = self.create_evaluating_project(project_metadata, issuer.clone()); + let project_id = self.create_evaluating_project(project_metadata, issuer.clone(), maybe_did); let evaluators = evaluations.accounts(); let prev_supply = self.get_plmc_total_supply(); @@ -553,10 +560,12 @@ impl< &mut self, project_metadata: ProjectMetadataOf, issuer: AccountIdOf, + maybe_did: Option, evaluations: Vec>, bids: Vec>, ) -> ProjectId { - let project_id = self.create_auctioning_project(project_metadata.clone(), issuer, evaluations.clone()); + let project_id = + self.create_auctioning_project(project_metadata.clone(), issuer, maybe_did, evaluations.clone()); if bids.is_empty() { self.start_community_funding(project_id).unwrap(); return project_id @@ -692,7 +701,11 @@ impl< } } - pub fn finish_funding(&mut self, project_id: ProjectId) -> Result<(), DispatchError> { + pub fn finish_funding( + &mut self, + project_id: ProjectId, + force_decision: Option, + ) -> Result<(), DispatchError> { if let Some(update_block) = self.get_update_block(project_id, &UpdateType::RemainderFundingStart) { self.execute(|| frame_system::Pallet::::set_block_number(update_block - One::one())); self.advance_time(1u32.into()).unwrap(); @@ -711,6 +724,13 @@ impl< ), "Project should be in Finished status" ); + if project_details.status == ProjectStatus::AwaitingProjectDecision { + if let Some(decision) = force_decision { + self.execute(|| { + crate::Pallet::::do_project_decision(project_id, decision).unwrap(); + }); + } + } Ok(()) } @@ -946,6 +966,7 @@ impl< &mut self, project_metadata: ProjectMetadataOf, issuer: AccountIdOf, + maybe_did: Option, evaluations: Vec>, bids: Vec>, contributions: Vec>, @@ -953,6 +974,7 @@ impl< let project_id = self.create_community_contributing_project( project_metadata.clone(), issuer, + maybe_did, evaluations.clone(), bids.clone(), ); @@ -1020,6 +1042,7 @@ impl< &mut self, project_metadata: ProjectMetadataOf, issuer: AccountIdOf, + maybe_did: Option, evaluations: Vec>, bids: Vec>, community_contributions: Vec>, @@ -1028,6 +1051,7 @@ impl< let project_id = self.create_remainder_contributing_project( project_metadata.clone(), issuer, + maybe_did, evaluations.clone(), bids.clone(), community_contributions.clone(), @@ -1036,7 +1060,7 @@ impl< match self.get_project_details(project_id).status { ProjectStatus::FundingSuccessful => return project_id, ProjectStatus::RemainderRound if remainder_contributions.is_empty() => { - self.finish_funding(project_id).unwrap(); + self.finish_funding(project_id, None).unwrap(); return project_id; }, _ => {}, @@ -1097,7 +1121,7 @@ impl< self.do_free_foreign_asset_assertions(prev_funding_asset_balances.merge_accounts(MergeOperation::Add)); assert_eq!(self.get_plmc_total_supply(), post_supply); - self.finish_funding(project_id).unwrap(); + self.finish_funding(project_id, None).unwrap(); if self.get_project_details(project_id).status == ProjectStatus::FundingSuccessful { // Check that remaining CTs are updated @@ -1126,6 +1150,33 @@ impl< project_id } + pub fn create_settled_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_finished_project( + project_metadata.clone(), + issuer.clone(), + maybe_did, + evaluations.clone(), + bids.clone(), + community_contributions.clone(), + remainder_contributions.clone(), + ); + + let settlement_start = self.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); + self.jump_to_block(settlement_start); + + self.settle_project(project_id).unwrap(); + project_id + } + pub fn create_project_at( &mut self, status: ProjectStatus, @@ -1140,6 +1191,7 @@ impl< ProjectStatus::FundingSuccessful => self.create_finished_project( project_metadata, issuer, + None, evaluations, bids, community_contributions, @@ -1148,15 +1200,17 @@ impl< ProjectStatus::RemainderRound => self.create_remainder_contributing_project( project_metadata, issuer, + None, evaluations, bids, community_contributions, ), ProjectStatus::CommunityRound => - self.create_community_contributing_project(project_metadata, issuer, evaluations, bids), - ProjectStatus::AuctionOpening => self.create_auctioning_project(project_metadata, issuer, evaluations), - ProjectStatus::EvaluationRound => self.create_evaluating_project(project_metadata, issuer), - ProjectStatus::Application => self.create_new_project(project_metadata, issuer), + self.create_community_contributing_project(project_metadata, issuer, None, evaluations, bids), + ProjectStatus::AuctionOpening => + 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/mod.rs b/pallets/funding/src/instantiator/mod.rs index 1b23f903d..fd11c8938 100644 --- a/pallets/funding/src/instantiator/mod.rs +++ b/pallets/funding/src/instantiator/mod.rs @@ -67,3 +67,6 @@ pub mod calculations; #[cfg(any(feature = "std", feature = "runtime-benchmarks"))] pub mod chain_interactions; + +#[cfg(test)] +mod tests; diff --git a/pallets/funding/src/instantiator/tests.rs b/pallets/funding/src/instantiator/tests.rs new file mode 100644 index 000000000..1bb3df471 --- /dev/null +++ b/pallets/funding/src/instantiator/tests.rs @@ -0,0 +1,222 @@ +use crate::{ + instantiator::{UserToForeignAssets, UserToPLMCBalance}, + mock::{new_test_ext, TestRuntime, PLMC}, + tests::{ + defaults::{bounded_name, bounded_symbol, default_evaluations, default_project_metadata, ipfs_hash}, + CT_DECIMALS, CT_UNIT, + }, + traits::ProvideAssetPrice, + *, +}; +use core::cell::RefCell; +use itertools::Itertools; +use polimec_common::{USD_DECIMALS, USD_UNIT}; +use sp_arithmetic::Percent; + +#[test] +fn dry_run_wap() { + let mut inst = tests::MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + + const ADAM: u32 = 60; + const TOM: u32 = 61; + const SOFIA: u32 = 62; + const FRED: u32 = 63; + const ANNA: u32 = 64; + const DAMIAN: u32 = 65; + + let accounts = vec![ADAM, TOM, SOFIA, FRED, ANNA, DAMIAN]; + + let bounded_name = bounded_name(); + let bounded_symbol = bounded_symbol(); + let metadata_hash = ipfs_hash(); + let normalized_price = PriceOf::::from_float(10.0); + let decimal_aware_price = + PriceProviderOf::::calculate_decimals_aware_price(normalized_price, USD_DECIMALS, CT_DECIMALS) + .unwrap(); + 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), + 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: 0u32, + policy_ipfs_cid: Some(metadata_hash), + }; + + // overfund with plmc + let plmc_fundings = accounts + .iter() + .map(|acc| UserToPLMCBalance { account: acc.clone(), plmc_amount: PLMC * 1_000_000 }) + .collect_vec(); + let usdt_fundings = accounts + .iter() + .map(|acc| UserToForeignAssets { + account: acc.clone(), + asset_amount: USD_UNIT * 1_000_000, + asset_id: AcceptedFundingAsset::USDT.to_assethub_id(), + }) + .collect_vec(); + inst.mint_plmc_to(plmc_fundings); + inst.mint_foreign_asset_to(usdt_fundings); + + let project_id = inst.create_auctioning_project(project_metadata.clone(), 0, None, default_evaluations()); + + let bids = vec![ + (ADAM, 10_000 * CT_UNIT).into(), + (TOM, 20_000 * CT_UNIT).into(), + (SOFIA, 20_000 * CT_UNIT).into(), + (FRED, 10_000 * CT_UNIT).into(), + (ANNA, 5_000 * CT_UNIT).into(), + (DAMIAN, 5_000 * CT_UNIT).into(), + ]; + + inst.bid_for_users(project_id, bids).unwrap(); + + inst.start_community_funding(project_id).unwrap(); + + 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()); + dbg!(bucket); + let dry_run_price = inst.dry_run_wap( + bucket, + project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size, + ); + dbg!(project_details.funding_amount_reached_usd); + + assert_eq!(dry_run_price, wap); +} + +#[test] +fn find_bucket_for_wap() { + let mut inst = tests::MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + + const ADAM: u32 = 60; + const TOM: u32 = 61; + const SOFIA: u32 = 62; + const FRED: u32 = 63; + const ANNA: u32 = 64; + const DAMIAN: u32 = 65; + + let accounts = vec![ADAM, TOM, SOFIA, FRED, ANNA, DAMIAN]; + + let bounded_name = bounded_name(); + let bounded_symbol = bounded_symbol(); + let metadata_hash = ipfs_hash(); + let normalized_price = PriceOf::::from_float(10.0); + let decimal_aware_price = + PriceProviderOf::::calculate_decimals_aware_price(normalized_price, USD_DECIMALS, CT_DECIMALS) + .unwrap(); + 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), + 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: 0u32, + policy_ipfs_cid: Some(metadata_hash), + }; + + // overfund with plmc + let plmc_fundings = accounts + .iter() + .map(|acc| UserToPLMCBalance { account: acc.clone(), plmc_amount: PLMC * 1_000_000 }) + .collect_vec(); + let usdt_fundings = accounts + .iter() + .map(|acc| UserToForeignAssets { + account: acc.clone(), + asset_amount: USD_UNIT * 1_000_000, + asset_id: AcceptedFundingAsset::USDT.to_assethub_id(), + }) + .collect_vec(); + inst.mint_plmc_to(plmc_fundings); + inst.mint_foreign_asset_to(usdt_fundings); + + let project_id = inst.create_auctioning_project(project_metadata.clone(), 0, None, default_evaluations()); + + let bids = vec![ + (ADAM, 10_000 * CT_UNIT).into(), + (TOM, 20_000 * CT_UNIT).into(), + (SOFIA, 20_000 * CT_UNIT).into(), + (FRED, 10_000 * CT_UNIT).into(), + (ANNA, 5_000 * CT_UNIT).into(), + (DAMIAN, 5_000 * CT_UNIT).into(), + ]; + + inst.bid_for_users(project_id, bids).unwrap(); + + inst.start_community_funding(project_id).unwrap(); + + let project_details = inst.get_project_details(project_id); + let wap = project_details.weighted_average_price.unwrap(); + dbg!(wap); + let bucket = inst.execute(|| Buckets::::get(project_id).unwrap()); + dbg!(bucket); + let dry_run_price = inst.dry_run_wap( + bucket, + project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size, + ); + dbg!(project_details.funding_amount_reached_usd); + + assert_eq!(dry_run_price, wap); + + let bucket_found = inst.find_bucket_for_wap(project_metadata.clone(), wap); + let wap_found = inst.dry_run_wap( + bucket_found, + project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size, + ); + assert_eq!(wap_found, wap); +} + +#[test] +fn generate_bids_from_bucket() { + let mut inst = tests::MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + + // Has a min price of 10.0 + let project_metadata = default_project_metadata(0); + let desired_real_wap = FixedU128::from_float(20.0f64); + let desired_price_aware_wap = + PriceProviderOf::::calculate_decimals_aware_price(desired_real_wap, USD_DECIMALS, CT_DECIMALS) + .unwrap(); + dbg!(desired_price_aware_wap); + let necessary_bucket = inst.find_bucket_for_wap(project_metadata.clone(), desired_price_aware_wap); + dbg!(&necessary_bucket); + let bids = inst.generate_bids_from_bucket( + project_metadata.clone(), + necessary_bucket, + 420, + |x| x + 1, + AcceptedFundingAsset::USDT, + ); + dbg!(&bids); + let project_id = + inst.create_community_contributing_project(project_metadata.clone(), 0, None, default_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); +} diff --git a/pallets/funding/src/instantiator/types.rs b/pallets/funding/src/instantiator/types.rs index 94def5617..866513dfa 100644 --- a/pallets/funding/src/instantiator/types.rs +++ b/pallets/funding/src/instantiator/types.rs @@ -284,6 +284,16 @@ impl From<(AccountIdOf, BalanceOf, u8, AcceptedFundingAsset)> f } } } +impl From<(AccountIdOf, BalanceOf, AcceptedFundingAsset)> for BidParams { + fn from((bidder, amount, asset): (AccountIdOf, BalanceOf, AcceptedFundingAsset)) -> Self { + Self { + bidder, + amount, + multiplier: 1u8.try_into().unwrap_or_else(|_| panic!("multiplier could not be created from 1u8")), + asset, + } + } +} impl Accounts for Vec> { type Account = AccountIdOf; diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 61737c099..2890d4be1 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -117,6 +117,8 @@ // This recursion limit is needed because we have too many benchmarks and benchmarking will fail if // we add more without this limit. #![cfg_attr(feature = "runtime-benchmarks", recursion_limit = "512")] +extern crate alloc; + pub use crate::weights::WeightInfo; use frame_support::{ traits::{ @@ -157,6 +159,7 @@ pub mod instantiator; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; +pub mod runtime_api; pub type AccountIdOf = ::AccountId; pub type ProjectId = u32; @@ -197,6 +200,7 @@ pub mod pallet { use frame_support::{ dispatch::{GetDispatchInfo, PostDispatchInfo}, pallet_prelude::*, + storage::KeyPrefixIterator, traits::{OnFinalize, OnIdle, OnInitialize}, }; use frame_system::pallet_prelude::*; @@ -254,8 +258,11 @@ pub mod pallet { /// The currency used for minting contribution tokens as fungible assets (i.e pallet-assets) type ContributionTokenCurrency: fungibles::Create, AssetId = ProjectId, Balance = BalanceOf> + fungibles::Destroy, AssetId = ProjectId, Balance = BalanceOf> - + fungibles::InspectEnumerable, Balance = BalanceOf> - + fungibles::metadata::Inspect> + + fungibles::InspectEnumerable< + AccountIdOf, + Balance = BalanceOf, + AssetsIterator = KeyPrefixIterator>, + > + fungibles::metadata::Inspect> + fungibles::metadata::Mutate> + fungibles::metadata::MetadataDeposit> + fungibles::Mutate, Balance = BalanceOf> diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs index df417a539..076ed7653 100644 --- a/pallets/funding/src/mock.rs +++ b/pallets/funding/src/mock.rs @@ -20,7 +20,10 @@ use super::*; use crate as pallet_funding; -use crate::traits::ProvideAssetPrice; +use crate::{ + runtime_api::{ExtrinsicHelpers, Leaderboards, ProjectInformation, ProjectParticipationIds, UserInformation}, + traits::ProvideAssetPrice, +}; use frame_support::{ construct_runtime, pallet_prelude::Weight, @@ -522,3 +525,56 @@ pub fn new_test_ext() -> sp_io::TestExternalities { }); ext } + +sp_api::mock_impl_runtime_apis! { + impl Leaderboards for TestRuntime { + fn top_evaluations(project_id: ProjectId, amount: u32) -> Vec> { + PolimecFunding::top_evaluations +(project_id, amount) + } + + fn top_bids(project_id: ProjectId, amount: u32) -> Vec> { + PolimecFunding::top_bids +(project_id, amount) + } + + fn top_contributions(project_id: ProjectId, amount: u32) -> Vec> { + PolimecFunding::top_contributions +(project_id, amount) + } + + fn top_projects_by_usd_raised(amount: u32) -> Vec<(ProjectId, ProjectMetadataOf, ProjectDetailsOf)> { + PolimecFunding::top_projects_by_usd_raised(amount) + } + + fn top_projects_by_usd_target_percent_reached(amount: u32) -> Vec<(ProjectId, ProjectMetadataOf, ProjectDetailsOf)> { + PolimecFunding::top_projects_by_usd_target_percent_reached(amount) + } + } + + impl UserInformation for TestRuntime { + fn contribution_tokens(account: AccountId) -> Vec<(ProjectId, BalanceOf)> { + PolimecFunding::contribution_tokens(account) + } + + fn all_project_participations_by_did(project_id: ProjectId, did: Did) -> Vec> { + PolimecFunding::all_project_participations_by_did(project_id, did) + } + } + + impl ProjectInformation for TestRuntime { + fn usd_target_percent_reached(project_id: ProjectId) -> FixedU128 { + PolimecFunding::usd_target_percent_reached(project_id) + } + + fn projects_by_did(did: Did) -> Vec { + PolimecFunding::projects_by_did(did) + } + } + + impl ExtrinsicHelpers for TestRuntime { + fn funding_asset_to_ct_amount(project_id: ProjectId, asset: AcceptedFundingAsset, asset_amount: BalanceOf) -> BalanceOf { + PolimecFunding::funding_asset_to_ct_amount(project_id, asset, asset_amount) + } + } +} diff --git a/pallets/funding/src/runtime_api.rs b/pallets/funding/src/runtime_api.rs new file mode 100644 index 000000000..4d56fa1f1 --- /dev/null +++ b/pallets/funding/src/runtime_api.rs @@ -0,0 +1,230 @@ +use crate::{traits::ProvideAssetPrice, *}; +use alloc::collections::BTreeMap; +use frame_support::traits::fungibles::{metadata::Inspect as MetadataInspect, Inspect, InspectEnumerable}; +use itertools::Itertools; +use parity_scale_codec::{Decode, Encode}; +use polimec_common::USD_DECIMALS; +use scale_info::TypeInfo; +use sp_runtime::traits::Zero; + +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)] +pub struct ProjectParticipationIds { + account: AccountIdOf, + evaluation_ids: Vec, + bid_ids: Vec, + contribution_ids: Vec, +} + +sp_api::decl_runtime_apis! { + #[api_version(1)] + pub trait Leaderboards { + /// Get the top evaluations made for a project by the amount of PLMC bonded + fn top_evaluations(project_id: ProjectId, amount: u32) -> Vec>; + + /// Get the top bids for a project by the amount of CTs bought. + fn top_bids(project_id: ProjectId, amount: u32) -> Vec>; + + /// Get the top contributions for a project by the amount of CTs bought. + fn top_contributions(project_id: ProjectId, amount: u32) -> Vec>; + + /// Get the top projects by the absolute USD value raised + fn top_projects_by_usd_raised(amount: u32) -> Vec<(ProjectId, ProjectMetadataOf, ProjectDetailsOf)>; + + /// Get the top project by the highest percentage of the target reached + fn top_projects_by_usd_target_percent_reached(amount: u32) -> Vec<(ProjectId, ProjectMetadataOf, ProjectDetailsOf)>; + } + + #[api_version(1)] + pub trait UserInformation { + /// Get all the contribution token balances for the participated projects + fn contribution_tokens(account: AccountIdOf) -> Vec<(ProjectId, BalanceOf)>; + + /// Get all the project participations made by a single DID. + fn all_project_participations_by_did(project_id: ProjectId, did: Did) -> Vec>; + } + + #[api_version(1)] + pub trait ProjectInformation { + /// Get the percentage of the target reached for a project + fn usd_target_percent_reached(project_id: ProjectId) -> FixedU128; + + /// Get all the projects created by a single DID. + fn projects_by_did(did: Did) -> Vec; + } + + #[api_version(1)] + pub trait ExtrinsicHelpers { + /// Get the current price of a contribution token (either current bucket in the auction, or WAP in contribution phase), + /// and calculate the amount of tokens that can be bought with the given amount USDT/USDC/DOT. + fn funding_asset_to_ct_amount(project_id: ProjectId, asset: AcceptedFundingAsset, asset_amount: BalanceOf) -> BalanceOf; + } +} + +impl Pallet { + pub fn top_evaluations +(project_id: ProjectId, amount: u32) -> Vec> { + Evaluations::::iter_prefix_values((project_id,)) + .sorted_by(|a, b| b.original_plmc_bond.cmp(&a.original_plmc_bond)) + .take(amount as usize) + .collect_vec() + } + + pub fn top_bids +(project_id: ProjectId, amount: u32) -> Vec> { + Bids::::iter_prefix_values((project_id,)) + .sorted_by(|a, b| b.final_ct_amount.cmp(&a.final_ct_amount)) + .take(amount as usize) + .collect_vec() + } + + pub fn top_contributions +(project_id: ProjectId, amount: u32) -> Vec> { + Contributions::::iter_prefix_values((project_id,)) + .sorted_by(|a, b| b.ct_amount.cmp(&a.ct_amount)) + .take(amount as usize) + .collect_vec() + } + + pub fn top_projects_by_usd_raised(amount: u32) -> Vec<(ProjectId, ProjectMetadataOf, ProjectDetailsOf)> { + ProjectsDetails::::iter() + .sorted_by(|a, b| b.1.funding_amount_reached_usd.cmp(&a.1.funding_amount_reached_usd)) + .take(amount as usize) + .map(|(project_id, project_details)| { + let project_metadata = ProjectsMetadata::::get(project_id).expect("Project not found"); + (project_id, project_metadata, project_details) + }) + .collect_vec() + } + + pub fn top_projects_by_usd_target_percent_reached( + amount: u32, + ) -> Vec<(ProjectId, ProjectMetadataOf, ProjectDetailsOf)> { + ProjectsDetails::::iter() + .map(|(project_id, project_details)| { + let funding_reached = project_details.funding_amount_reached_usd; + let funding_target = project_details.fundraising_target_usd; + let funding_ratio = FixedU128::from_rational(funding_reached.into(), funding_target.into()); + (project_id, project_details, funding_ratio) + }) + .sorted_by(|a, b| b.2.cmp(&a.2)) + .take(amount as usize) + .map(|(project_id, project_details, _funding_ratio)| { + let project_metadata = ProjectsMetadata::::get(project_id).expect("Project not found"); + (project_id, project_metadata, project_details) + }) + .collect_vec() + } + + pub fn contribution_tokens(account: AccountIdOf) -> Vec<(ProjectId, BalanceOf)> { + let asset_ids = ::ContributionTokenCurrency::asset_ids(); + asset_ids + .filter_map(|asset_id| { + let balance = ::ContributionTokenCurrency::balance(asset_id, &account); + if balance > Zero::zero() { + Some((asset_id, balance)) + } else { + None + } + }) + .sorted_by(|a, b| b.1.cmp(&a.1)) + .collect_vec() + } + + pub fn funding_asset_to_ct_amount( + project_id: ProjectId, + asset: AcceptedFundingAsset, + asset_amount: BalanceOf, + ) -> BalanceOf { + let project_details = ProjectsDetails::::get(project_id).expect("Project not found"); + let funding_asset_id = asset.to_assethub_id(); + let funding_asset_decimals = T::FundingCurrency::decimals(funding_asset_id); + let funding_asset_usd_price = + T::PriceProvider::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) + .expect("Price not found"); + let usd_ticket_size = funding_asset_usd_price.saturating_mul_int(asset_amount); + + let mut ct_amount = Zero::zero(); + + // Contribution phase + if let Some(wap) = project_details.weighted_average_price { + ct_amount = wap.reciprocal().expect("Bad math").saturating_mul_int(usd_ticket_size); + } + // Auction phase, we need to consider multiple buckets + else { + let mut usd_to_spend = usd_ticket_size; + let mut current_bucket = Buckets::::get(project_id).expect("Bucket not found"); + while usd_to_spend > Zero::zero() { + let bucket_price = current_bucket.current_price; + + let ct_to_buy = bucket_price.reciprocal().expect("Bad math").saturating_mul_int(usd_to_spend); + let ct_to_buy = ct_to_buy.min(current_bucket.amount_left); + + ct_amount = ct_amount.saturating_add(ct_to_buy); + // if usd spent is 0, we will have an infinite loop + let usd_spent = bucket_price.saturating_mul_int(ct_to_buy).max(One::one()); + usd_to_spend = usd_to_spend.saturating_sub(usd_spent); + + current_bucket.update(ct_to_buy) + } + } + + ct_amount + } + + pub fn all_project_participations_by_did(project_id: ProjectId, did: Did) -> Vec> { + let evaluations = Evaluations::::iter_prefix((project_id,)) + .filter(|((_account_id, _evaluation_id), evaluation)| evaluation.did == did) + .map(|((account_id, evaluation_id), _evaluation)| (account_id, evaluation_id)) + .collect_vec(); + + let bids = Bids::::iter_prefix((project_id,)) + .filter(|((_account_id, _bid_id), bid)| bid.did == did) + .map(|((account_id, bid_id), _bid)| (account_id, bid_id)) + .collect_vec(); + + let contributions = Contributions::::iter_prefix((project_id,)) + .filter(|((_account_id, _contribution_id), contribution)| contribution.did == did) + .map(|((account_id, contribution_id), _contribution)| (account_id, contribution_id)) + .collect_vec(); + + let mut map: BTreeMap, (Vec, Vec, Vec)> = BTreeMap::new(); + + for (account_id, evaluation_id) in evaluations { + map.entry(account_id).or_insert_with(|| (Vec::new(), Vec::new(), Vec::new())).0.push(evaluation_id); + } + + for (account_id, bid_id) in bids { + map.entry(account_id).or_insert_with(|| (Vec::new(), Vec::new(), Vec::new())).1.push(bid_id); + } + + for (account_id, contribution_id) in contributions { + map.entry(account_id).or_insert_with(|| (Vec::new(), Vec::new(), Vec::new())).2.push(contribution_id); + } + + let output = map + .into_iter() + .map(|(account, (evaluation_ids, bid_ids, contribution_ids))| ProjectParticipationIds { + account, + evaluation_ids, + bid_ids, + contribution_ids, + }) + .collect(); + + output + } + + pub fn usd_target_percent_reached(project_id: ProjectId) -> FixedU128 { + let project_details = ProjectsDetails::::get(project_id).expect("Project not found"); + let funding_reached = project_details.funding_amount_reached_usd; + let funding_target = project_details.fundraising_target_usd; + FixedU128::from_rational(funding_reached.into(), funding_target.into()) + } + + pub fn projects_by_did(did: Did) -> Vec { + ProjectsDetails::::iter() + .filter(|(_project_id, project_details)| project_details.issuer_did == did) + .map(|(project_id, _)| project_id) + .collect() + } +} diff --git a/pallets/funding/src/tests/1_application.rs b/pallets/funding/src/tests/1_application.rs index cd4486ba5..2f39a2f83 100644 --- a/pallets/funding/src/tests/1_application.rs +++ b/pallets/funding/src/tests/1_application.rs @@ -17,7 +17,7 @@ mod round_flow { let issuer = ISSUER_1; let project_metadata = default_project_metadata(issuer); - inst.create_evaluating_project(project_metadata, issuer); + inst.create_evaluating_project(project_metadata, issuer, None); } } } @@ -38,9 +38,9 @@ mod create_project_extrinsic { let project_2 = default_project_metadata(ISSUER_2); let project_3 = default_project_metadata(ISSUER_3); - let created_project_1_id = inst.create_evaluating_project(project_1, ISSUER_1); - let created_project_2_id = inst.create_evaluating_project(project_2, ISSUER_2); - let created_project_3_id = inst.create_evaluating_project(project_3, ISSUER_3); + let created_project_1_id = inst.create_evaluating_project(project_1, ISSUER_1, None); + let created_project_2_id = inst.create_evaluating_project(project_2, ISSUER_2, None); + let created_project_3_id = inst.create_evaluating_project(project_3, ISSUER_3, None); assert_eq!(created_project_1_id, 0); assert_eq!(created_project_2_id, 1); @@ -53,7 +53,7 @@ mod create_project_extrinsic { let mut issuer = ISSUER_1; for _ in 0..512 { let project_metadata = default_project_metadata(issuer); - inst.create_evaluating_project(project_metadata, issuer); + inst.create_evaluating_project(project_metadata, issuer, None); inst.advance_time(1u64).unwrap(); issuer += 1; } @@ -190,7 +190,7 @@ mod create_project_extrinsic { Error::::HasActiveProject ); }); - inst.finish_funding(1).unwrap(); + inst.finish_funding(1, None).unwrap(); assert_eq!(inst.get_project_details(1).status, ProjectStatus::FundingFailed); inst.execute(|| { assert_ok!(Pallet::::create_project( @@ -209,7 +209,7 @@ mod create_project_extrinsic { inst.contribute_for_users(2, default_community_buys()).unwrap(); inst.start_remainder_or_end_funding(2).unwrap(); inst.contribute_for_users(2, default_remainder_buys()).unwrap(); - inst.finish_funding(2).unwrap(); + inst.finish_funding(2, None).unwrap(); assert_eq!(inst.get_project_details(2).status, ProjectStatus::FundingSuccessful); assert_ok!(inst.execute(|| crate::Pallet::::create_project( RuntimeOrigin::signed(ISSUER_1), @@ -803,7 +803,7 @@ mod edit_project_extrinsic { generate_did_from_account(ISSUER_1), project_metadata.clone().policy_ipfs_cid.unwrap(), ); - let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); project_metadata.minimum_price = PriceProviderOf::::calculate_decimals_aware_price( PriceOf::::from_float(15.0), @@ -836,7 +836,7 @@ mod edit_project_extrinsic { generate_did_from_account(ISSUER_1), project_metadata.clone().policy_ipfs_cid.unwrap(), ); - let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); let mut new_metadata_1 = project_metadata.clone(); let new_policy_hash = ipfs_hash(); new_metadata_1.minimum_price = PriceProviderOf::::calculate_decimals_aware_price( @@ -922,7 +922,7 @@ mod edit_project_extrinsic { project_metadata.policy_ipfs_cid = None; inst.mint_plmc_to(default_plmc_balances()); let jwt = get_mock_jwt(ISSUER_1, InvestorType::Institutional, generate_did_from_account(ISSUER_1)); - let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); let mut new_metadata = project_metadata.clone(); let new_policy_hash = ipfs_hash(); new_metadata.policy_ipfs_cid = Some(new_policy_hash); @@ -946,7 +946,7 @@ mod edit_project_extrinsic { generate_did_from_account(ISSUER_1), project_metadata.clone().policy_ipfs_cid.unwrap(), ); - let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); let mut new_metadata = project_metadata.clone(); let new_price = PriceOf::::from_float(1f64); @@ -1005,8 +1005,8 @@ mod edit_project_extrinsic { project_metadata_2.clone().policy_ipfs_cid.unwrap(), ); - let project_id_1 = inst.create_new_project(project_metadata_1.clone(), ISSUER_1); - let project_id_2 = inst.create_new_project(project_metadata_2.clone(), ISSUER_2); + let project_id_1 = inst.create_new_project(project_metadata_1.clone(), ISSUER_1, None); + let project_id_2 = inst.create_new_project(project_metadata_2.clone(), ISSUER_2, None); inst.execute(|| { assert_noop!( @@ -1041,7 +1041,7 @@ mod edit_project_extrinsic { generate_did_from_account(ISSUER_1), project_metadata.clone().policy_ipfs_cid.unwrap(), ); - let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); inst.start_evaluation(project_id, ISSUER_1).unwrap(); inst.execute(|| { assert_noop!( @@ -1068,7 +1068,7 @@ mod edit_project_extrinsic { project_metadata.clone().policy_ipfs_cid.unwrap(), ); - let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); inst.execute(|| { assert_noop!( @@ -1122,7 +1122,7 @@ mod remove_project_extrinsic { generate_did_from_account(ISSUER_1), project_metadata.clone().policy_ipfs_cid.unwrap(), ); - let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); assert_ok!(inst.execute(|| crate::Pallet::::remove_project( RuntimeOrigin::signed(ISSUER_1), jwt.clone(), @@ -1202,7 +1202,7 @@ mod remove_project_extrinsic { generate_did_from_account(ISSUER_1), project_metadata.clone().policy_ipfs_cid.unwrap(), ); - let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); inst.execute(|| { assert_noop!( crate::Pallet::::remove_project( @@ -1227,7 +1227,7 @@ mod remove_project_extrinsic { project_metadata.clone().policy_ipfs_cid.unwrap(), ); - let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); inst.execute(|| { assert_noop!( @@ -1252,7 +1252,7 @@ mod remove_project_extrinsic { generate_did_from_account(ISSUER_1), project_metadata.clone().policy_ipfs_cid.unwrap(), ); - let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); inst.start_evaluation(project_id, ISSUER_1).unwrap(); inst.execute(|| { assert_noop!( diff --git a/pallets/funding/src/tests/2_evaluation.rs b/pallets/funding/src/tests/2_evaluation.rs index 34d6021b5..5c194f0c8 100644 --- a/pallets/funding/src/tests/2_evaluation.rs +++ b/pallets/funding/src/tests/2_evaluation.rs @@ -16,7 +16,7 @@ mod round_flow { let project_metadata = default_project_metadata(issuer); let evaluations = default_evaluations(); - inst.create_auctioning_project(project_metadata, issuer, evaluations); + inst.create_auctioning_project(project_metadata, issuer, None, evaluations); } #[test] @@ -28,10 +28,10 @@ mod round_flow { let project4 = default_project_metadata(ISSUER_4); let evaluations = default_evaluations(); - inst.create_auctioning_project(project1, ISSUER_1, evaluations.clone()); - inst.create_auctioning_project(project2, ISSUER_2, evaluations.clone()); - inst.create_auctioning_project(project3, ISSUER_3, evaluations.clone()); - inst.create_auctioning_project(project4, ISSUER_4, evaluations); + inst.create_auctioning_project(project1, ISSUER_1, None, evaluations.clone()); + inst.create_auctioning_project(project2, ISSUER_2, None, evaluations.clone()); + inst.create_auctioning_project(project3, ISSUER_3, None, evaluations.clone()); + inst.create_auctioning_project(project4, ISSUER_4, None, evaluations); } #[test] @@ -48,7 +48,7 @@ mod round_flow { let evaluation_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); inst.mint_plmc_to(evaluation_plmc); - let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1); + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); inst.evaluate_for_users(project_id, evaluations.clone()).unwrap(); let old_price = ::PriceProvider::get_price(PLMC_FOREIGN_ID).unwrap(); @@ -61,7 +61,7 @@ mod round_flow { let evaluation_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); inst.mint_plmc_to(evaluation_plmc); - let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_2); + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_2, None); inst.evaluate_for_users(project_id, evaluations.clone()).unwrap(); let old_price = ::PriceProvider::get_price(PLMC_FOREIGN_ID).unwrap(); @@ -124,7 +124,7 @@ mod round_flow { project_metadata.mainnet_token_max_supply = project_metadata.total_allocation_size; let issuer: AccountIdOf = (10_000 + inst.get_new_nonce()).try_into().unwrap(); - let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer); + let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer, None); let evaluation_threshold = inst.execute(|| ::EvaluationSuccessThreshold::get()); let evaluation_threshold_ct = evaluation_threshold * project_metadata.total_allocation_size; @@ -224,7 +224,7 @@ mod round_flow { inst.mint_plmc_to(plmc_eval_deposits.clone()); inst.mint_plmc_to(plmc_existential_deposits.clone()); - let project_id = inst.create_evaluating_project(project_metadata, issuer); + let project_id = inst.create_evaluating_project(project_metadata, issuer, None); let evaluation_end = inst .get_project_details(project_id) @@ -265,7 +265,7 @@ mod start_evaluation_extrinsic { let issuer = ISSUER_1; let project_metadata = default_project_metadata(issuer); - let project_id = inst.create_new_project(project_metadata.clone(), issuer); + let project_id = inst.create_new_project(project_metadata.clone(), issuer, None); let jwt = get_mock_jwt_with_cid( issuer, InvestorType::Institutional, @@ -288,7 +288,7 @@ mod start_evaluation_extrinsic { let issuer_did = generate_did_from_account(issuer); let project_metadata = default_project_metadata(issuer); - let project_id = inst.create_new_project(project_metadata.clone(), issuer); + let project_id = inst.create_new_project(project_metadata.clone(), issuer, None); let jwt = get_mock_jwt_with_cid( issuer, InvestorType::Institutional, @@ -349,7 +349,7 @@ mod start_evaluation_extrinsic { let issuer = ISSUER_1; let project_metadata = default_project_metadata(issuer); - let project_id = inst.create_new_project(project_metadata.clone(), issuer); + let project_id = inst.create_new_project(project_metadata.clone(), issuer, None); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Application); inst.execute(|| { @@ -391,7 +391,7 @@ mod start_evaluation_extrinsic { let issuer = ISSUER_1; let project_metadata = default_project_metadata(issuer); - let project_id = inst.create_new_project(project_metadata.clone(), issuer); + let project_id = inst.create_new_project(project_metadata.clone(), issuer, None); let jwt = get_mock_jwt_with_cid( issuer, InvestorType::Institutional, @@ -421,7 +421,7 @@ mod start_evaluation_extrinsic { let mut project_metadata = default_project_metadata(issuer); project_metadata.policy_ipfs_cid = None; - let project_id = inst.create_new_project(project_metadata.clone(), issuer); + let project_id = inst.create_new_project(project_metadata.clone(), issuer, None); let jwt = get_mock_jwt(issuer, InvestorType::Institutional, generate_did_from_account(issuer)); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Application); inst.execute(|| { @@ -437,7 +437,7 @@ mod start_evaluation_extrinsic { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_metadata = default_project_metadata(ISSUER_1); - let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); let jwt = get_mock_jwt_with_cid( ISSUER_1, InvestorType::Institutional, @@ -486,7 +486,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 project_id = inst.create_evaluating_project(project_metadata.clone(), issuer); + let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer, None); let evaluations = vec![ (EVALUATOR_1, 500 * USD_UNIT).into(), @@ -539,7 +539,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 project_id = inst.create_evaluating_project(project_metadata.clone(), issuer); + let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer, None); let evaluation = UserToUSDBalance::new(EVALUATOR_1, 500 * USD_UNIT); let necessary_plmc = inst.calculate_evaluation_plmc_spent(vec![evaluation.clone()], true); @@ -567,7 +567,7 @@ mod evaluate_extrinsic { fn storage_check() { 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); + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); let evaluation = UserToUSDBalance::new(EVALUATOR_1, 500 * USD_UNIT); let necessary_plmc = inst.calculate_evaluation_plmc_spent(vec![evaluation.clone()], false); let plmc_existential_deposits = necessary_plmc.accounts().existential_deposits(); @@ -637,7 +637,7 @@ mod evaluate_extrinsic { ); }); - let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer); + let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer, None); inst.execute(|| { assert_ok!(PolimecFunding::evaluate( RuntimeOrigin::signed(EVALUATOR_4), @@ -660,7 +660,7 @@ mod evaluate_extrinsic { inst.start_auction(project_id, ISSUER_1).unwrap(); inst.start_community_funding(project_id).unwrap(); inst.start_remainder_or_end_funding(project_id).unwrap(); - inst.finish_funding(project_id).unwrap(); + inst.finish_funding(project_id, None).unwrap(); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); @@ -726,7 +726,8 @@ 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, default_evaluations()); + let project_id = + inst.create_auctioning_project(project_metadata.clone(), issuer, None, default_evaluations()); inst.execute(|| { assert_noop!( @@ -763,7 +764,7 @@ mod evaluate_extrinsic { inst.mint_plmc_to(insufficient_eval_deposits); inst.mint_plmc_to(plmc_existential_deposits); - let project_id = inst.create_evaluating_project(project_metadata, issuer); + let project_id = inst.create_evaluating_project(project_metadata, issuer, None); let dispatch_error = inst.evaluate_for_users(project_id, evaluations); assert_err!(dispatch_error, TokenError::FundsUnavailable) @@ -784,7 +785,7 @@ mod evaluate_extrinsic { inst.mint_plmc_to(evaluating_plmc); inst.mint_plmc_to(plmc_insufficient_existential_deposit); - let project_id = inst.create_evaluating_project(project_metadata, issuer); + let project_id = inst.create_evaluating_project(project_metadata, issuer, None); let dispatch_error = inst.evaluate_for_users(project_id, evaluations); assert_err!(dispatch_error, TokenError::FundsUnavailable) @@ -799,7 +800,7 @@ mod evaluate_extrinsic { .collect_vec(); let failing_evaluation = UserToUSDBalance::new(EVALUATOR_1, 1000 * CT_UNIT); - let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1); + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); let plmc_for_evaluating = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); @@ -827,7 +828,7 @@ mod evaluate_extrinsic { .collect_vec(); let failing_evaluation = UserToUSDBalance::new(EVALUATOR_1, 100 * USD_UNIT); - let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1); + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); let plmc_for_evaluating = inst.calculate_evaluation_plmc_spent(evaluations.clone(), false); let plmc_existential_deposits = evaluations.accounts().existential_deposits(); @@ -853,7 +854,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 project_id = inst.create_evaluating_project(project_metadata.clone(), issuer); + let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer, None); let evaluation = UserToUSDBalance::new(EVALUATOR_1, 500 * USD_UNIT); let necessary_plmc = inst.calculate_evaluation_plmc_spent(vec![evaluation.clone()], false); @@ -893,7 +894,7 @@ mod evaluate_extrinsic { fn issuer_cannot_evaluate_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_evaluating_project(project_metadata.clone(), ISSUER_1); + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); assert_err!( inst.execute(|| crate::Pallet::::do_evaluate( &(&ISSUER_1 + 1), @@ -912,7 +913,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 project_id = inst.create_evaluating_project(project_metadata.clone(), issuer); + let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer, None); let evaluation = UserToUSDBalance::new(EVALUATOR_1, 500 * USD_UNIT); let necessary_plmc = inst.calculate_evaluation_plmc_spent(vec![evaluation.clone()], true); @@ -956,7 +957,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 project_id = inst.create_evaluating_project(project_metadata.clone(), issuer); + let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer, None); let evaluator = EVALUATOR_1; let jwt = get_mock_jwt_with_cid( evaluator, @@ -998,7 +999,7 @@ mod evaluate_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_evaluating_project(project_metadata.clone(), ISSUER_1); + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); inst.execute(|| { assert_noop!( diff --git a/pallets/funding/src/tests/3_auction.rs b/pallets/funding/src/tests/3_auction.rs index 0cb2bba20..e2a3dde2a 100644 --- a/pallets/funding/src/tests/3_auction.rs +++ b/pallets/funding/src/tests/3_auction.rs @@ -17,7 +17,8 @@ mod round_flow { 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, evaluations, bids); + let _project_id = + inst.create_community_contributing_project(project_metadata, ISSUER_1, None, evaluations, bids); } #[test] @@ -30,10 +31,10 @@ mod round_flow { let evaluations = default_evaluations(); let bids = default_bids(); - inst.create_community_contributing_project(project1, ISSUER_1, evaluations.clone(), bids.clone()); - inst.create_community_contributing_project(project2, ISSUER_2, evaluations.clone(), bids.clone()); - inst.create_community_contributing_project(project3, ISSUER_3, evaluations.clone(), bids.clone()); - inst.create_community_contributing_project(project4, ISSUER_4, evaluations, bids); + 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); } #[test] @@ -102,7 +103,8 @@ mod round_flow { inst.mint_plmc_to(plmc_fundings); inst.mint_foreign_asset_to(usdt_fundings); - let project_id = inst.create_auctioning_project(project_metadata, ISSUER_1, default_evaluations()); + let project_id = + inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); let bids = vec![ (ADAM, 10_000 * CT_UNIT).into(), @@ -150,7 +152,7 @@ mod round_flow { bids.push(second_bucket_bid); let project_id = - inst.create_community_contributing_project(project_metadata.clone(), issuer, evaluations, bids); + inst.create_community_contributing_project(project_metadata.clone(), issuer, None, evaluations, bids); let bidder_5_bid = inst.execute(|| Bids::::iter_prefix_values((project_id, BIDDER_6)).next().unwrap()); let wabgp = inst.get_project_details(project_id).weighted_average_price.unwrap(); @@ -178,6 +180,7 @@ mod round_flow { let project_id = inst.create_community_contributing_project( project_metadata.clone(), ISSUER_1, + None, evaluations.clone(), bids, ); @@ -191,6 +194,7 @@ mod round_flow { let project_id = inst.create_community_contributing_project( project_metadata.clone(), ISSUER_2, + None, evaluations.clone(), bids, ); @@ -228,7 +232,7 @@ mod round_flow { let bids = vec![bid_1, bid_2, bid_3, bid_4, bid_5]; - let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, evaluations); + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); let plmc_fundings = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( &bids, @@ -290,7 +294,8 @@ mod round_flow { fn bids_get_rejected_and_refunded_part_two() { 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, default_evaluations()); + let project_id = + inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); let total_auction_ct_amount = project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; @@ -384,7 +389,7 @@ mod round_flow { let issuer = ISSUER_1; let project_metadata = default_project_metadata(issuer); let evaluations = default_evaluations(); - let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, evaluations); + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); let details = inst.get_project_details(project_id); let opening_end = details.phase_transition_points.auction_opening.end().unwrap(); @@ -408,7 +413,7 @@ mod round_flow { let project_metadata = default_project_metadata(issuer); let evaluations = default_evaluations(); let bids = default_bids(); - let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, evaluations); + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); let necessary_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( &bids, @@ -489,7 +494,7 @@ mod round_flow { inst.mint_plmc_to(plmc_fundings); inst.mint_foreign_asset_to(usdt_fundings); - let project_id = inst.create_auctioning_project(project_metadata, ISSUER_1, default_evaluations()); + let project_id = inst.create_auctioning_project(project_metadata, ISSUER_1, None, default_evaluations()); let bids = vec![ (ADAM, 10_000 * CT_UNIT, 1, AcceptedFundingAsset::USDT).into(), @@ -602,7 +607,7 @@ mod round_flow { default_evaluators(), default_weights(), ); - let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, evaluations); + 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; @@ -692,6 +697,7 @@ mod round_flow { let project_id = inst.create_community_contributing_project( project_metadata.clone(), ISSUER_1, + None, inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()), all_bids, ); @@ -703,6 +709,35 @@ mod round_flow { let higher_than_wap_bids = all_bids.iter().filter(|bid| bid.original_ct_usd_price > wap).collect_vec(); assert_eq!(higher_than_wap_bids.len(), (max_bids_per_project - 1u32) as usize); } + + #[test] + 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 bucket_size = Percent::from_percent(10) * auction_allocation; + let bids = vec![ + (BIDDER_1, auction_allocation).into(), + (BIDDER_2, bucket_size).into(), + (BIDDER_3, bucket_size).into(), + (BIDDER_4, bucket_size).into(), + (BIDDER_5, bucket_size).into(), + (BIDDER_6, bucket_size).into(), + ]; + + let project_id = inst.create_community_contributing_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + bids, + ); + + let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); + dbg!(wap); + assert!(wap > project_metadata.minimum_price); + } } #[cfg(test)] @@ -713,7 +748,7 @@ mod round_flow { 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); + 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(|| { @@ -746,7 +781,7 @@ mod start_auction_extrinsic { #[test] fn pallet_can_start_auction_automatically() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1); + let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); let evaluations = default_evaluations(); let required_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); @@ -765,7 +800,7 @@ mod start_auction_extrinsic { #[test] fn issuer_can_start_auction_manually() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1); + let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); let evaluations = default_evaluations(); let required_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); inst.mint_plmc_to(required_plmc); @@ -780,7 +815,7 @@ mod start_auction_extrinsic { #[test] fn stranger_cannot_start_auction_manually() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1); + let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); let evaluations = default_evaluations(); let required_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); inst.mint_plmc_to(required_plmc); @@ -805,7 +840,7 @@ mod start_auction_extrinsic { #[test] fn cannot_start_auction_manually_before_evaluation_finishes() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1); + let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); inst.execute(|| { assert_noop!( PolimecFunding::do_start_auction_opening(ISSUER_1, project_id), @@ -817,7 +852,7 @@ mod start_auction_extrinsic { #[test] fn cannot_start_auction_manually_if_evaluation_fails() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1); + let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); inst.advance_time(::EvaluationDuration::get() + 1).unwrap(); inst.execute(|| { assert_noop!( @@ -835,7 +870,7 @@ mod start_auction_extrinsic { fn auction_doesnt_start_automatically_if_evaluation_fails() { // Test our success assumption is ok let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1); + let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); let evaluations = default_evaluations(); let required_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); inst.mint_plmc_to(required_plmc); @@ -844,7 +879,7 @@ mod start_auction_extrinsic { // Main test with failed evaluation let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1); + let project_id = inst.create_evaluating_project(default_project_metadata(ISSUER_1), ISSUER_1, None); let evaluation_end_execution = inst.get_update_block(project_id, &UpdateType::EvaluationEnd).unwrap(); inst.execute(|| System::set_block_number(evaluation_end_execution - 1)); @@ -874,7 +909,7 @@ mod bid_extrinsic { let evaluator_bid = BidParams::new(evaluator_bidder, 600 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); evaluations.push((evaluator_bidder, evaluation_amount).into()); - let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, evaluations); + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); let already_bonded_plmc = inst .calculate_evaluation_plmc_spent(vec![(evaluator_bidder, evaluation_amount).into()], false)[0] @@ -967,10 +1002,12 @@ mod bid_extrinsic { inst.mint_foreign_asset_to(usdt_fundings.clone()); inst.mint_foreign_asset_to(usdt_fundings.clone()); - let project_id_all = inst.create_auctioning_project(project_metadata_all, ISSUER_1, evaluations.clone()); + let project_id_all = + inst.create_auctioning_project(project_metadata_all, ISSUER_1, None, evaluations.clone()); assert_ok!(inst.bid_for_users(project_id_all, vec![usdt_bid.clone(), usdc_bid.clone(), dot_bid.clone()])); - let project_id_usdt = inst.create_auctioning_project(project_metadata_usdt, ISSUER_2, evaluations.clone()); + let project_id_usdt = + inst.create_auctioning_project(project_metadata_usdt, ISSUER_2, None, evaluations.clone()); assert_ok!(inst.bid_for_users(project_id_usdt, vec![usdt_bid.clone()])); assert_err!( inst.bid_for_users(project_id_usdt, vec![usdc_bid.clone()]), @@ -981,7 +1018,8 @@ mod bid_extrinsic { Error::::FundingAssetNotAccepted ); - let project_id_usdc = inst.create_auctioning_project(project_metadata_usdc, ISSUER_3, evaluations.clone()); + let project_id_usdc = + inst.create_auctioning_project(project_metadata_usdc, ISSUER_3, None, evaluations.clone()); assert_err!( inst.bid_for_users(project_id_usdc, vec![usdt_bid.clone()]), Error::::FundingAssetNotAccepted @@ -992,7 +1030,8 @@ mod bid_extrinsic { Error::::FundingAssetNotAccepted ); - let project_id_dot = inst.create_auctioning_project(project_metadata_dot, ISSUER_4, evaluations.clone()); + let project_id_dot = + inst.create_auctioning_project(project_metadata_dot, ISSUER_4, None, evaluations.clone()); assert_err!( inst.bid_for_users(project_id_dot, vec![usdt_bid.clone()]), Error::::FundingAssetNotAccepted @@ -1055,7 +1094,7 @@ mod bid_extrinsic { let project_metadata = default_project_metadata(ISSUER_1); let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); - let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, evaluations); + let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); // Professional bids: 0x multiplier should fail assert_err!( test_bid_setup(&mut inst, project_id, BIDDER_1, InvestorType::Professional, 0), @@ -1105,7 +1144,7 @@ mod bid_extrinsic { project_metadata.auction_round_allocation_percentage = Percent::from_percent(50u8); let evaluations = default_evaluations(); - let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, evaluations); + 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( @@ -1198,7 +1237,8 @@ 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, default_evaluations()); + let project_id = + inst.create_auctioning_project(project_metadata.clone(), issuer, None, default_evaluations()); let bid = BidParams::new(BIDDER_4, 500 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); let plmc_required = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( @@ -1249,7 +1289,7 @@ mod bid_extrinsic { inst.start_community_funding(project_id).unwrap(); inst.start_remainder_or_end_funding(project_id).unwrap(); - inst.finish_funding(project_id).unwrap(); + inst.finish_funding(project_id, None).unwrap(); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); @@ -1284,7 +1324,8 @@ 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, default_evaluations()); + let project_id = + inst.create_auctioning_project(project_metadata.clone(), issuer, None, default_evaluations()); let bid = BidParams::new(BIDDER_4, 500 * CT_UNIT, 5u8, AcceptedFundingAsset::USDT); let plmc_required = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( @@ -1354,7 +1395,7 @@ mod bid_extrinsic { inst.contribute_for_users(project_id, contributions).unwrap(); inst.start_remainder_or_end_funding(project_id).unwrap(); - inst.finish_funding(project_id).unwrap(); + inst.finish_funding(project_id, None).unwrap(); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); @@ -1420,7 +1461,7 @@ mod bid_extrinsic { let evaluator_bid = BidParams::new(evaluator_bidder, 600 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); evaluations.push((evaluator_bidder, evaluation_amount).into()); - let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, evaluations); + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); let necessary_usdt_for_bid = inst.calculate_auction_funding_asset_charged_with_given_price( &vec![evaluator_bid.clone()], @@ -1449,8 +1490,10 @@ mod bid_extrinsic { let evaluator_bid = BidParams::new(evaluator_bidder, 600 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); evaluations_1.push((evaluator_bidder, evaluation_amount).into()); - let _project_id_1 = inst.create_auctioning_project(project_metadata_1.clone(), ISSUER_1, evaluations_1); - let project_id_2 = inst.create_auctioning_project(project_metadata_2.clone(), ISSUER_2, evaluations_2); + let _project_id_1 = + inst.create_auctioning_project(project_metadata_1.clone(), ISSUER_1, None, evaluations_1); + let project_id_2 = + inst.create_auctioning_project(project_metadata_2.clone(), ISSUER_2, None, evaluations_2); // Necessary Mints let already_bonded_plmc = inst @@ -1498,7 +1541,7 @@ mod bid_extrinsic { fn cannot_bid_before_auction_round() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_metadata = default_project_metadata(ISSUER_1); - let _ = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1); + let _ = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); let did = generate_did_from_account(BIDDER_2); let investor_type = InvestorType::Institutional; @@ -1532,7 +1575,7 @@ mod bid_extrinsic { let bids = (0u32..max_bids_per_project - 1).map(|i| (i as u32 + 420u32, 5000 * CT_UNIT).into()).collect_vec(); - let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, evaluations); + let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); let plmc_for_bidding = inst.calculate_auction_plmc_charged_with_given_price( &bids.clone(), @@ -1637,7 +1680,7 @@ mod bid_extrinsic { 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(); - let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, evaluations); + let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); let plmc_for_bidding = inst.calculate_auction_plmc_charged_with_given_price( &bids.clone(), @@ -1741,7 +1784,8 @@ mod bid_extrinsic { let evaluations = default_evaluations(); - let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, evaluations.clone()); + let project_id = + inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations.clone()); inst.mint_plmc_to(vec![(BIDDER_1, 50_000 * CT_UNIT).into(), (BIDDER_2, 50_000 * CT_UNIT).into()]); @@ -1803,7 +1847,8 @@ mod bid_extrinsic { let evaluations = default_evaluations(); - let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, evaluations.clone()); + let project_id = + inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations.clone()); inst.mint_plmc_to(vec![ (BIDDER_1, 200_000 * PLMC).into(), @@ -1884,7 +1929,8 @@ mod bid_extrinsic { }; let evaluations = default_evaluations(); - let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, evaluations.clone()); + let project_id = + inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations.clone()); inst.mint_plmc_to(vec![ (BIDDER_1, 500_000 * CT_UNIT).into(), @@ -2001,7 +2047,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, default_evaluations()); + let project_id = + inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); assert_err!( inst.execute(|| crate::Pallet::::do_bid( &(&ISSUER_1 + 1), @@ -2021,7 +2068,8 @@ mod bid_extrinsic { 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, default_evaluations()); + let project_id = + inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); let bids = vec![BidParams::::new(BIDDER_1, 10_000, 1u8, AcceptedFundingAsset::USDC)]; let did = generate_did_from_account(bids[0].bidder); @@ -2046,7 +2094,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, default_evaluations()); + let project_id = + inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); inst.execute(|| { assert_noop!( diff --git a/pallets/funding/src/tests/4_community.rs b/pallets/funding/src/tests/4_community.rs index f1f68e10f..18649b4e7 100644 --- a/pallets/funding/src/tests/4_community.rs +++ b/pallets/funding/src/tests/4_community.rs @@ -17,6 +17,7 @@ mod round_flow { let _ = inst.create_remainder_contributing_project( default_project_metadata(ISSUER_1), ISSUER_1, + None, default_evaluations(), default_bids(), default_community_buys(), @@ -37,6 +38,7 @@ mod round_flow { inst.create_remainder_contributing_project( project1, ISSUER_1, + None, evaluations.clone(), bids.clone(), community_buys.clone(), @@ -44,6 +46,7 @@ mod round_flow { inst.create_remainder_contributing_project( project2, ISSUER_2, + None, evaluations.clone(), bids.clone(), community_buys.clone(), @@ -51,11 +54,12 @@ mod round_flow { inst.create_remainder_contributing_project( project3, ISSUER_3, + None, evaluations.clone(), bids.clone(), community_buys.clone(), ); - inst.create_remainder_contributing_project(project4, ISSUER_4, evaluations, bids, community_buys); + inst.create_remainder_contributing_project(project4, ISSUER_4, None, evaluations, bids, community_buys); } #[test] @@ -68,6 +72,7 @@ mod round_flow { let project_id = inst.create_community_contributing_project( default_project_metadata(ISSUER_1), ISSUER_1, + None, default_evaluations(), bids, ); @@ -121,6 +126,7 @@ mod round_flow { let project_id = inst.create_community_contributing_project( project_metadata.clone(), ISSUER_1, + None, default_evaluations(), default_bids(), ); @@ -239,8 +245,13 @@ mod round_flow { default_evaluators(), default_weights(), ); - let project_id = - inst.create_community_contributing_project(project_metadata.clone(), issuer, evaluations, vec![]); + 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); @@ -287,7 +298,7 @@ mod round_flow { assert_eq!(inst.get_project_details(project_id).remaining_contribution_tokens, 0); // We can successfully finish the project - inst.finish_funding(project_id).unwrap(); + inst.finish_funding(project_id, None).unwrap(); }; for decimals in 6..=18 { @@ -335,6 +346,7 @@ mod community_contribute_extrinsic { let project_id = inst.create_community_contributing_project( project_metadata.clone(), ISSUER_1, + None, evaluations, default_bids(), ); @@ -432,7 +444,8 @@ mod community_contribute_extrinsic { let bob_bid: BidParams = (bob, 1337 * CT_UNIT).into(); let all_bids = bids.iter().chain(vec![bob_bid.clone()].iter()).cloned().collect_vec(); - let project_id = inst.create_auctioning_project(default_project_metadata(ISSUER_2), ISSUER_2, evaluations); + let project_id = + inst.create_auctioning_project(default_project_metadata(ISSUER_2), ISSUER_2, None, evaluations); let evaluation_plmc_bond = inst.execute(|| Balances::balance_on_hold(&HoldReason::Evaluation(project_id).into(), &bob)); @@ -508,6 +521,7 @@ mod community_contribute_extrinsic { let project_id = inst.create_community_contributing_project( project_metadata_all.clone(), ISSUER_1, + None, default_evaluations(), default_bids(), ); @@ -613,7 +627,7 @@ mod community_contribute_extrinsic { default_multipliers(), ); let project_id = - inst.create_community_contributing_project(project_metadata.clone(), ISSUER_1, evaluations, bids); + inst.create_community_contributing_project(project_metadata.clone(), ISSUER_1, None, evaluations, bids); // Professional contributions: 0x multiplier should fail assert_err!( @@ -672,6 +686,7 @@ mod community_contribute_extrinsic { inst.create_community_contributing_project( default_project_metadata(issuer), issuer, + None, default_evaluations(), default_bids(), ) @@ -740,7 +755,8 @@ mod community_contribute_extrinsic { .cloned() .collect_vec(); - let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, default_evaluations()); + 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(), @@ -852,6 +868,7 @@ mod community_contribute_extrinsic { let project_id = inst.create_community_contributing_project( project_metadata.clone(), issuer, + None, default_evaluations(), vec![], ); @@ -896,7 +913,7 @@ mod community_contribute_extrinsic { }); inst.start_remainder_or_end_funding(project_id).unwrap(); - inst.finish_funding(project_id).unwrap(); + inst.finish_funding(project_id, None).unwrap(); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); @@ -935,6 +952,7 @@ mod community_contribute_extrinsic { let project_id = inst.create_community_contributing_project( project_metadata.clone(), issuer, + None, default_evaluations(), default_bids(), ); @@ -979,7 +997,7 @@ mod community_contribute_extrinsic { }); inst.start_remainder_or_end_funding(project_id).unwrap(); - inst.finish_funding(project_id).unwrap(); + inst.finish_funding(project_id, None).unwrap(); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AwaitingProjectDecision); inst.execute(|| { @@ -1059,6 +1077,7 @@ mod community_contribute_extrinsic { let project_id = inst.create_community_contributing_project( default_project_metadata(ISSUER_1), ISSUER_1, + None, default_evaluations(), default_bids(), ); @@ -1128,6 +1147,7 @@ mod community_contribute_extrinsic { let project_id = inst.create_community_contributing_project( project_metadata.clone(), ISSUER_1, + None, default_evaluations(), default_bids(), ); @@ -1162,6 +1182,7 @@ mod community_contribute_extrinsic { let project_id = inst.create_community_contributing_project( project_metadata.clone(), ISSUER_1, + None, default_evaluations(), bids, ); @@ -1227,6 +1248,7 @@ mod community_contribute_extrinsic { let project_id = inst.create_community_contributing_project( project_metadata.clone(), ISSUER_1, + None, default_evaluations(), default_bids(), ); @@ -1320,6 +1342,7 @@ mod community_contribute_extrinsic { let project_id = inst.create_community_contributing_project( project_metadata.clone(), ISSUER_1, + None, default_evaluations(), default_bids(), ); @@ -1494,6 +1517,7 @@ mod community_contribute_extrinsic { let project_id = inst.create_community_contributing_project( project_metadata.clone(), ISSUER_1, + None, default_evaluations(), default_bids(), ); @@ -1569,13 +1593,18 @@ mod community_contribute_extrinsic { #[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); - let evaluating_project = inst.create_evaluating_project(default_project_metadata(ISSUER_2), ISSUER_2); - let auctioning_project = - inst.create_auctioning_project(default_project_metadata(ISSUER_3), ISSUER_3, default_evaluations()); + 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 remaining_project = inst.create_remainder_contributing_project( default_project_metadata(ISSUER_4), ISSUER_4, + None, default_evaluations(), default_bids(), default_community_buys(), @@ -1583,6 +1612,7 @@ mod community_contribute_extrinsic { let finished_project = inst.create_finished_project( default_project_metadata(ISSUER_5), ISSUER_5, + None, default_evaluations(), default_bids(), default_community_buys(), @@ -1649,6 +1679,7 @@ mod community_contribute_extrinsic { let project_id_usdc = inst.create_community_contributing_project( project_metadata_usdc, ISSUER_2, + None, evaluations.clone(), usdc_bids.clone(), ); @@ -1660,6 +1691,7 @@ mod community_contribute_extrinsic { let project_id_usdt = inst.create_community_contributing_project( project_metadata_usdt, ISSUER_3, + None, evaluations.clone(), usdt_bids.clone(), ); @@ -1691,12 +1723,14 @@ mod community_contribute_extrinsic { 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(), ); @@ -1746,6 +1780,7 @@ mod community_contribute_extrinsic { let project_id = inst.create_community_contributing_project( project_metadata.clone(), ISSUER_1, + None, default_evaluations(), default_bids(), ); diff --git a/pallets/funding/src/tests/5_remainder.rs b/pallets/funding/src/tests/5_remainder.rs index 62880486b..ed4769f03 100644 --- a/pallets/funding/src/tests/5_remainder.rs +++ b/pallets/funding/src/tests/5_remainder.rs @@ -18,6 +18,7 @@ mod round_flow { let _ = inst.create_finished_project( default_project_metadata(ISSUER_1), ISSUER_1, + None, default_evaluations(), default_bids(), default_community_buys(), @@ -31,6 +32,7 @@ mod round_flow { let project_id = inst.create_remainder_contributing_project( default_project_metadata(ISSUER_1), ISSUER_1, + None, default_evaluations(), default_bids(), default_community_buys(), @@ -88,6 +90,7 @@ mod round_flow { let project_id = inst.create_remainder_contributing_project( project_metadata.clone(), ISSUER_1, + None, evaluations.clone(), bids.clone(), vec![], @@ -210,6 +213,7 @@ mod round_flow { let project_id = inst.create_remainder_contributing_project( project_metadata.clone(), issuer, + None, evaluations, vec![], vec![], @@ -260,7 +264,7 @@ mod round_flow { assert_eq!(inst.get_project_details(project_id).remaining_contribution_tokens, 0); // We can successfully finish the project - inst.finish_funding(project_id).unwrap(); + inst.finish_funding(project_id, None).unwrap(); }; for decimals in 6..=18 { @@ -308,6 +312,7 @@ mod remaining_contribute_extrinsic { let project_id = inst.create_remainder_contributing_project( project_metadata.clone(), ISSUER_1, + None, evaluations, default_bids(), vec![], @@ -406,7 +411,8 @@ mod remaining_contribute_extrinsic { let bob_bid: BidParams = (bob, 1337 * CT_UNIT).into(); let all_bids = bids.iter().chain(vec![bob_bid.clone()].iter()).cloned().collect_vec(); - let project_id = inst.create_auctioning_project(default_project_metadata(ISSUER_2), ISSUER_2, evaluations); + let project_id = + inst.create_auctioning_project(default_project_metadata(ISSUER_2), ISSUER_2, None, evaluations); let evaluation_plmc_bond = inst.execute(|| Balances::balance_on_hold(&HoldReason::Evaluation(project_id).into(), &bob)); @@ -524,6 +530,7 @@ mod remaining_contribute_extrinsic { let project_id_all = inst.create_remainder_contributing_project( project_metadata_all.clone(), ISSUER_1, + None, evaluations.clone(), default_bids(), vec![], @@ -558,6 +565,7 @@ mod remaining_contribute_extrinsic { let project_id_usdt = inst.create_remainder_contributing_project( project_metadata_usdt.clone(), ISSUER_2, + None, evaluations.clone(), usdt_bids, vec![], @@ -576,6 +584,7 @@ mod remaining_contribute_extrinsic { let project_id_usdc = inst.create_remainder_contributing_project( project_metadata_usdc.clone(), ISSUER_3, + None, evaluations.clone(), usdc_bids, vec![], @@ -594,6 +603,7 @@ mod remaining_contribute_extrinsic { let project_id_dot = inst.create_remainder_contributing_project( project_metadata_dot.clone(), ISSUER_4, + None, evaluations.clone(), dot_bids, vec![], @@ -685,6 +695,7 @@ mod remaining_contribute_extrinsic { let project_id = inst.create_remainder_contributing_project( project_metadata.clone(), ISSUER_1, + None, evaluations, bids, vec![], @@ -747,6 +758,7 @@ mod remaining_contribute_extrinsic { inst.create_remainder_contributing_project( default_project_metadata(issuer), issuer, + None, default_evaluations(), default_bids(), vec![], @@ -816,7 +828,8 @@ mod remaining_contribute_extrinsic { .cloned() .collect_vec(); - let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, default_evaluations()); + 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(), @@ -930,6 +943,7 @@ mod remaining_contribute_extrinsic { let project_id = inst.create_remainder_contributing_project( project_metadata.clone(), issuer, + None, default_evaluations(), vec![], vec![], @@ -974,7 +988,7 @@ mod remaining_contribute_extrinsic { )); }); - inst.finish_funding(project_id).unwrap(); + inst.finish_funding(project_id, None).unwrap(); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); @@ -1030,6 +1044,7 @@ mod remaining_contribute_extrinsic { let project_id = inst.create_remainder_contributing_project( project_metadata.clone(), issuer, + None, default_evaluations(), default_bids(), vec![], @@ -1074,7 +1089,7 @@ mod remaining_contribute_extrinsic { )); }); - inst.finish_funding(project_id).unwrap(); + inst.finish_funding(project_id, None).unwrap(); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AwaitingProjectDecision); inst.execute(|| { @@ -1188,6 +1203,7 @@ mod remaining_contribute_extrinsic { let _project_id = inst.create_finished_project( project_metadata.clone(), issuer, + None, evaluations, bids, community_contributions, @@ -1211,6 +1227,7 @@ mod remaining_contribute_extrinsic { let project_id = inst.create_remainder_contributing_project( default_project_metadata(ISSUER_1), ISSUER_1, + None, default_evaluations(), default_bids(), vec![], @@ -1281,6 +1298,7 @@ mod remaining_contribute_extrinsic { let project_id = inst.create_remainder_contributing_project( project_metadata.clone(), ISSUER_1, + None, default_evaluations(), default_bids(), default_community_buys(), @@ -1333,6 +1351,7 @@ mod remaining_contribute_extrinsic { let project_id = inst.create_remainder_contributing_project( project_metadata.clone(), ISSUER_1, + None, default_evaluations(), default_bids(), vec![], @@ -1446,6 +1465,7 @@ mod remaining_contribute_extrinsic { let project_id = inst.create_remainder_contributing_project( project_metadata.clone(), ISSUER_1, + None, default_evaluations(), default_bids(), vec![], @@ -1609,6 +1629,7 @@ mod remaining_contribute_extrinsic { let project_id = inst.create_remainder_contributing_project( project_metadata.clone(), ISSUER_1, + None, default_evaluations(), default_bids(), vec![], @@ -1689,6 +1710,7 @@ mod remaining_contribute_extrinsic { let project_id = inst.create_community_contributing_project( project_metadata.clone(), ISSUER_4, + None, default_evaluations(), default_bids(), ); @@ -1748,6 +1770,7 @@ mod remaining_contribute_extrinsic { let project_id_usdc = inst.create_remainder_contributing_project( project_metadata_usdc, ISSUER_3, + None, evaluations.clone(), usdc_bids, vec![], @@ -1760,6 +1783,7 @@ mod remaining_contribute_extrinsic { let project_id_usdt = inst.create_remainder_contributing_project( project_metadata_usdt, ISSUER_2, + None, evaluations.clone(), usdt_bids, vec![], @@ -1792,6 +1816,7 @@ mod remaining_contribute_extrinsic { let _project_id_1 = inst.create_remainder_contributing_project( project_metadata_1.clone(), ISSUER_1, + None, evaluations_1, default_bids(), vec![], @@ -1799,6 +1824,7 @@ mod remaining_contribute_extrinsic { let project_id_2 = inst.create_remainder_contributing_project( project_metadata_2.clone(), ISSUER_2, + None, evaluations_2, default_bids(), vec![], @@ -1849,6 +1875,7 @@ mod remaining_contribute_extrinsic { let project_id = inst.create_remainder_contributing_project( project_metadata.clone(), ISSUER_1, + None, default_evaluations(), default_bids(), vec![], diff --git a/pallets/funding/src/tests/6_funding_end.rs b/pallets/funding/src/tests/6_funding_end.rs index f76d94ba5..11df4b587 100644 --- a/pallets/funding/src/tests/6_funding_end.rs +++ b/pallets/funding/src/tests/6_funding_end.rs @@ -78,6 +78,37 @@ mod round_flow { EvaluatorsOutcome::Rewarded(expected_reward_info) ); } + + #[test] + 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 bucket_size = Percent::from_percent(10) * auction_allocation; + let bids = vec![ + (BIDDER_1, auction_allocation).into(), + (BIDDER_2, bucket_size).into(), + (BIDDER_3, bucket_size).into(), + (BIDDER_4, bucket_size).into(), + (BIDDER_5, bucket_size).into(), + (BIDDER_6, bucket_size).into(), + ]; + + let project_id = inst.create_finished_project( + project_metadata.clone(), + ISSUER_1, + None, + default_evaluations(), + bids, + default_community_buys(), + default_remainder_buys(), + ); + + let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); + dbg!(wap); + assert!(wap > project_metadata.minimum_price); + } } } @@ -139,8 +170,15 @@ mod decide_project_outcome { default_community_contributors(), default_multipliers(), ); - let project_id = - inst.create_finished_project(project_metadata, ISSUER_1, evaluations, bids, contributions, vec![]); + let project_id = inst.create_finished_project( + project_metadata, + ISSUER_1, + None, + evaluations, + bids, + contributions, + vec![], + ); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AwaitingProjectDecision); inst.advance_time(1u64 + ::ManualAcceptanceDuration::get()).unwrap(); @@ -190,6 +228,7 @@ mod decide_project_outcome { let project_id = inst.create_finished_project( project_metadata.clone(), ISSUER_1, + None, evaluations, bids, contributions, @@ -281,24 +320,25 @@ mod decide_project_outcome { }; // Application - let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1); + let project_id = inst.create_new_project(project_metadata.clone(), ISSUER_1, None); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::Application); call_fails(project_id, ISSUER_1, &mut inst); // Evaluation - let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_2); + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_2, None); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::EvaluationRound); call_fails(project_id, ISSUER_2, &mut inst); // EvaluationFailed - let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_3); + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_3, None); let transition_block = inst.get_update_block(project_id, &UpdateType::EvaluationEnd).unwrap(); inst.jump_to_block(transition_block); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); call_fails(project_id, ISSUER_3, &mut inst); // Auction - let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_4, default_evaluations()); + let project_id = + inst.create_auctioning_project(project_metadata.clone(), ISSUER_4, None, default_evaluations()); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::AuctionOpening); call_fails(project_id, ISSUER_4, &mut inst); @@ -306,6 +346,7 @@ mod decide_project_outcome { let project_id = inst.create_community_contributing_project( project_metadata.clone(), ISSUER_5, + None, default_evaluations(), default_bids(), ); @@ -316,6 +357,7 @@ mod decide_project_outcome { let project_id = inst.create_remainder_contributing_project( project_metadata.clone(), ISSUER_6, + None, default_evaluations(), default_bids(), vec![], @@ -327,6 +369,7 @@ mod decide_project_outcome { let project_id = inst.create_finished_project( project_metadata.clone(), ISSUER_7, + None, default_evaluations(), default_bids(), default_community_buys(), @@ -339,6 +382,7 @@ mod decide_project_outcome { let project_id = inst.create_finished_project( project_metadata.clone(), ISSUER_8, + None, default_evaluations(), vec![default_bids()[1].clone()], vec![], diff --git a/pallets/funding/src/tests/7_settlement.rs b/pallets/funding/src/tests/7_settlement.rs index ab957065f..66a8afd60 100644 --- a/pallets/funding/src/tests/7_settlement.rs +++ b/pallets/funding/src/tests/7_settlement.rs @@ -114,6 +114,7 @@ mod settle_successful_evaluation_extrinsic { let project_id = inst.create_finished_project( project_metadata.clone(), ISSUER_1, + None, vec![ UserToUSDBalance::new(EVALUATOR_1, 500_000 * USD_UNIT), UserToUSDBalance::new(EVALUATOR_2, 250_000 * USD_UNIT), @@ -323,6 +324,7 @@ mod settle_successful_bid_extrinsic { let project_id = inst.create_finished_project( project_metadata.clone(), issuer, + None, evaluations, vec![bid_1, bid_2], community_contributions, @@ -492,7 +494,7 @@ mod settle_successful_bid_extrinsic { bids.extend(rejected_bid.clone()); bids.extend(accepted_bid.clone()); - let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, evaluations); + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); // Mint the necessary bidding balances let bidders_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( @@ -531,7 +533,7 @@ mod settle_successful_bid_extrinsic { // Finish and Settle project inst.start_remainder_or_end_funding(project_id).unwrap(); - inst.finish_funding(project_id).unwrap(); + inst.finish_funding(project_id, None).unwrap(); inst.advance_time(::SuccessToSettlementTime::get()).unwrap(); inst.settle_project(project_id).unwrap(); @@ -675,6 +677,7 @@ mod settle_successful_contribution_extrinsic { let project_id = inst.create_remainder_contributing_project( project_metadata.clone(), issuer, + None, evaluations, bids, community_contributions, @@ -705,7 +708,7 @@ mod settle_successful_contribution_extrinsic { )); }); - inst.finish_funding(project_id).unwrap(); + inst.finish_funding(project_id, None).unwrap(); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingSuccessful); let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); inst.jump_to_block(settlement_block); @@ -1093,6 +1096,7 @@ mod settle_failed_bid_extrinsic { let project_id = inst.create_finished_project( project_metadata.clone(), issuer, + None, evaluations, vec![bid_1, bid_2], community_contributions, @@ -1306,6 +1310,7 @@ mod settle_failed_contribution_extrinsic { let project_id = inst.create_remainder_contributing_project( project_metadata.clone(), issuer, + None, evaluations, bids, community_contributions, @@ -1336,7 +1341,7 @@ mod settle_failed_contribution_extrinsic { )); }); - inst.finish_funding(project_id).unwrap(); + inst.finish_funding(project_id, None).unwrap(); assert_eq!(inst.get_project_details(project_id).status, ProjectStatus::FundingFailed); let settlement_block = inst.get_update_block(project_id, &UpdateType::StartSettlement).unwrap(); inst.jump_to_block(settlement_block); diff --git a/pallets/funding/src/tests/8_ct_migration.rs b/pallets/funding/src/tests/8_ct_migration.rs index 1f7da6995..bf9349de8 100644 --- a/pallets/funding/src/tests/8_ct_migration.rs +++ b/pallets/funding/src/tests/8_ct_migration.rs @@ -12,6 +12,7 @@ mod pallet_migration { let project_id = inst.create_finished_project( default_project_metadata(ISSUER_1), ISSUER_1, + None, default_evaluations(), default_bids(), default_community_buys(), @@ -64,6 +65,7 @@ mod pallet_migration { let project_id = inst.create_finished_project( default_project_metadata(ISSUER_1), ISSUER_1, + None, default_evaluations(), default_bids(), default_community_buys(), @@ -193,6 +195,7 @@ mod offchain_migration { let project_id = inst.create_finished_project( default_project_metadata(ISSUER_1), ISSUER_1, + None, default_evaluations(), default_bids(), default_community_buys(), @@ -204,6 +207,7 @@ mod offchain_migration { let project_id = inst.create_finished_project( default_project_metadata(ISSUER_1), ISSUER_1, + None, default_evaluations(), default_bids(), default_community_buys(), @@ -230,6 +234,7 @@ mod offchain_migration { let project_id = inst.create_finished_project( default_project_metadata(ISSUER_1), ISSUER_1, + None, default_evaluations(), default_bids(), default_community_buys(), diff --git a/pallets/funding/src/tests/misc.rs b/pallets/funding/src/tests/misc.rs index 31845a100..a313f112d 100644 --- a/pallets/funding/src/tests/misc.rs +++ b/pallets/funding/src/tests/misc.rs @@ -194,6 +194,7 @@ mod helper_functions { let project_id = inst.create_community_contributing_project( project_metadata.clone(), ISSUER_1, + None, default_evaluations(), bids.clone(), ); diff --git a/pallets/funding/src/tests/mod.rs b/pallets/funding/src/tests/mod.rs index 90c2f5777..2913b986f 100644 --- a/pallets/funding/src/tests/mod.rs +++ b/pallets/funding/src/tests/mod.rs @@ -21,11 +21,11 @@ use sp_arithmetic::{traits::Zero, Percent, Perquintill}; use sp_runtime::TokenError; use sp_std::cell::RefCell; use std::iter::zip; -type MockInstantiator = +pub type MockInstantiator = Instantiator::AllPalletsWithoutSystem, RuntimeEvent>; -const CT_DECIMALS: u8 = 15; -const CT_UNIT: u128 = 10_u128.pow(CT_DECIMALS as u32); -const USDT_UNIT: u128 = USD_UNIT; +pub const CT_DECIMALS: u8 = 15; +pub const CT_UNIT: u128 = 10_u128.pow(CT_DECIMALS as u32); +pub const USDT_UNIT: u128 = USD_UNIT; const IPFS_CID: &str = "QmeuJ24ffwLAZppQcgcggJs3n689bewednYkuc8Bx5Gngz"; const ISSUER_1: AccountId = 11; @@ -72,6 +72,7 @@ mod funding_end; mod misc; #[path = "5_remainder.rs"] mod remainder; +mod runtime_api; #[path = "7_settlement.rs"] mod settlement; @@ -325,7 +326,7 @@ pub mod defaults { default_community_contributors(), default_multipliers(), ); - instantiator.create_finished_project(project_metadata, ISSUER_1, evaluations, bids, contributions, vec![]) + instantiator.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids, contributions, vec![]) } pub fn default_bids_from_ct_percent(percent: u8) -> Vec> { @@ -393,7 +394,8 @@ pub fn create_project_with_funding_percentage( default_community_contributors(), default_multipliers(), ); - let project_id = inst.create_finished_project(project_metadata, ISSUER_1, evaluations, bids, contributions, vec![]); + let project_id = + inst.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids, contributions, vec![]); match inst.get_project_details(project_id).status { ProjectStatus::AwaitingProjectDecision => { @@ -443,3 +445,71 @@ pub fn create_project_with_funding_percentage( (inst, project_id) } + +pub fn create_finished_project_with_usd_raised( + mut inst: MockInstantiator, + usd_raised: BalanceOf, + usd_target: BalanceOf, +) -> (MockInstantiator, ProjectId) { + let issuer = inst.get_new_nonce() as u32; + 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); + + dbg!(project_metadata.minimum_price); + 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 + }; + dbg!(required_price); + + let evaluations = default_evaluations(); + + let bids = inst.generate_bids_that_take_price_to(project_metadata.clone(), required_price, 420, |acc| acc + 1u32); + + let project_id = inst.create_community_contributing_project(project_metadata, issuer, None, evaluations, bids); + + let project_details = inst.get_project_details(project_id); + let wap = project_details.weighted_average_price.unwrap(); + dbg!(wap); + + 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_multipliers(), + ); + let plmc_required = inst.calculate_contributed_plmc_spent(community_contributions.clone(), required_price, true); + let usdt_required = inst.calculate_contributed_funding_asset_spent(community_contributions.clone(), required_price); + inst.mint_plmc_to(plmc_required); + inst.mint_foreign_asset_to(usdt_required); + inst.contribute_for_users(project_id, community_contributions).unwrap(); + inst.start_remainder_or_end_funding(project_id).unwrap(); + if inst.get_project_details(project_id).status == ProjectStatus::RemainderRound { + inst.finish_funding(project_id, Some(FundingOutcomeDecision::AcceptFunding)).unwrap(); + } + + let project_details = inst.get_project_details(project_id); + dbg!(project_details.remaining_contribution_tokens); + assert_eq!(project_details.status, ProjectStatus::FundingSuccessful); + // 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); + + (inst, project_id) +} diff --git a/pallets/funding/src/tests/runtime_api.rs b/pallets/funding/src/tests/runtime_api.rs new file mode 100644 index 000000000..fdf277fed --- /dev/null +++ b/pallets/funding/src/tests/runtime_api.rs @@ -0,0 +1,707 @@ +use super::*; +use crate::runtime_api::{ExtrinsicHelpers, Leaderboards, ProjectInformation, UserInformation}; + +#[test] +fn top_evaluations +() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let evaluations = vec![ + UserToUSDBalance::new(EVALUATOR_1, 500_000 * USD_UNIT), + UserToUSDBalance::new(EVALUATOR_2, 250_000 * USD_UNIT), + UserToUSDBalance::new(EVALUATOR_3, 320_000 * USD_UNIT), + UserToUSDBalance::new(EVALUATOR_4, 1_000_000 * USD_UNIT), + UserToUSDBalance::new(EVALUATOR_1, 1_000 * USD_UNIT), + ]; + let project_id = inst.create_auctioning_project(default_project_metadata(ISSUER_1), ISSUER_1, None, evaluations); + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let top_1 = TestRuntime::top_evaluations +(&TestRuntime, block_hash, project_id, 1).unwrap(); + let evaluator_4_evaluation = Evaluations::::get((project_id, EVALUATOR_4, 3)).unwrap(); + assert!(top_1.len() == 1 && top_1[0] == evaluator_4_evaluation); + + let top_4_evaluators = TestRuntime::top_evaluations +(&TestRuntime, block_hash, project_id, 4) + .unwrap() + .into_iter() + .map(|evaluation| evaluation.evaluator) + .collect_vec(); + assert_eq!(top_4_evaluators, vec![EVALUATOR_4, EVALUATOR_1, EVALUATOR_3, EVALUATOR_2]); + + let top_6_evaluators = TestRuntime::top_evaluations +(&TestRuntime, block_hash, project_id, 6) + .unwrap() + .into_iter() + .map(|evaluation| evaluation.evaluator) + .collect_vec(); + assert_eq!(top_6_evaluators, vec![EVALUATOR_4, EVALUATOR_1, EVALUATOR_3, EVALUATOR_2, EVALUATOR_1]); + }); +} + +#[test] +fn top_bids +() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let bids = vec![ + (BIDDER_1, 8000 * CT_UNIT).into(), + (BIDDER_2, 501 * CT_UNIT).into(), + (BIDDER_3, 1200 * CT_UNIT).into(), + (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, + ); + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let top_1 = TestRuntime::top_bids +(&TestRuntime, block_hash, project_id, 1).unwrap(); + let bidder_4_evaluation = Bids::::get((project_id, BIDDER_4, 3)).unwrap(); + assert!(top_1.len() == 1 && top_1[0] == bidder_4_evaluation); + + let top_4_bidders = TestRuntime::top_bids +(&TestRuntime, block_hash, project_id, 4) + .unwrap() + .into_iter() + .map(|evaluation| evaluation.bidder) + .collect_vec(); + assert_eq!(top_4_bidders, vec![BIDDER_4, BIDDER_1, BIDDER_3, BIDDER_2]); + + let top_6_bidders = TestRuntime::top_bids +(&TestRuntime, block_hash, project_id, 6) + .unwrap() + .into_iter() + .map(|evaluation| evaluation.bidder) + .collect_vec(); + assert_eq!(top_6_bidders, vec![BIDDER_4, BIDDER_1, BIDDER_3, BIDDER_2, BIDDER_1]); + }); +} + +#[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()))); + + let (inst, project_id_1) = create_finished_project_with_usd_raised(inst, 400_000 * USD_UNIT, 1_000_000 * USD_UNIT); + let (inst, project_id_2) = + create_finished_project_with_usd_raised(inst, 1_200_000 * USD_UNIT, 1_000_000 * USD_UNIT); + let (inst, project_id_3) = + create_finished_project_with_usd_raised(inst, 3_000_000 * USD_UNIT, 1_000_000 * USD_UNIT); + let (inst, project_id_4) = create_finished_project_with_usd_raised(inst, 840_000 * USD_UNIT, 1_000_000 * USD_UNIT); + let (mut inst, project_id_5) = + create_finished_project_with_usd_raised(inst, 980_000 * USD_UNIT, 1_000_000 * USD_UNIT); + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let top_1 = TestRuntime::top_projects_by_usd_raised(&TestRuntime, block_hash, 1u32).unwrap(); + let project_3_details = ProjectsDetails::::get(project_id_3).unwrap(); + let project_3_metadata = ProjectsMetadata::::get(project_id_3).unwrap(); + assert!(top_1.len() == 1 && top_1[0] == (project_id_3, project_3_metadata, project_3_details)); + + let top_4 = TestRuntime::top_projects_by_usd_raised(&TestRuntime, block_hash, 4u32) + .unwrap() + .into_iter() + .map(|(project_id, project_metadata, project_details)| { + let stored_metadata = ProjectsMetadata::::get(project_id).unwrap(); + let stored_details = ProjectsDetails::::get(project_id).unwrap(); + assert!(project_metadata == stored_metadata && project_details == stored_details); + project_id + }) + .collect_vec(); + + assert_eq!(top_4, vec![project_id_3, project_id_2, project_id_5, project_id_4]); + + let top_6 = TestRuntime::top_projects_by_usd_raised(&TestRuntime, block_hash, 6u32) + .unwrap() + .into_iter() + .map(|(project_id, project_metadata, project_details)| { + let stored_metadata = ProjectsMetadata::::get(project_id).unwrap(); + let stored_details = ProjectsDetails::::get(project_id).unwrap(); + assert!(project_metadata == stored_metadata && project_details == stored_details); + project_id + }) + .collect_vec(); + + assert_eq!(top_6, vec![project_id_3, project_id_2, project_id_5, project_id_4, project_id_1]); + }); +} + +#[test] +fn top_projects_by_usd_target_percent_reached() { + let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let (inst, project_id_1) = + create_finished_project_with_usd_raised(inst, 2_000_000 * USD_UNIT, 1_000_000 * USD_UNIT); + let (inst, project_id_2) = create_finished_project_with_usd_raised(inst, 945_000 * USD_UNIT, 1_000_000 * USD_UNIT); + let (inst, project_id_3) = create_finished_project_with_usd_raised(inst, 500_000 * USD_UNIT, 100_000 * USD_UNIT); + + let (mut inst, project_id_4) = create_finished_project_with_usd_raised(inst, 50_000 * USD_UNIT, 100_000 * USD_UNIT); + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let top_1 = TestRuntime::top_projects_by_usd_target_percent_reached(&TestRuntime, block_hash, 1u32).unwrap(); + let project_3_details = ProjectsDetails::::get(project_id_3).unwrap(); + let project_3_metadata = ProjectsMetadata::::get(project_id_3).unwrap(); + assert!(top_1.len() == 1 && top_1[0] == (project_id_3, project_3_metadata, project_3_details)); + + let top_3 = TestRuntime::top_projects_by_usd_target_percent_reached(&TestRuntime, block_hash, 3u32) + .unwrap() + .into_iter() + .map(|(project_id, project_metadata, project_details)| { + let stored_metadata = ProjectsMetadata::::get(project_id).unwrap(); + let stored_details = ProjectsDetails::::get(project_id).unwrap(); + assert!(project_metadata == stored_metadata && project_details == stored_details); + project_id + }) + .collect_vec(); + + assert_eq!(top_3, vec![project_id_3, project_id_1, project_id_2]); + + let top_6 = TestRuntime::top_projects_by_usd_target_percent_reached(&TestRuntime, block_hash, 6u32) + .unwrap() + .into_iter() + .map(|(project_id, project_metadata, project_details)| { + let stored_metadata = ProjectsMetadata::::get(project_id).unwrap(); + let stored_details = ProjectsDetails::::get(project_id).unwrap(); + assert!(project_metadata == stored_metadata && project_details == stored_details); + project_id + }) + .collect_vec(); + + assert_eq!(top_6, vec![project_id_3, project_id_1, project_id_2, project_id_4]); + }); +} + +#[test] +fn contribution_tokens() { + let bob = 420; + let mut contributions_with_bob_1 = default_community_buys(); + 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_buys(); + 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_buys(); + 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_buys(); + 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_buys(), + ); + let _project_id_2 = inst.create_settled_project( + default_project_metadata(ISSUER_2), + ISSUER_2, + None, + default_evaluations(), + default_bids(), + default_community_buys(), + default_remainder_buys(), + ); + let _project_id_3 = inst.create_settled_project( + default_project_metadata(ISSUER_3), + ISSUER_3, + None, + default_evaluations(), + default_bids(), + default_community_buys(), + default_remainder_buys(), + ); + 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_buys(), + ); + let _project_id_5 = inst.create_settled_project( + default_project_metadata(ISSUER_5), + ISSUER_5, + None, + default_evaluations(), + default_bids(), + default_community_buys(), + default_remainder_buys(), + ); + 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_buys(), + ); + 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_buys(), + ); + + let expected_items = vec![ + (project_id_4, bob_amount_2), + (project_id_1, bob_amount_1), + (project_id_6, bob_amount_3), + (project_id_7, bob_amount_4), + ]; + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let bob_items = TestRuntime::contribution_tokens(&TestRuntime, block_hash, bob.clone()).unwrap(); + assert_eq!(bob_items, expected_items); + }); +} + +#[test] +fn funding_asset_to_ct_amount() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + + // We want to use a funding asset that is not equal to 1 USD + // Sanity check + assert_eq!( + PriceProviderOf::::get_price(AcceptedFundingAsset::DOT.to_assethub_id()).unwrap(), + PriceOf::::from_float(69.0f64) + ); + + let dot_amount: u128 = 1350_0_000_000_000; + // USD Ticket = 93_150 USD + + // 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 wap = project_metadata_1.minimum_price; + assert_eq!(inst.get_project_details(project_id_1).weighted_average_price.unwrap(), wap); + + // Price of ct is min price = 10 USD/CT + let expected_ct_amount_contribution = 9_315 * CT_UNIT; + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let ct_amount = TestRuntime::funding_asset_to_ct_amount( + &TestRuntime, + block_hash, + project_id_1, + AcceptedFundingAsset::DOT, + dot_amount, + ) + .unwrap(); + assert_eq!(ct_amount, expected_ct_amount_contribution); + }); + + // Medium case, contribution at a wap that is not the minimum price. + let project_metadata_2 = default_project_metadata(ISSUER_2); + let new_price = PriceOf::::from_float(16.3f64); + let decimal_aware_price = + PriceProviderOf::::calculate_decimals_aware_price(new_price, USD_DECIMALS, CT_DECIMALS).unwrap(); + + let bids = + inst.generate_bids_that_take_price_to(project_metadata_2.clone(), decimal_aware_price, 420u32, |acc| acc + 1); + let project_id_2 = inst.create_community_contributing_project( + project_metadata_2.clone(), + ISSUER_2, + None, + default_evaluations(), + bids, + ); + // Sanity check + let project_details = inst.get_project_details(project_id_2); + assert_eq!(project_details.weighted_average_price.unwrap(), decimal_aware_price); + + // 5'714.72... rounded down + let expected_ct_amount_contribution = 5_714_720_000_000_000_000; + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let ct_amount = TestRuntime::funding_asset_to_ct_amount( + &TestRuntime, + block_hash, + project_id_2, + AcceptedFundingAsset::DOT, + dot_amount, + ) + .unwrap(); + assert_close_enough!(ct_amount, expected_ct_amount_contribution, Perquintill::from_float(0.999f64)); + }); + + // 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 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) + // Price should be at 16 USD/CT + bucket.current_price = bucket.initial_price + bucket.delta_price * FixedU128::from_float(6.0f64); + bucket.amount_left = bucket.delta_amount; + let bids = inst.generate_bids_from_bucket( + project_metadata_3.clone(), + bucket, + 420u32, + |acc| acc + 1, + AcceptedFundingAsset::USDT, + ); + let necessary_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + &bids, + project_metadata_3.clone(), + None, + true, + ); + let necessary_usdt = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( + &bids, + project_metadata_3.clone(), + None, + ); + inst.mint_plmc_to(necessary_plmc); + inst.mint_foreign_asset_to(necessary_usdt); + inst.bid_for_users(project_id_3, bids).unwrap(); + + // Sanity check + let expected_price = PriceOf::::from_float(16.0f64); + let decimal_aware_expected_price = + PriceProviderOf::::calculate_decimals_aware_price(expected_price, USD_DECIMALS, CT_DECIMALS) + .unwrap(); + let current_bucket = inst.execute(|| Buckets::::get(project_id_3).unwrap()); + assert_eq!(current_bucket.current_price, decimal_aware_expected_price); + + dbg!(current_bucket.current_price.saturating_mul_int(current_bucket.amount_left)); + + let dot_amount: u128 = 217_0_000_000_000; + let expected_ct_amount: u128 = 935_812_500_000_000_000; + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let ct_amount = TestRuntime::funding_asset_to_ct_amount( + &TestRuntime, + block_hash, + project_id_3, + AcceptedFundingAsset::DOT, + dot_amount, + ) + .unwrap(); + assert_eq!(ct_amount, expected_ct_amount); + }); + + // Hard case, a bid goes over multiple buckets + // We take the same project from before, and we add a bid that goes over 3 buckets. + // Bucket size is 50k CTs, and current price is 16 USD/CT + // We need to buy 50k at 16 , 50k at 17, and 13.5k at 18 = 1893k USD + + // Amount needed to spend 1893k USD through several buckets with DOT at 69 USD/DOT + let dot_amount = 27_434_7_826_086_956u128; + let expected_ct_amount = 113_500 * CT_UNIT; + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let ct_amount = TestRuntime::funding_asset_to_ct_amount( + &TestRuntime, + block_hash, + project_id_3, + AcceptedFundingAsset::DOT, + dot_amount, + ) + .unwrap(); + assert_close_enough!(ct_amount, expected_ct_amount, Perquintill::from_float(0.9999)); + }); +} + +#[test] +fn all_project_participations_by_did() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + + let did_user = generate_did_from_account(420); + let project_metadata = default_project_metadata(ISSUER_1); + let cid = project_metadata.clone().policy_ipfs_cid.unwrap(); + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); + + let evaluations = vec![ + UserToUSDBalance::new(EVALUATOR_1, 500_000 * USD_UNIT), + UserToUSDBalance::new(EVALUATOR_2, 250_000 * USD_UNIT), + UserToUSDBalance::new(EVALUATOR_3, 320_000 * USD_UNIT), + ]; + let bids = vec![ + BidParams::new(BIDDER_1, 400_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_2, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ]; + let community_contributions = vec![ + ContributionParams::new(BUYER_1, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_2, 130_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_3, 30_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_4, 210_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_5, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ]; + let remainder_contributions = vec![ + ContributionParams::new(EVALUATOR_2, 20_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_2, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BIDDER_1, 30_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ]; + + let evaluations_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); + let bids_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + &bids, + project_metadata.clone(), + None, + true, + ); + let community_contributions_plmc = inst.calculate_contributed_plmc_spent( + community_contributions.clone(), + project_metadata.minimum_price.clone(), + true, + ); + let remainder_contributions_plmc = inst.calculate_contributed_plmc_spent( + remainder_contributions.clone(), + project_metadata.minimum_price.clone(), + true, + ); + let all_plmc = inst.generic_map_operation( + vec![evaluations_plmc, bids_plmc, community_contributions_plmc, remainder_contributions_plmc], + MergeOperation::Add, + ); + inst.mint_plmc_to(all_plmc); + + let bids_usdt = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( + &bids, + project_metadata.clone(), + None, + ); + let community_contributions_usdt = inst.calculate_contributed_funding_asset_spent( + community_contributions.clone(), + project_metadata.minimum_price.clone(), + ); + let remainder_contributions_usdt = inst.calculate_contributed_funding_asset_spent( + remainder_contributions.clone(), + project_metadata.minimum_price.clone(), + ); + let all_usdt = inst.generic_map_operation( + vec![bids_usdt, community_contributions_usdt, remainder_contributions_usdt], + MergeOperation::Add, + ); + inst.mint_foreign_asset_to(all_usdt); + + inst.evaluate_for_users(project_id, evaluations[..1].to_vec()).unwrap(); + for evaluation in evaluations[1..].to_vec() { + let jwt = get_mock_jwt_with_cid(evaluation.account, InvestorType::Retail, did_user.clone(), cid.clone()); + inst.execute(|| { + PolimecFunding::evaluate(RuntimeOrigin::signed(evaluation.account), jwt, project_id, evaluation.usd_amount) + .unwrap(); + }); + } + + inst.start_auction(project_id, ISSUER_1).unwrap(); + + inst.bid_for_users(project_id, bids[..1].to_vec()).unwrap(); + for bid in bids[1..].to_vec() { + let jwt = get_mock_jwt_with_cid(bid.bidder, InvestorType::Institutional, did_user.clone(), cid.clone()); + inst.execute(|| { + PolimecFunding::bid( + RuntimeOrigin::signed(bid.bidder), + jwt, + project_id, + bid.amount, + bid.multiplier, + bid.asset, + ) + .unwrap(); + }); + } + + inst.start_community_funding(project_id).unwrap(); + + inst.contribute_for_users(project_id, community_contributions).unwrap(); + + inst.start_remainder_or_end_funding(project_id).unwrap(); + + for contribution in remainder_contributions { + let jwt = + get_mock_jwt_with_cid(contribution.contributor, InvestorType::Professional, did_user.clone(), cid.clone()); + inst.execute(|| { + PolimecFunding::remaining_contribute( + RuntimeOrigin::signed(contribution.contributor), + jwt, + project_id, + contribution.amount, + contribution.multiplier, + contribution.asset, + ) + .unwrap(); + }); + } + + inst.finish_funding(project_id, None).unwrap(); + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let items = + TestRuntime::all_project_participations_by_did(&TestRuntime, block_hash, project_id, did_user).unwrap(); + dbg!(items); + }); +} + +#[test] +fn usd_target_percent_reached() { + let inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let (inst, project_id_1) = + create_finished_project_with_usd_raised(inst, 2_000_000 * USD_UNIT, 1_000_000 * USD_UNIT); + let (inst, project_id_2) = create_finished_project_with_usd_raised(inst, 945_000 * USD_UNIT, 1_000_000 * USD_UNIT); + let (inst, project_id_3) = create_finished_project_with_usd_raised(inst, 517_000 * USD_UNIT, 100_000 * USD_UNIT); + + let (mut inst, project_id_4) = create_finished_project_with_usd_raised(inst, 50_000 * USD_UNIT, 100_000 * USD_UNIT); + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let percent_200: FixedU128 = + TestRuntime::usd_target_percent_reached(&TestRuntime, block_hash, project_id_1).unwrap(); + assert_close_enough!( + percent_200.into_inner(), + FixedU128::from_float(2.0f64).into_inner(), + Perquintill::from_float(0.999) + ); + + let percent_94_5: FixedU128 = + TestRuntime::usd_target_percent_reached(&TestRuntime, block_hash, project_id_2).unwrap(); + assert_close_enough!( + percent_94_5.into_inner(), + FixedU128::from_float(0.945f64).into_inner(), + Perquintill::from_float(0.999) + ); + + let percent_517: FixedU128 = + TestRuntime::usd_target_percent_reached(&TestRuntime, block_hash, project_id_3).unwrap(); + assert_close_enough!( + percent_517.into_inner(), + FixedU128::from_float(5.17f64).into_inner(), + Perquintill::from_float(0.999) + ); + + let percent_50: FixedU128 = + TestRuntime::usd_target_percent_reached(&TestRuntime, block_hash, project_id_4).unwrap(); + assert_close_enough!( + percent_50.into_inner(), + FixedU128::from_float(0.5f64).into_inner(), + Perquintill::from_float(0.999) + ); + }); +} + +#[test] +fn projects_by_did() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let did_user = generate_did_from_account(420); + + 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_buys(), + default_remainder_buys(), + ); + + let _project_id_2 = inst.create_settled_project( + default_project_metadata(ISSUER_1), + ISSUER_1, + None, + default_evaluations(), + default_bids(), + default_community_buys(), + default_remainder_buys(), + ); + + let project_id_3 = inst.create_settled_project( + default_project_metadata(ISSUER_2), + ISSUER_2, + Some(did_user.clone()), + default_evaluations(), + default_bids(), + default_community_buys(), + default_remainder_buys(), + ); + + let _project_id_4 = inst.create_settled_project( + default_project_metadata(ISSUER_3), + ISSUER_3, + None, + default_evaluations(), + default_bids(), + default_community_buys(), + default_remainder_buys(), + ); + + inst.execute(|| { + let block_hash = System::block_hash(System::block_number()); + let project_ids = TestRuntime::projects_by_did(&TestRuntime, block_hash, did_user).unwrap(); + assert_eq!(project_ids, vec![project_id_1, project_id_3]); + }); +} diff --git a/pallets/linear-release/src/tests.rs b/pallets/linear-release/src/tests.rs index d5d81baa9..901e860b3 100644 --- a/pallets/linear-release/src/tests.rs +++ b/pallets/linear-release/src/tests.rs @@ -125,9 +125,9 @@ fn check_vesting_status_for_multi_schedule_account() { assert_eq!(Balances::balance_on_hold(&MockRuntimeHoldReason::Reason, &2), 20 * ED); assert_ok!(Vesting::vested_transfer(Some(4).into(), 2, sched1, MockRuntimeHoldReason::Reason)); assert_eq!(Balances::balance_on_hold(&MockRuntimeHoldReason::Reason, &2), 29 * ED); // Why 29 and not 30? Because sched1 is already unlocking. - // Free balance is the one set in Genesis inside the Balances pallet - // + the one from the vested transfer. - // BUT NOT the one in sched0, since the vesting will start at block #10. + // Free balance is the one set in Genesis inside the Balances pallet + // + the one from the vested transfer. + // BUT NOT the one in sched0, since the vesting will start at block #10. let balance = Balances::balance(&2); assert_eq!(balance, ED * (2)); // The most recently added schedule exists. @@ -193,7 +193,7 @@ fn unvested_balance_should_not_transfer() { ExtBuilder::default().existential_deposit(10).build().execute_with(|| { let user1_free_balance = Balances::free_balance(1); assert_eq!(user1_free_balance, 50); // Account 1 has free balance - // Account 1 has only 5 units vested at block 1 (plus 50 unvested) + // Account 1 has only 5 units vested at block 1 (plus 50 unvested) assert_eq!(Vesting::vesting_balance(&1, MockRuntimeHoldReason::Reason), Some(5)); // Account 1 cannot send more than vested amount... assert_noop!(Balances::transfer_allow_death(Some(1).into(), 2, 56), TokenError::FundsUnavailable); }); @@ -205,13 +205,13 @@ fn vested_balance_should_transfer() { assert_eq!(System::block_number(), 1); let user1_free_balance = Balances::free_balance(1); assert_eq!(user1_free_balance, 50); // Account 1 has free balance - // Account 1 has only 5 units vested at block 1 (plus 50 unvested) + // Account 1 has only 5 units vested at block 1 (plus 50 unvested) assert_eq!(Vesting::vesting_balance(&1, MockRuntimeHoldReason::Reason), Some(5)); assert_noop!(Balances::transfer_allow_death(Some(1).into(), 2, 45), TokenError::Frozen); // Account 1 free balance - ED is < 45 assert_ok!(Vesting::vest(Some(1).into(), MockRuntimeHoldReason::Reason)); let user1_free_balance = Balances::free_balance(1); assert_eq!(user1_free_balance, 55); // Account 1 has free balance - // Account 1 has vested 1 unit at block 1 (plus 50 unvested) + // Account 1 has vested 1 unit at block 1 (plus 50 unvested) assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 45)); // After the vest it can now send the 45 UNIT }); } @@ -259,7 +259,7 @@ fn vested_balance_should_transfer_using_vest_other() { ExtBuilder::default().existential_deposit(10).build().execute_with(|| { let user1_free_balance = Balances::free_balance(1); assert_eq!(user1_free_balance, 50); // Account 1 has free balance - // Account 1 has only 5 units vested at block 1 (plus 50 unvested) + // Account 1 has only 5 units vested at block 1 (plus 50 unvested) assert_eq!(Vesting::vesting_balance(&1, MockRuntimeHoldReason::Reason), Some(5)); assert_ok!(Vesting::vest_other(Some(2).into(), 1, MockRuntimeHoldReason::Reason)); assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 55 - 10)); @@ -317,7 +317,7 @@ fn extra_balance_should_transfer() { // Account 2 has no units vested at block 1, but gained 100 assert_ok!(Balances::transfer_allow_death(Some(2).into(), 3, 100 - 10)); // Account 2 can send extra - // units gained + // units gained }); } @@ -327,7 +327,7 @@ fn liquid_funds_should_transfer_with_delayed_vesting() { let user12_free_balance = Balances::free_balance(12); assert_eq!(user12_free_balance, 1280); // Account 12 has free balance - // Account 12 has liquid funds + // Account 12 has liquid funds assert_eq!(Vesting::vesting_balance(&12, MockRuntimeHoldReason::Reason), Some(0)); // Account 12 has delayed vesting diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index b1cdbb005..9a1b4ff20 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -34,14 +34,16 @@ use frame_support::{ }; use frame_system::{EnsureRoot, EnsureRootWithSuccess, EnsureSigned, EnsureSignedBy}; use pallet_democracy::GetElectorate; -use pallet_funding::DaysToBlocks; - +use pallet_funding::{ + runtime_api::ProjectParticipationIds, types::AcceptedFundingAsset, BidInfoOf, ContributionInfoOf, DaysToBlocks, + EvaluationInfoOf, ProjectDetailsOf, ProjectId, ProjectMetadataOf, +}; use parachains_common::{ message_queue::{NarrowOriginToSibling, ParaIdToSibling}, AssetIdForTrustBackedAssets as AssetId, }; use parity_scale_codec::Encode; -use polimec_common::credentials::EnsureInvestor; +use polimec_common::credentials::{Did, EnsureInvestor}; use polkadot_runtime_common::{ xcm_sender::NoPriceForMessageDelivery, BlockHashCount, CurrencyToVote, SlowAdjustingFeeUpdate, }; @@ -54,7 +56,7 @@ use sp_runtime::{ IdentifyAccount, IdentityLookup, OpaqueKeys, Verify, }, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, MultiSignature, SaturatedConversion, + ApplyExtrinsicResult, FixedU128, MultiSignature, SaturatedConversion, }; use sp_std::{cmp::Ordering, prelude::*}; use sp_version::RuntimeVersion; @@ -1391,6 +1393,54 @@ impl_runtime_apis! { } } + impl pallet_funding::runtime_api::Leaderboards for Runtime { + fn top_evaluations(project_id: ProjectId, amount: u32) -> Vec> { + Funding::top_evaluations(project_id, amount) + } + + fn top_bids(project_id: ProjectId, amount: u32) -> Vec> { + Funding::top_bids(project_id, amount) + } + + fn top_contributions(project_id: ProjectId, amount: u32) -> Vec> { + Funding::top_contributions(project_id, amount) + } + + fn top_projects_by_usd_raised(amount: u32) -> Vec<(ProjectId, ProjectMetadataOf, ProjectDetailsOf)> { + Funding::top_projects_by_usd_raised(amount) + } + + fn top_projects_by_usd_target_percent_reached(amount: u32) -> Vec<(ProjectId, ProjectMetadataOf, ProjectDetailsOf)> { + Funding::top_projects_by_usd_target_percent_reached(amount) + } + } + + impl pallet_funding::runtime_api::UserInformation for Runtime { + fn contribution_tokens(account: AccountId) -> Vec<(ProjectId, Balance)> { + Funding::contribution_tokens(account) + } + + fn all_project_participations_by_did(project_id: ProjectId, did: Did) -> Vec> { + Funding::all_project_participations_by_did(project_id, did) + } + } + + impl pallet_funding::runtime_api::ProjectInformation for Runtime { + fn usd_target_percent_reached(project_id: ProjectId) -> FixedU128 { + Funding::usd_target_percent_reached(project_id) + } + + fn projects_by_did(did: Did) -> Vec { + Funding::projects_by_did(did) + } + } + + impl pallet_funding::runtime_api::ExtrinsicHelpers for Runtime { + fn funding_asset_to_ct_amount(project_id: ProjectId, asset: AcceptedFundingAsset, asset_amount: Balance) -> Balance { + Funding::funding_asset_to_ct_amount(project_id, asset, asset_amount) + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) {