diff --git a/benchmarked-extrinsics.rs b/benchmarked-extrinsics.rs new file mode 100644 index 000000000..7a50290bf --- /dev/null +++ b/benchmarked-extrinsics.rs @@ -0,0 +1,155 @@ +// 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-02-05, 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:10 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::CTAmountOversubscribed` (r:1 w:1) + /// Proof: `Funding::CTAmountOversubscribed` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`) + /// Storage: `Funding::Bids` (r:0 w:10) + /// Proof: `Funding::Bids` (`max_values`: None, `max_size`: Some(309), added: 2784, mode: `MaxEncodedLen`) + /// The range of component `x` is `[1, 10]`. + fn bid(x: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `2890` + // Estimated: `7404 + x * (2535 ±0)` + // Minimum execution time: 184_000_000 picoseconds. + Weight::from_parts(139_444_380, 7404) + // Standard Error: 76_200 + .saturating_add(Weight::from_parts(59_216_258, 0).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().reads(15_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(x.into()))) + .saturating_add(T::DbWeight::get().writes(8_u64)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(x.into()))) + .saturating_add(Weight::from_parts(0, 2535).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:10 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::CTAmountOversubscribed` (r:1 w:1) + /// Proof: `Funding::CTAmountOversubscribed` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`) + /// Storage: `Funding::Bids` (r:0 w:10) + /// Proof: `Funding::Bids` (`max_values`: None, `max_size`: Some(309), added: 2784, mode: `MaxEncodedLen`) + /// The range of component `x` is `[1, 10]`. + fn bid(x: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `2890` + // Estimated: `7404 + x * (2535 ±0)` + // Minimum execution time: 184_000_000 picoseconds. + Weight::from_parts(139_444_380, 7404) + // Standard Error: 76_200 + .saturating_add(Weight::from_parts(59_216_258, 0).saturating_mul(x.into())) + .saturating_add(RocksDbWeight::get().reads(15_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(x.into()))) + .saturating_add(RocksDbWeight::get().writes(8_u64)) + .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(x.into()))) + .saturating_add(Weight::from_parts(0, 2535).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 b5fac0c73..7ffea6b54 100644 --- a/integration-tests/src/tests/ct_migration.rs +++ b/integration-tests/src/tests/ct_migration.rs @@ -70,7 +70,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 +159,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!( @@ -171,10 +171,6 @@ fn create_settled_project() -> (ProjectId, Vec) { pallet_funding::Evaluations::::iter_prefix_values((project_id,)) .map(|eval| eval.evaluator) .chain(pallet_funding::Bids::::iter_prefix_values((project_id,)).map(|bid| bid.bidder)) - .chain( - pallet_funding::Contributions::::iter_prefix_values((project_id,)) - .map(|contribution| contribution.contributor), - ) .collect(); participants.sort(); participants.dedup(); @@ -211,8 +207,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!( @@ -221,12 +217,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 = pallet_funding::Bids::::iter_prefix_values((project_id,)).collect_vec(); + 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(|bid| bid.bidder.clone())) + .chain(bids_to_settle.iter().map(|x| x.bidder.clone())) .collect(); participants.sort(); participants.dedup(); @@ -242,9 +238,10 @@ fn create_project_with_unsettled_participation(participation_type: Participation .unwrap() } - let start = if participation_type == ParticipationType::Bid { 1 } else { 0 }; - for bid in bids_to_settle[start..].iter() { - PolimecFunding::settle_bid(RuntimeOrigin::signed(alice()), project_id, bid.bidder.clone(), bid.id).unwrap() + 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 bid in bids_to_settle[..end].iter() { + PolimecFunding::settle_bid(RuntimeOrigin::signed(alice()), project_id, bid.id).unwrap() } let evaluations = diff --git a/integration-tests/src/tests/e2e.rs b/integration-tests/src/tests/e2e.rs index b330b6193..43617bd8b 100644 --- a/integration-tests/src/tests/e2e.rs +++ b/integration-tests/src/tests/e2e.rs @@ -266,7 +266,6 @@ fn participate_with_checks( mut inst: IntegrationInstantiator, project_id: ProjectId, participation_type: ParticipationType, - participation_id: u32, user: [u8; 32], mode: ParticipationMode, investor_type: InvestorType, @@ -311,9 +310,6 @@ fn participate_with_checks( if participation_type == ParticipationType::Bid { PolimecFunding::bid(PolimecOrigin::signed(user.clone()), user_jwt, project_id, ct_amount, mode, funding_asset) .unwrap(); - let stored_bid = Bids::::get((project_id, user.clone(), participation_id)); - // dbg!(&stored_bid); - assert!(stored_bid.is_some()); } let post_participation_free_plmc = PolimecBalances::free_balance(user.clone()); @@ -399,7 +395,6 @@ fn e2e_test() { inst, project_id, ParticipationType::Bid, - bid_id, user, mode, investor_type, @@ -425,13 +420,8 @@ 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()); - PolimecFunding::settle_bid( - PolimecOrigin::signed(rejected_bidder.clone()), - project_id, - rejected_bidder.clone(), - rejected_bid_id, - ) - .unwrap(); + PolimecFunding::settle_bid(PolimecOrigin::signed(rejected_bidder.clone()), project_id, rejected_bid_id) + .unwrap(); let post_bid_free_plmc = PolimecBalances::free_balance(rejected_bidder.clone()); let post_bid_reserved_plmc = PolimecBalances::reserved_balance(rejected_bidder.clone()); @@ -663,3 +653,12 @@ fn e2e_test() { } }); } + +#[test] +fn sandbox() { + let bid_weight = ::WeightInfo::bid(1); + let process_weight = ::WeightInfo::process_next_oversubscribed_bid(); + + dbg!(bid_weight); + dbg!(process_weight); +} diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs index 728ce9d0d..58a475432 100644 --- a/pallets/funding/src/benchmarking.rs +++ b/pallets/funding/src/benchmarking.rs @@ -21,7 +21,6 @@ use super::*; use crate::{instantiator::*, traits::SetPrices}; use polimec_common::assets::AcceptedFundingAsset; -use ParticipationMode::{Classic, OTM}; use frame_benchmarking::v2::*; use frame_support::{ @@ -72,9 +71,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 +83,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 +229,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 +308,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 +323,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 +372,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 +448,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 +463,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 +476,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 +564,72 @@ 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, - ); + // First bucket with whole allocation should be outbid. + assert_eq!(cutoff, (project_metadata.minimum_price, 0)); + } - 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); - } + // We benchmark the worst case, which is a new cutoff being calculated. + // This doesn't happen when the first bid we read is partially accepted instead of rejected. + #[benchmark] + fn process_next_oversubscribed_bid() { + // * setup * + let mut inst = BenchInstantiator::::new(None); + ::SetPrices::set_prices(); - let current_bucket = Buckets::::get(project_id).unwrap(); - assert_eq!(current_bucket, expected_bucket); + // We can't see events at block 0 + inst.advance_time(1u32.into()); - // 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 issuer = account::>("issuer", 0, 0); + let bidder = account::>("bidder", 0, 0); + whitelist_account!(bidder); + + let project_metadata = default_project_metadata::(issuer.clone()); + let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); + let first_bucket_bids = inst.generate_bids_from_total_ct_percent(project_metadata.clone(), 100, 10); + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); - let free_plmc = inst.get_free_plmc_balances_for(vec![bidder.clone()])[0].plmc_amount; - assert_eq!(free_plmc, total_free_plmc); + inst.mint_necessary_tokens_for_bids(project_id, first_bucket_bids.clone()); + inst.bid_for_users(project_id, first_bucket_bids.clone()).unwrap(); - 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 oversubscribing_bid_amount = first_bucket_bids[9].amount; + let oversubscribing_bid = BidParams::from(( + bidder.clone(), + Institutional, + oversubscribing_bid_amount, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + )); + inst.mint_necessary_tokens_for_bids(project_id, vec![oversubscribing_bid.clone()]); + inst.bid_for_users(project_id, vec![oversubscribing_bid.clone()]).unwrap(); - let free_usdt = inst.get_free_funding_asset_balance_for(usdt_id(), bidder); - assert_eq!(free_usdt, total_free_usdt); + Pallet::::do_process_next_oversubscribed_bid(project_id).unwrap(); + let pre_cutoff = OutbidBidsCutoff::::get(project_id).unwrap(); - // 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"); - } + inst.mint_necessary_tokens_for_bids(project_id, vec![oversubscribing_bid.clone()]); + inst.bid_for_users(project_id, vec![oversubscribing_bid.clone()]).unwrap(); + + #[extrinsic_call] + process_next_oversubscribed_bid(RawOrigin::Signed(bidder), project_id); + + // * validity checks * + let oversubscribed_amount = CTAmountOversubscribed::::get(project_id); + assert!(oversubscribed_amount.is_zero()); + + let rejected_bid = Bids::::get((project_id, 9)).unwrap(); + assert_eq!(rejected_bid.status, BidStatus::Rejected); + + let rejected_bid = Bids::::get((project_id, 8)).unwrap(); + assert_eq!(rejected_bid.status, BidStatus::Rejected); + + let post_cutoff = OutbidBidsCutoff::::get(project_id).unwrap(); + assert_ne!(pre_cutoff, post_cutoff); } // end_funding has 2 logic paths: @@ -868,13 +706,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 +735,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 +799,41 @@ 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 bids = inst.generate_bids_from_bucket( - project_metadata.clone(), - target_bucket, - bidder_accounts.next().unwrap(), - |_| bidder_accounts.next().unwrap(), - AcceptedFundingAsset::USDT, - ); - - let project_id = inst.create_finished_project( - project_metadata.clone(), - issuer, - None, - default_evaluations::(), - bids.clone(), - ); + let increase = project_metadata.minimum_price * PriceOf::::saturating_from_rational(5, 10); + let target_wap = project_metadata.minimum_price + increase; - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); + 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 bidder = bids.last().unwrap().bidder.clone(); - whitelist_account!(bidder); + let project_id = + inst.create_finished_project(project_metadata.clone(), issuer, None, evaluations, bids.clone()); 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() + }); - // Make sure a refund has to happen - assert!(bid_to_settle.original_ct_usd_price > wap); + let bidder = bid_to_settle.bidder.clone(); + whitelist_account!(bidder); + + 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.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.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,8 +841,9 @@ mod benchmarks { project_id, account: bidder.clone(), id: bid_to_settle.id, - 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(), ); @@ -1033,14 +860,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); @@ -1059,8 +883,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); @@ -1077,13 +901,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); @@ -1092,6 +916,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(|_| { @@ -1104,12 +933,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 = @@ -1140,8 +963,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] @@ -1153,14 +976,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(), @@ -1197,14 +1016,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(), @@ -1254,14 +1069,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(), @@ -1328,14 +1139,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(), @@ -1408,7 +1215,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); @@ -1424,7 +1231,7 @@ mod benchmarks { BidParams::from(( participant.clone(), Institutional, - (500 * CT_UNIT).into(), + (50 * CT_UNIT).into(), ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, )) @@ -1432,8 +1239,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 = @@ -1480,9 +1287,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); @@ -1498,7 +1305,7 @@ mod benchmarks { BidParams::from(( participant.clone(), Institutional, - (500 * CT_UNIT).into(), + (50 * CT_UNIT).into(), ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, )) @@ -1506,9 +1313,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 = @@ -1571,14 +1379,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(), @@ -1610,14 +1414,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(), @@ -1651,14 +1451,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..8a6713b0e 100644 --- a/pallets/funding/src/functions/2_evaluation.rs +++ b/pallets/funding/src/functions/2_evaluation.rs @@ -45,6 +45,10 @@ impl Pallet { // * Branch in possible project paths * // Successful path return if is_funded { + let mut project_ids = ProjectsInAuctionRound::::get().to_vec(); + project_ids.push(project_id); + let project_ids = WeakBoundedVec::force_from(project_ids, None); + ProjectsInAuctionRound::::put(project_ids); Self::transition_project( project_id, project_details, diff --git a/pallets/funding/src/functions/3_auction.rs b/pallets/funding/src/functions/3_auction.rs index 67894453c..4039d9d0e 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, @@ -68,7 +64,6 @@ impl Pallet { // 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 +79,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,10 +92,20 @@ 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, }; + + 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_id == *j + 1); + *maybe_indexes = Some((*i, bid_id)); + } else { + *maybe_indexes = Some((bid_id, bid_id)); + } + }); + Self::do_perform_bid(perform_params)?; perform_bid_calls = perform_bid_calls.saturating_add(1); @@ -112,10 +118,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] @@ -131,26 +134,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, @@ -171,10 +171,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, 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 { + CTAmountOversubscribed::::mutate(project_id, |amount| *amount = amount.saturating_add(ct_amount)); + } Self::deposit_event(Event::Bid { project_id, @@ -190,4 +193,73 @@ impl Pallet { Ok(new_bid) } + + /// Go over one oversubscribed bid and update the cutoff. + pub fn do_process_next_oversubscribed_bid(project_id: ProjectId) -> 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 ct_amount_oversubscribed = CTAmountOversubscribed::::get(project_id); + + ensure!(ct_amount_oversubscribed > Zero::zero(), Error::::NoBidsOversubscribed); + + let current_cutoff: (PriceOf, u32); + + // Adjust initial cutoff if necessary + if let Some((price, index)) = maybe_current_cutoff { + let bid = Bids::::get((project_id, index)).ok_or(Error::::ImpossibleState)?; + if matches!(bid.status, BidStatus::PartiallyAccepted(_)) { + current_cutoff = (price, index); + } else { + let (new_price, new_index) = Self::get_next_cutoff(project_id, bucket.delta_price, price, index)?; + current_cutoff = (new_price, new_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); + } + + let (_price, index) = current_cutoff; + + let mut bid = Bids::::get((project_id, index)).ok_or(Error::::ImpossibleState)?; + + let bid_amount = match bid.status { + BidStatus::PartiallyAccepted(ct_amount_accepted) => ct_amount_accepted, + _ => bid.original_ct_amount, + }; + + if bid_amount > ct_amount_oversubscribed { + bid.status = BidStatus::PartiallyAccepted(bid_amount.saturating_sub(ct_amount_oversubscribed)); + Bids::::insert((project_id, bid.id), bid); + ct_amount_oversubscribed = Zero::zero(); + } else { + bid.status = BidStatus::Rejected; + Bids::::insert((project_id, bid.id), bid); + ct_amount_oversubscribed = ct_amount_oversubscribed.saturating_sub(bid_amount); + } + + OutbidBidsCutoff::::set(project_id, Some(current_cutoff)); + CTAmountOversubscribed::::insert(project_id, ct_amount_oversubscribed); + + 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 6ff0cede1..b1c709ceb 100644 --- a/pallets/funding/src/functions/5_funding_end.rs +++ b/pallets/funding/src/functions/5_funding_end.rs @@ -1,62 +1,57 @@ #[allow(clippy::wildcard_imports)] use super::*; +use itertools::Itertools; impl Pallet { #[transactional] pub fn do_end_funding(project_id: ProjectId) -> DispatchResult { // * Get variables * let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; - let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; let bucket = Buckets::::get(project_id).ok_or(Error::::BucketNotFound)?; - let remaining_cts = project_details.remaining_contribution_tokens; let now = >::block_number(); let issuer_did = project_details.issuer_did.clone(); // * Validity checks * ensure!( - // Can end due to running out of CTs - remaining_cts == Zero::zero() || - // or the last funding round ending - project_details.round_duration.ended(now) && matches!(project_details.status, ProjectStatus::AuctionRound), + project_details.round_duration.ended(now) && matches!(project_details.status, ProjectStatus::AuctionRound), Error::::TooEarlyForRound ); - // * Calculate WAP * + let mut project_ids = ProjectsInAuctionRound::::get().to_vec(); + let (pos, _) = project_ids.iter().find_position(|id| **id == project_id).ok_or(Error::::ImpossibleState)?; + project_ids.remove(pos); + ProjectsInAuctionRound::::put(WeakBoundedVec::force_from(project_ids, None)); + let auction_allocation_size = project_metadata.total_allocation_size; - let weighted_token_price = bucket.calculate_wap(auction_allocation_size); + let weighted_average_price = bucket.calculate_wap(auction_allocation_size); + project_details.weighted_average_price = Some(weighted_average_price); + + let bucket_price_higher_than_initial = bucket.current_price > bucket.initial_price; + let sold_percent = + Perquintill::from_rational(auction_allocation_size - bucket.amount_left, auction_allocation_size); + let threshold = T::FundingSuccessThreshold::get(); + let sold_more_than_min = sold_percent >= threshold; - // * Update Storage * - let _calculation_result = - Self::decide_winning_bids(project_id, project_metadata.total_allocation_size, weighted_token_price)?; - let mut updated_project_details = - ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; + let funding_successful = bucket_price_higher_than_initial || sold_more_than_min; DidWithActiveProjects::::set(issuer_did, None); - // * Calculate new variables * - let funding_target = updated_project_details.fundraising_target_usd; - let funding_reached = updated_project_details.funding_amount_reached_usd; - let funding_ratio = Perquintill::from_rational(funding_reached, funding_target); + let usd_raised = Self::calculate_usd_sold_from_bucket(bucket.clone(), project_metadata.total_allocation_size); + project_details.funding_amount_reached_usd = usd_raised; + ProjectsDetails::::insert(project_id, project_details.clone()); // * Update project status * - let next_status = if funding_ratio < T::FundingSuccessThreshold::get() { - updated_project_details.evaluation_round_info.evaluators_outcome = Some(EvaluatorsOutcome::Slashed); - ProjectStatus::FundingFailed - } else { + let next_status = if funding_successful { let reward_info = Self::generate_evaluator_rewards_info(project_id)?; - updated_project_details.evaluation_round_info.evaluators_outcome = - Some(EvaluatorsOutcome::Rewarded(reward_info)); + project_details.evaluation_round_info.evaluators_outcome = Some(EvaluatorsOutcome::Rewarded(reward_info)); ProjectStatus::FundingSuccessful + } else { + project_details.evaluation_round_info.evaluators_outcome = Some(EvaluatorsOutcome::Slashed); + ProjectStatus::FundingFailed }; - Self::transition_project( - project_id, - updated_project_details.clone(), - project_details.status, - next_status, - None, - true, - )?; + Self::transition_project(project_id, project_details.clone(), project_details.status, next_status, None, true)?; Ok(()) } diff --git a/pallets/funding/src/functions/6_settlement.rs b/pallets/funding/src/functions/6_settlement.rs index e406f8d5a..e9b31a64d 100644 --- a/pallets/funding/src/functions/6_settlement.rs +++ b/pallets/funding/src/functions/6_settlement.rs @@ -154,18 +154,23 @@ impl Pallet { Ok(()) } - pub fn do_settle_bid(bid: BidInfoOf, project_id: ProjectId) -> DispatchResult { + pub fn do_settle_bid(project_id: ProjectId, bid_id: u32) -> DispatchResult { let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; let funding_success = matches!(project_details.status, ProjectStatus::SettlementStarted(FundingOutcome::Success)); - let wap = project_details.weighted_average_price.ok_or(Error::::ImpossibleState)?; + let wap = project_details.weighted_average_price.unwrap_or(project_metadata.minimum_price); + let mut bid = Bids::::get((project_id, bid_id)).ok_or(Error::::ParticipationNotFound)?; ensure!( matches!(project_details.status, ProjectStatus::SettlementStarted(..)) || bid.status == BidStatus::Rejected, Error::::SettlementNotStarted ); + if bid.status == BidStatus::YetUnknown { + bid.status = BidStatus::Accepted; + } + // 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 let BidRefund { final_ct_usd_price, final_ct_amount, refunded_plmc, refunded_funding_asset_amount } = @@ -186,7 +191,7 @@ impl Pallet { Self::release_participation_bond_for(&bid.bidder, refunded_plmc)?; } - if funding_success && bid.status != BidStatus::Rejected { + if funding_success { let ct_vesting_duration = Self::set_plmc_bond_release_with_mode( bid.bidder.clone(), bid.plmc_bond.saturating_sub(refunded_plmc), @@ -214,12 +219,13 @@ impl Pallet { )?; } - Bids::::remove((project_id, bid.bidder.clone(), bid.id)); + Bids::::remove((project_id, bid.id)); Self::deposit_event(Event::BidSettled { project_id, account: bid.bidder, id: bid.id, + status: bid.status, final_ct_amount, final_ct_usd_price, }); @@ -236,7 +242,7 @@ impl Pallet { ) -> Result, DispatchError> { let final_ct_usd_price = if bid.original_ct_usd_price > wap { wap } else { bid.original_ct_usd_price }; let multiplier: MultiplierOf = bid.mode.multiplier().try_into().map_err(|_| Error::::BadMath)?; - if bid.status == BidStatus::Rejected || !funding_success { + if !funding_success || bid.status == BidStatus::Rejected { return Ok(BidRefund:: { final_ct_usd_price, final_ct_amount: Zero::zero(), @@ -244,7 +250,11 @@ impl Pallet { refunded_funding_asset_amount: bid.funding_asset_amount_locked, }); } - let final_ct_amount = bid.final_ct_amount(); + let final_ct_amount = match bid.status { + BidStatus::Accepted => bid.original_ct_amount, + BidStatus::PartiallyAccepted(accepted_amount) => accepted_amount, + _ => Zero::zero(), + }; let new_ticket_size = final_ct_usd_price.checked_mul_int(final_ct_amount).ok_or(Error::::BadMath)?; let new_plmc_bond = Self::calculate_plmc_bond(new_ticket_size, multiplier)?; @@ -265,13 +275,9 @@ impl Pallet { // We use closers to do an early return if just one of these storage iterators returns a value. let no_evaluations_remaining = || Evaluations::::iter_prefix((project_id,)).next().is_none(); let no_bids_remaining = || Bids::::iter_prefix((project_id,)).next().is_none(); - let no_contributions_remaining = || Contributions::::iter_prefix((project_id,)).next().is_none(); // Check if there are any evaluations, bids or contributions remaining - ensure!( - no_evaluations_remaining() && no_bids_remaining() && no_contributions_remaining(), - Error::::SettlementNotComplete - ); + ensure!(no_evaluations_remaining() && no_bids_remaining(), Error::::SettlementNotComplete); // Mark the project as settled Self::transition_project( @@ -414,22 +420,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/instantiator/chain_interactions.rs b/pallets/funding/src/instantiator/chain_interactions.rs index 427bbfe12..716a9f595 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 { @@ -478,7 +474,7 @@ impl< .for_each(|(_, evaluation)| Pallet::::do_settle_evaluation(evaluation, project_id).unwrap()); Bids::::iter_prefix((project_id,)) - .for_each(|(_, bid)| Pallet::::do_settle_bid(bid, project_id).unwrap()); + .for_each(|(_, bid)| Pallet::::do_settle_bid(project_id, bid.id).unwrap()); if mark_as_settled { crate::Pallet::::do_mark_project_as_settled(project_id).unwrap(); @@ -494,6 +490,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); @@ -580,14 +582,37 @@ impl< bids: Vec>, is_successful: bool, ) { + 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 { - let account = bid.bidder.clone(); - assert_eq!(self.execute(|| { Bids::::iter_prefix_values((&project_id, &account)).count() }), 0); - let amount: Balance = bid.final_ct_amount(); + // 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, - amount, + bid.bidder, + bid_ct_amount, bid.id, ParticipationType::Bid, bid.receiving_account, @@ -759,20 +784,7 @@ impl< let status = self.go_to_next_state(project_id); if status == ProjectStatus::FundingSuccessful { - // Check that remaining CTs are updated - let project_details = self.get_project_details(project_id); - // if our bids were creating an oversubscription, then just take the total allocation size - let auction_bought_tokens = bids - .iter() - .map(|bid| bid.amount) - .fold(Balance::zero(), |acc, item| item + acc) - .min(project_metadata.total_allocation_size); - - assert_eq!( - project_details.remaining_contribution_tokens, - project_metadata.total_allocation_size - auction_bought_tokens, - "Remaining CTs are incorrect" - ); + self.test_ct_not_created_for(project_id); } else if status == ProjectStatus::FundingFailed { self.test_ct_not_created_for(project_id); } else { @@ -799,9 +811,25 @@ impl< bids.clone(), ); + // if our bids were creating an oversubscription, then just take the total allocation size + let auction_bought_tokens = bids + .iter() + .map(|bid| bid.amount) + .fold(Balance::zero(), |acc, item| item + acc) + .min(project_metadata.total_allocation_size); + assert!(matches!(self.go_to_next_state(project_id), ProjectStatus::SettlementStarted(_))); self.settle_project(project_id, mark_as_settled); + + // Check that remaining CTs are updated + let project_details = self.get_project_details(project_id); + assert_eq!( + project_details.remaining_contribution_tokens, + project_metadata.total_allocation_size - auction_bought_tokens, + "Remaining CTs are incorrect" + ); + project_id } } diff --git a/pallets/funding/src/instantiator/tests.rs b/pallets/funding/src/instantiator/tests.rs index 592dfde9e..d71fca25e 100644 --- a/pallets/funding/src/instantiator/tests.rs +++ b/pallets/funding/src/instantiator/tests.rs @@ -77,7 +77,8 @@ fn dry_run_wap() { inst.bid_for_users(project_id, bids).unwrap(); - assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful)); + let next_state = inst.go_to_next_state(project_id); + assert!(matches!(next_state, ProjectStatus::FundingSuccessful)); let project_details = inst.get_project_details(project_id); let wap = project_details.weighted_average_price.unwrap(); diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 586099a9a..4c8fa5a1f 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>; @@ -411,6 +385,9 @@ pub mod pallet { /// StorageMap containing additional information for the projects, relevant for correctness of the protocol pub type ProjectsDetails = StorageMap<_, Blake2_128Concat, ProjectId, ProjectDetailsOf>; + #[pallet::storage] + pub type ProjectsInAuctionRound = StorageValue<_, WeakBoundedVec>, ValueQuery>; + #[pallet::storage] /// Keep track of the PLMC bonds made to each project by each evaluator pub type Evaluations = StorageNMap< @@ -425,41 +402,34 @@ pub mod pallet { #[pallet::storage] /// StorageMap containing the bids for each project and user - pub type Bids = StorageNMap< - _, - ( - NMapKey, - NMapKey>, - NMapKey, - ), - BidInfoOf, - >; + pub type Bids = + StorageNMap<_, (NMapKey, NMapKey), BidInfoOf>; #[pallet::storage] - /// Contributions made during the Community and Remainder round. i.e token buys - pub type Contributions = StorageNMap< - _, - ( - NMapKey, - NMapKey>, - NMapKey, - ), - ContributionInfoOf, - >; + /// 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 BidBucketBounds = + StorageDoubleMap<_, Blake2_128Concat, ProjectId, Blake2_128Concat, PriceOf, (u32, u32), OptionQuery>; #[pallet::storage] - pub type AuctionBoughtUSD = - StorageNMap<_, (NMapKey, NMapKey), Balance, ValueQuery>; + /// 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 ContributionBoughtUSD = + pub type AuctionBoughtUSD = StorageNMap<_, (NMapKey, NMapKey), Balance, ValueQuery>; #[pallet::storage] 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 @@ -467,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)>; @@ -481,10 +444,6 @@ pub mod pallet { #[pallet::storage] pub type DidWithActiveProjects = StorageMap<_, Blake2_128Concat, Did, ProjectId, OptionQuery>; - #[pallet::storage] - pub type DidWithWinningBids = - StorageDoubleMap<_, Blake2_128Concat, ProjectId, Blake2_128Concat, Did, bool, ValueQuery>; - #[pallet::event] #[pallet::generate_deposit(pub (super) fn deposit_event)] pub enum Event { @@ -536,6 +495,7 @@ pub mod pallet { project_id: ProjectId, account: AccountIdOf, id: u32, + status: BidStatus, final_ct_amount: Balance, final_ct_usd_price: PriceOf, }, @@ -655,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 @@ -757,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())?; @@ -776,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, @@ -784,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())?; @@ -802,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, @@ -817,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())?; @@ -842,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, @@ -859,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())?; @@ -910,15 +858,9 @@ pub mod pallet { #[pallet::call_index(13)] #[pallet::weight(WeightInfoOf::::settle_accepted_bid_with_refund())] - pub fn settle_bid( - origin: OriginFor, - project_id: ProjectId, - bidder: AccountIdOf, - bid_id: u32, - ) -> DispatchResult { + pub fn settle_bid(origin: OriginFor, project_id: ProjectId, bid_id: u32) -> DispatchResult { let _caller = ensure_signed(origin)?; - let bid = Bids::::get((project_id, bidder, bid_id)).ok_or(Error::::ParticipationNotFound)?; - Self::do_settle_bid(bid, project_id) + Self::do_settle_bid(project_id, bid_id) } #[pallet::call_index(18)] @@ -943,7 +885,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, @@ -997,7 +940,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, @@ -1008,7 +952,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, @@ -1027,6 +972,40 @@ pub mod pallet { Self::do_mark_project_ct_migration_as_finished(project_id) } } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_idle(_n: BlockNumberFor, available_weight: Weight) -> Weight { + let projects = ProjectsInAuctionRound::::get(); + if projects.is_empty() { + return ::DbWeight::get().reads(1); + } + + let mut weight_consumed = ::DbWeight::get().reads(1); + let process_weight = ::DbWeight::get().reads_writes(1, 1); + + for project_id in projects { + loop { + // Check weight before processing each bid + if weight_consumed.saturating_add(process_weight).all_gt(available_weight) { + return weight_consumed; + } + + weight_consumed.saturating_accrue(process_weight); + match Self::do_process_next_oversubscribed_bid(project_id) { + // Returns Ok if a bid was processed successfully + Ok(_) => continue, + // Returns Err if there are no more bids to process, so we go to next project in the auction round + Err(_) => { + break; + }, + } + } + } + + weight_consumed + } + } } pub mod xcm_executor_impl { diff --git a/pallets/funding/src/tests/3_auction.rs b/pallets/funding/src/tests/3_auction.rs index 4db22179f..ffd551865 100644 --- a/pallets/funding/src/tests/3_auction.rs +++ b/pallets/funding/src/tests/3_auction.rs @@ -1,4 +1,5 @@ use super::*; +use crate::ParticipationMode::*; use frame_support::traits::{fungible::InspectFreeze, fungibles::metadata::Inspect}; use polimec_common::assets::{AcceptedFundingAsset, AcceptedFundingAsset::USDT}; use sp_core::bounded_vec; @@ -19,7 +20,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 +32,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()); @@ -39,41 +40,6 @@ mod round_flow { inst.create_finished_project(project4, ISSUER_4, None, evaluations, bids); } - #[test] - fn auction_gets_percentage_of_ct_total_allocation() { - let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let project_metadata = default_project_metadata(ISSUER_1); - let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), 5); - let total_allocation = project_metadata.total_allocation_size; - let auction_allocation = total_allocation; - - let bids = vec![(BIDDER_1, Retail, auction_allocation).into()]; - let project_id = - inst.create_finished_project(project_metadata.clone(), ISSUER_1, None, evaluations.clone(), bids); - let mut bid_infos = Bids::::iter_prefix_values((project_id,)); - let bid_info = inst.execute(|| bid_infos.next().unwrap()); - assert!(inst.execute(|| bid_infos.next().is_none())); - assert_eq!(bid_info.original_ct_amount, auction_allocation); - - let project_metadata = default_project_metadata(ISSUER_2); - let bids = - vec![(BIDDER_1, Retail, auction_allocation).into(), (BIDDER_1, Institutional, 1000 * CT_UNIT).into()]; - let project_id = - inst.create_finished_project(project_metadata.clone(), ISSUER_2, None, evaluations.clone(), bids); - let project_details = inst.get_project_details(project_id); - - let bid_info_1 = inst.execute(|| Bids::::get((project_id, BIDDER_1, 1)).unwrap()); - let bid_info_2 = inst.execute(|| Bids::::get((project_id, BIDDER_1, 2)).unwrap()); - assert!(inst.execute(|| bid_infos.next().is_none())); - assert_eq!( - bid_info_1.status, - BidStatus::PartiallyAccepted(auction_allocation - 1000 * CT_UNIT), - "Should not be able to buy more than auction allocation" - ); - assert_eq!(bid_info_2.status, BidStatus::Accepted, "Should outbid the previous bid"); - assert_eq!(project_details.remaining_contribution_tokens, total_allocation - auction_allocation); - } - #[test] fn no_bids_made() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); @@ -229,61 +195,149 @@ mod round_flow { } #[test] - fn all_bids_but_one_have_price_higher_than_wap() { + fn auction_oversubscription() { 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_metadata = default_project_metadata(ISSUER_1); + 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(), ISSUER_1, None, inst.generate_successful_evaluations(project_metadata.clone(), 5), - all_bids, + bids, ); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); + assert!(wap > project_metadata.minimum_price); + } - let all_bids = inst.execute(|| Bids::::iter_prefix_values((project_id,)).collect_vec()); + #[test] + fn on_idle_clears_oversubscribed_bids() { + 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(), 100, 10); + + let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); + inst.mint_necessary_tokens_for_bids(project_id, bids.clone()); + inst.bid_for_users(project_id, bids.clone()).unwrap(); - 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); + // Check full rejection of one bid by another + let last_bid_amount = bids[9].amount; + let oversubscribed_bid = + BidParams::::from((BIDDER_1, Retail, last_bid_amount, Classic(1u8), USDT)); + inst.mint_necessary_tokens_for_bids(project_id, vec![oversubscribed_bid.clone()]); + inst.bid_for_users(project_id, vec![oversubscribed_bid.clone()]).unwrap(); + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id)), last_bid_amount); + inst.advance_time(1); + let rejected_bid = inst.execute(|| Bids::::get((project_id, 9)).unwrap()); + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id)), Zero::zero()); + assert_eq!(rejected_bid.status, BidStatus::Rejected); + let yet_unknown_bid = inst.execute(|| Bids::::get((project_id, 8)).unwrap()); + assert_eq!(yet_unknown_bid.status, BidStatus::YetUnknown); + + // Check multiple bid rejections by one bid + let multiple_bids_amount = bids[8].amount + bids[7].amount + bids[6].amount; + let multiple_bids = + BidParams::::from((BIDDER_1, Retail, multiple_bids_amount, Classic(1u8), USDT)); + inst.mint_necessary_tokens_for_bids(project_id, vec![multiple_bids.clone()]); + inst.bid_for_users(project_id, vec![multiple_bids.clone()]).unwrap(); + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id)), multiple_bids_amount); + inst.advance_time(1); + let rejected_bid_1 = inst.execute(|| Bids::::get((project_id, 8)).unwrap()); + let rejected_bid_2 = inst.execute(|| Bids::::get((project_id, 7)).unwrap()); + let rejected_bid_3 = inst.execute(|| Bids::::get((project_id, 6)).unwrap()); + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id)), Zero::zero()); + assert_eq!(rejected_bid_1.status, BidStatus::Rejected); + assert_eq!(rejected_bid_2.status, BidStatus::Rejected); + assert_eq!(rejected_bid_3.status, BidStatus::Rejected); + let yet_unknown_bid = inst.execute(|| Bids::::get((project_id, 5)).unwrap()); + assert_eq!(yet_unknown_bid.status, BidStatus::YetUnknown); + + // Check partial rejection of one bid by another + let partial_bid_amount = last_bid_amount / 2; + let partial_bid = + BidParams::::from((BIDDER_1, Retail, partial_bid_amount, Classic(1u8), USDT)); + inst.mint_necessary_tokens_for_bids(project_id, vec![partial_bid.clone()]); + inst.bid_for_users(project_id, vec![partial_bid.clone()]).unwrap(); + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id)), partial_bid_amount); + inst.advance_time(1); + let rejected_bid = inst.execute(|| Bids::::get((project_id, 5)).unwrap()); + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id)), Zero::zero()); + assert_eq!(rejected_bid.status, BidStatus::PartiallyAccepted(last_bid_amount - partial_bid_amount)); + let yet_unknown_bid = inst.execute(|| Bids::::get((project_id, 4)).unwrap()); + assert_eq!(yet_unknown_bid.status, BidStatus::YetUnknown); } #[test] - fn auction_oversubscription() { + fn on_idle_clears_multiple_oversubscribed_projects() { 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, - ); + // Create two projects with different metadata + let project_metadata_1 = default_project_metadata(ISSUER_1); + let project_metadata_2 = default_project_metadata(ISSUER_2); - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - assert!(wap > project_metadata.minimum_price); + // Generate evaluations and bids for both projects + let evaluations_1 = inst.generate_successful_evaluations(project_metadata_1.clone(), 5); + let evaluations_2 = inst.generate_successful_evaluations(project_metadata_2.clone(), 5); + let bids_1 = inst.generate_bids_from_total_ct_percent(project_metadata_1.clone(), 100, 5); + let bids_2 = inst.generate_bids_from_total_ct_percent(project_metadata_2.clone(), 100, 5); + + // Create two projects in auctioning state + let project_id_1 = + inst.create_auctioning_project(project_metadata_1.clone(), ISSUER_1, None, evaluations_1); + let project_id_2 = + inst.create_auctioning_project(project_metadata_2.clone(), ISSUER_2, None, evaluations_2); + + // Place initial bids for both projects + inst.mint_necessary_tokens_for_bids(project_id_1, bids_1.clone()); + inst.mint_necessary_tokens_for_bids(project_id_2, bids_2.clone()); + inst.bid_for_users(project_id_1, bids_1.clone()).unwrap(); + inst.bid_for_users(project_id_2, bids_2.clone()).unwrap(); + + // Create oversubscribed bids for both projects + let oversubscribed_bid_1 = + BidParams::::from((BIDDER_1, Retail, bids_1[4].amount, Classic(1u8), USDT)); + let oversubscribed_bid_2 = + BidParams::::from((BIDDER_2, Retail, bids_2[4].amount, Classic(1u8), USDT)); + + // Place oversubscribed bids + inst.mint_necessary_tokens_for_bids(project_id_1, vec![oversubscribed_bid_1.clone()]); + inst.mint_necessary_tokens_for_bids(project_id_2, vec![oversubscribed_bid_2.clone()]); + inst.bid_for_users(project_id_1, vec![oversubscribed_bid_1.clone()]).unwrap(); + inst.bid_for_users(project_id_2, vec![oversubscribed_bid_2.clone()]).unwrap(); + + // Verify both projects are oversubscribed + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id_1)), bids_1[4].amount); + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id_2)), bids_2[4].amount); + + // Advance time to trigger on_idle + inst.advance_time(1); + + // Verify oversubscribed amounts are cleared for both projects + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id_1)), Zero::zero()); + assert_eq!(inst.execute(|| CTAmountOversubscribed::::get(project_id_2)), Zero::zero()); + + // Verify bid statuses for both projects + let rejected_bid_1 = inst.execute(|| Bids::::get((project_id_1, 4)).unwrap()); + let rejected_bid_2 = inst.execute(|| Bids::::get((project_id_2, 9)).unwrap()); + assert_eq!(rejected_bid_1.status, BidStatus::Rejected); + assert_eq!(rejected_bid_2.status, BidStatus::Rejected); + + let yet_unknown_bid_1 = inst.execute(|| Bids::::get((project_id_1, 3)).unwrap()); + let yet_unknown_bid_2 = inst.execute(|| Bids::::get((project_id_2, 8)).unwrap()); + assert_eq!(yet_unknown_bid_1.status, BidStatus::YetUnknown); + assert_eq!(yet_unknown_bid_2.status, BidStatus::YetUnknown); + + inst.go_to_next_state(project_id_1); + inst.go_to_next_state(project_id_2); + + assert!(inst.execute(|| ProjectsInAuctionRound::::get()).to_vec().is_empty()) } } } @@ -295,7 +349,7 @@ mod bid_extrinsic { #[cfg(test)] mod success { use super::*; - use frame_support::pallet_prelude::DispatchResultWithPostInfo; + use frame_support::dispatch::DispatchResult; #[test] fn evaluation_bond_counts_towards_bid() { @@ -350,8 +404,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); @@ -472,7 +525,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; @@ -702,7 +755,7 @@ 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, 0).unwrap(); }); let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); @@ -718,7 +771,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); @@ -786,7 +848,7 @@ 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, 0).unwrap(); }); let free_balance = inst.get_free_plmc_balance_for(BIDDER_4); @@ -856,17 +918,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 = @@ -926,6 +988,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); @@ -1335,222 +1430,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()))); @@ -2080,7 +1959,7 @@ mod end_auction_extrinsic { AcceptedFundingAsset::USDC, )); let bid_3 = BidParams::from(( - BIDDER_1, + BIDDER_5, Professional, 10_000 * CT_UNIT, ParticipationMode::Classic(5u8), @@ -2101,8 +1980,8 @@ mod end_auction_extrinsic { AcceptedFundingAsset::DOT, )); // post bucketing, the bids look like this: - // (BIDDER_1, 5k) - (BIDDER_2, 40k) - (BIDDER_1, 5k) - (BIDDER_1, 5k) - (BIDDER_3 - 5k) - (BIDDER_3 - 1k) - (BIDDER_4 - 2k) - // | -------------------- 10USD ----------------------|---- 11 USD ---|---- 12 USD ----|----------- 13 USD -------------| + // (BIDDER_1, 5k) - (BIDDER_2, 40k) - (BIDDER_5, 5k) - (BIDDER_5, 5k) - (BIDDER_3 - 5k) - (BIDDER_3 - 1k) - (BIDDER_4 - 2k) + // | -------------------- 10 USD ----------------------|---- 11 USD ---|---- 12 USD ----|----------- 13 USD -------------| // post wap ~ 1.0557252: // (Accepted, 5k) - (Partially, 32k) - (Rejected, 5k) - (Accepted, 5k) - (Accepted - 5k) - (Accepted - 1k) - (Accepted - 2k) @@ -2140,11 +2019,25 @@ 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, 2)).unwrap()); + let _bidder_5_accepted_bid = inst.execute(|| Bids::::get((project_id, 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(), + bidder_5_rejected_bid.bidder, + ); + + assert!(matches!( + inst.go_to_next_state(project_id), + ProjectStatus::SettlementStarted(FundingOutcome::Success) + )); + inst.settle_project(project_id, true); + let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); 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); @@ -2152,47 +2045,36 @@ mod end_auction_extrinsic { vec![returned_funding_assets.clone(), prev_funding_asset_balances], MergeOperation::Add, ); - let expected_reserved_plmc = - inst.generic_map_operation(vec![plmc_amounts.clone(), returned_auction_plmc], MergeOperation::Subtract); + let expected_reserved_plmc = inst.generic_map_operation( + vec![plmc_amounts.clone(), returned_auction_plmc.clone()], + MergeOperation::Subtract, + ); let expected_final_funding_spent = inst.generic_map_operation( - vec![funding_asset_amounts.clone(), returned_funding_assets], + vec![funding_asset_amounts.clone(), returned_funding_assets.clone()], MergeOperation::Subtract, ); let expected_issuer_funding = inst.sum_funding_asset_mappings(vec![expected_final_funding_spent]); // Assertions about rejected bid - let rejected_bid = inst.execute(|| Bids::::get((project_id, BIDDER_1, 2)).unwrap()); - assert_eq!(rejected_bid.status, BidStatus::Rejected); - let bidder_plmc_pre_balance = inst.get_free_plmc_balance_for(rejected_bid.bidder); - let bidder_funding_asset_pre_balance = - inst.get_free_funding_asset_balance_for(rejected_bid.funding_asset.id(), rejected_bid.bidder); - inst.execute(|| { - PolimecFunding::settle_bid( - RuntimeOrigin::signed(rejected_bid.bidder), - project_id, - rejected_bid.bidder, - 2, - ) - }) - .unwrap(); - let bidder_plmc_post_balance = inst.get_free_plmc_balance_for(rejected_bid.bidder); - let bidder_funding_asset_post_balance = - inst.get_free_funding_asset_balance_for(rejected_bid.funding_asset.id(), rejected_bid.bidder); - assert!(inst.execute(|| Bids::::get((project_id, BIDDER_1, 2))).is_none()); - assert_eq!(bidder_plmc_post_balance, bidder_plmc_pre_balance + rejected_bid.plmc_bond); - assert_eq!( - bidder_funding_asset_post_balance, - bidder_funding_asset_pre_balance + rejected_bid.funding_asset_amount_locked + 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, ); - // Any refunds on bids that were accepted/partially accepted will be done at the settlement once funding finishes + // Bidder 5's accepted bid should have some refunds due to paying the wap in the end instead of the bucket price. + // Bidder 5's rejected bid should have a full refund + let bidder_5_returned_plmc = + returned_auction_plmc.iter().find(|x| x.account == BIDDER_5).unwrap().plmc_amount; + 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, 2))).is_none()); + assert_eq!(bidder_5_plmc_post_balance, bidder_5_plmc_pre_balance + bidder_5_returned_plmc); assert_eq!( - inst.execute(|| Bids::::get((project_id, BIDDER_2, 1)).unwrap()).status, - BidStatus::PartiallyAccepted(32_000 * CT_UNIT) + bidder_5_funding_asset_post_balance, + bidder_5_funding_asset_pre_balance + bidder_5_returned_funding_asset ); - assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); - - inst.settle_project(project_id, true); inst.do_free_plmc_assertions(expected_free_plmc); inst.do_reserved_plmc_assertions(expected_reserved_plmc, HoldReason::Participation.into()); @@ -2285,5 +2167,112 @@ mod end_auction_extrinsic { Perquintill::from_float(0.99) ); } + + #[test] + fn oversubscribed_bid_can_get_refund_and_bid_again_in_auction_round() { + 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(), 100, 10); + + let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, evaluations); + + inst.mint_necessary_tokens_for_bids(project_id, bids.clone()); + inst.bid_for_users(project_id, bids.clone()).unwrap(); + + // First bid is OTM + let first_bid_to_refund = bids[9].clone(); + // Second is classic + let second_bid_to_refund = bids[8].clone(); + + let oversubscribing_bids = vec![ + ( + BIDDER_1, + Retail, + first_bid_to_refund.amount + 200 * CT_UNIT, + ParticipationMode::Classic(1), + AcceptedFundingAsset::USDT, + ) + .into(), + ( + BIDDER_2, + Retail, + second_bid_to_refund.amount, + ParticipationMode::Classic(1), + AcceptedFundingAsset::USDT, + ) + .into(), + ]; + + inst.mint_necessary_tokens_for_bids(project_id, oversubscribing_bids.clone()); + inst.bid_for_users(project_id, vec![oversubscribing_bids[0].clone()]).unwrap(); + + inst.process_oversubscribed_bids(project_id); + + let pre_first_refund_bidder_plmc_balance = inst.get_free_plmc_balance_for(first_bid_to_refund.bidder); + let pre_first_refund_bidder_funding_asset_balance = + inst.get_free_funding_asset_balance_for(first_bid_to_refund.asset.id(), first_bid_to_refund.bidder); + + let first_bid = inst.execute(|| Bids::::get((project_id, 9)).unwrap()); + assert!(matches!(first_bid.status, BidStatus::Rejected)); + let second_bid = inst.execute(|| Bids::::get((project_id, 8)).unwrap()); + assert!(matches!(second_bid.status, BidStatus::PartiallyAccepted(_))); + + inst.execute(|| { + assert_ok!(Pallet::::do_settle_bid(project_id, 9)); + assert_noop!( + Pallet::::do_settle_bid(project_id, 8), + Error::::SettlementNotStarted + ); + }); + + let post_first_refund_bidder_plmc_balance = inst.get_free_plmc_balance_for(first_bid_to_refund.bidder); + let post_first_refund_bidder_funding_asset_balance = + inst.get_free_funding_asset_balance_for(first_bid_to_refund.asset.id(), first_bid_to_refund.bidder); + + // OTM bid doesnt give PLMC refund to bidder + assert_eq!(post_first_refund_bidder_plmc_balance, pre_first_refund_bidder_plmc_balance); + let mut funding_asset_refund = first_bid.funding_asset_amount_locked; + let usd_ticket = first_bid.original_ct_usd_price.saturating_mul_int(first_bid.original_ct_amount); + inst.add_otm_fee_to(&mut funding_asset_refund, usd_ticket, first_bid_to_refund.asset); + assert_eq!( + post_first_refund_bidder_funding_asset_balance, + pre_first_refund_bidder_funding_asset_balance + funding_asset_refund + ); + + inst.bid_for_users(project_id, vec![oversubscribing_bids[1].clone()]).unwrap(); + + inst.process_oversubscribed_bids(project_id); + let pre_second_refund_bidder_plmc_balance = inst.get_free_plmc_balance_for(second_bid_to_refund.bidder); + let pre_second_refund_bidder_funding_asset_balance = + inst.get_free_funding_asset_balance_for(second_bid_to_refund.asset.id(), second_bid_to_refund.bidder); + + let second_bid = inst.execute(|| Bids::::get((project_id, 8)).unwrap()); + assert!(matches!(second_bid.status, BidStatus::Rejected)); + let third_bid = inst.execute(|| Bids::::get((project_id, 7)).unwrap()); + assert!(matches!(third_bid.status, BidStatus::PartiallyAccepted(_))); + + inst.execute(|| { + assert_ok!(Pallet::::do_settle_bid(project_id, 8)); + assert_noop!( + Pallet::::do_settle_bid(project_id, 7), + Error::::SettlementNotStarted + ); + }); + + let post_second_refund_bidder_plmc_balance = inst.get_free_plmc_balance_for(second_bid_to_refund.bidder); + let post_second_refund_bidder_funding_asset_balance = + inst.get_free_funding_asset_balance_for(second_bid_to_refund.asset.id(), second_bid_to_refund.bidder); + + // Classic bid + assert_eq!( + post_second_refund_bidder_plmc_balance, + pre_second_refund_bidder_plmc_balance + second_bid.plmc_bond + ); + assert_eq!( + post_second_refund_bidder_funding_asset_balance, + pre_second_refund_bidder_funding_asset_balance + second_bid.funding_asset_amount_locked + ); + } } } diff --git a/pallets/funding/src/tests/6_settlement.rs b/pallets/funding/src/tests/6_settlement.rs index 871f3c192..06744f9ae 100644 --- a/pallets/funding/src/tests/6_settlement.rs +++ b/pallets/funding/src/tests/6_settlement.rs @@ -15,8 +15,8 @@ mod round_flow { let percentage = 100u8; let (mut inst, project_id) = create_project_with_funding_percentage(percentage, true); let evaluations = inst.get_evaluations(project_id); - let bids = inst.get_bids(project_id); + let bids = inst.get_bids(project_id); inst.settle_project(project_id, true); inst.assert_total_funding_paid_out(project_id, bids.clone()); @@ -41,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![ @@ -102,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))), @@ -240,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, @@ -250,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)); @@ -472,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; @@ -496,8 +520,7 @@ mod settle_bid_extrinsic { let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); // Partial amount bid assertions - let partial_amount_bid_stored = - inst.execute(|| Bids::::get((project_id, BIDDER_1, 0)).unwrap()); + let partial_amount_bid_stored = inst.execute(|| Bids::::get((project_id, 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( @@ -519,14 +542,22 @@ mod settle_bid_extrinsic { project_metadata.funding_destination_account, ); - inst.execute(|| { - assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 0)); - }); + let lower_price_bid_stored = inst.execute(|| Bids::::get((project_id, 1)).unwrap()); + let pre_issuer_dot_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::DOT.id(), + project_metadata.funding_destination_account, + ); + + inst.settle_project(project_id, true); let post_issuer_usdt_balance = inst.get_free_funding_asset_balance_for( AcceptedFundingAsset::USDT.id(), project_metadata.funding_destination_account, ); + let post_issuer_dot_balance = inst.get_free_funding_asset_balance_for( + AcceptedFundingAsset::DOT.id(), + project_metadata.funding_destination_account, + ); inst.assert_funding_asset_free_balance( BIDDER_1, @@ -557,7 +588,6 @@ mod settle_bid_extrinsic { inst.assert_plmc_free_balance(BIDDER_1, expected_plmc_refund + expected_final_plmc_bonded + ed); // Price > wap bid assertions - let lower_price_bid_stored = inst.execute(|| Bids::::get((project_id, BIDDER_2, 1)).unwrap()); let expected_final_plmc_bonded = inst .calculate_auction_plmc_charged_with_given_price(&vec![lower_price_bid_params.clone()], wap)[0] .plmc_amount; @@ -567,20 +597,6 @@ mod settle_bid_extrinsic { let expected_plmc_refund = lower_price_bid_stored.plmc_bond - expected_final_plmc_bonded; let expected_dot_refund = lower_price_bid_stored.funding_asset_amount_locked - expected_final_dot_paid; - let pre_issuer_dot_balance = inst.get_free_funding_asset_balance_for( - AcceptedFundingAsset::DOT.id(), - project_metadata.funding_destination_account, - ); - - inst.execute(|| { - assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_2, 1)); - }); - - let post_issuer_dot_balance = inst.get_free_funding_asset_balance_for( - AcceptedFundingAsset::DOT.id(), - project_metadata.funding_destination_account, - ); - inst.assert_funding_asset_free_balance( BIDDER_2, AcceptedFundingAsset::DOT.id(), @@ -628,6 +644,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; @@ -648,7 +672,7 @@ 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, 0)).unwrap()); let pre_issuer_usdc_balance = inst.get_free_funding_asset_balance_for( AcceptedFundingAsset::USDT.id(), @@ -656,7 +680,7 @@ 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, 0)); }); let post_issuer_usdc_balance = inst.get_free_funding_asset_balance_for( @@ -726,7 +750,7 @@ 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, 0)).unwrap()); let pre_issuer_usdc_balance = inst.get_free_funding_asset_balance_for( AcceptedFundingAsset::USDT.id(), @@ -734,7 +758,7 @@ 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, 0)); }); let post_issuer_usdc_balance = inst.get_free_funding_asset_balance_for( @@ -767,6 +791,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; @@ -790,17 +822,14 @@ 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()); - assert_eq!(rejected_bid_stored.status, BidStatus::Rejected); + let rejected_bid_stored = inst.execute(|| Bids::::get((project_id, 0)).unwrap()); let pre_issuer_usdt_balance = inst.get_free_funding_asset_balance_for( AcceptedFundingAsset::USDT.id(), project_metadata.funding_destination_account, ); - inst.execute(|| { - assert_ok!(PolimecFunding::settle_bid(RuntimeOrigin::signed(BIDDER_1), project_id, BIDDER_1, 0)); - }); + inst.settle_project(project_id, true); let post_issuer_usdt_balance = inst.get_free_funding_asset_balance_for( AcceptedFundingAsset::USDT.id(), @@ -835,23 +864,16 @@ mod settle_bid_extrinsic { fn cannot_settle_twice() { let percentage = 100u8; let (mut inst, project_id) = create_project_with_funding_percentage(percentage, true); - let first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); inst.execute(|| { let bidder = first_bid.bidder; assert_ok!(crate::Pallet::::settle_bid( RuntimeOrigin::signed(bidder), project_id, - bidder, first_bid.id )); assert_noop!( - crate::Pallet::::settle_bid( - RuntimeOrigin::signed(bidder), - project_id, - bidder, - first_bid.id - ), + crate::Pallet::::settle_bid(RuntimeOrigin::signed(bidder), project_id, first_bid.id), Error::::ParticipationNotFound ); }); @@ -861,17 +883,11 @@ 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 first_bid = inst.get_bids(project_id).into_iter().next().unwrap(); let bidder = first_bid.bidder; inst.execute(|| { assert_noop!( - crate::Pallet::::settle_bid( - RuntimeOrigin::signed(bidder), - project_id, - bidder, - first_bid.id - ), + crate::Pallet::::settle_bid(RuntimeOrigin::signed(bidder), project_id, first_bid.id), Error::::SettlementNotStarted ); }); diff --git a/pallets/funding/src/tests/runtime_api.rs b/pallets/funding/src/tests/runtime_api.rs index 330331304..5971fa2ff 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, 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( @@ -537,11 +517,7 @@ fn get_message_to_sign_by_receiving_account() { #[test] fn get_next_vesting_schedule_merge_candidates() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - let evaluations = vec![ - EvaluationParams::from((EVALUATOR_1, 500_000 * USD_UNIT)), - EvaluationParams::from((EVALUATOR_2, 250_000 * USD_UNIT)), - EvaluationParams::from((BIDDER_1, 320_000 * USD_UNIT)), - ]; + let evaluations = vec![EvaluationParams::from((EVALUATOR_1, 500_000 * USD_UNIT))]; let bids = vec![ BidParams::from(( BIDDER_1, @@ -572,16 +548,18 @@ 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_finished_project( - default_project_metadata(ISSUER_1), - ISSUER_1, - None, - evaluations.clone(), - bids.clone(), - ); - assert_eq!(ProjectStatus::SettlementStarted(FundingOutcome::Success), inst.go_to_next_state(project_id)); - inst.settle_project(project_id, 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); let hold_reason: mock::RuntimeHoldReason = HoldReason::Participation.into(); let bidder_1_schedules = @@ -595,12 +573,11 @@ fn get_next_vesting_schedule_merge_candidates() { block_hash, BIDDER_1, HoldReason::Participation.into(), - // around 4 weeks of blocks - 210_000, + 300_000, ) .unwrap() .unwrap(); - assert_eq!((idx_1, idx_2), (1, 2)); + assert_eq!((idx_1, idx_2), (0, 1)); // Merging the two schedules deletes them and creates a new one at the end of the vec. LinearRelease::merge_schedules(RuntimeOrigin::signed(BIDDER_1), idx_1, idx_2, hold_reason).unwrap(); @@ -610,8 +587,7 @@ fn get_next_vesting_schedule_merge_candidates() { block_hash, BIDDER_1, HoldReason::Participation.into(), - // around 4 weeks of blocks - 210_000, + 300_000, ) .unwrap() .unwrap(); @@ -623,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())); @@ -779,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); @@ -897,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/weights.rs b/pallets/funding/src/weights.rs index 7dbe35dd3..914c40062 100644 --- a/pallets/funding/src/weights.rs +++ b/pallets/funding/src/weights.rs @@ -59,7 +59,8 @@ pub trait WeightInfo { fn start_evaluation() -> Weight; fn evaluate(x: u32, ) -> Weight; fn end_evaluation_failure() -> Weight; - fn bid(x: u32, y: u32, ) -> Weight; + fn bid(x: u32, ) -> Weight; + fn process_next_oversubscribed_bid() -> Weight; fn end_auction(x: u32, y: u32, ) -> Weight; fn contribute(x: u32, ) -> Weight; fn end_funding_project_successful() -> Weight; @@ -204,48 +205,68 @@ 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(502), added: 2977, mode: `MaxEncodedLen`) + /// 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(342), added: 2817, mode: `MaxEncodedLen`) + /// 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::BidCounts` (r:1 w:1) - /// Proof: `Funding::BidCounts` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) - /// Storage: `Funding::Bids` (r:7 w:10) - /// Proof: `Funding::Bids` (`max_values`: None, `max_size`: Some(273), added: 2748, 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:10 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(110), added: 2585, mode: `MaxEncodedLen`) + /// 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(36), added: 2511, mode: `MaxEncodedLen`) + /// 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(140), added: 2615, mode: `MaxEncodedLen`) + /// 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(254), added: 2729, mode: `MaxEncodedLen`) + /// 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(1149), added: 3624, mode: `MaxEncodedLen`) + /// 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(210), added: 2685, mode: `MaxEncodedLen`) + /// 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(134), added: 2609, mode: `MaxEncodedLen`) - /// The range of component `x` is `[0, 6]`. - /// The range of component `y` is `[0, 10]`. - fn bid(x: u32, y: u32, ) -> Weight { + /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) + /// Storage: `Funding::CTAmountOversubscribed` (r:1 w:1) + /// Proof: `Funding::CTAmountOversubscribed` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`) + /// Storage: `Funding::Bids` (r:0 w:10) + /// Proof: `Funding::Bids` (`max_values`: None, `max_size`: Some(309), added: 2784, mode: `MaxEncodedLen`) + /// The range of component `x` is `[1, 10]`. + fn bid(x: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2870 + x * (192 ±0)` - // Estimated: `6208 + x * (2748 ±0)` - // Minimum execution time: 279_441_000 picoseconds. - Weight::from_parts(215_188_573, 6208) - // Standard Error: 264_218 - .saturating_add(Weight::from_parts(3_406_167, 0).saturating_mul(x.into())) - // Standard Error: 165_665 - .saturating_add(Weight::from_parts(71_144_235, 0).saturating_mul(y.into())) - .saturating_add(T::DbWeight::get().reads(16_u64)) + // Measured: `2890` + // Estimated: `7404 + x * (2535 ±0)` + // Minimum execution time: 184_000_000 picoseconds. + Weight::from_parts(139_444_380, 7404) + // Standard Error: 76_200 + .saturating_add(Weight::from_parts(59_216_258, 0).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().reads(15_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(x.into()))) .saturating_add(T::DbWeight::get().writes(8_u64)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(y.into()))) - .saturating_add(Weight::from_parts(0, 2748).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(x.into()))) + .saturating_add(Weight::from_parts(0, 2535).saturating_mul(x.into())) + } + /// Storage: `Funding::ProjectsMetadata` (r:1 w:0) + /// Proof: `Funding::ProjectsMetadata` (`max_values`: None, `max_size`: Some(437), added: 2912, mode: `MaxEncodedLen`) + /// Storage: `Funding::Buckets` (r:1 w:0) + /// Proof: `Funding::Buckets` (`max_values`: None, `max_size`: Some(100), added: 2575, 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::CTAmountOversubscribed` (r:1 w:1) + /// Proof: `Funding::CTAmountOversubscribed` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`) + /// Storage: `Funding::Bids` (r:2 w:1) + /// Proof: `Funding::Bids` (`max_values`: None, `max_size`: Some(309), added: 2784, mode: `MaxEncodedLen`) + /// Storage: `Funding::BidBucketBounds` (r:1 w:0) + /// Proof: `Funding::BidBucketBounds` (`max_values`: None, `max_size`: Some(60), added: 2535, mode: `MaxEncodedLen`) + fn process_next_oversubscribed_bid() -> Weight { + // Proof Size summary in bytes: + // Measured: `1630` + // Estimated: `6558` + // Minimum execution time: 29_000_000 picoseconds. + Weight::from_parts(30_000_000, 6558) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } /// Storage: `Funding::ProjectsMetadata` (r:1 w:0) /// Proof: `Funding::ProjectsMetadata` (`max_values`: None, `max_size`: Some(502), added: 2977, mode: `MaxEncodedLen`) @@ -760,48 +781,68 @@ 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(502), added: 2977, mode: `MaxEncodedLen`) + /// 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(342), added: 2817, mode: `MaxEncodedLen`) + /// 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::BidCounts` (r:1 w:1) - /// Proof: `Funding::BidCounts` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) - /// Storage: `Funding::Bids` (r:7 w:10) - /// Proof: `Funding::Bids` (`max_values`: None, `max_size`: Some(273), added: 2748, 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:10 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(110), added: 2585, mode: `MaxEncodedLen`) + /// 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(36), added: 2511, mode: `MaxEncodedLen`) + /// 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(140), added: 2615, mode: `MaxEncodedLen`) + /// 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(254), added: 2729, mode: `MaxEncodedLen`) + /// 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(1149), added: 3624, mode: `MaxEncodedLen`) + /// 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(210), added: 2685, mode: `MaxEncodedLen`) + /// 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(134), added: 2609, mode: `MaxEncodedLen`) - /// The range of component `x` is `[0, 6]`. - /// The range of component `y` is `[0, 10]`. - fn bid(x: u32, y: u32, ) -> Weight { + /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) + /// Storage: `Funding::CTAmountOversubscribed` (r:1 w:1) + /// Proof: `Funding::CTAmountOversubscribed` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`) + /// Storage: `Funding::Bids` (r:0 w:10) + /// Proof: `Funding::Bids` (`max_values`: None, `max_size`: Some(309), added: 2784, mode: `MaxEncodedLen`) + /// The range of component `x` is `[1, 10]`. + fn bid(x: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2870 + x * (192 ±0)` - // Estimated: `6208 + x * (2748 ±0)` - // Minimum execution time: 279_441_000 picoseconds. - Weight::from_parts(215_188_573, 6208) - // Standard Error: 264_218 - .saturating_add(Weight::from_parts(3_406_167, 0).saturating_mul(x.into())) - // Standard Error: 165_665 - .saturating_add(Weight::from_parts(71_144_235, 0).saturating_mul(y.into())) - .saturating_add(RocksDbWeight::get().reads(16_u64)) + // Measured: `2890` + // Estimated: `7404 + x * (2535 ±0)` + // Minimum execution time: 184_000_000 picoseconds. + Weight::from_parts(139_444_380, 7404) + // Standard Error: 76_200 + .saturating_add(Weight::from_parts(59_216_258, 0).saturating_mul(x.into())) + .saturating_add(RocksDbWeight::get().reads(15_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(x.into()))) .saturating_add(RocksDbWeight::get().writes(8_u64)) - .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(y.into()))) - .saturating_add(Weight::from_parts(0, 2748).saturating_mul(x.into())) + .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(x.into()))) + .saturating_add(Weight::from_parts(0, 2535).saturating_mul(x.into())) + } + /// Storage: `Funding::ProjectsMetadata` (r:1 w:0) + /// Proof: `Funding::ProjectsMetadata` (`max_values`: None, `max_size`: Some(437), added: 2912, mode: `MaxEncodedLen`) + /// Storage: `Funding::Buckets` (r:1 w:0) + /// Proof: `Funding::Buckets` (`max_values`: None, `max_size`: Some(100), added: 2575, 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::CTAmountOversubscribed` (r:1 w:1) + /// Proof: `Funding::CTAmountOversubscribed` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`) + /// Storage: `Funding::Bids` (r:2 w:1) + /// Proof: `Funding::Bids` (`max_values`: None, `max_size`: Some(309), added: 2784, mode: `MaxEncodedLen`) + /// Storage: `Funding::BidBucketBounds` (r:1 w:0) + /// Proof: `Funding::BidBucketBounds` (`max_values`: None, `max_size`: Some(60), added: 2535, mode: `MaxEncodedLen`) + fn process_next_oversubscribed_bid() -> Weight { + // Proof Size summary in bytes: + // Measured: `1630` + // Estimated: `6558` + // Minimum execution time: 29_000_000 picoseconds. + Weight::from_parts(30_000_000, 6558) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } /// Storage: `Funding::ProjectsMetadata` (r:1 w:0) /// Proof: `Funding::ProjectsMetadata` (`max_values`: None, `max_size`: Some(502), added: 2977, mode: `MaxEncodedLen`) diff --git a/runtimes/polimec/src/weights/pallet_funding.rs b/runtimes/polimec/src/weights/pallet_funding.rs index 493c4933d..2f4354e13 100644 --- a/runtimes/polimec/src/weights/pallet_funding.rs +++ b/runtimes/polimec/src/weights/pallet_funding.rs @@ -156,49 +156,68 @@ impl pallet_funding::WeightInfo for WeightInfo { /// 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(502), added: 2977, mode: `MaxEncodedLen`) + /// 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(342), added: 2817, mode: `MaxEncodedLen`) + /// 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::BidCounts` (r:1 w:1) - /// Proof: `Funding::BidCounts` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) - /// Storage: `Funding::Bids` (r:7 w:10) - /// Proof: `Funding::Bids` (`max_values`: None, `max_size`: Some(273), added: 2748, 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:10 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(110), added: 2585, mode: `MaxEncodedLen`) + /// 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(36), added: 2511, mode: `MaxEncodedLen`) + /// 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(140), added: 2615, mode: `MaxEncodedLen`) + /// 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(254), added: 2729, mode: `MaxEncodedLen`) + /// 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(210), added: 2685, mode: `MaxEncodedLen`) + /// 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(134), added: 2609, mode: `MaxEncodedLen`) - /// The range of component `x` is `[0, 6]`. - /// The range of component `y` is `[0, 10]`. - fn bid(x: u32, y: u32, ) -> Weight { + /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) + /// Storage: `Funding::CTAmountOversubscribed` (r:1 w:1) + /// Proof: `Funding::CTAmountOversubscribed` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`) + /// Storage: `Funding::Bids` (r:0 w:10) + /// Proof: `Funding::Bids` (`max_values`: None, `max_size`: Some(309), added: 2784, mode: `MaxEncodedLen`) + /// The range of component `x` is `[1, 10]`. + fn bid(x: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2866 + x * (192 ±0)` - // Estimated: `6208 + x * (2748 ±0)` - // Minimum execution time: 274_581_000 picoseconds. - Weight::from_parts(211_541_829, 0) - .saturating_add(Weight::from_parts(0, 6208)) - // Standard Error: 261_236 - .saturating_add(Weight::from_parts(3_389_028, 0).saturating_mul(x.into())) - // Standard Error: 163_796 - .saturating_add(Weight::from_parts(68_717_795, 0).saturating_mul(y.into())) - .saturating_add(T::DbWeight::get().reads(16)) + // Measured: `2890` + // Estimated: `7404 + x * (2535 ±0)` + // Minimum execution time: 184_000_000 picoseconds. + Weight::from_parts(139_444_380, 7404) + // Standard Error: 76_200 + .saturating_add(Weight::from_parts(59_216_258, 0).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().reads(15_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(x.into()))) - .saturating_add(T::DbWeight::get().writes(8)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(y.into()))) - .saturating_add(Weight::from_parts(0, 2748).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().writes(8_u64)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(x.into()))) + .saturating_add(Weight::from_parts(0, 2535).saturating_mul(x.into())) + } + /// Storage: `Funding::ProjectsMetadata` (r:1 w:0) + /// Proof: `Funding::ProjectsMetadata` (`max_values`: None, `max_size`: Some(437), added: 2912, mode: `MaxEncodedLen`) + /// Storage: `Funding::Buckets` (r:1 w:0) + /// Proof: `Funding::Buckets` (`max_values`: None, `max_size`: Some(100), added: 2575, 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::CTAmountOversubscribed` (r:1 w:1) + /// Proof: `Funding::CTAmountOversubscribed` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`) + /// Storage: `Funding::Bids` (r:2 w:1) + /// Proof: `Funding::Bids` (`max_values`: None, `max_size`: Some(309), added: 2784, mode: `MaxEncodedLen`) + /// Storage: `Funding::BidBucketBounds` (r:1 w:0) + /// Proof: `Funding::BidBucketBounds` (`max_values`: None, `max_size`: Some(60), added: 2535, mode: `MaxEncodedLen`) + fn process_next_oversubscribed_bid() -> Weight { + // Proof Size summary in bytes: + // Measured: `1630` + // Estimated: `6558` + // Minimum execution time: 29_000_000 picoseconds. + Weight::from_parts(30_000_000, 6558) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } /// Storage: `Funding::ProjectsMetadata` (r:1 w:0) /// Proof: `Funding::ProjectsMetadata` (`max_values`: None, `max_size`: Some(502), added: 2977, mode: `MaxEncodedLen`)