diff --git a/benchmarked-extrinsics.rs b/benchmarked-extrinsics.rs new file mode 100644 index 000000000..62c9be435 --- /dev/null +++ b/benchmarked-extrinsics.rs @@ -0,0 +1,153 @@ +// Polimec Blockchain – https://www.polimec.org/ +// Copyright (C) Polimec 2022. All rights reserved. + +// The Polimec Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Polimec Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@polimec.org + + +//! Autogenerated weights for `pallet_funding` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 39.0.0 +//! DATE: 2025-01-29, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `Mac.home`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("polimec-paseo-local")`, DB CACHE: `1024` + +// Executed Command: +// target/production/polimec-node +// benchmark +// pallet +// --chain=polimec-paseo-local +// --steps=50 +// --repeat=20 +// --pallet=pallet-funding +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=bid +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=benchmarked-extrinsics.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_funding`. +pub trait WeightInfo { + fn bid(x: u32, ) -> Weight; +} + +/// Weights for `pallet_funding` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// Storage: `Funding::ProjectsMetadata` (r:1 w:0) + /// Proof: `Funding::ProjectsMetadata` (`max_values`: None, `max_size`: Some(437), added: 2912, mode: `MaxEncodedLen`) + /// Storage: `Funding::ProjectsDetails` (r:1 w:0) + /// Proof: `Funding::ProjectsDetails` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + /// Storage: `Funding::Buckets` (r:1 w:1) + /// Proof: `Funding::Buckets` (`max_values`: None, `max_size`: Some(100), added: 2575, mode: `MaxEncodedLen`) + /// Storage: `Funding::NextBidId` (r:1 w:1) + /// Proof: `Funding::NextBidId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Funding::BidBucketBounds` (r:11 w:10) + /// Proof: `Funding::BidBucketBounds` (`max_values`: None, `max_size`: Some(60), added: 2535, mode: `MaxEncodedLen`) + /// Storage: `Funding::AuctionBoughtUSD` (r:1 w:1) + /// Proof: `Funding::AuctionBoughtUSD` (`max_values`: None, `max_size`: Some(118), added: 2593, mode: `MaxEncodedLen`) + /// Storage: `Oracle::Values` (r:2 w:0) + /// Proof: `Oracle::Values` (`max_values`: None, `max_size`: Some(634), added: 3109, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Metadata` (r:1 w:0) + /// Proof: `ForeignAssets::Metadata` (`max_values`: None, `max_size`: Some(738), added: 3213, mode: `MaxEncodedLen`) + /// Storage: `Funding::Evaluations` (r:1 w:0) + /// Proof: `Funding::Evaluations` (`max_values`: None, `max_size`: Some(337), added: 2812, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(175), added: 2650, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Asset` (r:1 w:1) + /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Account` (r:2 w:2) + /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) + /// Storage: `Funding::OutbidBidsCutoff` (r:1 w:1) + /// Proof: `Funding::OutbidBidsCutoff` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Funding::Bids` (r:999 w:19) + /// Proof: `Funding::Bids` (`max_values`: None, `max_size`: Some(341), added: 2816, mode: `MaxEncodedLen`) + /// The range of component `x` is `[1, 1000]`. + fn bid(x: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `4850 + x * (226 ±0)` + // Estimated: `28875 + x * (2816 ±0)` + // Minimum execution time: 829_000_000 picoseconds. + Weight::from_parts(767_882_619, 28875) + // Standard Error: 8_838 + .saturating_add(Weight::from_parts(5_852_038, 0).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().reads(26_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(x.into()))) + .saturating_add(T::DbWeight::get().writes(36_u64)) + .saturating_add(Weight::from_parts(0, 2816).saturating_mul(x.into())) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// Storage: `Funding::ProjectsMetadata` (r:1 w:0) + /// Proof: `Funding::ProjectsMetadata` (`max_values`: None, `max_size`: Some(437), added: 2912, mode: `MaxEncodedLen`) + /// Storage: `Funding::ProjectsDetails` (r:1 w:0) + /// Proof: `Funding::ProjectsDetails` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + /// Storage: `Funding::Buckets` (r:1 w:1) + /// Proof: `Funding::Buckets` (`max_values`: None, `max_size`: Some(100), added: 2575, mode: `MaxEncodedLen`) + /// Storage: `Funding::NextBidId` (r:1 w:1) + /// Proof: `Funding::NextBidId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Funding::BidBucketBounds` (r:11 w:10) + /// Proof: `Funding::BidBucketBounds` (`max_values`: None, `max_size`: Some(60), added: 2535, mode: `MaxEncodedLen`) + /// Storage: `Funding::AuctionBoughtUSD` (r:1 w:1) + /// Proof: `Funding::AuctionBoughtUSD` (`max_values`: None, `max_size`: Some(118), added: 2593, mode: `MaxEncodedLen`) + /// Storage: `Oracle::Values` (r:2 w:0) + /// Proof: `Oracle::Values` (`max_values`: None, `max_size`: Some(634), added: 3109, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Metadata` (r:1 w:0) + /// Proof: `ForeignAssets::Metadata` (`max_values`: None, `max_size`: Some(738), added: 3213, mode: `MaxEncodedLen`) + /// Storage: `Funding::Evaluations` (r:1 w:0) + /// Proof: `Funding::Evaluations` (`max_values`: None, `max_size`: Some(337), added: 2812, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(175), added: 2650, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Asset` (r:1 w:1) + /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssets::Account` (r:2 w:2) + /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) + /// Storage: `Funding::OutbidBidsCutoff` (r:1 w:1) + /// Proof: `Funding::OutbidBidsCutoff` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Funding::Bids` (r:999 w:19) + /// Proof: `Funding::Bids` (`max_values`: None, `max_size`: Some(341), added: 2816, mode: `MaxEncodedLen`) + /// The range of component `x` is `[1, 1000]`. + fn bid(x: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `4850 + x * (226 ±0)` + // Estimated: `28875 + x * (2816 ±0)` + // Minimum execution time: 829_000_000 picoseconds. + Weight::from_parts(767_882_619, 28875) + // Standard Error: 8_838 + .saturating_add(Weight::from_parts(5_852_038, 0).saturating_mul(x.into())) + .saturating_add(RocksDbWeight::get().reads(26_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(x.into()))) + .saturating_add(RocksDbWeight::get().writes(36_u64)) + .saturating_add(Weight::from_parts(0, 2816).saturating_mul(x.into())) + } +} \ No newline at end of file diff --git a/integration-tests/src/tests/ct_migration.rs b/integration-tests/src/tests/ct_migration.rs index 3159c835c..226553cf8 100644 --- a/integration-tests/src/tests/ct_migration.rs +++ b/integration-tests/src/tests/ct_migration.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use frame_support::WeakBoundedVec; use crate::{constants::PricesBuilder, *}; use frame_support::traits::{fungible::Mutate, fungibles::Inspect}; use itertools::Itertools; @@ -70,7 +71,7 @@ fn get_migrations_for_participants( for participant in participants { let (status, migrations) = pallet_funding::UserMigrations::::get((project_id, participant.clone())).unwrap(); - user_migrations.insert(participant, (status, Migrations::from(migrations.into()))); + user_migrations.insert(participant, (status, Migrations::from(migrations.to_vec()))); } }); user_migrations @@ -159,8 +160,8 @@ fn create_settled_project() -> (ProjectId, Vec) { let mut inst = IntegrationInstantiator::new(None); let project_metadata = default_project_metadata(ISSUER.into()); - let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 8); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 30); PolimecNet::execute_with(|| { let project_id = inst.create_finished_project(project_metadata, ISSUER.into(), None, evaluations, bids); assert_eq!( @@ -207,8 +208,8 @@ fn create_project_with_unsettled_participation(participation_type: Participation let mut inst = IntegrationInstantiator::new(None); PolimecNet::execute_with(|| { let project_metadata = default_project_metadata(ISSUER.into()); - let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 8); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 30); let project_id = inst.create_finished_project(project_metadata, ISSUER.into(), None, evaluations, bids); assert_eq!( @@ -217,12 +218,12 @@ fn create_project_with_unsettled_participation(participation_type: Participation ); let evaluations_to_settle = pallet_funding::Evaluations::::iter_prefix_values((project_id,)).collect_vec(); - let bids_to_settle = PolimecFunding::get_ordered_bid_settlements(project_id); + let bids_to_settle = inst.get_bids(project_id); let mut participants: Vec = evaluations_to_settle .iter() .map(|eval| eval.evaluator.clone()) - .chain(bids_to_settle.iter().map(|x| x.1.clone())) + .chain(bids_to_settle.iter().map(|x| x.bidder.clone())) .collect(); participants.sort(); participants.dedup(); @@ -240,8 +241,8 @@ fn create_project_with_unsettled_participation(participation_type: Participation let proposed_start = if participation_type == ParticipationType::Bid { 1 } else { 0 }; let end = if proposed_start == 1 { bids_to_settle.len() - 1 } else { bids_to_settle.len() }; - for (project_id, bidder, id) in bids_to_settle[..end].iter() { - PolimecFunding::settle_bid(RuntimeOrigin::signed(alice()), *project_id, bidder.clone(), *id).unwrap() + for bid in bids_to_settle[..end].iter() { + PolimecFunding::settle_bid(RuntimeOrigin::signed(alice()), project_id, bid.original_ct_usd_price, bid.id).unwrap() } let evaluations = diff --git a/integration-tests/src/tests/defaults.rs b/integration-tests/src/tests/defaults.rs index 3ad2b3037..362e2c2ae 100644 --- a/integration-tests/src/tests/defaults.rs +++ b/integration-tests/src/tests/defaults.rs @@ -68,7 +68,7 @@ pub fn default_project_metadata(issuer: AccountId) -> ProjectMetadataOf::get((project_id, user.clone(), participation_id)); - // dbg!(&stored_bid); - assert!(stored_bid.is_some()); } let post_participation_free_plmc = PolimecBalances::free_balance(user.clone()); @@ -425,10 +422,11 @@ fn e2e_test() { let prev_treasury_usdt_balance = PolimecForeignAssets::balance(USDT.id(), otm_project_sub_account.clone()); let prev_escrow_usdt_balance = PolimecForeignAssets::balance(USDT.id(), funding_escrow_account.clone()); + let rejected_bidder_bid = inst.get_bid(rejected_bid_id); PolimecFunding::settle_bid( PolimecOrigin::signed(rejected_bidder.clone()), project_id, - rejected_bidder.clone(), + rejected_bidder_bid.original_ct_usd_price, rejected_bid_id, ) .unwrap(); @@ -663,3 +661,23 @@ fn e2e_test() { } }); } + +#[test] +fn sandbox() { + fn bid(x: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `4850 + x * (226 ±0)` + // Estimated: `28875 + x * (2816 ±0)` + // Minimum execution time: 829_000_000 picoseconds. + Weight::from_parts(767_882_619, 28875) + // Standard Error: 8_838 + .saturating_add(Weight::from_parts(5_852_038, 0).saturating_mul(x.into())) + .saturating_add(::DbWeight::get().reads(26_u64)) + .saturating_add(::DbWeight::get().reads((1_u64).saturating_mul(x.into()))) + .saturating_add(::DbWeight::get().writes(36_u64)) + .saturating_add(Weight::from_parts(0, 2816).saturating_mul(x.into())) + } + + let max_weight = bid(1000); + dbg!(max_weight); +} diff --git a/integration-tests/src/tests/oracle.rs b/integration-tests/src/tests/oracle.rs index c4564bb37..42c3a5485 100644 --- a/integration-tests/src/tests/oracle.rs +++ b/integration-tests/src/tests/oracle.rs @@ -131,8 +131,8 @@ fn pallet_funding_works() { assert_ok!(Oracle::feed_values(RuntimeOrigin::signed(charlie.clone()), values([4.84, 1.0, 1.0, 2500.0, 0.4]))); let project_metadata = default_project_metadata(ISSUER.into()); - let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 8); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 30); let _project_id = inst.create_finished_project(project_metadata, ISSUER.into(), None, evaluations, bids); }); } diff --git a/justfile b/justfile index 8ba10b791..babf2428e 100644 --- a/justfile +++ b/justfile @@ -99,8 +99,8 @@ benchmark-pallet chain="polimec-paseo-local" pallet="pallet-dispenser": benchmark-extrinsics pallet="pallet-funding" extrinsics="*" : cargo run --features runtime-benchmarks --profile=production -p polimec-node benchmark pallet \ --chain=polimec-paseo-local \ - --steps=10 \ - --repeat=5 \ + --steps=50 \ + --repeat=20 \ --pallet={{ pallet }} \ --no-storage-info \ --no-median-slopes \ diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs index 2c400181f..4b0f0bf5d 100644 --- a/pallets/funding/src/benchmarking.rs +++ b/pallets/funding/src/benchmarking.rs @@ -72,9 +72,9 @@ where .unwrap(), bidding_ticket_sizes: BiddingTicketSizes { - professional: TicketSize::new(10u128 * USD_UNIT, None), - institutional: TicketSize::new(10u128 * USD_UNIT, None), - retail: TicketSize::new(10u128 * USD_UNIT, None), + professional: TicketSize::new(100u128 * USD_UNIT, None), + institutional: TicketSize::new(100u128 * USD_UNIT, None), + retail: TicketSize::new(100u128 * USD_UNIT, None), phantom: Default::default(), }, participation_currencies: vec![USDT, USDC, DOT, WETH].try_into().unwrap(), @@ -84,90 +84,6 @@ where } } -pub fn default_evaluations() -> Vec> -where - ::Price: From, - T::Hash: From, -{ - let threshold = ::EvaluationSuccessThreshold::get(); - let default_project_metadata: ProjectMetadataOf = - default_project_metadata::(account::>("issuer", 0, 0)); - let funding_target = - default_project_metadata.minimum_price.saturating_mul_int(default_project_metadata.total_allocation_size); - let evaluation_target = threshold * funding_target; - - vec![ - EvaluationParams::from(( - account::>("evaluator_1", 0, 0), - Percent::from_percent(35) * evaluation_target, - )), - EvaluationParams::from(( - account::>("evaluator_2", 0, 0), - Percent::from_percent(35) * evaluation_target, - )), - EvaluationParams::from(( - account::>("evaluator_3", 0, 0), - Percent::from_percent(35) * evaluation_target, - )), - ] -} - -pub fn default_bids() -> Vec> -where - ::Price: From, - T::Hash: From, -{ - let default_project_metadata = default_project_metadata::(account::>("issuer", 0, 0)); - let auction_funding_target = - default_project_metadata.minimum_price.saturating_mul_int(default_project_metadata.total_allocation_size); - let inst = BenchInstantiator::::new(None); - - inst.generate_bids_from_total_usd( - default_project_metadata.clone(), - Percent::from_percent(95) * auction_funding_target, - 5, - ) -} - -pub fn full_bids() -> Vec> -where - T: Config, - ::Price: From, - T::Hash: From, -{ - let inst = BenchInstantiator::::new(None); - let default_project = default_project_metadata::(account::>("issuer", 0, 0)); - let total_ct_for_bids = default_project.total_allocation_size; - let total_usd_for_bids = default_project.minimum_price.checked_mul_int(total_ct_for_bids).unwrap(); - inst.generate_bids_from_total_usd(default_project.clone(), total_usd_for_bids, 5) -} - -pub fn default_weights() -> Vec { - vec![20u8, 15u8, 10u8, 25u8, 30u8] -} - -pub fn default_evaluators() -> Vec> { - vec![ - account::>("evaluator_1", 0, 0), - account::>("evaluator_2", 0, 0), - account::>("evaluator_3", 0, 0), - account::>("evaluator_4", 0, 0), - account::>("evaluator_5", 0, 0), - ] -} -pub fn default_bidders() -> Vec> { - vec![ - account::>("bidder_1", 0, 0), - account::>("bidder_2", 0, 0), - account::>("bidder_3", 0, 0), - account::>("bidder_4", 0, 0), - account::>("bidder_5", 0, 0), - ] -} - -pub fn default_bidder_modes() -> Vec { - vec![Classic(10u8), Classic(3u8), OTM, OTM, Classic(4u8)] -} /// Grab an account, seeded by a name and index. pub fn string_account( name: scale_info::prelude::string::String, @@ -314,7 +230,7 @@ mod benchmarks { bidding_ticket_sizes: BiddingTicketSizes { professional: TicketSize::new(5000 * USD_UNIT, Some(10_000 * USD_UNIT)), institutional: TicketSize::new(5000 * USD_UNIT, Some(10_000 * USD_UNIT)), - retail: TicketSize::new(10 * USD_UNIT, Some(10_000 * USD_UNIT)), + retail: TicketSize::new(100 * USD_UNIT, Some(10_000 * USD_UNIT)), phantom: Default::default(), }, participation_currencies: vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC].try_into().unwrap(), @@ -393,15 +309,13 @@ mod benchmarks { } #[benchmark] - fn evaluate( - // How many other evaluations the user did for that same project - x: Linear<0, { T::MaxEvaluationsPerUser::get() - 1 }>, - ) { + fn evaluate() { // setup let mut inst = BenchInstantiator::::new(None); ::SetPrices::set_prices(); - // We can't see events at block 0 inst.advance_time(1u32.into()); + // We can't see events at block 0 + inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); let test_evaluator = account::>("evaluator", 0, 0); @@ -410,25 +324,16 @@ mod benchmarks { let project_metadata = default_project_metadata::(issuer.clone()); let project_id = inst.create_evaluating_project(project_metadata.clone(), issuer, None); - let existing_evaluation = EvaluationParams::from((test_evaluator.clone(), (200 * USD_UNIT).into())); let extrinsic_evaluation = EvaluationParams::from((test_evaluator.clone(), (1_000 * USD_UNIT).into())); - let existing_evaluations = vec![existing_evaluation; x as usize]; - let plmc_for_existing_evaluations = inst.calculate_evaluation_plmc_spent(existing_evaluations.clone()); let plmc_for_extrinsic_evaluation = inst.calculate_evaluation_plmc_spent(vec![extrinsic_evaluation.clone()]); let existential_plmc: Vec> = plmc_for_extrinsic_evaluation.accounts().existential_deposits(); inst.mint_plmc_to(existential_plmc); - inst.mint_plmc_to(plmc_for_existing_evaluations.clone()); inst.mint_plmc_to(plmc_for_extrinsic_evaluation.clone()); - // do "x" evaluations for this user - inst.evaluate_for_users(project_id, existing_evaluations).expect("All evaluations are accepted"); - let extrinsic_plmc_bonded = plmc_for_extrinsic_evaluation[0].plmc_amount; - let total_expected_plmc_bonded = inst - .sum_balance_mappings(vec![plmc_for_existing_evaluations.clone(), plmc_for_extrinsic_evaluation.clone()]); let jwt = get_mock_jwt_with_cid( extrinsic_evaluation.account.clone(), @@ -468,7 +373,7 @@ mod benchmarks { let bonded_plmc = inst .get_reserved_plmc_balances_for(vec![extrinsic_evaluation.account.clone()], HoldReason::Evaluation.into())[0] .plmc_amount; - assert_eq!(bonded_plmc, total_expected_plmc_bonded); + assert_eq!(bonded_plmc, extrinsic_plmc_bonded); // Events frame_system::Pallet::::assert_last_event( @@ -544,10 +449,8 @@ mod benchmarks { #[benchmark] fn bid( - // amount of already made bids by the same user. Leave 10 bids available to make the extrinsic pass in case y = max (10) - x: Linear<0, { T::MaxBidsPerUser::get() - 10 }>, - // amount of times when `perform_bid` is called (i.e. into how many buckets the bid is spread) - y: Linear<0, 10>, + // Amount of bids that are outbid by this one. + x: Linear<1, 1000>, ) { // * setup * let mut inst = BenchInstantiator::::new(None); @@ -561,10 +464,10 @@ mod benchmarks { whitelist_account!(bidder); let mut project_metadata = default_project_metadata::(issuer.clone()); - project_metadata.mainnet_token_max_supply = 100_000 * CT_UNIT; - project_metadata.total_allocation_size = 100_000 * CT_UNIT; + project_metadata.mainnet_token_max_supply = 40_000 * CT_UNIT; + project_metadata.total_allocation_size = 40_000 * CT_UNIT; project_metadata.minimum_price = PriceProviderOf::::calculate_decimals_aware_price( - PriceOf::::checked_from_rational(100, 1).unwrap(), + PriceOf::::checked_from_rational(10, 1).unwrap(), USD_DECIMALS, CT_DECIMALS, ) @@ -574,145 +477,75 @@ mod benchmarks { let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); - let existing_bid = BidParams::from(( - bidder.clone(), - Institutional, - (50 * CT_UNIT).into(), - ParticipationMode::Classic(5u8), - AcceptedFundingAsset::USDT, - )); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 100, x); + let total_ct_bid = project_metadata.total_allocation_size; - let existing_bids = vec![existing_bid; x as usize]; - let existing_bids_post_bucketing = - inst.get_actual_price_charged_for_bucketed_bids(&existing_bids, project_metadata.clone(), None); - let plmc_for_existing_bids = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &existing_bids, + let bids_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + &bids, + project_metadata.clone(), + None, + ); + let bids_funding_assets = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( + &bids, project_metadata.clone(), None, ); - let usdt_for_existing_bids: Vec> = inst - .calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &existing_bids, - project_metadata.clone(), - None, - ); - let escrow_account = Pallet::::fund_account_id(project_id); - let prev_total_escrow_usdt_locked = - inst.get_free_funding_asset_balances_for(vec![(escrow_account.clone(), usdt_id())]); - - inst.mint_plmc_ed_if_required(plmc_for_existing_bids.accounts()); - inst.mint_plmc_to(plmc_for_existing_bids.clone()); - inst.mint_funding_asset_ed_if_required(usdt_for_existing_bids.to_account_asset_map()); - inst.mint_funding_asset_to(usdt_for_existing_bids.clone()); - - // do "x" contributions for this user - inst.bid_for_users(project_id, existing_bids.clone()).unwrap(); - - // to call do_perform_bid several times, we need the bucket to reach its limit. You can only bid over 10 buckets - // in a single bid, since the increase delta is 10% of the total allocation, and you cannot bid more than the allocation. - let mut ct_amount = (50 * CT_UNIT).into(); - let mut maybe_filler_bid = None; - let new_bidder = account::>("new_bidder", 0, 0); - - let mut usdt_for_filler_bidder = - vec![UserToFundingAsset::::new(new_bidder.clone(), Zero::zero(), AcceptedFundingAsset::USDT.id())]; - if y > 0 { - let current_bucket = Buckets::::get(project_id).unwrap(); - // first lets bring the bucket to almost its limit with another bidder: - assert!(new_bidder.clone() != bidder.clone()); - let bid_params = BidParams::from(( - new_bidder.clone(), - Institutional, - current_bucket.amount_left, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )); - maybe_filler_bid = Some(bid_params.clone()); - let plmc_for_new_bidder = inst.calculate_auction_plmc_charged_with_given_price( - &vec![bid_params.clone()], - current_bucket.current_price, - ); - let usdt_for_new_bidder = inst.calculate_auction_funding_asset_charged_with_given_price( - &vec![bid_params.clone()], - current_bucket.current_price, - ); - - inst.mint_plmc_ed_if_required(vec![(new_bidder.clone())]); - inst.mint_plmc_to(plmc_for_new_bidder); - - inst.mint_funding_asset_ed_if_required(vec![(new_bidder, AcceptedFundingAsset::USDT.id())]); - inst.mint_funding_asset_to(usdt_for_new_bidder.clone()); - - inst.bid_for_users(project_id, vec![bid_params]).unwrap(); - - let auction_allocation = project_metadata.total_allocation_size; - let bucket_size = Percent::from_percent(10) * auction_allocation; - ct_amount = bucket_size * (y as u128); - usdt_for_filler_bidder = usdt_for_new_bidder; - } + inst.mint_plmc_ed_if_required(bids.accounts()); + inst.mint_plmc_to(bids_plmc); + + inst.mint_funding_asset_ed_if_required(bids.to_account_asset_map()); + inst.mint_funding_asset_to(bids_funding_assets); + + inst.bid_for_users(project_id, bids).unwrap(); + + let current_bucket = Buckets::::get(project_id).unwrap(); let extrinsic_bid = BidParams::from(( bidder.clone(), Institutional, - ct_amount, + total_ct_bid, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, )); - let original_extrinsic_bid = extrinsic_bid.clone(); - let current_bucket = Buckets::::get(project_id).unwrap(); - // we need to call this after bidding `x` amount of times, to get the latest bucket from storage - let extrinsic_bids_post_bucketing = inst.get_actual_price_charged_for_bucketed_bids( + + let extrinsic_bid_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( + &vec![extrinsic_bid.clone()], + project_metadata.clone(), + Some(current_bucket), + ); + let extrinsic_bid_funding_asset = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( &vec![extrinsic_bid.clone()], project_metadata.clone(), Some(current_bucket), ); - assert_eq!(extrinsic_bids_post_bucketing.len(), (y as usize).max(1usize)); - - let plmc_for_extrinsic_bids: Vec> = inst - .calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &vec![extrinsic_bid.clone()], - project_metadata.clone(), - Some(current_bucket), - ); - let usdt_for_extrinsic_bids: Vec> = inst - .calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &vec![extrinsic_bid], - project_metadata.clone(), - Some(current_bucket), - ); - inst.mint_plmc_ed_if_required(plmc_for_extrinsic_bids.accounts()); - inst.mint_plmc_to(plmc_for_extrinsic_bids.clone()); - inst.mint_funding_asset_ed_if_required(usdt_for_extrinsic_bids.to_account_asset_map()); - inst.mint_funding_asset_to(usdt_for_extrinsic_bids.clone()); - - let total_free_plmc = inst.get_ed(); - let total_plmc_participation_bonded = - inst.sum_balance_mappings(vec![plmc_for_extrinsic_bids.clone(), plmc_for_existing_bids.clone()]); - let total_free_usdt = inst.get_funding_asset_ed(AcceptedFundingAsset::USDT.id()); - let total_escrow_usdt_locked = inst.sum_funding_asset_mappings(vec![ - prev_total_escrow_usdt_locked.clone(), - usdt_for_extrinsic_bids.clone(), - usdt_for_existing_bids.clone(), - usdt_for_filler_bidder.clone(), - ])[0] - .1; + inst.mint_plmc_ed_if_required(extrinsic_bid_plmc.accounts()); + inst.mint_plmc_to(extrinsic_bid_plmc.clone()); + inst.mint_funding_asset_ed_if_required(extrinsic_bid_funding_asset.to_account_asset_map()); + inst.mint_funding_asset_to(extrinsic_bid_funding_asset.clone()); + + let extrinsic_bids_post_bucketing = inst.get_actual_price_charged_for_bucketed_bids( + &vec![extrinsic_bid.clone()], + project_metadata.clone(), + Some(current_bucket), + ); + dbg!(extrinsic_bids_post_bucketing.len()); let jwt = get_mock_jwt_with_cid( - original_extrinsic_bid.bidder.clone(), + bidder.clone(), InvestorType::Institutional, - generate_did_from_account(original_extrinsic_bid.bidder.clone()), + generate_did_from_account(bidder.clone()), project_metadata.clone().policy_ipfs_cid.unwrap(), ); #[extrinsic_call] bid( - RawOrigin::Signed(original_extrinsic_bid.bidder.clone()), + RawOrigin::Signed(bidder.clone()), jwt, project_id, - original_extrinsic_bid.amount, - original_extrinsic_bid.mode, - original_extrinsic_bid.asset, + extrinsic_bid.amount, + extrinsic_bid.mode, + extrinsic_bid.asset, ); // * validity checks * @@ -732,66 +565,15 @@ mod benchmarks { plmc_bond: None, when: None, }; - Bids::::iter_prefix_values((project_id, bidder.clone())) + Bids::::iter_prefix_values((project_id,)) .find(|stored_bid| bid_filter.matches_bid(stored_bid)) .expect("bid not found"); } - // Bucket Storage Check - let bucket_delta_amount = Percent::from_percent(10) * project_metadata.total_allocation_size; - let ten_percent_in_price: ::Price = - PriceOf::::checked_from_rational(1, 10).unwrap() * project_metadata.minimum_price; + let cutoff = OutbidBidsCutoff::::get(project_id).unwrap(); - let mut expected_bucket = Bucket::new( - project_metadata.total_allocation_size, - project_metadata.minimum_price, - ten_percent_in_price, - bucket_delta_amount, - ); - - for (bid_params, _price_) in existing_bids_post_bucketing.clone() { - expected_bucket.update(bid_params.amount); - } - if let Some(bid_params) = maybe_filler_bid { - expected_bucket.update(bid_params.amount); - } - for (bid_params, _price_) in extrinsic_bids_post_bucketing.clone() { - expected_bucket.update(bid_params.amount); - } - - let current_bucket = Buckets::::get(project_id).unwrap(); - assert_eq!(current_bucket, expected_bucket); - - // Balances - let bonded_plmc = - inst.get_reserved_plmc_balances_for(vec![bidder.clone()], HoldReason::Participation.into())[0].plmc_amount; - assert_eq!(bonded_plmc, total_plmc_participation_bonded); - - let free_plmc = inst.get_free_plmc_balances_for(vec![bidder.clone()])[0].plmc_amount; - assert_eq!(free_plmc, total_free_plmc); - - let escrow_account = Pallet::::fund_account_id(project_id); - let locked_usdt = inst.get_free_funding_asset_balance_for(usdt_id(), escrow_account.clone()); - assert_eq!(locked_usdt, total_escrow_usdt_locked); - - let free_usdt = inst.get_free_funding_asset_balance_for(usdt_id(), bidder); - assert_eq!(free_usdt, total_free_usdt); - - // Events - for (bid_params, _price_) in extrinsic_bids_post_bucketing { - let maybe_event = find_event! { - T, - Event::::Bid { - project_id, - ct_amount, - mode, .. - }, - project_id == project_id, - ct_amount == bid_params.amount, - mode == bid_params.mode - }; - assert!(maybe_event.is_some(), "Event not found"); - } + // First bucket with whole allocation should be outbid. + assert_eq!(cutoff, (project_metadata.minimum_price, 0)); } // end_funding has 2 logic paths: @@ -868,13 +650,10 @@ mod benchmarks { let issuer = account::>("issuer", 0, 0); whitelist_account!(anyone); - let project_id = inst.create_finished_project( - default_project_metadata::(issuer.clone()), - issuer, - None, - default_evaluations::(), - default_bids::(), - ); + let project_metadata = default_project_metadata::(issuer.clone()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 30); + let project_id = inst.create_finished_project(project_metadata, issuer, None, evaluations, bids); #[extrinsic_call] start_settlement(RawOrigin::Signed(anyone), project_id); @@ -900,16 +679,19 @@ mod benchmarks { inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); - let evaluations: Vec> = default_evaluations::(); + let project_metadata = default_project_metadata::(issuer.clone()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10); let evaluator: AccountIdOf = evaluations[0].account.clone(); whitelist_account!(evaluator); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 30); + let project_id = inst.create_finished_project( default_project_metadata::(issuer.clone()), issuer, None, evaluations, - default_bids::(), + bids, ); let evaluation_to_settle = @@ -961,53 +743,48 @@ mod benchmarks { inst.advance_time(1u32.into()); let issuer = account::>("issuer", 0, 0); - let mut bidder_accounts = default_bidders::().into_iter(); let project_metadata = default_project_metadata::(issuer.clone()); - // let target_wap = project_metadata.minimum_price + project_metadata.minimum_price * >::saturating_from_rational(1, 10); - let mut target_bucket = >::create_bucket_from_metadata(&project_metadata.clone()).unwrap(); - target_bucket.update(target_bucket.amount_left); - target_bucket.update(target_bucket.amount_left); + let increase = project_metadata.minimum_price * PriceOf::::saturating_from_rational(5, 10); + let target_wap = project_metadata.minimum_price + increase; - let bids = inst.generate_bids_from_bucket( - project_metadata.clone(), - target_bucket, - bidder_accounts.next().unwrap(), - |_| bidder_accounts.next().unwrap(), - AcceptedFundingAsset::USDT, - ); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10); + let bids = inst.generate_bids_that_take_price_to(project_metadata.clone(), target_wap); - let project_id = inst.create_finished_project( - project_metadata.clone(), - issuer, - None, - default_evaluations::(), - bids.clone(), - ); + let project_id = + inst.create_finished_project(project_metadata.clone(), issuer, None, evaluations, bids.clone()); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - let bidder = bids.last().unwrap().bidder.clone(); - whitelist_account!(bidder); - assert_ok!(>::do_start_settlement(project_id)); - let bid_to_settle = - inst.execute(|| Bids::::iter_prefix_values((project_id, bidder.clone())).next().unwrap()); + let bid_to_settle = inst.execute(|| { + let mut bids_iter = Bids::::iter_prefix_values((project_id,)); + bids_iter.find(|b| matches!(b.status, BidStatus::PartiallyAccepted(_))).unwrap() + }); + + let bidder = bid_to_settle.bidder.clone(); + whitelist_account!(bidder); - // Make sure a refund has to happen - assert!(bid_to_settle.original_ct_usd_price > wap); + let BidStatus::PartiallyAccepted(expected_ct_amount) = bid_to_settle.status else { + unreachable!(); + }; #[extrinsic_call] - settle_bid(RawOrigin::Signed(bidder.clone()), project_id, bidder.clone(), bid_to_settle.id); + settle_bid( + RawOrigin::Signed(bidder.clone()), + project_id, + bid_to_settle.original_ct_usd_price, + bid_to_settle.id, + ); // * validity checks * // Storage - assert!(Bids::::get((project_id, bidder.clone(), bid_to_settle.id)).is_none()); + assert!(Bids::::get((project_id, bid_to_settle.original_ct_usd_price, bid_to_settle.id)).is_none()); // Balances let ct_amount = inst.get_ct_asset_balances_for(project_id, vec![bidder.clone()])[0]; - assert_eq!(bid_to_settle.original_ct_amount, ct_amount); + assert_eq!(expected_ct_amount, ct_amount); // Events frame_system::Pallet::::assert_last_event( @@ -1015,9 +792,9 @@ mod benchmarks { project_id, account: bidder.clone(), id: bid_to_settle.id, - status: BidStatus::Accepted, - final_ct_amount: bid_to_settle.original_ct_amount, - final_ct_usd_price: wap, + status: bid_to_settle.status, + final_ct_amount: expected_ct_amount, + final_ct_usd_price: bid_to_settle.original_ct_usd_price, } .into(), ); @@ -1034,14 +811,11 @@ mod benchmarks { whitelist_account!(anyone); let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_settled_project( - project_metadata.clone(), - issuer.clone(), - None, - default_evaluations::(), - default_bids::(), - false, - ); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 95, 30); + + let project_id = + inst.create_settled_project(project_metadata.clone(), issuer.clone(), None, evaluations, bids, false); #[extrinsic_call] mark_project_as_settled(RawOrigin::Signed(anyone), project_id); @@ -1060,8 +834,8 @@ mod benchmarks { let issuer = account::>("issuer", 0, 0); let project_metadata = default_project_metadata::(issuer.clone()); - let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 1); - let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 1); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 30); let project_id = inst.create_settled_project(project_metadata.clone(), issuer.clone(), None, evaluations, bids, true); @@ -1078,13 +852,13 @@ mod benchmarks { // * validity checks * let project_details = inst.get_project_details(project_id); assert_eq!(project_details.status, ProjectStatus::CTMigrationStarted); - assert_eq!(UnmigratedCounter::::get(project_id), 2); + assert_eq!(UnmigratedCounter::::get(project_id), 40); } #[benchmark] fn confirm_offchain_migration( // Amount of migrations to confirm for a single user - x: Linear<1, { <::MaxBidsPerUser>::get() }>, + x: Linear<1, 100>, ) { // setup let mut inst = BenchInstantiator::::new(None); @@ -1093,6 +867,11 @@ mod benchmarks { let issuer = account::>("issuer", 0, 0); let participant = account::>("test_participant", 0, 0); + let project_metadata = default_project_metadata::(issuer.clone()); + + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10); + + let mut bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 50); let max_bids = x; let participant_bids = (0..max_bids) .map(|_| { @@ -1105,12 +884,6 @@ mod benchmarks { )) }) .collect_vec(); - - let project_metadata = default_project_metadata::(issuer.clone()); - - let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 1); - - let mut bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 1); bids.extend(participant_bids); let project_id = @@ -1141,8 +914,8 @@ mod benchmarks { // * validity checks * let project_details = inst.get_project_details(project_id); assert_eq!(project_details.status, ProjectStatus::CTMigrationStarted); - // Evaluations and Bids had 1 each where it was a different account that the one we just confirmed. - assert_eq!(UnmigratedCounter::::get(project_id), 2); + + assert_eq!(UnmigratedCounter::::get(project_id), 10 + 50); } #[benchmark] @@ -1154,14 +927,10 @@ mod benchmarks { let issuer = account::>("issuer", 0, 0); let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_settled_project( - project_metadata.clone(), - issuer.clone(), - None, - default_evaluations::(), - default_bids::(), - true, - ); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 30); + let project_id = + inst.create_settled_project(project_metadata.clone(), issuer.clone(), None, evaluations, bids, true); let jwt = get_mock_jwt_with_cid( issuer.clone(), @@ -1198,14 +967,10 @@ mod benchmarks { let issuer = account::>("issuer", 0, 0); let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_settled_project( - project_metadata.clone(), - issuer.clone(), - None, - default_evaluations::(), - default_bids::(), - true, - ); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 30); + let project_id = + inst.create_settled_project(project_metadata.clone(), issuer.clone(), None, evaluations, bids, true); let jwt = get_mock_jwt_with_cid( issuer.clone(), @@ -1255,14 +1020,10 @@ mod benchmarks { let issuer = account::>("issuer", 0, 0); let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_settled_project( - project_metadata.clone(), - issuer.clone(), - None, - default_evaluations::(), - default_bids::(), - true, - ); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 30); + let project_id = + inst.create_settled_project(project_metadata.clone(), issuer.clone(), None, evaluations, bids, true); let jwt = get_mock_jwt_with_cid( issuer.clone(), @@ -1329,14 +1090,10 @@ mod benchmarks { let issuer = account::>("issuer", 0, 0); let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_settled_project( - project_metadata.clone(), - issuer.clone(), - None, - default_evaluations::(), - default_bids::(), - true, - ); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 30); + let project_id = + inst.create_settled_project(project_metadata.clone(), issuer.clone(), None, evaluations, bids, true); let jwt = get_mock_jwt_with_cid( issuer.clone(), @@ -1409,7 +1166,7 @@ mod benchmarks { #[benchmark] fn send_pallet_migration_for( // Amount of migrations to confirm for a single user - x: Linear<1, { ::MaxBidsPerUser::get() }>, + x: Linear<1, 100>, ) { // setup let mut inst = BenchInstantiator::::new(None); @@ -1425,7 +1182,7 @@ mod benchmarks { BidParams::from(( participant.clone(), Institutional, - (500 * CT_UNIT).into(), + (50 * CT_UNIT).into(), ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, )) @@ -1433,8 +1190,8 @@ mod benchmarks { .collect_vec(); let project_metadata = default_project_metadata::(issuer.clone()); - let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let mut bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 1); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10); + let mut bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 40); bids.extend(participant_bids); let project_id = @@ -1481,9 +1238,9 @@ mod benchmarks { } #[benchmark] - fn confirm_pallet_migrations( + fn confirm_pallet_migration( // Amount of migrations to confirm for a single user - x: Linear<1, { ::MaxBidsPerUser::get() }>, + x: Linear<1, 100>, ) { // setup let mut inst = BenchInstantiator::::new(None); @@ -1499,7 +1256,7 @@ mod benchmarks { BidParams::from(( participant.clone(), Institutional, - (500 * CT_UNIT).into(), + (50 * CT_UNIT).into(), ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, )) @@ -1507,9 +1264,10 @@ mod benchmarks { .collect_vec(); let project_metadata = default_project_metadata::(issuer.clone()); - let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 1); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10); + + let mut bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 50); - let mut bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 1); bids.extend(participant_bids); let project_id = @@ -1572,14 +1330,10 @@ mod benchmarks { let issuer = account::>("issuer", 0, 0); let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_settled_project( - project_metadata.clone(), - issuer.clone(), - None, - default_evaluations::(), - default_bids::(), - true, - ); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 30); + let project_id = + inst.create_settled_project(project_metadata.clone(), issuer.clone(), None, evaluations, bids, true); let jwt = get_mock_jwt_with_cid( issuer.clone(), @@ -1611,14 +1365,10 @@ mod benchmarks { let issuer = account::>("issuer", 0, 0); let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_settled_project( - project_metadata.clone(), - issuer.clone(), - None, - default_evaluations::(), - default_bids::(), - true, - ); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 30); + let project_id = + inst.create_settled_project(project_metadata.clone(), issuer.clone(), None, evaluations, bids, true); let jwt = get_mock_jwt_with_cid( issuer.clone(), @@ -1652,14 +1402,10 @@ mod benchmarks { let issuer = account::>("issuer", 0, 0); let project_metadata = default_project_metadata::(issuer.clone()); - let project_id = inst.create_settled_project( - project_metadata.clone(), - issuer.clone(), - None, - default_evaluations::(), - default_bids::(), - true, - ); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 10); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 34, 30); + let project_id = + inst.create_settled_project(project_metadata.clone(), issuer.clone(), None, evaluations, bids, true); let jwt = get_mock_jwt_with_cid( issuer.clone(), diff --git a/pallets/funding/src/functions/2_evaluation.rs b/pallets/funding/src/functions/2_evaluation.rs index a859dc519..a83366ff0 100644 --- a/pallets/funding/src/functions/2_evaluation.rs +++ b/pallets/funding/src/functions/2_evaluation.rs @@ -76,7 +76,7 @@ impl Pallet { did: Did, whitelisted_policy: Cid, receiving_account: Junction, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { // * Get variables * let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; @@ -88,8 +88,6 @@ impl Pallet { let early_evaluation_reward_threshold_usd = T::EvaluationSuccessThreshold::get() * project_details.fundraising_target_usd; let evaluation_round_info = &mut project_details.evaluation_round_info; - let total_evaluations_count = EvaluationCounts::::get(project_id); - let user_evaluations_count = Evaluations::::iter_prefix((project_id, evaluator)).count() as u32; let project_policy = project_metadata.policy_ipfs_cid.ok_or(Error::::ImpossibleState)?; let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; @@ -102,8 +100,6 @@ impl Pallet { project_details.round_duration.started(now) && !project_details.round_duration.ended(now), Error::::IncorrectRound ); - ensure!(total_evaluations_count < T::MaxEvaluationsPerProject::get(), Error::::TooManyProjectParticipations); - ensure!(user_evaluations_count < T::MaxEvaluationsPerUser::get(), Error::::TooManyUserParticipations); ensure!( project_metadata.participants_account_type.junction_is_supported(&receiving_account), Error::::UnsupportedReceiverAccountJunction @@ -146,7 +142,6 @@ impl Pallet { evaluation_round_info.total_bonded_usd = evaluation_round_info.total_bonded_usd.saturating_add(usd_amount); evaluation_round_info.total_bonded_plmc = evaluation_round_info.total_bonded_plmc.saturating_add(plmc_bond); ProjectsDetails::::insert(project_id, project_details); - EvaluationCounts::::mutate(project_id, |c| *c = c.saturating_add(1)); // * Emit events * Self::deposit_event(Event::Evaluation { @@ -156,9 +151,6 @@ impl Pallet { plmc_amount: plmc_bond, }); - Ok(PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::evaluate(user_evaluations_count)), - pays_fee: Pays::No, - }) + Ok(()) } } diff --git a/pallets/funding/src/functions/3_auction.rs b/pallets/funding/src/functions/3_auction.rs index 80a02ea4c..131006c0c 100644 --- a/pallets/funding/src/functions/3_auction.rs +++ b/pallets/funding/src/functions/3_auction.rs @@ -3,7 +3,7 @@ use super::*; impl Pallet { #[transactional] - pub fn do_bid(params: DoBidParams) -> DispatchResultWithPostInfo { + pub fn do_bid(params: DoBidParams) -> DispatchResult { // * Get variables * let DoBidParams { bidder, @@ -23,7 +23,6 @@ impl Pallet { let mut current_bucket = Buckets::::get(project_id).ok_or(Error::::BucketNotFound)?; let now = >::block_number(); let mut amount_to_bid = ct_amount; - let total_bids_for_project = BidCounts::::get(project_id); let project_policy = project_metadata.policy_ipfs_cid.ok_or(Error::::ImpossibleState)?; // User will spend at least this amount of USD for his bid(s). More if the bid gets split into different buckets @@ -32,9 +31,6 @@ impl Pallet { // weight return variables let mut perform_bid_calls = 0; - let existing_bids = Bids::::iter_prefix_values((project_id, bidder.clone())).collect::>(); - let existing_bids_amount = existing_bids.len() as u32; - let metadata_ticket_size_bounds = match investor_type { InvestorType::Institutional => project_metadata.bidding_ticket_sizes.institutional, InvestorType::Professional => project_metadata.bidding_ticket_sizes.professional, @@ -64,11 +60,11 @@ impl Pallet { metadata_ticket_size_bounds.usd_ticket_above_minimum_per_participation(min_total_ticket_size), Error::::TooLow ); + ensure!(min_total_ticket_size <= MAX_USD_TICKET_PER_BID_EXTRINSIC, Error::::TooHigh); ensure!(mode.multiplier() <= max_multiplier && mode.multiplier() > 0u8, Error::::ForbiddenMultiplier); // Note: We limit the CT Amount to the auction allocation size, to avoid long-running loops. ensure!(ct_amount <= project_metadata.total_allocation_size, Error::::TooHigh); - ensure!(existing_bids.len() < T::MaxBidsPerUser::get() as usize, Error::::TooManyUserParticipations); ensure!( project_metadata.participants_account_type.junction_is_supported(&receiving_account), Error::::UnsupportedReceiverAccountJunction @@ -84,6 +80,7 @@ impl Pallet { current_bucket.amount_left }; let bid_id = NextBidId::::get(); + let auction_oversubscribed = current_bucket.current_price > current_bucket.initial_price; let perform_params = DoPerformBidParams { bidder: bidder.clone(), @@ -96,22 +93,22 @@ impl Pallet { now, did: did.clone(), metadata_ticket_size_bounds, - total_bids_by_bidder: existing_bids_amount.saturating_add(perform_bid_calls), - total_bids_for_project: total_bids_for_project.saturating_add(perform_bid_calls), receiving_account, + auction_oversubscribed, }; - let bid_info = Self::do_perform_bid(perform_params)?; - let bid_index = bid_info.id; - BidsSettlementOrder::::mutate(project_id, current_bucket.current_price, |maybe_indexes| { + BidBucketBounds::::mutate(project_id, current_bucket.current_price, |maybe_indexes| { if let Some((i, j)) = maybe_indexes { // TODO: remove the debug_assert before the PR is merged. - debug_assert!(bid_index == *j + 1); - *maybe_indexes = Some((*i, bid_index)); + debug_assert!(bid_id == *j + 1); + *maybe_indexes = Some((*i, bid_id)); } else { - *maybe_indexes = Some((bid_index, bid_index)); + *maybe_indexes = Some((bid_id, bid_id)); } }); + + Self::do_perform_bid(perform_params)?; + perform_bid_calls = perform_bid_calls.saturating_add(1); // Update the current bucket and reduce the amount to bid by the amount we just bid @@ -122,10 +119,7 @@ impl Pallet { // Note: If the bucket has been exhausted, the 'update' function has already made the 'current_bucket' point to the next one. Buckets::::insert(project_id, current_bucket); - Ok(PostDispatchInfo { - actual_weight: Some(WeightInfoOf::::bid(existing_bids_amount, perform_bid_calls)), - pays_fee: Pays::No, - }) + Ok(()) } #[transactional] @@ -141,26 +135,23 @@ impl Pallet { now, did, metadata_ticket_size_bounds, - total_bids_by_bidder, - total_bids_for_project, receiving_account, + auction_oversubscribed, } = do_perform_bid_params; - let ticket_size = ct_usd_price.checked_mul_int(ct_amount).ok_or(Error::::BadMath)?; + let usd_ticket_size = ct_usd_price.checked_mul_int(ct_amount).ok_or(Error::::BadMath)?; let total_usd_bid_by_did = AuctionBoughtUSD::::get((project_id, did.clone())); let multiplier: MultiplierOf = mode.multiplier().try_into().map_err(|_| Error::::BadMath)?; ensure!( metadata_ticket_size_bounds - .usd_ticket_below_maximum_per_did(total_usd_bid_by_did.saturating_add(ticket_size)), + .usd_ticket_below_maximum_per_did(total_usd_bid_by_did.saturating_add(usd_ticket_size)), Error::::TooHigh ); - ensure!(total_bids_by_bidder < T::MaxBidsPerUser::get(), Error::::TooManyUserParticipations); - ensure!(total_bids_for_project < T::MaxBidsPerProject::get(), Error::::TooManyProjectParticipations); // * Calculate new variables * - let plmc_bond = Self::calculate_plmc_bond(ticket_size, multiplier).map_err(|_| Error::::BadMath)?; - let funding_asset_amount_locked = Self::calculate_funding_asset_amount(ticket_size, funding_asset)?; + let plmc_bond = Self::calculate_plmc_bond(usd_ticket_size, multiplier).map_err(|_| Error::::BadMath)?; + let funding_asset_amount_locked = Self::calculate_funding_asset_amount(usd_ticket_size, funding_asset)?; let new_bid = BidInfoOf:: { id: bid_id, @@ -181,10 +172,13 @@ impl Pallet { Self::bond_plmc_with_mode(&bidder, project_id, plmc_bond, mode, funding_asset)?; Self::try_funding_asset_hold(&bidder, project_id, funding_asset_amount_locked, funding_asset.id())?; - Bids::::insert((project_id, bidder.clone(), bid_id), &new_bid); + Bids::::insert((project_id, ct_usd_price, bid_id), &new_bid); NextBidId::::set(bid_id.saturating_add(One::one())); - BidCounts::::mutate(project_id, |c| *c = c.saturating_add(1)); - AuctionBoughtUSD::::mutate((project_id, did), |amount| *amount = amount.saturating_add(ticket_size)); + AuctionBoughtUSD::::mutate((project_id, did), |amount| *amount = amount.saturating_add(usd_ticket_size)); + + if auction_oversubscribed { + Self::update_outbid_bids_cutoff(project_id, ct_amount)?; + } Self::deposit_event(Event::Bid { project_id, @@ -200,4 +194,76 @@ impl Pallet { Ok(new_bid) } + + fn update_outbid_bids_cutoff(project_id: ProjectId, ct_amount: Balance) -> DispatchResult { + let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; + let bucket = Buckets::::get(project_id).ok_or(Error::::BucketNotFound)?; + let maybe_current_cutoff = OutbidBidsCutoff::::get(project_id); + let mut current_cutoff: (PriceOf, u32); + let mut remaining_ct_amount = ct_amount; + + // Adjust initial cutoff if necessary + if let Some((price, index)) = maybe_current_cutoff { + let bid = Bids::::get((project_id, price, index)).ok_or(Error::::ImpossibleState)?; + if !matches!(bid.status, BidStatus::PartiallyAccepted(_)) { + let (new_price, new_index) = Self::get_next_cutoff(project_id, bucket.delta_price, price, index)?; + current_cutoff = (new_price, new_index); + } else { + current_cutoff = (price, index); + } + } else { + // Initialize to the first bucket + let first_price = project_metadata.minimum_price; + let first_bucket_bounds = + BidBucketBounds::::get(project_id, first_price).ok_or(Error::::ImpossibleState)?; + current_cutoff = (first_price, first_bucket_bounds.1); + } + + while remaining_ct_amount > Zero::zero() { + let (price, index) = current_cutoff; + + let mut bid = Bids::::get((project_id, price, index)).ok_or(Error::::ImpossibleState)?; + + let bid_amount = match bid.status { + BidStatus::PartiallyAccepted(amount) => amount, + _ => bid.original_ct_amount, + }; + + if bid_amount > remaining_ct_amount { + bid.status = BidStatus::PartiallyAccepted(bid_amount.saturating_sub(remaining_ct_amount)); + Bids::::insert((project_id, bid.original_ct_usd_price, bid.id), bid); + remaining_ct_amount = Zero::zero(); + } else { + if let BidStatus::PartiallyAccepted(_) = bid.status { + bid.status = BidStatus::Rejected; + Bids::::insert((project_id, bid.original_ct_usd_price, bid.id), bid); + } + remaining_ct_amount = remaining_ct_amount.saturating_sub(bid_amount); + } + + // Move to the next cutoff if we still have remaining amount to process + if remaining_ct_amount > Zero::zero() { + current_cutoff = Self::get_next_cutoff(project_id, bucket.delta_price, price, index)?; + } + } + + OutbidBidsCutoff::::set(project_id, Some(current_cutoff)); + Ok(()) + } + + pub fn get_next_cutoff( + project_id: ProjectId, + delta_price: PriceOf, + current_price: PriceOf, + current_index: u32, + ) -> Result<(PriceOf, u32), DispatchError> { + let bounds = BidBucketBounds::::get(project_id, current_price).ok_or(Error::::ImpossibleState)?; + if current_index == bounds.0 { + let new_price = current_price.saturating_add(delta_price); + let new_bounds = BidBucketBounds::::get(project_id, new_price).ok_or(Error::::ImpossibleState)?; + Ok((new_price, new_bounds.1)) + } else { + Ok((current_price, current_index.saturating_sub(1))) + } + } } diff --git a/pallets/funding/src/functions/5_funding_end.rs b/pallets/funding/src/functions/5_funding_end.rs index 19689a183..0c2f290d7 100644 --- a/pallets/funding/src/functions/5_funding_end.rs +++ b/pallets/funding/src/functions/5_funding_end.rs @@ -17,15 +17,6 @@ impl Pallet { Error::::TooEarlyForRound ); - let last_bought_price = if bucket.current_price == bucket.initial_price { - bucket.initial_price - } else if bucket.amount_left < bucket.delta_amount { - bucket.current_price - } else { - bucket.current_price - bucket.delta_price - }; - NextBidPriceToSettle::::insert(project_id, last_bought_price); - let auction_allocation_size = project_metadata.total_allocation_size; let weighted_average_price = bucket.calculate_wap(auction_allocation_size); project_details.weighted_average_price = Some(weighted_average_price); diff --git a/pallets/funding/src/functions/6_settlement.rs b/pallets/funding/src/functions/6_settlement.rs index 6451051d7..a11a4f6aa 100644 --- a/pallets/funding/src/functions/6_settlement.rs +++ b/pallets/funding/src/functions/6_settlement.rs @@ -19,7 +19,6 @@ use polimec_common::{ ReleaseSchedule, }; use sp_runtime::{traits::Zero, Perquintill}; -use sp_std::cmp::Ordering; impl Pallet { #[transactional] @@ -155,50 +154,45 @@ impl Pallet { Ok(()) } - pub fn do_settle_bid(project_id: ProjectId, bidder: AccountIdOf, bid_id: u32) -> DispatchResult { + pub fn do_settle_bid(project_id: ProjectId, bid_price: PriceOf, bid_id: u32) -> DispatchResult { let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; - let bucket = Buckets::::get(project_id).ok_or(Error::::BucketNotFound)?; let funding_success = matches!(project_details.status, ProjectStatus::SettlementStarted(FundingOutcome::Success)); let wap = project_details.weighted_average_price.ok_or(Error::::ImpossibleState)?; - let mut bid = Bids::::get((project_id, bidder.clone(), bid_id)).ok_or(Error::::ParticipationNotFound)?; - let next_bid_price = NextBidPriceToSettle::::get(project_id).ok_or(Error::::NotAllowed)?; - let (next_bid_index, last_bid_index_at_this_price) = - BidsSettlementOrder::::get(project_id, next_bid_price).ok_or(Error::::NotAllowed)?; + let mut bid = Bids::::get((project_id, bid_price, bid_id)).ok_or(Error::::ParticipationNotFound)?; + let maybe_outbid_bids_cutoff = OutbidBidsCutoff::::get(project_id); + + // Determine if the bid is outbid + match maybe_outbid_bids_cutoff { + Some((cutoff_price, cutoff_index)) => { + if cutoff_price > bid_price || (cutoff_price == bid_price && cutoff_index < bid_id) { + bid.status = BidStatus::Rejected + } else if cutoff_price == bid_price && cutoff_index == bid_id { + // nothing + } else { + bid.status = BidStatus::Accepted + } + }, + + None => bid.status = BidStatus::Accepted, // If there's no cutoff, the bid is not outbid + }; + + let bid_ct_amount = match bid.status { + BidStatus::YetUnknown => return Err(Error::::ImpossibleState.into()), + BidStatus::Accepted => bid.original_ct_amount, + BidStatus::Rejected => Zero::zero(), + BidStatus::PartiallyAccepted(amount) => amount, + }; ensure!( - matches!(project_details.status, ProjectStatus::SettlementStarted(..)), + matches!(bid.status, BidStatus::Rejected | BidStatus::PartiallyAccepted(_)) || + matches!(project_details.status, ProjectStatus::SettlementStarted(..)), Error::::SettlementNotStarted ); - ensure!(bid_id == next_bid_index, Error::::NotAllowed); - ensure!(bid.original_ct_usd_price == next_bid_price, Error::::NotAllowed); - match bid.original_ct_amount.cmp(&project_details.remaining_contribution_tokens) { - Ordering::Greater if project_details.remaining_contribution_tokens == 0 => { - bid.status = BidStatus::Rejected; - }, - Ordering::Greater => { - bid.status = BidStatus::PartiallyAccepted(project_details.remaining_contribution_tokens); - project_details.remaining_contribution_tokens = 0; - }, - _ => { - bid.status = BidStatus::Accepted; - project_details.remaining_contribution_tokens = - project_details.remaining_contribution_tokens.checked_sub(bid.original_ct_amount).unwrap_or(0); - }, - } - - if bid_id == last_bid_index_at_this_price { - BidsSettlementOrder::::remove(project_id, next_bid_price); - NextBidPriceToSettle::::set(project_id, Some(next_bid_price.saturating_sub(bucket.delta_price))); - } else { - BidsSettlementOrder::::insert( - project_id, - next_bid_price, - (bid_id.saturating_add(1), last_bid_index_at_this_price), - ); - } + project_details.remaining_contribution_tokens = + project_details.remaining_contribution_tokens.saturating_sub(bid_ct_amount); // Return the full bid amount to refund if bid is rejected or project failed, // Return a partial amount if the project succeeded, and the wap > paid price or bid is partially accepted @@ -248,7 +242,7 @@ impl Pallet { )?; } - Bids::::remove((project_id, bid.bidder.clone(), bid.id)); + Bids::::remove((project_id, bid_price, bid.id)); ProjectsDetails::::insert(project_id, project_details); Self::deposit_event(Event::BidSettled { @@ -450,22 +444,25 @@ impl Pallet { vesting_time: BlockNumberFor, receiving_account: Junction, ) -> DispatchResult { - UserMigrations::::try_mutate((project_id, origin), |maybe_migrations| -> DispatchResult { - let migration_origin = MigrationOrigin { user: receiving_account, id, participation_type }; - let vesting_time: u64 = vesting_time.try_into().map_err(|_| Error::::BadMath)?; - let migration_info: MigrationInfo = (ct_amount, vesting_time).into(); - let migration = Migration::new(migration_origin, migration_info); - if let Some((_, migrations)) = maybe_migrations { - migrations.try_push(migration).map_err(|_| Error::::TooManyMigrations)?; - } else { - let mut migrations = BoundedVec::<_, MaxParticipationsPerUser>::new(); - migrations.try_push(migration).map_err(|_| Error::::TooManyMigrations)?; - *maybe_migrations = Some((MigrationStatus::NotStarted, migrations)); - - UnmigratedCounter::::mutate(project_id, |counter| *counter = counter.saturating_add(1)); - } + let (status, user_migrations) = UserMigrations::::get((project_id, origin)) + .unwrap_or((MigrationStatus::NotStarted, WeakBoundedVec::<_, ConstU32<10_000>>::force_from(vec![], None))); - Ok(()) - }) + if user_migrations.is_empty() { + UnmigratedCounter::::mutate(project_id, |counter| *counter = counter.saturating_add(1)); + } + + let mut user_migrations = user_migrations.to_vec(); + let migration_origin = MigrationOrigin { user: receiving_account, id, participation_type }; + let vesting_time: u64 = vesting_time.try_into().map_err(|_| Error::::BadMath)?; + let migration_info: MigrationInfo = (ct_amount, vesting_time).into(); + let migration = Migration::new(migration_origin, migration_info); + user_migrations.push(migration); + + UserMigrations::::insert( + (project_id, origin), + (status, WeakBoundedVec::<_, ConstU32<10_000>>::force_from(user_migrations, None)), + ); + + Ok(()) } } diff --git a/pallets/funding/src/functions/7_ct_migration.rs b/pallets/funding/src/functions/7_ct_migration.rs index e5bac6ec8..3f4eb97b7 100644 --- a/pallets/funding/src/functions/7_ct_migration.rs +++ b/pallets/funding/src/functions/7_ct_migration.rs @@ -418,10 +418,6 @@ impl Pallet { let migration_readiness_check = migration_info.migration_readiness_check.ok_or(Error::::ChannelNotReady)?; let project_para_id = migration_info.parachain_id; let now = >::block_number(); - ensure!( - Self::user_has_no_participations(project_id, participant.clone()), - Error::::ParticipationsNotSettled - ); let (_, migrations) = UserMigrations::::get((project_id, participant.clone())).ok_or(Error::::NoMigrationsFound)?; diff --git a/pallets/funding/src/functions/misc.rs b/pallets/funding/src/functions/misc.rs index 982da7ed1..4d21a0cf4 100644 --- a/pallets/funding/src/functions/misc.rs +++ b/pallets/funding/src/functions/misc.rs @@ -266,21 +266,21 @@ impl Pallet { available_bytes_for_migration_per_message.saturating_div(one_migration_bytes) } - /// Check if the user has no participations (left) in the project. - pub fn user_has_no_participations(project_id: ProjectId, user: AccountIdOf) -> bool { - Evaluations::::iter_prefix_values((project_id, user.clone())).next().is_none() && - Bids::::iter_prefix_values((project_id, user.clone())).next().is_none() - } + // /// Check if the user has no participations (left) in the project. + // pub fn user_has_no_participations(project_id: ProjectId, user: AccountIdOf) -> bool { + // Evaluations::::iter_prefix_values((project_id, user.clone())).next().is_none() && + // Bids::::iter_prefix_values((project_id, user.clone())).next().is_none() + // } pub fn construct_migration_xcm_message( - migrations: BoundedVec>, + migrations: WeakBoundedVec>, query_id: QueryId, pallet_index: PalletIndex, ) -> Xcm<()> { // TODO: adjust this as benchmarks for polimec-receiver are written const MAX_WEIGHT: Weight = Weight::from_parts(10_000, 0); const MAX_RESPONSE_WEIGHT: Weight = Weight::from_parts(700_000_000, 50_000); - let migrations_item = Migrations::from(migrations.into()); + let migrations_item = Migrations::from(migrations.to_vec()); // First byte is the pallet index, second byte is the call index let mut encoded_call = vec![pallet_index, 0]; diff --git a/pallets/funding/src/instantiator/calculations.rs b/pallets/funding/src/instantiator/calculations.rs index 06135f82a..bb7875f0f 100644 --- a/pallets/funding/src/instantiator/calculations.rs +++ b/pallets/funding/src/instantiator/calculations.rs @@ -509,8 +509,8 @@ impl< self.generate_evaluations_from_total_usd(usd_threshold, evaluations_count) } - pub fn generate_bids_from_total_ct_amount(&self, bids_count: u8, total_ct_bid: Balance) -> Vec> { - // This range should be allowed for all investor types. + pub fn generate_bids_from_total_ct_amount(&self, bids_count: u32, total_ct_bid: Balance) -> Vec> { + // Use u128 for multipliers to allow for larger values let mut multipliers = (1u8..=5u8).cycle(); let modes = (0..bids_count) @@ -528,25 +528,32 @@ impl< let funding_assets = vec![USDT, USDC, DOT, WETH, USDT].into_iter().cycle().take(bids_count as usize).collect_vec(); - // Even distribution of weights totaling 100% among bids. + // Use Perquintill for precise weight distribution let weights = { if bids_count == 0 { return vec![]; } - let base = 100 / bids_count; - let remainder = 100 % bids_count; - let mut result = vec![base; bids_count as usize]; - for i in 0..remainder { - result[i as usize] += 1; + // Convert to Perquintill for higher precision division + let one = Perquintill::from_percent(100); + let per_bid = one / bids_count; + let mut remaining = one; + let mut result = Vec::with_capacity(bids_count as usize); + + // Distribute weights evenly with maximum precision + for _ in 0..bids_count - 1 { + result.push(per_bid); + remaining = remaining - per_bid; } + // Add remaining weight to the last bid to ensure total is exactly 100% + result.push(remaining); result }; - let bidders = (0..bids_count as u32).map(|i| self.account_from_u32(i, "BIDDER")).collect_vec(); + let bidders = (0..bids_count).map(|i| self.account_from_u32(i, "BIDDER")).collect_vec(); izip!(weights, bidders, modes, investor_types, funding_assets) .map(|(weight, bidder, mode, investor_type, funding_asset)| { - let token_amount = Percent::from_percent(weight) * total_ct_bid; + let token_amount = weight * total_ct_bid; BidParams::from((bidder, investor_type, token_amount, mode, funding_asset)) }) .collect() @@ -556,7 +563,7 @@ impl< &self, project_metadata: ProjectMetadataOf, usd_amount: Balance, - bids_count: u8, + bids_count: u32, ) -> Vec> { let min_price = project_metadata.minimum_price; let total_allocation_size = project_metadata.total_allocation_size; @@ -581,24 +588,10 @@ impl< let mut target_wap: PriceOf = min_price * target_wap_multiplicator; let mut bucket = self.find_bucket_for_wap(project_metadata.clone(), target_wap); - let first_account = self.account_from_u32(0, "BIDDER"); - let next_account = |acc: AccountIdOf| -> AccountIdOf { - let acc_bytes = acc.encode(); - let account_string = String::from_utf8_lossy(&acc_bytes); - let entropy = (0, account_string).using_encoded(blake2_256); - Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) - .expect("infinite length input; no invalid inputs for type; qed") - }; - let evaluations = self.generate_successful_evaluations(project_metadata.clone(), 5); // Initial bid generation - let mut bids = self.generate_bids_that_take_price_to( - project_metadata.clone(), - target_wap, - first_account.clone(), - next_account, - ); + let mut bids = self.generate_bids_that_take_price_to(project_metadata.clone(), target_wap); // Get initial USD amount let project_id = self.create_finished_project( @@ -638,12 +631,7 @@ impl< previous_wap = target_wap; bucket = self.find_bucket_for_wap(project_metadata.clone(), target_wap); - bids = self.generate_bids_that_take_price_to( - project_metadata.clone(), - target_wap, - first_account.clone(), - next_account, - ); + bids = self.generate_bids_that_take_price_to(project_metadata.clone(), target_wap); let project_id = self.create_finished_project( project_metadata.clone(), @@ -664,7 +652,7 @@ impl< &self, project_metadata: ProjectMetadataOf, percent_funding: u8, - bids_count: u8, + bids_count: u32, ) -> Vec> { let total_allocation_size = project_metadata.total_allocation_size; let total_ct_bid = Percent::from_percent(percent_funding) * total_allocation_size; @@ -747,26 +735,43 @@ impl< } // We assume a single bid can cover the whole first bucket. Make sure the ticket sizes allow this. - pub fn generate_bids_from_bucket( + pub fn generate_bids_from_bucket( &self, project_metadata: ProjectMetadataOf, bucket: BucketOf, - mut starting_account: AccountIdOf, - mut increment_account: F, funding_asset: AcceptedFundingAsset, - ) -> Vec> - where - F: FnMut(AccountIdOf) -> AccountIdOf, - { + ) -> Vec> { if bucket.current_price == bucket.initial_price { return vec![] } let auction_allocation = project_metadata.total_allocation_size; - let mut generate_bid = |ct_amount| -> BidParams { - let bid = (starting_account.clone(), Retail, ct_amount, funding_asset).into(); - starting_account = increment_account(starting_account.clone()); - bid + let mut starting_account = self.account_from_u32(0, "BIDDER"); + let mut increment_account = |acc: AccountIdOf| -> AccountIdOf { + let acc_bytes = acc.encode(); + let account_string = String::from_utf8_lossy(&acc_bytes); + let entropy = (0, account_string).using_encoded(blake2_256); + Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed") + }; + + let ct_price = bucket.current_price; + let max_ct_per_bid = ct_price.reciprocal().unwrap().saturating_mul_int(MAX_USD_TICKET_PER_BID_EXTRINSIC); + let mut generate_bids = |ct_amount| -> Vec> { + let mut remaining_ct_amount = ct_amount; + let mut bids = vec![]; + let min_ct_per_bid = ct_price + .reciprocal() + .unwrap() + .saturating_mul_int(project_metadata.bidding_ticket_sizes.retail.usd_minimum_per_participation); + while remaining_ct_amount > 0 && remaining_ct_amount > min_ct_per_bid { + let amount = max_ct_per_bid.min(remaining_ct_amount); + let bid = (starting_account.clone(), Retail, amount, funding_asset).into(); + starting_account = increment_account(starting_account.clone()); + bids.push(bid); + remaining_ct_amount -= amount + } + bids }; let step_amounts = ((bucket.current_price - bucket.initial_price) / bucket.delta_price).saturating_mul_int(1u8); @@ -774,43 +779,32 @@ impl< let mut bids = Vec::new(); - let first_bid = generate_bid(auction_allocation); - bids.push(first_bid); + let first_bids = generate_bids(auction_allocation); + bids.extend_from_slice(&first_bids[..]); for _i in 0u8..step_amounts - 1u8 { - let full_bucket_bid = generate_bid(bucket.delta_amount); - bids.push(full_bucket_bid); + let full_bucket_bids = generate_bids(bucket.delta_amount); + bids.extend_from_slice(&full_bucket_bids[..]); } // 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); + let last_bids = generate_bids(last_bid_amount); + bids.extend_from_slice(&last_bids[..]); } bids } - pub fn generate_bids_that_take_price_to( + pub fn generate_bids_that_take_price_to( &self, project_metadata: ProjectMetadataOf, desired_price: PriceOf, - bidder_account: AccountIdOf, - next_bidder_account: F, - ) -> Vec> - where - F: FnMut(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, - ) + self.generate_bids_from_bucket(project_metadata, necessary_bucket, AcceptedFundingAsset::USDT) } // Make sure the bids are in the order they were made diff --git a/pallets/funding/src/instantiator/chain_interactions.rs b/pallets/funding/src/instantiator/chain_interactions.rs index d87fa5b73..da78dd2ef 100644 --- a/pallets/funding/src/instantiator/chain_interactions.rs +++ b/pallets/funding/src/instantiator/chain_interactions.rs @@ -428,11 +428,7 @@ impl< new_details.status } - pub fn evaluate_for_users( - &mut self, - project_id: ProjectId, - bonds: Vec>, - ) -> DispatchResultWithPostInfo { + pub fn evaluate_for_users(&mut self, project_id: ProjectId, bonds: Vec>) -> DispatchResult { let project_policy = self.get_project_metadata(project_id).policy_ipfs_cid.unwrap(); for EvaluationParams { account, usd_amount, receiving_account } in bonds { self.execute(|| { @@ -446,7 +442,7 @@ impl< ) })?; } - Ok(().into()) + Ok(()) } pub fn bid_for_users(&mut self, project_id: ProjectId, bids: Vec>) -> DispatchResultWithPostInfo { @@ -477,10 +473,9 @@ impl< Evaluations::::iter_prefix((project_id,)) .for_each(|(_, evaluation)| Pallet::::do_settle_evaluation(evaluation, project_id).unwrap()); - let ordered_bid_settlements = Pallet::::get_ordered_bid_settlements(project_id); - for (project_id, bidder, id) in ordered_bid_settlements { - Pallet::::do_settle_bid(project_id, bidder, id).unwrap(); - } + Bids::::iter_prefix((project_id,)).for_each(|(_, bid)| { + Pallet::::do_settle_bid(project_id, bid.original_ct_usd_price, bid.id).unwrap() + }); if mark_as_settled { crate::Pallet::::do_mark_project_as_settled(project_id).unwrap(); @@ -496,6 +491,12 @@ impl< self.execute(|| Bids::::iter_prefix_values((project_id,)).collect()) } + pub fn get_bid(&mut self, bid_id: u32) -> BidInfoOf { + self.execute(|| { + Bids::::iter_values().find(|bid| bid.id == bid_id).unwrap() + }) + } + // Used to check all the USDT/USDC/DOT was paid to the issuer funding account pub fn assert_total_funding_paid_out(&mut self, project_id: ProjectId, bids: Vec>) { let project_metadata = self.get_project_metadata(project_id); @@ -579,19 +580,40 @@ impl< pub fn assert_bids_migrations_created( &mut self, project_id: ProjectId, - settlement_ordered_bids: Vec>, + bids: Vec>, is_successful: bool, ) { - let mut amount_left = self.get_project_metadata(project_id).total_allocation_size; - for bid in settlement_ordered_bids { - let account = bid.bidder.clone(); - let bid_amount = amount_left.min(bid.original_ct_amount); - amount_left -= bid_amount; - assert_eq!(self.execute(|| { Bids::::iter_prefix_values((&project_id, &account)).count() }), 0); + assert_eq!(self.execute(|| { Bids::::iter_prefix_values((&project_id,)).count() }), 0); + + let maybe_outbid_bids_cutoff = self.execute(|| OutbidBidsCutoff::::get(project_id)); + for bid in bids { + // Determine if the bid is outbid + let bid_is_outbid = match maybe_outbid_bids_cutoff { + Some((cutoff_price, cutoff_index)) => + cutoff_price > bid.original_ct_usd_price || + (cutoff_price == bid.original_ct_usd_price && cutoff_index <= bid.id), + None => false, // If there's no cutoff, the bid is not outbid + }; + + let bid_ct_amount = if let Some((bucket_price, index)) = maybe_outbid_bids_cutoff { + if bucket_price == bid.original_ct_usd_price && index == bid.id { + match bid.status { + BidStatus::PartiallyAccepted(ct_amount) => ct_amount, + _ => Zero::zero(), + } + } else if bid_is_outbid { + Zero::zero() + } else { + bid.original_ct_amount + } + } else { + bid.original_ct_amount + }; + self.assert_migration( project_id, - account, - bid_amount, + bid.bidder, + bid_ct_amount, bid.id, ParticipationType::Bid, bid.receiving_account, diff --git a/pallets/funding/src/instantiator/tests.rs b/pallets/funding/src/instantiator/tests.rs index 221955380..680c98285 100644 --- a/pallets/funding/src/instantiator/tests.rs +++ b/pallets/funding/src/instantiator/tests.rs @@ -178,13 +178,7 @@ fn generate_bids_from_bucket() { PriceProviderOf::::calculate_decimals_aware_price(desired_real_wap, USD_DECIMALS, CT_DECIMALS) .unwrap(); let necessary_bucket = inst.find_bucket_for_wap(project_metadata.clone(), desired_price_aware_wap); - let bids = inst.generate_bids_from_bucket( - project_metadata.clone(), - necessary_bucket, - 420, - |x| x + 1, - AcceptedFundingAsset::USDT, - ); + let bids = inst.generate_bids_from_bucket(project_metadata.clone(), necessary_bucket, AcceptedFundingAsset::USDT); let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let project_id = inst.create_finished_project(project_metadata.clone(), 0, None, evaluations, bids); let project_details = inst.get_project_details(project_id); diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index e3db7a72e..f05bb66e6 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -83,7 +83,7 @@ use frame_support::{ tokens::{fungible, fungibles}, AccountTouch, ContainsPair, }, - BoundedVec, PalletId, + BoundedVec, PalletId, WeakBoundedVec, }; use frame_system::pallet_prelude::BlockNumberFor; pub use pallet::*; @@ -264,30 +264,10 @@ pub mod pallet { Success = (AccountIdOf, Did, InvestorType, Cid), >; - /// Max individual bids per project. Used to estimate worst case weight for price calculation - #[pallet::constant] - type MaxBidsPerProject: Get; - - /// Max individual bids per project. Used to estimate worst case weight for price calculation - #[pallet::constant] - type MaxBidsPerUser: Get; - /// Range of max_capacity_thresholds values for the hrmp config where we accept the incoming channel request #[pallet::constant] type MaxCapacityThresholds: Get>; - /// Max individual contributions per project per user. Used to estimate worst case weight for price calculation - #[pallet::constant] - type MaxContributionsPerUser: Get; - - /// Max individual evaluations per project. Used to estimate worst case weight for price calculation - #[pallet::constant] - type MaxEvaluationsPerProject: Get; - - /// How many distinct evaluations per user per project - #[pallet::constant] - type MaxEvaluationsPerUser: Get; - #[pallet::constant] type MinUsdPerEvaluation: Get; @@ -393,12 +373,6 @@ pub mod pallet { #[pallet::storage] pub type NextContributionId = StorageValue<_, u32, ValueQuery>; - #[pallet::storage] - pub type BidCounts = StorageMap<_, Blake2_128Concat, ProjectId, u32, ValueQuery>; - - #[pallet::storage] - pub type EvaluationCounts = StorageMap<_, Blake2_128Concat, ProjectId, u32, ValueQuery>; - #[pallet::storage] /// A StorageMap containing the primary project information of projects pub type ProjectsMetadata = StorageMap<_, Blake2_128Concat, ProjectId, ProjectMetadataOf>; @@ -427,24 +401,24 @@ pub mod pallet { /// StorageMap containing the bids for each project and user pub type Bids = StorageNMap< _, - ( - NMapKey, - NMapKey>, - NMapKey, - ), + (NMapKey, NMapKey>, NMapKey), BidInfoOf, >; #[pallet::storage] /// StorageMap containing the first bid that should be settled at a certain price point, and the last bid available at that price point. /// Bids should be settled from the higest price first, and then from the lowest index first. Both indexes are inclusive. - pub type BidsSettlementOrder = + pub type BidBucketBounds = StorageDoubleMap<_, Blake2_128Concat, ProjectId, Blake2_128Concat, PriceOf, (u32, u32), OptionQuery>; #[pallet::storage] - /// Bid settlement function knows the price of the next bid to settle from this function. It should then update this map - /// if its settling the last index at that price, to the previous price of the bucket. - pub type NextBidPriceToSettle = StorageMap<_, Blake2_128Concat, ProjectId, PriceOf, OptionQuery>; + /// This map allows bidders to release their bid early if they were outbid. + /// The map contains the bucket price and bid index of the last bid to be outbid. + /// Indexes higher than the one stored here in the same bucket can be released. + /// All bids in buckets lower than the one stored here can also be released. + /// The last bid to be considered outbid might be partially rejected, and so that should be refunded by the new + /// bidder in the "bid" call. + pub type OutbidBidsCutoff = StorageMap<_, Blake2_128Concat, ProjectId, (PriceOf, u32), OptionQuery>; #[pallet::storage] pub type AuctionBoughtUSD = @@ -454,7 +428,8 @@ pub mod pallet { pub type UserMigrations = StorageNMap< _, (NMapKey, NMapKey>), - (MigrationStatus, BoundedVec>), + // We assume an upper bound of 10k migrations per user. This is not tracked, but is a sensible amount. + (MigrationStatus, WeakBoundedVec>), >; /// Counts how many participants have not yet migrated their CTs. Counter goes up on each settlement, and goes @@ -462,13 +437,6 @@ pub mod pallet { #[pallet::storage] pub type UnmigratedCounter = StorageMap<_, Blake2_128Concat, ProjectId, u32, ValueQuery>; - pub struct MaxParticipationsPerUser(PhantomData); - impl Get for MaxParticipationsPerUser { - fn get() -> u32 { - T::MaxBidsPerUser::get() + T::MaxEvaluationsPerUser::get() - } - } - #[pallet::storage] pub type ActiveMigrationQueue = StorageMap<_, Blake2_128Concat, QueryId, (ProjectId, T::AccountId)>; @@ -647,8 +615,6 @@ pub mod pallet { FundingAssetNotAccepted, /// The user already has the maximum number of participations in this project. TooManyUserParticipations, - /// The project already has the maximum number of participations. - TooManyProjectParticipations, /// The user is not allowed to use the selected multiplier. ForbiddenMultiplier, /// The user has a winning bid in the auction round and is not allowed to participate @@ -749,13 +715,14 @@ pub mod pallet { /// Bond PLMC for a project in the evaluation stage #[pallet::call_index(4)] - #[pallet::weight(WeightInfoOf::::evaluate(::MaxEvaluationsPerUser::get()))] + #[pallet::weight(WeightInfoOf::::start_settlement())] + // TODO: Change weight after benchmarks pub fn evaluate( origin: OriginFor, jwt: UntrustedToken, project_id: ProjectId, #[pallet::compact] usd_amount: Balance, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let (account, did, _investor_type, whitelisted_policy) = T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?; @@ -768,7 +735,8 @@ pub mod pallet { } #[pallet::call_index(40)] - #[pallet::weight(WeightInfoOf::::evaluate(::MaxEvaluationsPerUser::get()))] + #[pallet::weight(WeightInfoOf::::start_settlement())] + // TODO: Change weight after benchmarks pub fn evaluate_with_receiving_account( origin: OriginFor, jwt: UntrustedToken, @@ -776,7 +744,7 @@ pub mod pallet { #[pallet::compact] usd_amount: Balance, receiving_account: Junction, signature_bytes: [u8; 65], - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let (account, did, _investor_type, whitelisted_policy) = T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?; @@ -794,14 +762,8 @@ pub mod pallet { /// Bid for a project in the Auction round #[pallet::call_index(7)] - #[pallet::weight( - WeightInfoOf::::bid( - ::MaxBidsPerUser::get(), - // Assuming the current bucket is full, and has a price higher than the minimum. - // This user is buying 100% of the bid allocation. - // Since each bucket has 10% of the allocation, one bid can be split into a max of 10 - 10 - ))] + #[pallet::weight(WeightInfoOf::::start_settlement())] + // TODO: Change weight after benchmarks pub fn bid( origin: OriginFor, jwt: UntrustedToken, @@ -809,7 +771,7 @@ pub mod pallet { #[pallet::compact] ct_amount: Balance, mode: ParticipationMode, funding_asset: AcceptedFundingAsset, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let (bidder, did, investor_type, whitelisted_policy) = T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?; @@ -834,14 +796,8 @@ pub mod pallet { } #[pallet::call_index(70)] - #[pallet::weight( - WeightInfoOf::::bid( - ::MaxBidsPerUser::get(), - // Assuming the current bucket is full, and has a price higher than the minimum. - // This user is buying 100% of the bid allocation. - // Since each bucket has 10% of the allocation, one bid can be split into a max of 10 - 10 - ))] + #[pallet::weight(WeightInfoOf::::start_settlement())] + // TODO: Change weight after benchmarks pub fn bid_with_receiving_account( origin: OriginFor, jwt: UntrustedToken, @@ -851,7 +807,7 @@ pub mod pallet { funding_asset: AcceptedFundingAsset, receiving_account: Junction, signature_bytes: [u8; 65], - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let (bidder, did, investor_type, whitelisted_policy) = T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?; @@ -905,11 +861,11 @@ pub mod pallet { pub fn settle_bid( origin: OriginFor, project_id: ProjectId, - bidder: AccountIdOf, + bid_price: PriceOf, bid_id: u32, ) -> DispatchResult { let _caller = ensure_signed(origin)?; - Self::do_settle_bid(project_id, bidder, bid_id) + Self::do_settle_bid(project_id, bid_price, bid_id) } #[pallet::call_index(18)] @@ -934,7 +890,8 @@ pub mod pallet { } #[pallet::call_index(20)] - #[pallet::weight(WeightInfoOf::::confirm_offchain_migration(MaxParticipationsPerUser::::get()))] + #[pallet::weight(WeightInfoOf::::start_settlement())] + // TODO: Change weight after benchmarks pub fn confirm_offchain_migration( origin: OriginFor, project_id: ProjectId, @@ -988,7 +945,8 @@ pub mod pallet { } #[pallet::call_index(24)] - #[pallet::weight(WeightInfoOf::::send_pallet_migration_for(MaxParticipationsPerUser::::get()))] + #[pallet::weight(WeightInfoOf::::start_settlement())] + // TODO: Change weight after benchmarks pub fn send_pallet_migration_for( origin: OriginFor, project_id: ProjectId, @@ -999,7 +957,8 @@ pub mod pallet { } #[pallet::call_index(25)] - #[pallet::weight(WeightInfoOf::::confirm_pallet_migrations(MaxParticipationsPerUser::::get()))] + #[pallet::weight(WeightInfoOf::::start_settlement())] + // TODO: Change weight after benchmarks pub fn confirm_pallet_migrations( origin: OriginFor, query_id: QueryId, diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs index eafe5d83f..7249992cf 100644 --- a/pallets/funding/src/mock.rs +++ b/pallets/funding/src/mock.rs @@ -20,9 +20,7 @@ use super::*; use crate as pallet_funding; -use crate::runtime_api::{ - ExtrinsicHelpers, Leaderboards, ProjectInformation, ProjectParticipationIds, UserInformation, -}; +use crate::runtime_api::{ExtrinsicHelpers, Leaderboards, ProjectInformation, UserInformation}; use alloc::string::String; use core::ops::RangeInclusive; use frame_support::{ @@ -417,12 +415,7 @@ impl Config for TestRuntime { type FundingCurrency = ForeignAssets; type FundingSuccessThreshold = FundingSuccessThreshold; type InvestorOrigin = EnsureInvestor; - type MaxBidsPerProject = ConstU32<512>; - type MaxBidsPerUser = ConstU32<25>; type MaxCapacityThresholds = MaxCapacityThresholds; - type MaxContributionsPerUser = ConstU32<25>; - type MaxEvaluationsPerProject = ConstU32<512>; - type MaxEvaluationsPerUser = ConstU32<4>; type MaxMessageSizeThresholds = MaxMessageSizeThresholds; type MinUsdPerEvaluation = MinUsdPerEvaluation; type Multiplier = Multiplier; @@ -572,10 +565,6 @@ sp_api::mock_impl_runtime_apis! { fn contribution_tokens(account: AccountId) -> Vec<(ProjectId, Balance)> { 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 { @@ -612,9 +601,5 @@ sp_api::mock_impl_runtime_apis! { fn get_message_to_sign_by_receiving_account(project_id: ProjectId, polimec_account: AccountId) -> Option { PolimecFunding::get_message_to_sign_by_receiving_account(project_id, polimec_account) } - - fn get_ordered_bid_settlements(project_id: ProjectId) -> Vec<(ProjectId, AccountId, u32)> { - PolimecFunding::get_ordered_bid_settlements(project_id) - } } } diff --git a/pallets/funding/src/runtime_api.rs b/pallets/funding/src/runtime_api.rs index b1e17f86a..10e06b45b 100644 --- a/pallets/funding/src/runtime_api.rs +++ b/pallets/funding/src/runtime_api.rs @@ -3,17 +3,9 @@ use crate::{traits::BondingRequirementCalculation, *}; use alloc::{collections::BTreeMap, string::String}; use frame_support::traits::fungibles::{Inspect, InspectEnumerable}; use itertools::Itertools; -use parity_scale_codec::{Decode, Encode}; use polimec_common::{assets::AcceptedFundingAsset, credentials::InvestorType, ProvideAssetPrice, USD_DECIMALS}; -use scale_info::TypeInfo; use sp_core::Get; use sp_runtime::traits::Zero; -#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)] -pub struct ProjectParticipationIds { - account: AccountIdOf, - evaluation_ids: Vec, - bid_ids: Vec, -} sp_api::decl_runtime_apis! { #[api_version(2)] @@ -35,9 +27,6 @@ sp_api::decl_runtime_apis! { pub trait UserInformation { /// Get all the contribution token balances for the participated projects fn contribution_tokens(account: AccountIdOf) -> Vec<(ProjectId, Balance)>; - - /// 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)] @@ -71,10 +60,6 @@ sp_api::decl_runtime_apis! { /// Gets the hex encoded bytes of the message needed to be signed by the receiving account to participate in the project. /// The message will first be prefixed with a blockchain-dependent string, then hashed, and then signed. fn get_message_to_sign_by_receiving_account(project_id: ProjectId, polimec_account: AccountIdOf) -> Option; - - /// Gets a list of bid settlement parameters in the order they should be called. First item should be called first. - fn get_ordered_bid_settlements(project_id: ProjectId) -> Vec<(ProjectId, AccountIdOf, u32)>; - } } @@ -332,65 +317,6 @@ impl Pallet { Pallet::::get_substrate_message_to_sign(polimec_account, project_id) } - pub fn get_ordered_bid_settlements(project_id: ProjectId) -> Vec<(ProjectId, AccountIdOf, u32)> { - let mut bids = Bids::::iter_prefix_values((project_id,)).collect_vec(); - if bids.is_empty() { - return vec![] - } - let last_bucket = Buckets::::get(project_id).expect("Bucket not found"); - let mut current_settling_price = NextBidPriceToSettle::::get(project_id).expect("Next bid price not found"); - let (mut first_index, mut last_index) = - BidsSettlementOrder::::get(project_id, current_settling_price).expect("Settlement order not found"); - let mut ordered_bid_params = Vec::new(); - - loop { - for i in first_index..=last_index { - if let Some(position) = bids.iter().position(|bid| bid.id == i) { - let bid = bids.swap_remove(position); - ordered_bid_params.push((project_id, bid.bidder.clone(), bid.id)); - } - } - current_settling_price = current_settling_price.saturating_sub(last_bucket.delta_price); - if let Some((new_first_index, new_last_index)) = - BidsSettlementOrder::::get(project_id, current_settling_price) - { - first_index = new_first_index; - last_index = new_last_index; - } else { - break - } - } - - ordered_bid_params - } - - 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(); - - #[allow(clippy::type_complexity)] - let mut map: BTreeMap, (Vec, Vec)> = BTreeMap::new(); - - for (account_id, evaluation_id) in evaluations { - map.entry(account_id).or_insert_with(|| (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())).1.push(bid_id); - } - - map.into_iter() - .map(|(account, (evaluation_ids, bid_ids))| ProjectParticipationIds { account, evaluation_ids, bid_ids }) - .collect() - } - 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; diff --git a/pallets/funding/src/storage_migrations.rs b/pallets/funding/src/storage_migrations.rs index 94b27ab2a..01b0de955 100644 --- a/pallets/funding/src/storage_migrations.rs +++ b/pallets/funding/src/storage_migrations.rs @@ -16,130 +16,5 @@ use polimec_common::migration_types::{MigrationInfo, ParticipationType}; use xcm::v4::Location; /// The current storage version -pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(6); +pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(7); pub const LOG: &str = "runtime::funding::migration"; - -pub mod v5_storage_items { - - use super::*; - #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo, Serialize, Deserialize)] - pub struct OldProjectMetadata { - /// Token Metadata - pub token_information: CurrencyMetadata, - /// Mainnet Token Max Supply - pub mainnet_token_max_supply: Balance, - /// Total allocation of Contribution Tokens available for the Funding Round. - pub total_allocation_size: Balance, - /// Percentage of the total allocation of Contribution Tokens available for the Auction Round - pub auction_round_allocation_percentage: Percent, - /// The minimum price per token in USD, decimal-aware. See [`calculate_decimals_aware_price()`](crate::traits::ProvideAssetPrice::calculate_decimals_aware_price) for more information. - pub minimum_price: Price, - /// Maximum and minimum ticket sizes for auction round - pub bidding_ticket_sizes: BiddingTicketSizes, - /// Participation currencies (e.g stablecoin, DOT, KSM) - /// e.g. https://github.com/paritytech/substrate/blob/427fd09bcb193c1e79dec85b1e207c718b686c35/frame/uniques/src/types.rs#L110 - /// For now is easier to handle the case where only just one Currency is accepted - pub participation_currencies: - BoundedVec>, - pub funding_destination_account: AccountId, - /// Additional metadata - pub policy_ipfs_cid: Option, - } - - #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] - pub struct OldMigrationOrigin { - pub user: Location, - pub id: u32, - pub participation_type: ParticipationType, - } - impl PartialOrd for OldMigrationOrigin { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } - } - impl Ord for OldMigrationOrigin { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - if self.participation_type == other.participation_type { - self.id.cmp(&other.id) - } else { - self.participation_type.cmp(&other.participation_type) - } - } - } - #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] - pub struct OldMigration { - pub origin: OldMigrationOrigin, - pub info: MigrationInfo, - } -} - -pub mod v6 { - use super::*; - use crate::{storage_migrations::v5_storage_items::OldMigration, MaxParticipationsPerUser}; - use polimec_common::migration_types::{Migration, MigrationOrigin, MigrationStatus}; - - type OldProjectMetadataOf = super::v5_storage_items::OldProjectMetadata< - BoundedVec>, - ::Balance, - PriceOf, - AccountIdOf, - Cid, - >; - - pub struct UncheckedMigrationToV6(PhantomData); - impl UncheckedOnRuntimeUpgrade for UncheckedMigrationToV6 { - fn on_runtime_upgrade() -> frame_support::weights::Weight { - let mut items = 0; - log::info!("Starting migration to V5"); - let translate_project_details = |_key, item: OldProjectMetadataOf| -> Option> { - items += 1; - - Some(ProjectMetadataOf:: { - token_information: item.token_information, - mainnet_token_max_supply: item.mainnet_token_max_supply, - total_allocation_size: item.total_allocation_size, - minimum_price: item.minimum_price, - bidding_ticket_sizes: item.bidding_ticket_sizes, - participation_currencies: item.participation_currencies, - funding_destination_account: item.funding_destination_account, - policy_ipfs_cid: item.policy_ipfs_cid, - participants_account_type: ParticipantsAccountType::Polkadot, - }) - }; - crate::ProjectsMetadata::::translate(translate_project_details); - - let translate_migration = - |_keys, - (status, migrations): (MigrationStatus, BoundedVec>)| - -> Option<(MigrationStatus, BoundedVec>)> { - let old_migrations = migrations.to_vec(); - let mut new_migrations = Vec::new(); - - for mut old_migration in old_migrations { - items += 1; - let origin_junction = old_migration.origin.user.interior.take_first().unwrap(); - let new_origin = MigrationOrigin { - user: origin_junction, - id: old_migration.origin.id, - participation_type: old_migration.origin.participation_type, - }; - new_migrations.push(Migration { origin: new_origin, info: old_migration.info }); - } - let new_migrations = new_migrations.try_into().ok()?; - Some((status, new_migrations)) - }; - crate::UserMigrations::::translate(translate_migration); - - log::info!("Migration to V5 completed. Migrated {} items", items); - T::DbWeight::get().reads_writes(items, items) - } - } - - pub type MigrationToV6 = frame_support::migrations::VersionedMigration< - 5, - 6, - UncheckedMigrationToV6, - crate::Pallet, - ::DbWeight, - >; -} diff --git a/pallets/funding/src/tests/1_application.rs b/pallets/funding/src/tests/1_application.rs index 51c73e2f4..3992d6e10 100644 --- a/pallets/funding/src/tests/1_application.rs +++ b/pallets/funding/src/tests/1_application.rs @@ -133,7 +133,7 @@ mod create_project_extrinsic { let failing_bids = vec![(BIDDER_1, Professional, 1000 * CT_UNIT).into(), (BIDDER_2, Retail, 1000 * CT_UNIT).into()]; let successful_evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let successful_bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 90, 5); + let successful_bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 90, 10); let accounts = vec![ vec![ISSUER_1], @@ -914,7 +914,7 @@ mod edit_project_extrinsic { bidding_ticket_sizes: BiddingTicketSizes { professional: TicketSize::new(10_000 * USD_UNIT, Some(20_000 * USD_UNIT)), institutional: TicketSize::new(20_000 * USD_UNIT, Some(30_000 * USD_UNIT)), - retail: TicketSize::new(10 * USD_UNIT, None), + retail: TicketSize::new(100 * USD_UNIT, None), phantom: Default::default(), }, participation_currencies: vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC] diff --git a/pallets/funding/src/tests/2_evaluation.rs b/pallets/funding/src/tests/2_evaluation.rs index 561a39f2e..a8cdcb155 100644 --- a/pallets/funding/src/tests/2_evaluation.rs +++ b/pallets/funding/src/tests/2_evaluation.rs @@ -831,63 +831,6 @@ mod evaluate_extrinsic { assert_err!(dispatch_error, TokenError::FundsUnavailable) } - #[test] - fn cannot_evaluate_more_than_project_limit() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let evaluations = (0u32..::MaxEvaluationsPerProject::get()) - .map(|i| EvaluationParams::::from((i as u64 + 420, 100u128 * CT_UNIT))) - .collect_vec(); - let failing_evaluation = EvaluationParams::from((EVALUATOR_1, 1000 * CT_UNIT)); - - let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); - - let plmc_for_evaluating = inst.calculate_evaluation_plmc_spent(evaluations.clone()); - - inst.mint_plmc_ed_if_required(plmc_for_evaluating.accounts()); - inst.mint_plmc_to(plmc_for_evaluating.clone()); - - inst.evaluate_for_users(project_id, evaluations.clone()).unwrap(); - - let plmc_for_failing_evaluating = inst.calculate_evaluation_plmc_spent(vec![failing_evaluation.clone()]); - - inst.mint_plmc_to(plmc_for_failing_evaluating.clone()); - - assert_err!( - inst.evaluate_for_users(project_id, vec![failing_evaluation]), - Error::::TooManyProjectParticipations - ); - } - - #[test] - fn cannot_evaluate_more_than_user_limit() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let evaluations = (0u32..::MaxEvaluationsPerUser::get()) - .map(|_| EvaluationParams::::from((EVALUATOR_1, 100u128 * USD_UNIT))) - .collect_vec(); - let failing_evaluation = EvaluationParams::from((EVALUATOR_1, 100 * USD_UNIT)); - - let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); - - let plmc_for_evaluating = inst.calculate_evaluation_plmc_spent(evaluations.clone()); - let plmc_existential_deposits = evaluations.accounts().existential_deposits(); - - inst.mint_plmc_to(plmc_for_evaluating.clone()); - inst.mint_plmc_to(plmc_existential_deposits.clone()); - - inst.evaluate_for_users(project_id, evaluations.clone()).unwrap(); - - let plmc_for_failing_evaluating = inst.calculate_evaluation_plmc_spent(vec![failing_evaluation.clone()]); - - inst.mint_plmc_to(plmc_for_failing_evaluating.clone()); - - assert_err!( - inst.evaluate_for_users(project_id, vec![failing_evaluation]), - Error::::TooManyUserParticipations - ); - } - #[test] fn cannot_use_balance_on_hold() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); diff --git a/pallets/funding/src/tests/3_auction.rs b/pallets/funding/src/tests/3_auction.rs index 1ca66d0fa..045300a7c 100644 --- a/pallets/funding/src/tests/3_auction.rs +++ b/pallets/funding/src/tests/3_auction.rs @@ -19,7 +19,7 @@ mod round_flow { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_metadata = default_project_metadata(ISSUER_1); let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 60, 5); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 60, 10); let _project_id = inst.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids); } @@ -31,7 +31,7 @@ mod round_flow { let project3 = default_project_metadata(ISSUER_3); let project4 = default_project_metadata(ISSUER_4); let evaluations = inst.generate_successful_evaluations(project1.clone(), 5); - let bids = inst.generate_bids_from_total_ct_percent(project1.clone(), 60, 5); + let bids = inst.generate_bids_from_total_ct_percent(project1.clone(), 60, 10); inst.create_finished_project(project1, ISSUER_1, None, evaluations.clone(), bids.clone()); inst.create_finished_project(project2, ISSUER_2, None, evaluations.clone(), bids.clone()); @@ -193,51 +193,17 @@ mod round_flow { } } - #[test] - fn all_bids_but_one_have_price_higher_than_wap() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let total_allocation = 10_000_000 * CT_UNIT; - let min_bid_ct = 500 * CT_UNIT; // 5k USD at 10USD/CT - let max_bids_per_project: u32 = ::MaxBidsPerProject::get(); - let big_bid: BidParams = (BIDDER_1, Institutional, total_allocation).into(); - let small_bids: Vec> = - (0..max_bids_per_project - 1).map(|i| (i as u64 + BIDDER_1, Retail, min_bid_ct).into()).collect(); - let all_bids = vec![vec![big_bid.clone()], small_bids.clone()].into_iter().flatten().collect_vec(); - - let mut project_metadata = default_project_metadata(ISSUER_1); - project_metadata.mainnet_token_max_supply = total_allocation; - project_metadata.total_allocation_size = total_allocation; - - let project_id = inst.create_finished_project( - project_metadata.clone(), - ISSUER_1, - None, - inst.generate_successful_evaluations(project_metadata.clone(), 5), - all_bids, - ); - - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - - let all_bids = inst.execute(|| Bids::::iter_prefix_values((project_id,)).collect_vec()); - - 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.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 bucket = Pallet::::create_bucket_from_metadata(&project_metadata).unwrap(); + let bids = inst.generate_bids_that_take_price_to( + project_metadata.clone(), + project_metadata.minimum_price + bucket.delta_price * FixedU128::from_float(3.0), + ); let project_id = inst.create_finished_project( project_metadata.clone(), @@ -260,7 +226,7 @@ mod bid_extrinsic { #[cfg(test)] mod success { use super::*; - use frame_support::pallet_prelude::DispatchResultWithPostInfo; + use frame_support::{dispatch::DispatchResult, pallet_prelude::DispatchResultWithPostInfo}; #[test] fn evaluation_bond_counts_towards_bid() { @@ -315,8 +281,7 @@ mod bid_extrinsic { assert_eq!(evaluation_items.len(), 1); assert_eq!(evaluation_items[0].current_plmc_bond, already_bonded_plmc - usable_evaluation_plmc); - let bid_items = - inst.execute(|| Bids::::iter_prefix_values((project_id, evaluator_bidder)).collect_vec()); + let bid_items = inst.execute(|| Bids::::iter_prefix_values((project_id,)).collect_vec()); assert_eq!(bid_items.len(), 1); assert_eq!(bid_items[0].plmc_bond, necessary_plmc_for_bid); @@ -437,7 +402,7 @@ mod bid_extrinsic { bidder: AccountIdOf, investor_type: InvestorType, u8_multiplier: u8, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let project_policy = inst.get_project_metadata(project_id).policy_ipfs_cid.unwrap(); let jwt = get_mock_jwt_with_cid(bidder, investor_type, generate_did_from_account(BIDDER_1), project_policy); let amount = 1000 * CT_UNIT; @@ -669,7 +634,13 @@ mod bid_extrinsic { assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); inst.execute(|| { - PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_4), project_id, BIDDER_4, 0).unwrap(); + PolimecFunding::settle_bid( + RuntimeOrigin::signed(BIDDER_4), + project_id, + project_metadata.minimum_price, + 0, + ) + .unwrap(); }); let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); @@ -685,7 +656,16 @@ mod bid_extrinsic { fn can_bid_with_frozen_tokens_funding_success() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let issuer = ISSUER_1; - let project_metadata = default_project_metadata(issuer); + let mut project_metadata = default_project_metadata(issuer); + let base_price = PriceOf::::from_float(1.0); + let decimal_aware_price = ::PriceProvider::calculate_decimals_aware_price( + base_price, + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + project_metadata.minimum_price = decimal_aware_price; + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); @@ -753,7 +733,13 @@ mod bid_extrinsic { assert_eq!(frozen_balance, frozen_amount); inst.execute(|| { - PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_4), project_id, BIDDER_4, 0).unwrap(); + PolimecFunding::settle_bid( + RuntimeOrigin::signed(BIDDER_4), + project_id, + project_metadata.minimum_price, + 0, + ) + .unwrap(); }); let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); @@ -823,17 +809,17 @@ mod bid_extrinsic { USDT_PARTICIPATION, ) }); + // USDT has the same decimals and price as our baseline USD let expected_plmc_bond = >::calculate_plmc_bond(USDT_PARTICIPATION, otm_multiplier).unwrap(); - let otm_escrow_account = - ::RootId::get().into_sub_account_truncating(project_id); + let otm_escrow_account = pallet_proxy_bonding::Pallet::::get_bonding_account(project_id); let otm_treasury_account = ::Treasury::get(); let otm_fee_recipient_account = ::FeeRecipient::get(); let funding_project_escrow = PolimecFunding::fund_account_id(project_id); - assert!(funding_project_escrow != otm_escrow_account); + assert_ne!(funding_project_escrow, otm_escrow_account); let pre_participation_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); let pre_participation_otm_escrow_held_plmc = @@ -893,6 +879,39 @@ mod bid_extrinsic { Perquintill::from_float(0.999) ); + let post_participation_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); + let post_participation_otm_escrow_held_plmc = + inst.get_reserved_plmc_balance_for(otm_escrow_account, HoldReason::Participation.into()); + let post_participation_otm_escrow_usdt = + inst.get_free_funding_asset_balance_for(usdt_id.clone(), otm_escrow_account); + let post_participation_otm_fee_recipient_usdt = + inst.get_free_funding_asset_balance_for(usdt_id.clone(), otm_fee_recipient_account); + let post_participation_buyer_usdt = inst.get_free_funding_asset_balance_for(usdt_id.clone(), BIDDER_1); + + assert_eq!( + post_participation_treasury_free_plmc, + pre_participation_treasury_free_plmc - expected_plmc_bond - inst.get_ed() + ); + assert_eq!( + post_participation_otm_escrow_held_plmc, + pre_participation_otm_escrow_held_plmc + expected_plmc_bond + ); + assert_close_enough!( + post_participation_otm_escrow_usdt, + pre_participation_otm_escrow_usdt + otm_usdt_fee, + Perquintill::from_float(0.999) + ); + assert_close_enough!( + post_participation_otm_fee_recipient_usdt, + pre_participation_otm_fee_recipient_usdt, + Perquintill::from_float(0.999) + ); + assert_close_enough!( + post_participation_buyer_usdt, + pre_participation_buyer_usdt - USDT_PARTICIPATION - otm_usdt_fee, + Perquintill::from_float(0.999) + ); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); inst.settle_project(project_id, true); @@ -1302,222 +1321,6 @@ mod bid_extrinsic { }); } - #[test] - fn cannot_bid_more_than_project_limit_count() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let mut project_metadata = default_project_metadata(ISSUER_1); - project_metadata.mainnet_token_max_supply = 1_000_000_000 * CT_UNIT; - project_metadata.total_allocation_size = 100_000_000 * CT_UNIT; - - let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let max_bids_per_project: u32 = ::MaxBidsPerProject::get(); - let bids = (0u32..max_bids_per_project - 1).map(|i| (i as u64 + 420, 5000 * CT_UNIT).into()).collect_vec(); - - 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(), project_metadata.minimum_price); - let usdt_for_bidding = inst.calculate_auction_funding_asset_charged_with_given_price( - &bids.clone(), - project_metadata.minimum_price, - ); - - inst.mint_plmc_ed_if_required(bids.accounts()); - inst.mint_plmc_to(plmc_for_bidding.clone()); - - inst.mint_funding_asset_ed_if_required(bids.to_account_asset_map()); - inst.mint_funding_asset_to(usdt_for_bidding.clone()); - - inst.bid_for_users(project_id, bids.clone()).unwrap(); - - let current_bucket = inst.execute(|| Buckets::::get(project_id)).unwrap(); - let remaining_ct = current_bucket.amount_left; - - // This bid should be split in 2, but the second one should fail, making the whole extrinsic fail and roll back storage - let failing_bid = BidParams::::from(( - BIDDER_1, - Retail, - remaining_ct + 5000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )); - let plmc_for_failing_bid = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &vec![failing_bid.clone()], - project_metadata.clone(), - Some(current_bucket), - ); - - let usdt_for_bidding = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &vec![failing_bid.clone()], - project_metadata.clone(), - Some(current_bucket), - ); - - inst.mint_plmc_to(plmc_for_failing_bid.clone()); - inst.mint_funding_asset_to(usdt_for_bidding.clone()); - - inst.execute(|| { - assert_noop!( - PolimecFunding::bid( - RuntimeOrigin::signed(failing_bid.bidder), - get_mock_jwt_with_cid( - failing_bid.bidder, - InvestorType::Professional, - generate_did_from_account(failing_bid.bidder), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - failing_bid.amount, - failing_bid.mode, - failing_bid.asset - ), - Error::::TooManyProjectParticipations - ); - }); - - // Now we test that after reaching the limit, just one bid is also not allowed - inst.execute(|| { - assert_ok!(PolimecFunding::bid( - RuntimeOrigin::signed(failing_bid.bidder), - get_mock_jwt_with_cid( - failing_bid.bidder, - InvestorType::Professional, - generate_did_from_account(failing_bid.bidder), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - remaining_ct, - failing_bid.mode, - failing_bid.asset - )); - }); - inst.execute(|| { - assert_noop!( - PolimecFunding::bid( - RuntimeOrigin::signed(failing_bid.bidder), - get_mock_jwt_with_cid( - failing_bid.bidder, - InvestorType::Professional, - generate_did_from_account(failing_bid.bidder), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - 5000 * CT_UNIT, - failing_bid.mode, - failing_bid.asset - ), - Error::::TooManyProjectParticipations - ); - }); - } - - #[test] - fn cannot_bid_more_than_user_limit_count() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let mut project_metadata = default_project_metadata(ISSUER_1); - project_metadata.mainnet_token_max_supply = 1_000_000_000 * CT_UNIT; - project_metadata.total_allocation_size = 100_000_000 * CT_UNIT; - - let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let max_bids_per_user: u32 = ::MaxBidsPerUser::get(); - let bids = (0u32..max_bids_per_user - 1u32).map(|_| (BIDDER_1, 5000 * CT_UNIT).into()).collect_vec(); - - 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(), project_metadata.minimum_price); - let usdt_for_bidding = inst.calculate_auction_funding_asset_charged_with_given_price( - &bids.clone(), - project_metadata.minimum_price, - ); - - inst.mint_plmc_ed_if_required(bids.accounts()); - inst.mint_plmc_to(plmc_for_bidding.clone()); - - inst.mint_funding_asset_ed_if_required(bids.to_account_asset_map()); - inst.mint_funding_asset_to(usdt_for_bidding.clone()); - - inst.bid_for_users(project_id, bids.clone()).unwrap(); - - let current_bucket = inst.execute(|| Buckets::::get(project_id)).unwrap(); - let remaining_ct = current_bucket.amount_left; - - // This bid should be split in 2, but the second one should fail, making the whole extrinsic fail and roll back storage - let failing_bid = BidParams::::from(( - BIDDER_1, - Retail, - remaining_ct + 5000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - )); - let plmc_for_failing_bid = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( - &vec![failing_bid.clone()], - project_metadata.clone(), - Some(current_bucket), - ); - let usdt_for_bidding = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( - &vec![failing_bid.clone()], - project_metadata.clone(), - Some(current_bucket), - ); - inst.mint_plmc_to(plmc_for_failing_bid.clone()); - inst.mint_funding_asset_to(usdt_for_bidding.clone()); - - inst.execute(|| { - assert_noop!( - PolimecFunding::bid( - RuntimeOrigin::signed(failing_bid.bidder), - get_mock_jwt_with_cid( - failing_bid.bidder, - InvestorType::Professional, - generate_did_from_account(failing_bid.bidder), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - failing_bid.amount, - failing_bid.mode, - failing_bid.asset - ), - Error::::TooManyUserParticipations - ); - }); - - // Now we test that after reaching the limit, just one bid is also not allowed - inst.execute(|| { - assert_ok!(PolimecFunding::bid( - RuntimeOrigin::signed(failing_bid.bidder), - get_mock_jwt_with_cid( - failing_bid.bidder, - InvestorType::Professional, - generate_did_from_account(failing_bid.bidder), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - remaining_ct, - failing_bid.mode, - failing_bid.asset - )); - }); - inst.execute(|| { - assert_noop!( - PolimecFunding::bid( - RuntimeOrigin::signed(failing_bid.bidder), - get_mock_jwt_with_cid( - failing_bid.bidder, - InvestorType::Professional, - generate_did_from_account(failing_bid.bidder), - project_metadata.clone().policy_ipfs_cid.unwrap() - ), - project_id, - 5000 * CT_UNIT, - failing_bid.mode, - failing_bid.asset - ), - Error::::TooManyUserParticipations - ); - }); - } - #[test] fn per_credential_type_ticket_size_minimums() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); @@ -2077,6 +1880,8 @@ mod end_auction_extrinsic { let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); + let bucket = inst.execute(|| Buckets::::get(project_id).unwrap()); + let plmc_amounts = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( &bids, project_metadata.clone(), @@ -2107,8 +1912,11 @@ mod end_auction_extrinsic { assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful)); - let bidder_5_rejected_bid = inst.execute(|| Bids::::get((project_id, BIDDER_5, 2)).unwrap()); - let bidder_5_accepted_bid = inst.execute(|| Bids::::get((project_id, BIDDER_5, 3)).unwrap()); + let bidder_5_rejected_bid = + inst.execute(|| Bids::::get((project_id, project_metadata.minimum_price, 2)).unwrap()); + let bidder_5_accepted_bid = inst.execute(|| { + Bids::::get((project_id, project_metadata.minimum_price + bucket.delta_price, 3)).unwrap() + }); let bidder_5_plmc_pre_balance = inst.get_free_plmc_balance_for(bidder_5_rejected_bid.bidder); let bidder_5_funding_asset_pre_balance = inst.get_free_funding_asset_balance_for( bidder_5_rejected_bid.funding_asset.id(), @@ -2125,7 +1933,7 @@ mod end_auction_extrinsic { let returned_auction_plmc = inst.calculate_auction_plmc_returned_from_all_bids_made(&bids, project_metadata.clone(), wap); let returned_funding_assets = - inst.calculate_auction_funding_asset_returned_from_all_bids_made(&bids, project_metadata, wap); + inst.calculate_auction_funding_asset_returned_from_all_bids_made(&bids, project_metadata.clone(), wap); let expected_free_plmc = inst .generic_map_operation(vec![returned_auction_plmc.clone(), prev_plmc_balances], MergeOperation::Add); @@ -2144,8 +1952,8 @@ mod end_auction_extrinsic { let expected_issuer_funding = inst.sum_funding_asset_mappings(vec![expected_final_funding_spent]); // Assertions about rejected bid - let bidder_plmc_post_balance = inst.get_free_plmc_balance_for(bidder_5_rejected_bid.bidder); - let bidder_funding_asset_post_balance = inst.get_free_funding_asset_balance_for( + let bidder_5_plmc_post_balance = inst.get_free_plmc_balance_for(bidder_5_rejected_bid.bidder); + let bidder_5_funding_asset_post_balance = inst.get_free_funding_asset_balance_for( bidder_5_rejected_bid.funding_asset.id(), bidder_5_rejected_bid.bidder, ); @@ -2157,10 +1965,12 @@ mod end_auction_extrinsic { let bidder_5_returned_funding_asset = returned_funding_assets.iter().find(|x| x.account == BIDDER_5).unwrap().asset_amount; - assert!(inst.execute(|| Bids::::get((project_id, BIDDER_1, 2))).is_none()); - assert_eq!(bidder_plmc_post_balance, bidder_5_plmc_pre_balance + bidder_5_returned_plmc); + assert!(inst + .execute(|| Bids::::get((project_id, project_metadata.minimum_price, 2))) + .is_none()); + assert_eq!(bidder_5_plmc_post_balance, bidder_5_plmc_pre_balance + bidder_5_returned_plmc); assert_eq!( - bidder_funding_asset_post_balance, + bidder_5_funding_asset_post_balance, bidder_5_funding_asset_pre_balance + bidder_5_returned_funding_asset ); diff --git a/pallets/funding/src/tests/5_funding_end.rs b/pallets/funding/src/tests/5_funding_end.rs index 0fb1f87e0..8b0d5c443 100644 --- a/pallets/funding/src/tests/5_funding_end.rs +++ b/pallets/funding/src/tests/5_funding_end.rs @@ -1,43 +1,6 @@ use super::*; use sp_runtime::PerThing; -#[cfg(test)] -mod round_flow { - use super::*; - - #[cfg(test)] - mod success { - use super::*; - - #[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.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, - inst.generate_successful_evaluations(project_metadata.clone(), 5), - bids, - ); - - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - assert!(wap > project_metadata.minimum_price); - } - } -} - #[cfg(test)] mod end_funding_extrinsic { use super::*; diff --git a/pallets/funding/src/tests/6_settlement.rs b/pallets/funding/src/tests/6_settlement.rs index 3c1272688..8d27fc54c 100644 --- a/pallets/funding/src/tests/6_settlement.rs +++ b/pallets/funding/src/tests/6_settlement.rs @@ -16,20 +16,12 @@ mod round_flow { let (mut inst, project_id) = create_project_with_funding_percentage(percentage, true); let evaluations = inst.get_evaluations(project_id); - let settlement_ordered_bids = inst.execute(|| { - Pallet::::get_ordered_bid_settlements(project_id) - .into_iter() - .map(|(project_id, account, bid_id)| { - Bids::::get((project_id, account, bid_id)).unwrap() - }) - .collect_vec() - }); - + let bids = inst.get_bids(project_id); inst.settle_project(project_id, true); - inst.assert_total_funding_paid_out(project_id, settlement_ordered_bids.clone()); + inst.assert_total_funding_paid_out(project_id, bids.clone()); inst.assert_evaluations_migrations_created(project_id, evaluations, true); - inst.assert_bids_migrations_created(project_id, settlement_ordered_bids, true); + inst.assert_bids_migrations_created(project_id, bids, true); } #[test] @@ -49,6 +41,15 @@ mod round_flow { fn ethereum_project_can_be_settled() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let mut project_metadata = default_project_metadata(ISSUER_1); + let base_price = PriceOf::::from_float(1.0); + let decimal_aware_price = ::PriceProvider::calculate_decimals_aware_price( + base_price, + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + project_metadata.minimum_price = decimal_aware_price; + project_metadata.participants_account_type = ParticipantsAccountType::Ethereum; let evaluations = vec![ @@ -110,7 +111,15 @@ mod round_flow { #[test] fn polkadot_project_with_different_receiving_accounts_can_be_settled() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); + let mut project_metadata = default_project_metadata(ISSUER_1); + let base_price = PriceOf::::from_float(1.0); + let decimal_aware_price = ::PriceProvider::calculate_decimals_aware_price( + base_price, + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + project_metadata.minimum_price = decimal_aware_price; let evaluations = vec![ EvaluationParams::from((EVALUATOR_1, 500_000 * USD_UNIT, polkadot_junction!(EVALUATOR_1 + 420))), @@ -248,7 +257,6 @@ mod settle_evaluation_extrinsic { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let mut project_metadata = default_project_metadata(ISSUER_1); project_metadata.total_allocation_size = 1_000_000 * CT_UNIT; - let project_id = inst.create_finished_project( project_metadata.clone(), ISSUER_1, @@ -258,7 +266,7 @@ mod settle_evaluation_extrinsic { EvaluationParams::from((EVALUATOR_2, 250_000 * USD_UNIT)), EvaluationParams::from((EVALUATOR_3, 320_000 * USD_UNIT)), ], - inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 100, 5), + inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 100, 30), ); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); @@ -480,6 +488,14 @@ mod settle_bid_extrinsic { let usdt_ed = inst.get_funding_asset_ed(AcceptedFundingAsset::USDT.id()); let dot_ed = inst.get_funding_asset_ed(AcceptedFundingAsset::DOT.id()); let mut project_metadata = default_project_metadata(ISSUER_1); + let base_price = PriceOf::::from_float(1.0); + let decimal_aware_price = ::PriceProvider::calculate_decimals_aware_price( + base_price, + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + project_metadata.minimum_price = decimal_aware_price; project_metadata.participation_currencies = bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; let auction_allocation = project_metadata.total_allocation_size; @@ -502,10 +518,11 @@ mod settle_bid_extrinsic { let project_id = inst.create_finished_project(project_metadata.clone(), ISSUER_1, None, evaluations, bids); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); + let bucket = inst.execute(|| Buckets::::get(project_id).unwrap()); // Partial amount bid assertions let partial_amount_bid_stored = - inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); + inst.execute(|| Bids::::get((project_id, project_metadata.minimum_price, 0)).unwrap()); let mut final_partial_amount_bid_params = partial_amount_bid_params.clone(); final_partial_amount_bid_params.amount = auction_allocation - 2000 * CT_UNIT; let expected_final_plmc_bonded = inst.calculate_auction_plmc_charged_with_given_price( @@ -527,7 +544,9 @@ mod settle_bid_extrinsic { project_metadata.funding_destination_account, ); - let lower_price_bid_stored = inst.execute(|| Bids::::get((project_id, BIDDER_2, 1)).unwrap()); + let lower_price_bid_stored = inst.execute(|| { + Bids::::get((project_id, project_metadata.minimum_price + bucket.delta_price, 1)).unwrap() + }); let pre_issuer_dot_balance = inst.get_free_funding_asset_balance_for( AcceptedFundingAsset::DOT.id(), project_metadata.funding_destination_account, @@ -629,6 +648,14 @@ mod settle_bid_extrinsic { let usdt_ed = inst.get_funding_asset_ed(AcceptedFundingAsset::USDT.id()); let mut project_metadata = default_project_metadata(ISSUER_1); + let base_price = PriceOf::::from_float(1.0); + let decimal_aware_price = ::PriceProvider::calculate_decimals_aware_price( + base_price, + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + project_metadata.minimum_price = decimal_aware_price; project_metadata.participation_currencies = bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; let auction_allocation = project_metadata.total_allocation_size; @@ -649,7 +676,8 @@ mod settle_bid_extrinsic { ); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); - let no_refund_bid_stored = inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); + let no_refund_bid_stored = + inst.execute(|| Bids::::get((project_id, project_metadata.minimum_price, 0)).unwrap()); let pre_issuer_usdc_balance = inst.get_free_funding_asset_balance_for( AcceptedFundingAsset::USDT.id(), @@ -657,7 +685,12 @@ mod settle_bid_extrinsic { ); inst.execute(|| { - assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 0)); + assert_ok!(PolimecFunding::settle_bid( + RuntimeOrigin::signed(BIDDER_1), + project_id, + project_metadata.minimum_price, + 0 + )); }); let post_issuer_usdc_balance = inst.get_free_funding_asset_balance_for( @@ -727,7 +760,8 @@ mod settle_bid_extrinsic { assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); // Partial amount bid assertions - let no_refund_bid_stored = inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); + let no_refund_bid_stored = + inst.execute(|| Bids::::get((project_id, project_metadata.minimum_price, 0)).unwrap()); let pre_issuer_usdc_balance = inst.get_free_funding_asset_balance_for( AcceptedFundingAsset::USDT.id(), @@ -735,7 +769,12 @@ mod settle_bid_extrinsic { ); inst.execute(|| { - assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 0)); + assert_ok!(PolimecFunding::settle_bid( + RuntimeOrigin::signed(BIDDER_1), + project_id, + project_metadata.minimum_price, + 0 + )); }); let post_issuer_usdc_balance = inst.get_free_funding_asset_balance_for( @@ -768,6 +807,14 @@ mod settle_bid_extrinsic { let ed = inst.get_ed(); let usdt_ed = inst.get_funding_asset_ed(AcceptedFundingAsset::USDT.id()); let mut project_metadata = default_project_metadata(ISSUER_1); + let base_price = PriceOf::::from_float(0.5); + let decimal_aware_price = ::PriceProvider::calculate_decimals_aware_price( + base_price, + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + project_metadata.minimum_price = decimal_aware_price; project_metadata.participation_currencies = bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; let auction_allocation = project_metadata.total_allocation_size; @@ -791,7 +838,8 @@ mod settle_bid_extrinsic { let project_id = inst.create_finished_project(project_metadata.clone(), ISSUER_1, None, evaluations, bids); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); - let rejected_bid_stored = inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); + let rejected_bid_stored = + inst.execute(|| Bids::::get((project_id, project_metadata.minimum_price, 0)).unwrap()); let pre_issuer_usdt_balance = inst.get_free_funding_asset_balance_for( AcceptedFundingAsset::USDT.id(), @@ -833,21 +881,21 @@ mod settle_bid_extrinsic { fn cannot_settle_twice() { let percentage = 100u8; let (mut inst, project_id) = create_project_with_funding_percentage(percentage, true); - + let project_metadata = inst.get_project_metadata(project_id); let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); inst.execute(|| { let bidder = first_bid.bidder; assert_ok!(crate::Pallet::::settle_bid( RuntimeOrigin::signed(bidder), project_id, - bidder, + project_metadata.minimum_price, first_bid.id )); assert_noop!( crate::Pallet::::settle_bid( RuntimeOrigin::signed(bidder), project_id, - bidder, + project_metadata.minimum_price, first_bid.id ), Error::::ParticipationNotFound @@ -859,7 +907,7 @@ mod settle_bid_extrinsic { fn cannot_be_called_before_settlement_started() { let percentage = 100u8; let (mut inst, project_id) = create_project_with_funding_percentage(percentage, false); - + let project_metadata = inst.get_project_metadata(project_id); let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); let bidder = first_bid.bidder; inst.execute(|| { @@ -867,7 +915,7 @@ mod settle_bid_extrinsic { crate::Pallet::::settle_bid( RuntimeOrigin::signed(bidder), project_id, - bidder, + project_metadata.minimum_price, first_bid.id ), Error::::SettlementNotStarted diff --git a/pallets/funding/src/tests/7_ct_migration.rs b/pallets/funding/src/tests/7_ct_migration.rs index c98bdb39e..ea39766b6 100644 --- a/pallets/funding/src/tests/7_ct_migration.rs +++ b/pallets/funding/src/tests/7_ct_migration.rs @@ -11,7 +11,7 @@ mod pallet_migration { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_metadata = default_project_metadata(ISSUER_1); let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 90, 5); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 90, 10); let project_id = inst.create_finished_project(project_metadata.clone(), ISSUER_1, None, evaluations, bids); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); inst.settle_project(project_id, true); @@ -49,12 +49,12 @@ mod pallet_migration { })) ); assert_eq!(project_details.status, ProjectStatus::CTMigrationStarted); - assert_eq!(inst.execute(|| UnmigratedCounter::::get(project_id)), 10); + assert_eq!(inst.execute(|| UnmigratedCounter::::get(project_id)), 15); } fn create_pallet_migration_project(mut inst: MockInstantiator) -> (ProjectId, MockInstantiator) { let evaluations = inst.generate_successful_evaluations(default_project_metadata(ISSUER_1), 5); - let bids = inst.generate_bids_from_total_ct_percent(default_project_metadata(ISSUER_1), 90, 5); + let bids = inst.generate_bids_from_total_ct_percent(default_project_metadata(ISSUER_1), 90, 10); let project_id = inst.create_finished_project(default_project_metadata(ISSUER_1), ISSUER_1, None, evaluations, bids); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); @@ -174,7 +174,7 @@ mod offchain_migration { fn start_offchain_migration() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let evaluations = inst.generate_successful_evaluations(default_project_metadata(ISSUER_1), 5); - let bids = inst.generate_bids_from_total_ct_percent(default_project_metadata(ISSUER_1), 90, 5); + let bids = inst.generate_bids_from_total_ct_percent(default_project_metadata(ISSUER_1), 90, 10); // Create migrations for 2 projects, to check the `remaining_participants` is unaffected by other projects let project_id = inst.create_finished_project( default_project_metadata(ISSUER_1), @@ -201,14 +201,14 @@ mod offchain_migration { }); let project_details = inst.get_project_details(project_id); - assert_eq!(inst.execute(|| UnmigratedCounter::::get(project_id)), 10); + assert_eq!(inst.execute(|| UnmigratedCounter::::get(project_id)), 15); assert_eq!(project_details.status, ProjectStatus::CTMigrationStarted); } fn create_offchain_migration_project(mut inst: MockInstantiator) -> (ProjectId, MockInstantiator) { let project_metadata = default_project_metadata(ISSUER_1); let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 90, 5); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 90, 10); let project_id = inst.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids); assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); inst.settle_project(project_id, true); diff --git a/pallets/funding/src/tests/misc.rs b/pallets/funding/src/tests/misc.rs index af00bb0d6..223ad4b5f 100644 --- a/pallets/funding/src/tests/misc.rs +++ b/pallets/funding/src/tests/misc.rs @@ -347,7 +347,7 @@ fn project_state_transition_event() { ISSUER_1, None, inst.generate_successful_evaluations(project_metadata.clone(), 5), - inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 80, 5), + inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 80, 10), true, ); @@ -376,3 +376,5 @@ fn project_state_transition_event() { assert_eq!(event, Event::ProjectPhaseTransition { project_id, phase: desired_transitions.next().unwrap() }); }); } + + diff --git a/pallets/funding/src/tests/mod.rs b/pallets/funding/src/tests/mod.rs index 7e2a6d752..f61061801 100644 --- a/pallets/funding/src/tests/mod.rs +++ b/pallets/funding/src/tests/mod.rs @@ -108,9 +108,9 @@ pub mod defaults { total_allocation_size: 500_000 * CT_UNIT, minimum_price: decimal_aware_price, bidding_ticket_sizes: BiddingTicketSizes { - professional: TicketSize::new(10 * USD_UNIT, None), - institutional: TicketSize::new(10 * USD_UNIT, None), - retail: TicketSize::new(10 * USD_UNIT, None), + professional: TicketSize::new(100 * USD_UNIT, None), + institutional: TicketSize::new(100 * USD_UNIT, None), + retail: TicketSize::new(100 * USD_UNIT, None), phantom: Default::default(), }, participation_currencies: vec![USDT, USDC, DOT, WETH].try_into().unwrap(), @@ -157,7 +157,7 @@ pub mod defaults { // Used only to generate values, not for chain interactions let inst = MockInstantiator::new(None); let project_metadata = default_project_metadata(ISSUER_1); - inst.generate_bids_from_total_ct_percent(project_metadata, percent, 5) + inst.generate_bids_from_total_ct_percent(project_metadata, percent, 10) } } @@ -166,7 +166,7 @@ pub fn create_project_with_funding_percentage(percentage: u8, start_settlement: let mut project_metadata = default_project_metadata(ISSUER_1); project_metadata.total_allocation_size = 1_000_000 * CT_UNIT; let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), percentage, 5); + let bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), percentage, 30); let project_id = inst.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids); diff --git a/pallets/funding/src/tests/runtime_api.rs b/pallets/funding/src/tests/runtime_api.rs index 9434224de..9dac771cf 100644 --- a/pallets/funding/src/tests/runtime_api.rs +++ b/pallets/funding/src/tests/runtime_api.rs @@ -51,12 +51,12 @@ fn top_bids() { ]; let project_metadata = default_project_metadata(ISSUER_1); let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let project_id = inst.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids); + let project_id = inst.create_finished_project(project_metadata.clone(), ISSUER_1, None, 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(); + let bidder_4_evaluation = Bids::::get((project_id, project_metadata.minimum_price, 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) @@ -176,7 +176,7 @@ fn contribution_tokens() { let bids_with_bob_1 = inst.generate_bids_from_total_ct_amount(1, bob_amount_1); let bob = bids_with_bob_1[0].bidder; - let bob_amount_2 = 500_000 * CT_UNIT; + let bob_amount_2 = 490_000 * CT_UNIT; let bids_with_bob_2 = inst.generate_bids_from_total_ct_amount(1, bob_amount_2); let bob_amount_3 = 300_020 * CT_UNIT; @@ -185,34 +185,28 @@ fn contribution_tokens() { let bob_amount_4 = 250_100 * CT_UNIT; let bids_with_bob_4 = inst.generate_bids_from_total_ct_amount(1, bob_amount_4); - let project_metadata = default_project_metadata(ISSUER_1); - let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let get_project = |issuer| { + let mut project_metadata = default_project_metadata(issuer); + let base_price = PriceOf::::from_float(1.0); + let decimal_aware_price = ::PriceProvider::calculate_decimals_aware_price( + base_price, + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + project_metadata.minimum_price = decimal_aware_price; + project_metadata + }; + + let evaluations = inst.generate_successful_evaluations(get_project(ISSUER_1), 5); let project_id_1 = - inst.create_settled_project(project_metadata, ISSUER_1, None, evaluations.clone(), bids_with_bob_1, true); - let project_id_2 = inst.create_settled_project( - default_project_metadata(ISSUER_2), - ISSUER_2, - None, - evaluations.clone(), - bids_with_bob_2, - true, - ); - let project_id_3 = inst.create_settled_project( - default_project_metadata(ISSUER_3), - ISSUER_3, - None, - evaluations.clone(), - bids_with_bob_3, - true, - ); - let project_id_4 = inst.create_settled_project( - default_project_metadata(ISSUER_4), - ISSUER_4, - None, - evaluations.clone(), - bids_with_bob_4, - true, - ); + inst.create_settled_project(get_project(ISSUER_1), ISSUER_1, None, evaluations.clone(), bids_with_bob_1, true); + let project_id_2 = + inst.create_settled_project(get_project(ISSUER_2), ISSUER_2, None, evaluations.clone(), bids_with_bob_2, true); + let project_id_3 = + inst.create_settled_project(get_project(ISSUER_3), ISSUER_3, None, evaluations.clone(), bids_with_bob_3, true); + let project_id_4 = + inst.create_settled_project(get_project(ISSUER_4), ISSUER_4, None, evaluations.clone(), bids_with_bob_4, true); let expected_items = vec![ (project_id_2, bob_amount_2), @@ -271,8 +265,7 @@ fn funding_asset_to_ct_amount_classic() { 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, 420, |acc| acc + 1); + let bids = inst.generate_bids_that_take_price_to(project_metadata_2.clone(), decimal_aware_price); let project_id_2 = inst.create_finished_project(project_metadata_2.clone(), ISSUER_2, None, evaluations.clone(), bids); // Sanity check @@ -303,13 +296,7 @@ fn funding_asset_to_ct_amount_classic() { // 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, - 420, - |acc| acc + 1, - AcceptedFundingAsset::USDT, - ); + let bids = inst.generate_bids_from_bucket(project_metadata_3.clone(), bucket, AcceptedFundingAsset::USDT); let necessary_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket(&bids, project_metadata_3.clone(), None); let necessary_usdt = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( @@ -413,8 +400,7 @@ fn funding_asset_to_ct_amount_otm() { 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, 420, |acc| acc + 1); + let bids = inst.generate_bids_that_take_price_to(project_metadata_2.clone(), decimal_aware_price); let project_id_2 = inst.create_finished_project(project_metadata_2.clone(), ISSUER_2, None, evaluations.clone(), bids); // Sanity check @@ -446,13 +432,7 @@ fn funding_asset_to_ct_amount_otm() { // 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, - 420, - |acc| acc + 1, - AcceptedFundingAsset::USDT, - ); + let bids = inst.generate_bids_from_bucket(project_metadata_3.clone(), bucket, AcceptedFundingAsset::USDT); let necessary_plmc = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket(&bids, project_metadata_3.clone(), None); let necessary_usdt = inst.calculate_auction_funding_asset_charged_from_all_bids_made_or_with_bucket( @@ -568,15 +548,15 @@ fn get_next_vesting_schedule_merge_candidates() { AcceptedFundingAsset::USDT, )), ]; + let mut project_metadata = default_project_metadata(ISSUER_1); + let base_price = PriceOf::::from_float(1.0); + let decimal_aware_price = + ::PriceProvider::calculate_decimals_aware_price(base_price, USD_DECIMALS, CT_DECIMALS) + .unwrap(); + project_metadata.minimum_price = decimal_aware_price; - let project_id = inst.create_settled_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - evaluations.clone(), - bids.clone(), - true, - ); + let project_id = + inst.create_settled_project(project_metadata, ISSUER_1, None, evaluations.clone(), bids.clone(), true); let events = inst.execute(|| System::events().into_iter().collect::>()); dbg!(events); @@ -619,12 +599,17 @@ fn get_next_vesting_schedule_merge_candidates() { fn calculate_otm_fee() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let mut project_metadata = default_project_metadata(ISSUER_1); + let base_price = PriceOf::::from_float(1.0); + let decimal_aware_price = + ::PriceProvider::calculate_decimals_aware_price(base_price, USD_DECIMALS, CT_DECIMALS) + .unwrap(); + project_metadata.minimum_price = decimal_aware_price; project_metadata.participation_currencies = bounded_vec![AcceptedFundingAsset::DOT]; let dot_id = AcceptedFundingAsset::DOT.id(); let dot_decimals = inst.execute(|| ForeignAssets::decimals(dot_id.clone())); let dot_unit = 10u128.pow(dot_decimals as u32); - let dot_ticket = 10_000 * dot_unit; + let dot_ticket = 1000 * dot_unit; let dot_ed = inst.get_funding_asset_ed(dot_id.clone()); let block_hash = inst.execute(|| System::block_hash(System::block_number())); @@ -775,7 +760,13 @@ 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 mut project_metadata = default_project_metadata(ISSUER_1); + let base_price = PriceOf::::from_float(1.0); + let decimal_aware_price = + ::PriceProvider::calculate_decimals_aware_price(base_price, USD_DECIMALS, CT_DECIMALS) + .unwrap(); + project_metadata.minimum_price = decimal_aware_price; + let cid = project_metadata.clone().policy_ipfs_cid.unwrap(); let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); @@ -893,7 +884,7 @@ fn projects_by_did() { let did_user = generate_did_from_account(420); let evaluations = inst.generate_successful_evaluations(default_project_metadata(ISSUER_1), 5); - let bids = inst.generate_bids_from_total_ct_percent(default_project_metadata(ISSUER_1), 80, 7); + let bids = inst.generate_bids_from_total_ct_percent(default_project_metadata(ISSUER_1), 80, 10); let project_id_1 = inst.create_settled_project( default_project_metadata(ISSUER_1), ISSUER_1, diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index 44b77eb20..2d8319369 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -42,6 +42,7 @@ pub mod config { #[allow(clippy::wildcard_imports)] use super::*; use crate::Balance; + use polimec_common::USD_UNIT; use sp_core::parameter_types; use xcm::v4::Location; @@ -152,6 +153,8 @@ pub mod config { pub const RETAIL_MAX_MULTIPLIER: u8 = 5u8; pub const PROFESSIONAL_MAX_MULTIPLIER: u8 = 10u8; pub const INSTITUTIONAL_MAX_MULTIPLIER: u8 = 25u8; + /// We limit this because an oversubscribed bid has to read through all bids it kicks off, and we need an upper bound on reads per block + pub const MAX_USD_TICKET_PER_BID_EXTRINSIC: Balance = 500_000 * USD_UNIT; parameter_types! { pub HereLocationGetter: Location = Location::here(); @@ -199,7 +202,7 @@ pub mod storage { let usd_unit = sp_arithmetic::traits::checked_pow(10u128, USD_DECIMALS as usize) .ok_or(MetadataError::BadTokenomics)?; - let min_bound_usd: Balance = usd_unit.checked_mul(10u128).ok_or(MetadataError::BadTokenomics)?; + let min_bound_usd: Balance = usd_unit.checked_mul(100u128).ok_or(MetadataError::BadTokenomics)?; self.bidding_ticket_sizes.is_valid(vec![ InvestorTypeUSDBounds::Professional((min_bound_usd, None).into()), InvestorTypeUSDBounds::Institutional((min_bound_usd, None).into()), @@ -794,9 +797,8 @@ pub mod extrinsic { pub now: BlockNumberFor, pub did: Did, pub metadata_ticket_size_bounds: TicketSize, - pub total_bids_by_bidder: u32, - pub total_bids_for_project: u32, pub receiving_account: Junction, + pub auction_oversubscribed: bool, } pub struct DoContributeParams { diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index 01002f81d..402d803a0 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -43,8 +43,8 @@ use frame_system::{EnsureRoot, EnsureRootWithSuccess, EnsureSigned, EnsureSigned use pallet_aura::Authorities; use pallet_democracy::GetElectorate; use pallet_funding::{ - runtime_api::ProjectParticipationIds, BidInfoOf, DaysToBlocks, EvaluationInfoOf, HereLocationGetter, - PriceProviderOf, ProjectDetailsOf, ProjectId, ProjectMetadataOf, + BidInfoOf, DaysToBlocks, EvaluationInfoOf, HereLocationGetter, PriceProviderOf, ProjectDetailsOf, ProjectId, + ProjectMetadataOf, }; use parachains_common::{ impls::AssetsToBlockAuthor, @@ -179,8 +179,8 @@ pub mod migrations { /// Unreleased migrations. Add new ones here: #[allow(unused_parens)] pub type Unreleased = ( - super::custom_migrations::asset_id_migration::FromOldAssetIdMigration, - pallet_funding::storage_migrations::v6::MigrationToV6, + // super::custom_migrations::asset_id_migration::FromOldAssetIdMigration, + // pallet_funding::storage_migrations::v6::MigrationToV6, ); } @@ -1076,12 +1076,7 @@ impl pallet_funding::Config for Runtime { type FundingCurrency = ForeignAssets; type FundingSuccessThreshold = FundingSuccessThreshold; type InvestorOrigin = EnsureInvestor; - type MaxBidsPerProject = ConstU32<512>; - type MaxBidsPerUser = ConstU32<16>; type MaxCapacityThresholds = MaxCapacityThresholds; - type MaxContributionsPerUser = ConstU32<16>; - type MaxEvaluationsPerProject = ConstU32<512>; - type MaxEvaluationsPerUser = ConstU32<16>; type MaxMessageSizeThresholds = MaxMessageSizeThresholds; type MinUsdPerEvaluation = MinUsdPerEvaluation; type Multiplier = pallet_funding::types::Multiplier; @@ -1523,10 +1518,6 @@ impl_runtime_apis! { 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 { @@ -1558,9 +1549,6 @@ impl_runtime_apis! { fn get_message_to_sign_by_receiving_account(project_id: ProjectId, polimec_account: AccountId) -> Option { Funding::get_message_to_sign_by_receiving_account(project_id, polimec_account) } - fn get_ordered_bid_settlements(project_id: ProjectId) -> Vec<(ProjectId, AccountId, u32)> { - Funding::get_ordered_bid_settlements(project_id) - } } #[cfg(feature = "try-runtime")]