diff --git a/Cargo.lock b/Cargo.lock index 18fde7cec..aa0bafbe8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7296,6 +7296,7 @@ dependencies = [ "pallet-balances", "pallet-insecure-randomness-collective-flip", "pallet-linear-release", + "pallet-proxy-bonding", "pallet-timestamp", "pallet-xcm", "parachains-common", @@ -7757,6 +7758,24 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-proxy-bonding" +version = "0.8.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-assets", + "pallet-balances", + "pallet-linear-release", + "parity-scale-codec", + "polimec-common", + "scale-info", + "serde", + "sp-io", + "sp-runtime", +] + [[package]] name = "pallet-ranked-collective" version = "35.0.0" @@ -8828,6 +8847,7 @@ dependencies = [ "pallet-parachain-staking", "pallet-preimage", "pallet-proxy", + "pallet-proxy-bonding", "pallet-scheduler", "pallet-session", "pallet-timestamp", @@ -10563,24 +10583,6 @@ dependencies = [ "prost 0.12.6", ] -[[package]] -name = "proxy-bonding" -version = "0.8.0" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "pallet-assets", - "pallet-balances", - "pallet-linear-release", - "parity-scale-codec", - "polimec-common", - "scale-info", - "serde", - "sp-io", - "sp-runtime", -] - [[package]] name = "psm" version = "0.1.21" diff --git a/Cargo.toml b/Cargo.toml index 951f2d8d3..84c3a5fbd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ pallet-parachain-staking = { path = "pallets/parachain-staking", default-feature pallet-linear-release = { path = "pallets/linear-release", default-features = false } polimec-receiver = { path = "pallets/polimec-receiver", default-features = false } on-slash-vesting = { path = "pallets/on-slash-vesting", default-features = false } +pallet-proxy-bonding = { path = "pallets/proxy-bonding", default-features = false } # Internal macros macros = { path = "macros" } diff --git a/pallets/funding/Cargo.toml b/pallets/funding/Cargo.toml index 247e98e2b..35e7c95e3 100644 --- a/pallets/funding/Cargo.toml +++ b/pallets/funding/Cargo.toml @@ -29,6 +29,7 @@ variant_count = "1.1.0" pallet-linear-release.workspace = true on-slash-vesting.workspace = true +pallet-proxy-bonding.workspace = true # Substrate dependencies frame-support.workspace = true @@ -95,6 +96,7 @@ std = [ "xcm-builder/std", "xcm-executor/std", "xcm/std", + "pallet-proxy-bonding/std" ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", @@ -115,6 +117,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "pallet-proxy-bonding/runtime-benchmarks" ] try-runtime = [ "frame-support/try-runtime", @@ -128,5 +131,6 @@ try-runtime = [ "polimec-common-test-utils?/try-runtime", "polimec-common/try-runtime", "sp-runtime/try-runtime", + "pallet-proxy-bonding/try-runtime" ] on-chain-release-build = [] diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs index 6a1e8a8bb..59c4f1ebb 100644 --- a/pallets/funding/src/benchmarking.rs +++ b/pallets/funding/src/benchmarking.rs @@ -1040,11 +1040,15 @@ mod benchmarks { let price = inst.get_project_details(project_id).weighted_average_price.unwrap(); - let contributions = - vec![ - ContributionParams::new(contributor.clone(), (50 * CT_UNIT).into(), 1u8, AcceptedFundingAsset::USDT); - x as usize + 1 - ]; + let contributions = vec![ + ContributionParams::new( + contributor.clone(), + (50 * CT_UNIT).into(), + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT + ); + x as usize + 1 + ]; let plmc = inst.calculate_contributed_plmc_spent(contributions.clone(), price, false); let usdt = inst.calculate_contributed_funding_asset_spent(contributions.clone(), price); @@ -1078,7 +1082,7 @@ mod benchmarks { jwt, project_id, contributions[0].amount, - contributions[0].multiplier, + contributions[0].mode, contributions[0].asset, ); @@ -1112,7 +1116,7 @@ mod benchmarks { funding_asset: AcceptedFundingAsset::USDT, funding_amount: usdt[0].asset_amount, plmc_bond: plmc[0].plmc_amount, - multiplier: contributions[0].multiplier, + mode: contributions[0].mode, } .into(), ); @@ -1504,7 +1508,7 @@ mod benchmarks { ContributionParams::::new( participant.clone(), (10 * CT_UNIT).into(), - 1u8, + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, ) }) @@ -1846,7 +1850,7 @@ mod benchmarks { ContributionParams::::new( participant.clone(), (10 * CT_UNIT).into(), - 1u8, + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, ) }) @@ -1937,7 +1941,7 @@ mod benchmarks { ContributionParams::::new( participant.clone(), (10 * CT_UNIT).into(), - 1u8, + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, ) }) diff --git a/pallets/funding/src/functions/2_evaluation.rs b/pallets/funding/src/functions/2_evaluation.rs index b7a837a10..8fa933f52 100644 --- a/pallets/funding/src/functions/2_evaluation.rs +++ b/pallets/funding/src/functions/2_evaluation.rs @@ -124,8 +124,9 @@ impl Pallet { let mut project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; let now = >::block_number(); let evaluation_id = NextEvaluationId::::get(); - let plmc_usd_price = T::PriceProvider::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS) - .ok_or(Error::::PriceNotFound)?; + let plmc_usd_price = + >::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS) + .ok_or(Error::::PriceNotFound)?; let early_evaluation_reward_threshold_usd = T::EvaluationSuccessThreshold::get() * project_details.fundraising_target_usd; let evaluation_round_info = &mut project_details.evaluation_round_info; diff --git a/pallets/funding/src/functions/3_auction.rs b/pallets/funding/src/functions/3_auction.rs index 855ff5063..0beee0336 100644 --- a/pallets/funding/src/functions/3_auction.rs +++ b/pallets/funding/src/functions/3_auction.rs @@ -60,16 +60,8 @@ impl Pallet { #[transactional] pub fn do_bid(params: DoBidParams) -> DispatchResultWithPostInfo { // * Get variables * - let DoBidParams { - bidder, - project_id, - ct_amount, - multiplier, - funding_asset, - investor_type, - did, - whitelisted_policy, - } = params; + let DoBidParams { bidder, project_id, ct_amount, mode, funding_asset, investor_type, did, whitelisted_policy } = + params; let project_metadata = ProjectsMetadata::::get(project_id).ok_or(Error::::ProjectMetadataNotFound)?; let project_details = ProjectsDetails::::get(project_id).ok_or(Error::::ProjectDetailsNotFound)?; @@ -120,7 +112,7 @@ impl Pallet { metadata_ticket_size_bounds.usd_ticket_above_minimum_per_participation(min_total_ticket_size), Error::::TooLow ); - ensure!(multiplier.into() <= max_multiplier && multiplier.into() > 0u8, Error::::ForbiddenMultiplier); + ensure!(mode.multiplier() <= max_multiplier && mode.multiplier() > 0u8, Error::::ForbiddenMultiplier); // Note: We limit the CT Amount to the auction allocation size, to avoid long-running loops. ensure!( @@ -145,7 +137,7 @@ impl Pallet { project_id, ct_amount, ct_usd_price: current_bucket.current_price, - multiplier, + mode, funding_asset, bid_id, now, @@ -179,7 +171,7 @@ impl Pallet { project_id, ct_amount, ct_usd_price, - multiplier, + mode, funding_asset, bid_id, now, @@ -191,6 +183,7 @@ impl Pallet { let 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 @@ -214,12 +207,12 @@ impl Pallet { original_ct_usd_price: ct_usd_price, funding_asset, funding_asset_amount_locked, - multiplier, + mode, plmc_bond, when: now, }; - Self::try_plmc_participation_lock(&bidder, project_id, plmc_bond)?; + 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); @@ -236,7 +229,7 @@ impl Pallet { funding_asset, funding_amount: funding_asset_amount_locked, plmc_bond, - multiplier, + mode, }); Ok(new_bid) diff --git a/pallets/funding/src/functions/4_contribution.rs b/pallets/funding/src/functions/4_contribution.rs index 99a1c9912..8035377ee 100644 --- a/pallets/funding/src/functions/4_contribution.rs +++ b/pallets/funding/src/functions/4_contribution.rs @@ -17,7 +17,7 @@ impl Pallet { contributor, project_id, ct_amount: token_amount, - multiplier, + mode, funding_asset, investor_type, did, @@ -48,7 +48,7 @@ impl Pallet { project_id, project_details: &mut project_details, buyable_tokens, - multiplier, + mode, funding_asset, investor_type, did, @@ -65,7 +65,7 @@ impl Pallet { project_id, project_details, buyable_tokens, - multiplier, + mode, funding_asset, investor_type, did, @@ -91,6 +91,7 @@ impl Pallet { InvestorType::Professional => PROFESSIONAL_MAX_MULTIPLIER, InvestorType::Institutional => INSTITUTIONAL_MAX_MULTIPLIER, }; + let multiplier: MultiplierOf = mode.multiplier().try_into().map_err(|_| Error::::ForbiddenMultiplier)?; // * Validity checks * ensure!(project_policy == whitelisted_policy, Error::::PolicyMismatch); @@ -125,7 +126,7 @@ impl Pallet { contributor: contributor.clone(), ct_amount: buyable_tokens, usd_contribution_amount: ticket_size, - multiplier, + mode: mode.clone(), funding_asset, funding_asset_amount, plmc_bond, @@ -133,7 +134,7 @@ impl Pallet { }; // Try adding the new contribution to the system - Self::try_plmc_participation_lock(&contributor, project_id, plmc_bond)?; + Self::bond_plmc_with_mode(&contributor, project_id, plmc_bond, mode, funding_asset)?; Self::try_funding_asset_hold(&contributor, project_id, funding_asset_amount, funding_asset.id())?; Contributions::::insert((project_id, contributor.clone(), contribution_id), &new_contribution); @@ -152,7 +153,7 @@ impl Pallet { funding_asset, funding_amount: funding_asset_amount, plmc_bond, - multiplier, + mode, }); // return correct weight function diff --git a/pallets/funding/src/functions/6_settlement.rs b/pallets/funding/src/functions/6_settlement.rs index f558ffd8d..f70854b1e 100644 --- a/pallets/funding/src/functions/6_settlement.rs +++ b/pallets/funding/src/functions/6_settlement.rs @@ -12,6 +12,7 @@ use frame_support::{ }, }; use on_slash_vesting::OnSlash; +use pallet_proxy_bonding::ReleaseType; use polimec_common::{ migration_types::{MigrationInfo, MigrationOrigin, MigrationStatus, ParticipationType}, ReleaseSchedule, @@ -33,6 +34,19 @@ impl Pallet { let escrow_account = Self::fund_account_id(project_id); if project_details.status == ProjectStatus::FundingSuccessful { + let otm_release_type = { + let multiplier: MultiplierOf = + ParticipationMode::OTM.multiplier().try_into().map_err(|_| Error::::ImpossibleState)?; + let duration = multiplier.calculate_vesting_duration::(); + let now = >::block_number(); + ReleaseType::Locked(duration.saturating_add(now)) + }; + >::set_release_type( + project_id, + HoldReason::Participation.into(), + otm_release_type, + ); + T::ContributionTokenCurrency::create(project_id, escrow_account.clone(), false, 1_u32.into())?; T::ContributionTokenCurrency::set( project_id, @@ -72,6 +86,13 @@ impl Pallet { false, )?; } else { + let otm_release_type = ReleaseType::Refunded; + >::set_release_type( + project_id, + HoldReason::Participation.into(), + otm_release_type, + ); + Self::transition_project( project_id, project_details, @@ -146,32 +167,31 @@ impl Pallet { Error::::SettlementNotStarted ); - // Return either the full amount to refund if bid is rejected/project failed, - // or a partial amount when the wap > paid price/bid is partially 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 } = Self::calculate_refund(&bid, funding_success, wap)?; - Self::release_participation_bond(&bid.bidder, refunded_plmc)?; + if bid.mode == ParticipationMode::OTM { + >::refund_fee( + project_id, + HoldReason::Participation.into(), + &bid.bidder, + refunded_plmc, + bid.funding_asset.id(), + )?; + } else { + Self::release_participation_bond_for(&bid.bidder, refunded_plmc)?; + } Self::release_funding_asset(project_id, &bid.bidder, refunded_funding_asset_amount, bid.funding_asset)?; if funding_success && bid.status != BidStatus::Rejected { - let funding_end_block = project_details.funding_end_block.ok_or(Error::::ImpossibleState)?; - - let vesting_info = - Self::calculate_vesting_info(&bid.bidder, bid.multiplier, bid.plmc_bond.saturating_sub(refunded_plmc)) - .map_err(|_| Error::::BadMath)?; - - if vesting_info.duration == 1u32.into() { - Self::release_participation_bond(&bid.bidder, vesting_info.total_amount)?; - } else { - VestingOf::::add_release_schedule( - &bid.bidder, - vesting_info.total_amount, - vesting_info.amount_per_block, - funding_end_block, - HoldReason::Participation.into(), - )?; - } + let ct_vesting_duration = Self::set_plmc_bond_release_with_mode( + bid.bidder.clone(), + bid.plmc_bond, + bid.mode, + project_details.funding_end_block.ok_or(Error::::ImpossibleState)?, + )?; Self::mint_contribution_tokens(project_id, &bid.bidder, final_ct_amount)?; @@ -181,7 +201,7 @@ impl Pallet { bid.id, ParticipationType::Bid, final_ct_amount, - vesting_info.duration, + ct_vesting_duration, )?; Self::release_funding_asset( @@ -213,7 +233,7 @@ impl Pallet { wap: PriceOf, ) -> 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 { return Ok(BidRefund:: { final_ct_usd_price, @@ -225,7 +245,7 @@ impl Pallet { let final_ct_amount = bid.final_ct_amount(); 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, bid.multiplier)?; + let new_plmc_bond = Self::calculate_plmc_bond(new_ticket_size, multiplier)?; let new_funding_asset_amount = Self::calculate_funding_asset_amount(new_ticket_size, bid.funding_asset)?; let refunded_plmc = bid.plmc_bond.saturating_sub(new_plmc_bond); let refunded_funding_asset_amount = bid.funding_asset_amount_locked.saturating_sub(new_funding_asset_amount); @@ -244,8 +264,17 @@ impl Pallet { let funding_end_block = project_details.funding_end_block.ok_or(Error::::ImpossibleState)?; if outcome == FundingOutcome::Failure { - // Release the held PLMC bond - Self::release_participation_bond(&contribution.contributor, contribution.plmc_bond)?; + if contribution.mode == ParticipationMode::OTM { + >::refund_fee( + project_id, + HoldReason::Participation.into(), + &contribution.contributor, + contribution.plmc_bond, + contribution.funding_asset.id(), + )?; + } else { + Self::release_participation_bond_for(&contribution.contributor, contribution.plmc_bond)?; + } Self::release_funding_asset( project_id, @@ -254,25 +283,12 @@ impl Pallet { contribution.funding_asset, )?; } else { - // Calculate the vesting info and add the release schedule - let vesting_info = Self::calculate_vesting_info( - &contribution.contributor, - contribution.multiplier, + let ct_vesting_duration = Self::set_plmc_bond_release_with_mode( + contribution.contributor.clone(), contribution.plmc_bond, - ) - .map_err(|_| Error::::BadMath)?; - - if vesting_info.duration == 1u32.into() { - Self::release_participation_bond(&contribution.contributor, vesting_info.total_amount)?; - } else { - VestingOf::::add_release_schedule( - &contribution.contributor, - vesting_info.total_amount, - vesting_info.amount_per_block, - funding_end_block, - HoldReason::Participation.into(), - )?; - } + contribution.mode, + funding_end_block, + )?; // Mint the contribution tokens Self::mint_contribution_tokens(project_id, &contribution.contributor, contribution.ct_amount)?; @@ -292,7 +308,7 @@ impl Pallet { contribution.id, ParticipationType::Contribution, contribution.ct_amount, - vesting_info.duration, + ct_vesting_duration, )?; final_ct_amount = contribution.ct_amount; @@ -367,7 +383,7 @@ impl Pallet { Ok(()) } - fn release_participation_bond(participant: &AccountIdOf, amount: Balance) -> DispatchResult { + fn release_participation_bond_for(participant: &AccountIdOf, amount: Balance) -> DispatchResult { if amount.is_zero() { return Ok(()); } @@ -376,6 +392,44 @@ impl Pallet { Ok(()) } + fn set_plmc_bond_release_with_mode( + participant: AccountIdOf, + plmc_amount: Balance, + mode: ParticipationMode, + funding_end_block: BlockNumberFor, + ) -> Result, DispatchError> { + let multiplier: MultiplierOf = mode.multiplier().try_into().map_err(|_| Error::::ImpossibleState)?; + match mode { + ParticipationMode::OTM => Ok(multiplier.calculate_vesting_duration::()), + ParticipationMode::Classic(_) => + Self::set_release_schedule_for(&participant, plmc_amount, multiplier, funding_end_block), + } + } + + fn set_release_schedule_for( + participant: &AccountIdOf, + plmc_amount: Balance, + multiplier: MultiplierOf, + funding_end_block: BlockNumberFor, + ) -> Result, DispatchError> { + // Calculate the vesting info and add the release schedule + let vesting_info = Self::calculate_vesting_info(participant, multiplier, plmc_amount)?; + + if vesting_info.duration == 1u32.into() { + Self::release_participation_bond_for(participant, vesting_info.total_amount)?; + } else { + VestingOf::::add_release_schedule( + participant, + vesting_info.total_amount, + vesting_info.amount_per_block, + funding_end_block, + HoldReason::Participation.into(), + )?; + } + + Ok(vesting_info.duration) + } + fn slash_evaluator(evaluation: &EvaluationInfoOf) -> Result { let slash_percentage = T::EvaluatorSlash::get(); let treasury_account = T::BlockchainOperationTreasury::get(); diff --git a/pallets/funding/src/functions/misc.rs b/pallets/funding/src/functions/misc.rs index 4c6a68fc0..45d365294 100644 --- a/pallets/funding/src/functions/misc.rs +++ b/pallets/funding/src/functions/misc.rs @@ -29,8 +29,9 @@ impl Pallet { } pub fn calculate_plmc_bond(ticket_size: Balance, multiplier: MultiplierOf) -> Result { - let plmc_usd_price = T::PriceProvider::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS) - .ok_or(Error::::PriceNotFound)?; + let plmc_usd_price = + >::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS) + .ok_or(Error::::PriceNotFound)?; let usd_bond = multiplier.calculate_bonding_requirement::(ticket_size).ok_or(Error::::BadMath)?; plmc_usd_price .reciprocal() @@ -45,7 +46,7 @@ impl Pallet { ) -> Result { let asset_id = asset_id.id(); let asset_decimals = T::FundingCurrency::decimals(asset_id); - let asset_usd_price = T::PriceProvider::get_decimals_aware_price(asset_id, USD_DECIMALS, asset_decimals) + let asset_usd_price = >::get_decimals_aware_price(asset_id, USD_DECIMALS, asset_decimals) .ok_or(Error::::PriceNotFound)?; asset_usd_price .reciprocal() @@ -130,6 +131,25 @@ impl Pallet { Ok((accepted_bid_len, rejected_bids.len() as u32)) } + pub fn bond_plmc_with_mode( + who: &T::AccountId, + project_id: ProjectId, + amount: Balance, + mode: ParticipationMode, + asset: AcceptedFundingAsset, + ) -> DispatchResult { + match mode { + ParticipationMode::Classic(_) => Self::try_plmc_participation_lock(who, project_id, amount), + ParticipationMode::OTM => pallet_proxy_bonding::Pallet::::bond_on_behalf_of( + project_id, + who.clone(), + amount, + asset.id(), + HoldReason::Participation.into(), + ), + } + } + pub fn try_plmc_participation_lock(who: &T::AccountId, project_id: ProjectId, amount: Balance) -> DispatchResult { // Check if the user has already locked tokens in the evaluation period let user_evaluations = Evaluations::::iter_prefix_values((project_id, who)); diff --git a/pallets/funding/src/instantiator/calculations.rs b/pallets/funding/src/instantiator/calculations.rs index 9830cf3e1..2eb3cef00 100644 --- a/pallets/funding/src/instantiator/calculations.rs +++ b/pallets/funding/src/instantiator/calculations.rs @@ -1,8 +1,10 @@ #[allow(clippy::wildcard_imports)] use super::*; +use crate::{Multiplier, MultiplierOf, ParticipationMode}; use core::cmp::Ordering; use itertools::GroupBy; use polimec_common::{ProvideAssetPrice, USD_DECIMALS}; + impl< T: Config, AllPalletsWithoutSystem: OnFinalize> + OnIdle> + OnInitialize>, @@ -23,7 +25,7 @@ impl< with_ed: bool, ) -> Vec> { let plmc_usd_price = self.execute(|| { - T::PriceProvider::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS).unwrap() + >::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS).unwrap() }); let mut output = Vec::new(); @@ -78,7 +80,7 @@ impl< with_ed: bool, ) -> Vec> { let plmc_usd_price = self.execute(|| { - T::PriceProvider::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS).unwrap() + >::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS).unwrap() }); let mut output = Vec::new(); @@ -104,7 +106,7 @@ impl< ) -> Vec> { let mut output = Vec::new(); let plmc_usd_price = self.execute(|| { - T::PriceProvider::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS).unwrap() + >::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS).unwrap() }); for (bid, price) in self.get_actual_price_charged_for_bucketed_bids(bids, project_metadata, maybe_bucket) { @@ -137,7 +139,7 @@ impl< grouped_by_price_bids.reverse(); let plmc_usd_price = self.execute(|| { - T::PriceProvider::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS).unwrap() + >::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS).unwrap() }); let mut remaining_cts = project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; @@ -205,7 +207,7 @@ impl< let funding_asset_id = bid.asset.id(); let funding_asset_decimals = self.execute(|| T::FundingCurrency::decimals(funding_asset_id)); let funding_asset_usd_price = self.execute(|| { - T::PriceProvider::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) + >::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) .unwrap() }); let usd_ticket_size = ct_price.saturating_mul_int(bid.amount); @@ -228,7 +230,7 @@ impl< let funding_asset_id = bid.asset.id(); let funding_asset_decimals = self.execute(|| T::FundingCurrency::decimals(funding_asset_id)); let funding_asset_usd_price = self.execute(|| { - T::PriceProvider::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) + >::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) .ok_or(Error::::PriceNotFound) .unwrap() }); @@ -264,9 +266,13 @@ impl< let funding_asset_id = bid.asset.id(); let funding_asset_decimals = self.execute(|| T::FundingCurrency::decimals(funding_asset_id)); let funding_asset_usd_price = self.execute(|| { - T::PriceProvider::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) - .ok_or(Error::::PriceNotFound) - .unwrap() + >::get_decimals_aware_price( + funding_asset_id, + USD_DECIMALS, + funding_asset_decimals, + ) + .ok_or(Error::::PriceNotFound) + .unwrap() }); let charged_usd_ticket_size = price_charged.saturating_mul_int(bid.amount); @@ -346,13 +352,15 @@ impl< with_ed: bool, ) -> Vec> { let plmc_usd_price = self.execute(|| { - T::PriceProvider::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS).unwrap() + >::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS).unwrap() }); let mut output = Vec::new(); for cont in contributions { let usd_ticket_size = token_usd_price.saturating_mul_int(cont.amount); - let usd_bond = cont.multiplier.calculate_bonding_requirement::(usd_ticket_size).unwrap(); + // Needs to be forced to allow for failure tests + let multiplier = Multiplier::force_new(cont.mode.multiplier()); + let usd_bond = multiplier.calculate_bonding_requirement::(usd_ticket_size).unwrap(); let mut plmc_bond = plmc_usd_price.reciprocal().unwrap().saturating_mul_int(usd_bond); if with_ed { plmc_bond = plmc_bond.saturating_add(self.get_ed()); @@ -420,7 +428,7 @@ impl< let funding_asset_id = cont.asset.id(); let funding_asset_decimals = self.execute(|| T::FundingCurrency::decimals(funding_asset_id)); let funding_asset_usd_price = self.execute(|| { - T::PriceProvider::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) + >::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) .ok_or(Error::::PriceNotFound) .unwrap() }); @@ -587,7 +595,12 @@ impl< let ticket_size = Percent::from_percent(weight) * usd_amount; let token_amount = final_price.reciprocal().unwrap().saturating_mul_int(ticket_size); - ContributionParams::new(bidder, token_amount, multiplier, AcceptedFundingAsset::USDT) + ContributionParams::new( + bidder, + token_amount, + ParticipationMode::Classic(multiplier), + AcceptedFundingAsset::USDT, + ) }) .collect() } @@ -608,7 +621,12 @@ impl< zip(zip(weights, contributors), multipliers) .map(|((weight, contributor), multiplier)| { let token_amount = Percent::from_percent(weight) * total_ct_bought; - ContributionParams::new(contributor, token_amount, multiplier, AcceptedFundingAsset::USDT) + ContributionParams::new( + contributor, + token_amount, + ParticipationMode::Classic(multiplier), + AcceptedFundingAsset::USDT, + ) }) .collect() } diff --git a/pallets/funding/src/instantiator/chain_interactions.rs b/pallets/funding/src/instantiator/chain_interactions.rs index a51f29871..4119d26ec 100644 --- a/pallets/funding/src/instantiator/chain_interactions.rs +++ b/pallets/funding/src/instantiator/chain_interactions.rs @@ -518,7 +518,7 @@ impl< bidder: bid.bidder, project_id, ct_amount: bid.amount, - multiplier: bid.multiplier, + mode: bid.mode, funding_asset: bid.asset, did, investor_type: InvestorType::Institutional, @@ -628,7 +628,7 @@ impl< contributor: cont.contributor, project_id, ct_amount: cont.amount, - multiplier: cont.multiplier, + mode: cont.mode, funding_asset: cont.asset, did, investor_type, diff --git a/pallets/funding/src/instantiator/types.rs b/pallets/funding/src/instantiator/types.rs index d303929e5..cfe33f5a6 100644 --- a/pallets/funding/src/instantiator/types.rs +++ b/pallets/funding/src/instantiator/types.rs @@ -1,5 +1,6 @@ #[allow(clippy::wildcard_imports)] use super::*; +use crate::ParticipationMode; use frame_support::{Deserialize, Serialize}; pub type RuntimeOriginOf = ::RuntimeOrigin; @@ -241,7 +242,7 @@ impl AccountMerge for Vec> { pub struct BidParams { pub bidder: AccountIdOf, pub amount: Balance, - pub multiplier: MultiplierOf, + pub mode: ParticipationMode, pub asset: AcceptedFundingAsset, } impl BidParams { @@ -316,43 +317,38 @@ impl Accounts for Vec> { pub struct ContributionParams { pub contributor: AccountIdOf, pub amount: Balance, - pub multiplier: MultiplierOf, + pub mode: ParticipationMode, pub asset: AcceptedFundingAsset, } impl ContributionParams { - pub fn new(contributor: AccountIdOf, amount: Balance, multiplier: u8, asset: AcceptedFundingAsset) -> Self { - Self { contributor, amount, multiplier: multiplier.try_into().map_err(|_| ()).unwrap(), asset } + pub fn new( + contributor: AccountIdOf, + amount: Balance, + mode: ParticipationMode, + asset: AcceptedFundingAsset, + ) -> Self { + Self { contributor, amount, mode, asset } } pub fn new_with_defaults(contributor: AccountIdOf, amount: Balance) -> Self { - Self { - contributor, - amount, - multiplier: 1u8.try_into().unwrap_or_else(|_| panic!("multiplier could not be created from 1u8")), - asset: AcceptedFundingAsset::USDT, - } + Self { contributor, amount, mode: ParticipationMode::Classic(1u8), asset: AcceptedFundingAsset::USDT } } } impl From<(AccountIdOf, Balance)> for ContributionParams { fn from((contributor, amount): (AccountIdOf, Balance)) -> Self { - Self { - contributor, - amount, - multiplier: 1u8.try_into().unwrap_or_else(|_| panic!("multiplier could not be created from 1u8")), - asset: AcceptedFundingAsset::USDT, - } + Self { contributor, amount, mode: ParticipationMode::Classic(1u8), asset: AcceptedFundingAsset::USDT } } } -impl From<(AccountIdOf, Balance, MultiplierOf)> for ContributionParams { - fn from((contributor, amount, multiplier): (AccountIdOf, Balance, MultiplierOf)) -> Self { - Self { contributor, amount, multiplier, asset: AcceptedFundingAsset::USDT } +impl From<(AccountIdOf, Balance, ParticipationMode)> for ContributionParams { + fn from((contributor, amount, mode): (AccountIdOf, Balance, ParticipationMode)) -> Self { + Self { contributor, amount, mode, asset: AcceptedFundingAsset::USDT } } } -impl From<(AccountIdOf, Balance, MultiplierOf, AcceptedFundingAsset)> for ContributionParams { +impl From<(AccountIdOf, Balance, ParticipationMode, AcceptedFundingAsset)> for ContributionParams { fn from( - (contributor, amount, multiplier, asset): (AccountIdOf, Balance, MultiplierOf, AcceptedFundingAsset), + (contributor, amount, mode, asset): (AccountIdOf, Balance, ParticipationMode, AcceptedFundingAsset), ) -> Self { - Self { contributor, amount, multiplier, asset } + Self { contributor, amount, mode, asset } } } impl Accounts for Vec> { @@ -377,7 +373,7 @@ pub struct BidInfoFilter { pub original_ct_usd_price: Option>, pub funding_asset: Option, pub funding_asset_amount_locked: Option, - pub multiplier: Option>, + pub mode: Option, pub plmc_bond: Option, pub when: Option>, } @@ -409,7 +405,7 @@ impl BidInfoFilter { { return false; } - if self.multiplier.is_some() && self.multiplier.unwrap() != bid.multiplier { + if self.mode.is_some() && self.mode.unwrap() != bid.mode { return false; } if self.plmc_bond.is_some() && self.plmc_bond.unwrap() != bid.plmc_bond { @@ -433,7 +429,7 @@ impl Default for BidInfoFilter { original_ct_usd_price: None, funding_asset: None, funding_asset_amount_locked: None, - multiplier: None, + mode: None, plmc_bond: None, when: None, } diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index b47dad639..afead2cd4 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -89,6 +89,7 @@ use pallet_xcm::ensure_response; use polimec_common::{ credentials::{Cid, Did, EnsureOriginWithCredentials, InvestorType, UntrustedToken}, migration_types::{Migration, MigrationStatus}, + PLMC_DECIMALS, PLMC_FOREIGN_ID, USD_DECIMALS, }; use polkadot_parachain_primitives::primitives::Id as ParaId; use sp_arithmetic::traits::{One, Saturating}; @@ -96,7 +97,6 @@ use sp_runtime::{traits::AccountIdConversion, FixedPointNumber, FixedU128}; use sp_std::{marker::PhantomData, prelude::*}; pub use types::*; use xcm::v4::{prelude::*, SendXcm}; - mod functions; pub mod storage_migrations; pub mod traits; @@ -125,7 +125,6 @@ pub type Balance = u128; pub type MultiplierOf = ::Multiplier; pub type PriceOf = ::Price; -pub type PriceProviderOf = ::PriceProvider; pub type StringLimitOf = ::StringLimit; pub type AssetIdOf = <::FundingCurrency as fungibles::Inspect<::AccountId>>::AssetId; @@ -134,18 +133,16 @@ pub type VestingInfoOf = VestingInfo>; pub type ProjectMetadataOf = ProjectMetadata>, PriceOf, AccountIdOf, Cid>; pub type ProjectDetailsOf = ProjectDetails, Did, BlockNumberFor, PriceOf, EvaluationRoundInfo>; pub type EvaluationInfoOf = EvaluationInfo, BlockNumberFor>; -pub type BidInfoOf = BidInfo, AccountIdOf, BlockNumberFor, MultiplierOf>; +pub type BidInfoOf = BidInfo, AccountIdOf, BlockNumberFor>; -pub type ContributionInfoOf = - ContributionInfo, BlockNumberFor, MultiplierOf>; +pub type ContributionInfoOf = ContributionInfo, BlockNumberFor>; pub type BucketOf = Bucket>; pub type WeightInfoOf = ::WeightInfo; pub type VestingOf = pallet_linear_release::Pallet; pub type BlockNumberToBalanceOf = ::BlockNumberToBalance; - -pub const PLMC_FOREIGN_ID: u32 = 3344; -pub const PLMC_DECIMALS: u8 = 10; +pub type RuntimeHoldReasonOf = ::RuntimeHoldReason; +pub type PriceProviderOf = ::PriceProvider; #[frame_support::pallet] pub mod pallet { @@ -163,7 +160,7 @@ pub mod pallet { use polimec_common::ProvideAssetPrice; use sp_arithmetic::Percent; use sp_runtime::{ - traits::{Convert, ConvertBack, Get}, + traits::{ConstU8, Convert, ConvertBack, Get}, Perquintill, }; @@ -182,7 +179,16 @@ pub mod pallet { frame_system::Config + pallet_balances::Config + pallet_xcm::Config - + pallet_linear_release::Config::RuntimeHoldReason> + + pallet_linear_release::Config> + + pallet_proxy_bonding::Config< + RuntimeHoldReason = RuntimeHoldReasonOf, + BondingToken = Self::NativeCurrency, + BondingTokenDecimals = ConstU8, + BondingTokenId = ConstU32, + UsdDecimals = ConstU8, + FeeToken = Self::FundingCurrency, + PriceProvider = PriceProviderOf, + > { /// A way to convert from and to the account type used in CT migrations type AccountId32Conversion: ConvertBack; @@ -297,7 +303,7 @@ pub mod pallet { /// The chains native currency type NativeCurrency: fungible::InspectHold, Balance = Balance> - + fungible::MutateHold, Balance = Balance, Reason = ::RuntimeHoldReason> + + fungible::MutateHold, Balance = Balance, Reason = RuntimeHoldReasonOf> + fungible::BalancedHold, Balance = Balance> + fungible::Mutate, Balance = Balance> + fungible::Inspect, Balance = Balance>; @@ -518,7 +524,7 @@ pub mod pallet { funding_asset: AcceptedFundingAsset, funding_amount: Balance, plmc_bond: Balance, - multiplier: MultiplierOf, + mode: ParticipationMode, }, /// A contribution was made for a project. i.e token purchase Contribution { @@ -529,7 +535,7 @@ pub mod pallet { funding_asset: AcceptedFundingAsset, funding_amount: Balance, plmc_bond: Balance, - multiplier: MultiplierOf, + mode: ParticipationMode, }, BidRefunded { project_id: ProjectId, @@ -819,7 +825,7 @@ pub mod pallet { jwt: UntrustedToken, project_id: ProjectId, #[pallet::compact] ct_amount: Balance, - multiplier: T::Multiplier, + mode: ParticipationMode, funding_asset: AcceptedFundingAsset, ) -> DispatchResultWithPostInfo { let (bidder, did, investor_type, whitelisted_policy) = @@ -828,7 +834,7 @@ pub mod pallet { bidder, project_id, ct_amount, - multiplier, + mode, funding_asset, did, investor_type, @@ -865,7 +871,7 @@ pub mod pallet { jwt: UntrustedToken, project_id: ProjectId, #[pallet::compact] ct_amount: Balance, - multiplier: MultiplierOf, + mode: ParticipationMode, funding_asset: AcceptedFundingAsset, ) -> DispatchResultWithPostInfo { let (contributor, did, investor_type, whitelisted_policy) = @@ -874,7 +880,7 @@ pub mod pallet { contributor, project_id, ct_amount, - multiplier, + mode, funding_asset, did, investor_type, diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs index ae1d4db9f..cb2ae6659 100644 --- a/pallets/funding/src/mock.rs +++ b/pallets/funding/src/mock.rs @@ -35,8 +35,8 @@ use frame_system as system; use frame_system::{EnsureRoot, RawOrigin as SystemRawOrigin}; use polimec_common::{credentials::EnsureInvestor, DummyXcmSender, ProvideAssetPrice, USD_UNIT}; use polkadot_parachain_primitives::primitives::Sibling; -use sp_arithmetic::Percent; -use sp_core::H256; +use sp_arithmetic::{Perbill, Percent}; +use sp_core::{ConstU8, H256}; use sp_runtime::{ traits::{BlakeTwo256, ConvertBack, ConvertInto, Get, IdentityLookup, TryConvert}, BuildStorage, Perquintill, @@ -53,7 +53,7 @@ pub const MILLI_PLMC: Balance = PLMC / 10u128.pow(3); pub const MICRO_PLMC: Balance = PLMC / 10u128.pow(6); pub const EXISTENTIAL_DEPOSIT: Balance = 10 * MILLI_PLMC; pub type Block = frame_system::mocking::MockBlock; -pub type AccountId = u32; +pub type AccountId = u64; pub type BlockNumber = u64; pub type Identifier = u32; pub type Price = FixedU128; @@ -71,7 +71,7 @@ pub const fn free_deposit() -> Balance { pub struct SignedToAccountIndex(PhantomData<(RuntimeOrigin, AccountId, Network)>); -impl, Network: Get>> +impl, Network: Get>> TryConvert for SignedToAccountIndex where RuntimeOrigin::PalletsOrigin: @@ -80,7 +80,7 @@ where fn try_convert(o: RuntimeOrigin) -> Result { o.try_with_caller(|caller| match caller.try_into() { Ok(SystemRawOrigin::Signed(who)) => - Ok(Junction::AccountIndex64 { network: Network::get(), index: Into::::into(who).into() }.into()), + Ok(Junction::AccountIndex64 { network: Network::get(), index: Into::::into(who).into() }.into()), Ok(other) => Err(other.into()), Err(other) => Err(other), }) @@ -296,7 +296,7 @@ parameter_types! { pub const CommunityRoundDuration: BlockNumber = 18u64; pub const RemainderRoundDuration: BlockNumber = 6u64; - pub const FundingPalletId: PalletId = PalletId(*b"py/cfund"); + pub const FundingPalletId: PalletId = PalletId(*b"plmc-fun"); pub FeeBrackets: Vec<(Percent, Balance)> = vec![ (Percent::from_percent(10), 1_000_000 * USD_UNIT), (Percent::from_percent(8), 4_000_000 * USD_UNIT), @@ -305,6 +305,7 @@ parameter_types! { pub EarlyEvaluationThreshold: Percent = Percent::from_percent(10); pub EvaluatorSlash: Percent = Percent::from_percent(20); pub BlockchainOperationTreasuryAccount: AccountId = AccountId::from(696969u32); + pub ProxyBondingTreasuryAccount: AccountId = AccountId::from(555u32); pub ContributionTreasury: AccountId = AccountId::from(4204204206u32); pub FundingSuccessThreshold: Perquintill = Perquintill::from_percent(33); } @@ -353,14 +354,14 @@ pub struct DummyConverter; impl sp_runtime::traits::Convert for DummyConverter { fn convert(a: AccountId) -> [u8; 32] { let mut account: [u8; 32] = [0u8; 32]; - account[0..4].copy_from_slice(a.to_le_bytes().as_slice()); + account[0..8].copy_from_slice(a.to_le_bytes().as_slice()); account } } impl ConvertBack for DummyConverter { fn convert_back(bytes: [u8; 32]) -> AccountId { - let account: [u8; 4] = bytes[0..3].try_into().unwrap(); - u32::from_le_bytes(account) + let account: [u8; 8] = bytes[0..7].try_into().unwrap(); + u64::from_le_bytes(account) } } thread_local! { @@ -435,6 +436,27 @@ impl Config for TestRuntime { type WeightInfo = weights::SubstrateWeight; } +parameter_types! { + pub const FeePercentage: Perbill = Perbill::from_percent(5); + pub const FeeRecipient: AccountId = 80085; + pub const RootId: PalletId = PalletId(*b"treasury"); +} +impl pallet_proxy_bonding::Config for TestRuntime { + type BondingToken = Balances; + type BondingTokenDecimals = ConstU8; + type BondingTokenId = ConstU32; + type FeePercentage = FeePercentage; + type FeeRecipient = FeeRecipient; + type FeeToken = ForeignAssets; + type Id = PalletId; + type PriceProvider = ConstPriceProvider; + type RootId = RootId; + type RuntimeEvent = RuntimeEvent; + type RuntimeHoldReason = RuntimeHoldReason; + type Treasury = ProxyBondingTreasuryAccount; + type UsdDecimals = ConstU8; +} + // Configure a mock runtime to test the pallet. construct_runtime!( pub enum TestRuntime @@ -448,6 +470,7 @@ construct_runtime!( ForeignAssets: pallet_assets::::{Pallet, Call, Storage, Event, Config}, PolkadotXcm: pallet_xcm, PolimecFunding: pallet_funding::{Pallet, Call, Storage, Event, HoldReason} = 52, + ProxyBonding: pallet_proxy_bonding, } ); @@ -461,6 +484,9 @@ pub fn new_test_ext() -> sp_io::TestExternalities { (::PalletId::get().into_account_truncating(), ed), (::ContributionTreasury::get(), ed), (::BlockchainOperationTreasury::get(), ed), + /// Treasury account needs PLMC for the One Token Model participations + (ProxyBondingTreasuryAccount::get(), 1_000_000 * PLMC), + (FeeRecipient::get(), ed), ], }, foreign_assets: ForeignAssetsConfig { @@ -468,19 +494,20 @@ pub fn new_test_ext() -> sp_io::TestExternalities { ( AcceptedFundingAsset::USDT.id(), ::PalletId::get().into_account_truncating(), - false, + // asset is sufficient, i.e. participants can hold only this asset to participate with OTM + true, 10, ), ( AcceptedFundingAsset::USDC.id(), ::PalletId::get().into_account_truncating(), - false, + true, 10, ), ( AcceptedFundingAsset::DOT.id(), ::PalletId::get().into_account_truncating(), - false, + true, 10, ), ], diff --git a/pallets/funding/src/runtime_api.rs b/pallets/funding/src/runtime_api.rs index dd2520862..aeeefa5ab 100644 --- a/pallets/funding/src/runtime_api.rs +++ b/pallets/funding/src/runtime_api.rs @@ -140,7 +140,7 @@ impl Pallet { let funding_asset_id = asset.id(); let funding_asset_decimals = T::FundingCurrency::decimals(funding_asset_id); let funding_asset_usd_price = - T::PriceProvider::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) + >::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) .expect("Price not found"); let usd_ticket_size = funding_asset_usd_price.saturating_mul_int(asset_amount); diff --git a/pallets/funding/src/tests/3_auction.rs b/pallets/funding/src/tests/3_auction.rs index 369de7c0a..48c9b5521 100644 --- a/pallets/funding/src/tests/3_auction.rs +++ b/pallets/funding/src/tests/3_auction.rs @@ -332,7 +332,7 @@ mod round_flow { contributor: BIDDER_1, project_id, ct_amount: 100, - multiplier: 1u8.try_into().unwrap(), + mode: ParticipationMode::Classic(1u8), funding_asset: AcceptedFundingAsset::USDT, did, investor_type, @@ -993,7 +993,7 @@ mod bid_extrinsic { bidder: BIDDER_2, project_id: 0, ct_amount: 1, - multiplier: 1u8.try_into().unwrap(), + mode: 1u8.try_into().unwrap(), funding_asset: AcceptedFundingAsset::USDT, did, investor_type, @@ -1243,7 +1243,7 @@ mod bid_extrinsic { bidder: BIDDER_1, project_id, ct_amount: 799 * CT_UNIT, - multiplier: 1u8.try_into().unwrap(), + mode: 1u8.try_into().unwrap(), funding_asset: AcceptedFundingAsset::USDT, did: generate_did_from_account(BIDDER_1), investor_type: InvestorType::Professional, @@ -1259,7 +1259,7 @@ mod bid_extrinsic { bidder: BIDDER_2, project_id, ct_amount: 1999 * CT_UNIT, - multiplier: 1u8.try_into().unwrap(), + mode: 1u8.try_into().unwrap(), funding_asset: AcceptedFundingAsset::USDT, did: generate_did_from_account(BIDDER_1), investor_type: InvestorType::Institutional, @@ -1326,7 +1326,7 @@ mod bid_extrinsic { bidder: BIDDER_2, project_id, ct_amount: smallest_ct_amount_at_8k_usd, - multiplier: 1u8.try_into().unwrap(), + mode: 1u8.try_into().unwrap(), funding_asset: AcceptedFundingAsset::USDT, did: generate_did_from_account(BIDDER_1), investor_type: InvestorType::Professional, @@ -1345,7 +1345,7 @@ mod bid_extrinsic { bidder: BIDDER_3, project_id, ct_amount: smallest_ct_amount_at_20k_usd, - multiplier: 1u8.try_into().unwrap(), + mode: 1u8.try_into().unwrap(), funding_asset: AcceptedFundingAsset::USDT, did: generate_did_from_account(BIDDER_1), investor_type: InvestorType::Institutional, @@ -1496,7 +1496,7 @@ mod bid_extrinsic { bidder: ISSUER_1, project_id, ct_amount: 5000 * CT_UNIT, - multiplier: 1u8.try_into().unwrap(), + mode: 1u8.try_into().unwrap(), funding_asset: AcceptedFundingAsset::USDT, did: generate_did_from_account(ISSUER_1), investor_type: InvestorType::Professional, @@ -1522,7 +1522,7 @@ mod bid_extrinsic { bidder: bids[0].bidder, project_id, ct_amount: bids[0].amount, - multiplier: bids[0].multiplier, + mode: bids[0].multiplier, funding_asset: bids[0].asset, did, investor_type, diff --git a/pallets/funding/src/tests/4_contribution.rs b/pallets/funding/src/tests/4_contribution.rs index ca9cf9b89..983ed0c1e 100644 --- a/pallets/funding/src/tests/4_contribution.rs +++ b/pallets/funding/src/tests/4_contribution.rs @@ -82,7 +82,12 @@ mod round_flow { let remaining_ct = inst.get_project_details(project_id).remaining_contribution_tokens; let ct_price = inst.get_project_details(project_id).weighted_average_price.expect("CT Price should exist"); - let contributions = vec![ContributionParams::new(BOB, remaining_ct, 1u8, AcceptedFundingAsset::USDT)]; + let contributions = vec![ContributionParams::new( + BOB, + remaining_ct, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + )]; let plmc_fundings = inst.calculate_contributed_plmc_spent(contributions.clone(), ct_price, false); let plmc_existential_deposits = plmc_fundings.accounts().existential_deposits(); let foreign_asset_fundings = @@ -290,7 +295,7 @@ mod round_flow { ), project_id, total_funding_ct, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), funding_asset, ))); @@ -380,7 +385,7 @@ mod contribute_extrinsic { ), project_id, usable_ct + slashable_ct, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, ), Error::::ParticipantNotEnoughFunds @@ -402,7 +407,7 @@ mod contribute_extrinsic { ), project_id, usable_ct / 2, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, )); }); @@ -422,7 +427,7 @@ mod contribute_extrinsic { ), project_id, usable_ct, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, )); }); @@ -515,7 +520,7 @@ mod contribute_extrinsic { ), project_id, usable_bob_ct, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, )); }); @@ -544,9 +549,24 @@ mod contribute_extrinsic { default_evaluations(), default_bids(), ); - let usdt_contribution = ContributionParams::new(BUYER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let usdc_contribution = ContributionParams::new(BUYER_2, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDC); - let dot_contribution = ContributionParams::new(BUYER_3, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::DOT); + let usdt_contribution = ContributionParams::new( + BUYER_1, + 10_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ); + let usdc_contribution = ContributionParams::new( + BUYER_2, + 10_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDC, + ); + let dot_contribution = ContributionParams::new( + BUYER_3, + 10_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::DOT, + ); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); @@ -588,14 +608,13 @@ mod contribute_extrinsic { project_policy, ); let amount = 1000 * CT_UNIT; - let multiplier = Multiplier::force_new(u8_multiplier); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); if u8_multiplier > 0 { let contribution = ContributionParams:: { contributor: contributor.clone(), amount, - multiplier, + mode: ParticipationMode::Classic(u8_multiplier), asset: AcceptedFundingAsset::USDT, }; @@ -613,7 +632,7 @@ mod contribute_extrinsic { jwt, project_id, amount, - multiplier, + ParticipationMode::Classic(u8_multiplier), AcceptedFundingAsset::USDT, ) }) @@ -787,7 +806,7 @@ mod contribute_extrinsic { ), project_id, 10 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, )); }); @@ -817,7 +836,12 @@ mod contribute_extrinsic { ); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - let contribution = ContributionParams::new(BUYER_4, 500 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + let contribution = ContributionParams::new( + BUYER_4, + 500 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ); let plmc_required = inst.calculate_contributed_plmc_spent(vec![contribution.clone()], wap, false); let frozen_amount = plmc_required[0].plmc_amount; let plmc_existential_deposits = plmc_required.accounts().existential_deposits(); @@ -850,7 +874,7 @@ mod contribute_extrinsic { ), project_id, contribution.amount, - contribution.multiplier, + contribution.mode, contribution.asset )); }); @@ -894,7 +918,12 @@ mod contribute_extrinsic { ); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); - let contribution = ContributionParams::new(BUYER_4, 500 * CT_UNIT, 5u8, AcceptedFundingAsset::USDT); + let contribution = ContributionParams::new( + BUYER_4, + 500 * CT_UNIT, + ParticipationMode::Classic(5u8), + AcceptedFundingAsset::USDT, + ); let plmc_required = inst.calculate_contributed_plmc_spent(vec![contribution.clone()], wap, false); let frozen_amount = plmc_required[0].plmc_amount; let plmc_existential_deposits = plmc_required.accounts().existential_deposits(); @@ -927,7 +956,7 @@ mod contribute_extrinsic { ), project_id, contribution.amount, - contribution.multiplier, + contribution.mode, contribution.asset )); }); @@ -987,7 +1016,7 @@ mod contribute_extrinsic { remainder_contributions.push(ContributionParams::new( participant, 10 * CT_UNIT, - 1u8, + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, )); @@ -1032,7 +1061,14 @@ mod contribute_extrinsic { let token_amount: Balance = CT_UNIT; let range = 0..::MaxContributionsPerUser::get(); let contributions: Vec> = range - .map(|_| ContributionParams::new(CONTRIBUTOR, token_amount, 1u8, AcceptedFundingAsset::USDT)) + .map(|_| { + ContributionParams::new( + CONTRIBUTOR, + token_amount, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ) + }) .collect(); let plmc_funding = inst.calculate_contributed_plmc_spent(contributions.clone(), token_price, false); @@ -1049,8 +1085,12 @@ mod contribute_extrinsic { assert!(inst.contribute_for_users(project_id, contributions).is_ok()); // Try to contribute again, but it should fail because the limit of contributions for a user-project was reached. - let over_limit_contribution = - ContributionParams::new(CONTRIBUTOR, token_amount, 1u8, AcceptedFundingAsset::USDT); + let over_limit_contribution = ContributionParams::new( + CONTRIBUTOR, + token_amount, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ); assert!(inst.contribute_for_users(project_id, vec![over_limit_contribution]).is_err()); // Check that the right amount of PLMC is bonded, and funding currency is transferred @@ -1098,7 +1138,7 @@ mod contribute_extrinsic { contributor: ISSUER_1, project_id, ct_amount: 500 * CT_UNIT, - multiplier: 1u8.try_into().unwrap(), + mode: ParticipationMode::Classic(1), funding_asset: AcceptedFundingAsset::USDT, did: generate_did_from_account(ISSUER_1), investor_type: InvestorType::Institutional, @@ -1142,7 +1182,7 @@ mod contribute_extrinsic { ), project_id, 10 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, ), Error::::UserHasWinningBid @@ -1221,7 +1261,7 @@ mod contribute_extrinsic { jwt, project_id, CT_UNIT / 2, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, ), Error::::TooLow @@ -1241,7 +1281,7 @@ mod contribute_extrinsic { jwt, project_id, 9_999, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, ), Error::::TooLow @@ -1262,7 +1302,7 @@ mod contribute_extrinsic { jwt, project_id, 19_999, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, ), Error::::TooLow @@ -1326,7 +1366,7 @@ mod contribute_extrinsic { buyer_1_jwt, project_id, 9000 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, )); }); @@ -1337,7 +1377,7 @@ mod contribute_extrinsic { buyer_2_jwt_same_did.clone(), project_id, 1001 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, ), Error::::TooHigh @@ -1350,7 +1390,7 @@ mod contribute_extrinsic { buyer_2_jwt_same_did, project_id, 1000 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, )); }); @@ -1374,7 +1414,7 @@ mod contribute_extrinsic { buyer_3_jwt, project_id, 1800 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, )); }); @@ -1385,7 +1425,7 @@ mod contribute_extrinsic { buyer_4_jwt_same_did.clone(), project_id, 201 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, ), Error::::TooHigh @@ -1398,7 +1438,7 @@ mod contribute_extrinsic { buyer_4_jwt_same_did, project_id, 200 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, )); }); @@ -1422,7 +1462,7 @@ mod contribute_extrinsic { buyer_5_jwt, project_id, 4690 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, )); }); @@ -1433,7 +1473,7 @@ mod contribute_extrinsic { buyer_6_jwt_same_did.clone(), project_id, 311 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, ), Error::::TooHigh @@ -1446,7 +1486,7 @@ mod contribute_extrinsic { buyer_6_jwt_same_did, project_id, 310 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, )); }); @@ -1470,7 +1510,12 @@ mod contribute_extrinsic { generate_did_from_account(BUYER_1), project_metadata.clone().policy_ipfs_cid.unwrap(), ); - let contribution = ContributionParams::new(BUYER_1, 1_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + let contribution = ContributionParams::new( + BUYER_1, + 1_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ); let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); // 1 unit less native asset than needed @@ -1492,7 +1537,7 @@ mod contribute_extrinsic { jwt.clone(), project_id, 1_000 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, ), Error::::ParticipantNotEnoughFunds @@ -1528,7 +1573,7 @@ mod contribute_extrinsic { jwt, project_id, 1_000 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT, ), Error::::ParticipantNotEnoughFunds @@ -1572,7 +1617,7 @@ mod contribute_extrinsic { ), project, 1000 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT ), Error::::IncorrectRound @@ -1609,9 +1654,24 @@ mod contribute_extrinsic { }) .collect::>(); - let usdt_contribution = ContributionParams::new(BUYER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let usdc_contribution = ContributionParams::new(BUYER_2, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDC); - let dot_contribution = ContributionParams::new(BUYER_3, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::DOT); + let usdt_contribution = ContributionParams::new( + BUYER_1, + 10_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ); + let usdc_contribution = ContributionParams::new( + BUYER_2, + 10_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDC, + ); + let dot_contribution = ContributionParams::new( + BUYER_3, + 10_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::DOT, + ); let project_id_usdc = inst.create_community_contributing_project( project_metadata_usdc, @@ -1653,8 +1713,12 @@ mod contribute_extrinsic { let evaluator_contributor = 69; let evaluation_amount = 420 * USD_UNIT; - let evaluator_contribution = - ContributionParams::new(evaluator_contributor, 600 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + let evaluator_contribution = ContributionParams::new( + evaluator_contributor, + 600 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ); evaluations_1.push((evaluator_contributor, evaluation_amount).into()); let _project_id_1 = inst.create_community_contributing_project( @@ -1702,7 +1766,7 @@ mod contribute_extrinsic { ), project_id_2, evaluator_contribution.amount, - evaluator_contribution.multiplier, + evaluator_contribution.mode, evaluator_contribution.asset ), Error::::ParticipantNotEnoughFunds @@ -1734,7 +1798,7 @@ mod contribute_extrinsic { ), project_id, 5000 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1), AcceptedFundingAsset::USDT ), Error::::PolicyMismatch @@ -1755,7 +1819,12 @@ mod contribute_extrinsic { ); let project_details = inst.get_project_details(project_id); let remaining_cts = project_details.remaining_contribution_tokens; - let glutton_contribution = ContributionParams::new(BUYER_1, remaining_cts, 4u8, AcceptedFundingAsset::USDT); + let glutton_contribution = ContributionParams::new( + BUYER_1, + remaining_cts, + ParticipationMode::Classic(4u8), + AcceptedFundingAsset::USDT, + ); let wap = project_details.weighted_average_price.unwrap(); let plmc_mint = inst.calculate_contributed_plmc_spent(vec![glutton_contribution.clone()], wap, true); let funding_asset_mint = @@ -1764,8 +1833,12 @@ mod contribute_extrinsic { inst.mint_funding_asset_to(funding_asset_mint); inst.contribute_for_users(project_id, vec![glutton_contribution.clone()]).unwrap(); - let failing_contribution = - ContributionParams::::new(BUYER_2, 1000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + let failing_contribution = ContributionParams::::new( + BUYER_2, + 1000 * CT_UNIT, + ParticipationMode::Classic(1), + AcceptedFundingAsset::USDT, + ); let plmc_mint = inst.calculate_contributed_plmc_spent(vec![glutton_contribution.clone()], wap, true); let funding_asset_mint = inst.calculate_contributed_funding_asset_spent(vec![glutton_contribution.clone()], wap); @@ -1783,7 +1856,7 @@ mod contribute_extrinsic { ), project_id, failing_contribution.amount, - failing_contribution.multiplier, + failing_contribution.mode, failing_contribution.asset ), Error::::ProjectSoldOut diff --git a/pallets/funding/src/tests/6_settlement.rs b/pallets/funding/src/tests/6_settlement.rs index c023bdbac..a0727710b 100644 --- a/pallets/funding/src/tests/6_settlement.rs +++ b/pallets/funding/src/tests/6_settlement.rs @@ -989,8 +989,12 @@ mod settle_contribution_extrinsic { let ed = inst.get_ed(); let project_metadata = default_project_metadata(ISSUER_1); - let contribution = - ContributionParams::::new(BUYER_1, 1000 * CT_UNIT, 2, AcceptedFundingAsset::USDT); + let contribution = ContributionParams::::new( + BUYER_1, + 1000 * CT_UNIT, + ParticipationMode::Classic(2), + AcceptedFundingAsset::USDT, + ); let project_id = inst.create_finished_project( project_metadata.clone(), @@ -1041,7 +1045,8 @@ mod settle_contribution_extrinsic { true, ); - let vesting_time = contribution.multiplier.calculate_vesting_duration::(); + let multiplier: MultiplierOf = contribution.mode.multiplier().try_into().ok().unwrap(); + let vesting_time = multiplier.calculate_vesting_duration::(); let current_block = inst.current_block(); inst.jump_to_block(current_block + vesting_time + 1); inst.execute(|| LinearRelease::vest(RuntimeOrigin::signed(BUYER_1), hold_reason).expect("Vesting failed")); @@ -1071,10 +1076,18 @@ mod settle_contribution_extrinsic { default_community_contributor_multipliers(), ); - let contribution_mul_1 = - ContributionParams::::new(BUYER_6, 1000 * CT_UNIT, 1, AcceptedFundingAsset::USDT); - let contribution_mul_2 = - ContributionParams::::new(BUYER_7, 1000 * CT_UNIT, 2, AcceptedFundingAsset::USDT); + let contribution_mul_1 = ContributionParams::::new( + BUYER_6, + 1000 * CT_UNIT, + ParticipationMode::Classic(1), + AcceptedFundingAsset::USDT, + ); + let contribution_mul_2 = ContributionParams::::new( + BUYER_7, + 1000 * CT_UNIT, + ParticipationMode::Classic(2), + AcceptedFundingAsset::USDT, + ); community_contributions.push(contribution_mul_1); @@ -1107,7 +1120,7 @@ mod settle_contribution_extrinsic { ), project_id, contribution_mul_2.amount, - contribution_mul_2.multiplier, + contribution_mul_2.mode, contribution_mul_2.asset )); }); diff --git a/pallets/funding/src/tests/misc.rs b/pallets/funding/src/tests/misc.rs index 76108ff14..6594d36ba 100644 --- a/pallets/funding/src/tests/misc.rs +++ b/pallets/funding/src/tests/misc.rs @@ -283,11 +283,36 @@ mod helper_functions { ); let contributions = vec![ - ContributionParams::new(CONTRIBUTOR_1, TOKEN_AMOUNT_1, MULTIPLIER_1, AcceptedFundingAsset::USDT), - ContributionParams::new(CONTRIBUTOR_2, TOKEN_AMOUNT_2, MULTIPLIER_2, AcceptedFundingAsset::USDT), - ContributionParams::new(CONTRIBUTOR_3, TOKEN_AMOUNT_3, MULTIPLIER_3, AcceptedFundingAsset::USDT), - ContributionParams::new(CONTRIBUTOR_4, TOKEN_AMOUNT_4, MULTIPLIER_4, AcceptedFundingAsset::USDT), - ContributionParams::new(CONTRIBUTOR_5, TOKEN_AMOUNT_5, MULTIPLIER_5, AcceptedFundingAsset::USDT), + ContributionParams::new( + CONTRIBUTOR_1, + TOKEN_AMOUNT_1, + ParticipationMode::Classic(MULTIPLIER_1), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new( + CONTRIBUTOR_2, + TOKEN_AMOUNT_2, + ParticipationMode::Classic(MULTIPLIER_2), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new( + CONTRIBUTOR_3, + TOKEN_AMOUNT_3, + ParticipationMode::Classic(MULTIPLIER_3), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new( + CONTRIBUTOR_4, + TOKEN_AMOUNT_4, + ParticipationMode::Classic(MULTIPLIER_4), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new( + CONTRIBUTOR_5, + TOKEN_AMOUNT_5, + ParticipationMode::Classic(MULTIPLIER_5), + AcceptedFundingAsset::USDT, + ), ]; let expected_plmc_spent = vec![ diff --git a/pallets/funding/src/tests/mod.rs b/pallets/funding/src/tests/mod.rs index f45a5c1a6..9aacdbac2 100644 --- a/pallets/funding/src/tests/mod.rs +++ b/pallets/funding/src/tests/mod.rs @@ -217,31 +217,106 @@ pub mod defaults { pub fn default_community_contributions() -> Vec> { vec![ - ContributionParams::new(BUYER_1, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BUYER_2, 130_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BUYER_3, 30_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BUYER_4, 210_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BUYER_5, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new( + BUYER_1, + 50_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new( + BUYER_2, + 130_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new( + BUYER_3, + 30_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new( + BUYER_4, + 210_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new( + BUYER_5, + 10_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), ] } pub fn default_remainder_contributions() -> Vec> { vec![ - ContributionParams::new(EVALUATOR_2, 20_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BUYER_2, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BIDDER_1, 30_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new( + EVALUATOR_2, + 20_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new( + BUYER_2, + 5_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new( + BIDDER_1, + 30_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), ] } pub fn knowledge_hub_buys() -> Vec> { vec![ - ContributionParams::new(BUYER_1, 4_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BUYER_2, 2_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BUYER_3, 2_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BUYER_4, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BUYER_5, 30_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BUYER_6, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BUYER_7, 2_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new( + BUYER_1, + 4_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new( + BUYER_2, + 2_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new( + BUYER_3, + 2_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new( + BUYER_4, + 5_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new( + BUYER_5, + 30_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new( + BUYER_6, + 5_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new( + BUYER_7, + 2_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), ] } diff --git a/pallets/funding/src/tests/runtime_api.rs b/pallets/funding/src/tests/runtime_api.rs index bff7d66e7..a2d10a05d 100644 --- a/pallets/funding/src/tests/runtime_api.rs +++ b/pallets/funding/src/tests/runtime_api.rs @@ -483,9 +483,14 @@ fn get_next_vesting_schedule_merge_candidates() { BidParams::new(BIDDER_2, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), ]; let remaining_contributions = vec![ - ContributionParams::new(BIDDER_1, 1_000 * CT_UNIT, 5u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BIDDER_1, 15_000 * CT_UNIT, 10u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BIDDER_1, 100 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BIDDER_1, 1_000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::USDT), + ContributionParams::new( + BIDDER_1, + 15_000 * CT_UNIT, + ParticipationMode::Classic(10u8), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new(BIDDER_1, 100 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT), ]; let project_id = inst.create_finished_project( @@ -563,16 +568,36 @@ fn all_project_participations_by_did() { BidParams::new(BIDDER_2, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), ]; let community_contributions = vec![ - ContributionParams::new(BUYER_1, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BUYER_2, 130_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BUYER_3, 30_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BUYER_4, 210_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BUYER_5, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new(BUYER_1, 50_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT), + ContributionParams::new( + BUYER_2, + 130_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new(BUYER_3, 30_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT), + ContributionParams::new( + BUYER_4, + 210_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new(BUYER_5, 10_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT), ]; let remainder_contributions = vec![ - ContributionParams::new(EVALUATOR_2, 20_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BUYER_2, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - ContributionParams::new(BIDDER_1, 30_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + ContributionParams::new( + EVALUATOR_2, + 20_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), + ContributionParams::new(BUYER_2, 5_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT), + ContributionParams::new( + BIDDER_1, + 30_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), ]; let evaluations_plmc = inst.calculate_evaluation_plmc_spent(evaluations.clone(), true); @@ -660,7 +685,7 @@ fn all_project_participations_by_did() { jwt, project_id, contribution.amount, - contribution.multiplier, + contribution.mode, contribution.asset, ) .unwrap(); diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index a18448195..434801f3c 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -48,12 +48,12 @@ pub mod config { Decode, Eq, PartialEq, - RuntimeDebug, TypeInfo, MaxEncodedLen, Copy, Ord, PartialOrd, + RuntimeDebug, Serialize, Deserialize, )] @@ -345,7 +345,7 @@ pub mod storage { } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] - pub struct BidInfo { + pub struct BidInfo { pub id: u32, pub project_id: ProjectId, pub bidder: AccountId, @@ -356,12 +356,12 @@ pub mod storage { pub original_ct_usd_price: Price, pub funding_asset: AcceptedFundingAsset, pub funding_asset_amount_locked: Balance, - pub multiplier: Multiplier, + pub mode: ParticipationMode, pub plmc_bond: Balance, pub when: BlockNumber, } - impl - BidInfo + impl + BidInfo { pub fn final_ct_amount(&self) -> Balance { match self.status { @@ -372,8 +372,8 @@ pub mod storage { } } - impl Ord - for BidInfo + impl Ord + for BidInfo { fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { match self.original_ct_usd_price.cmp(&other.original_ct_usd_price) { @@ -383,8 +383,8 @@ pub mod storage { } } - impl - PartialOrd for BidInfo + impl PartialOrd + for BidInfo { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -392,14 +392,14 @@ pub mod storage { } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] - pub struct ContributionInfo { + pub struct ContributionInfo { pub id: Id, pub did: Did, pub project_id: ProjectId, pub contributor: AccountId, pub ct_amount: Balance, pub usd_contribution_amount: Balance, - pub multiplier: Multiplier, + pub mode: ParticipationMode, pub funding_asset: AcceptedFundingAsset, pub funding_asset_amount: Balance, pub plmc_bond: Balance, @@ -800,12 +800,31 @@ pub mod inner { pub project_id: ProjectId, pub migration_origins: MigrationOrigins, } + + #[derive( + Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen, Serialize, Deserialize, + )] + pub enum ParticipationMode { + /// One Token Model. User only needs funding assets, and pays a fee to bond treasury PLMC. + OTM, + /// Normal model. User needs to bond PLMC based on a multiplier, and pays no fee. + Classic(u8), + } + impl ParticipationMode { + pub fn multiplier(&self) -> u8 { + match self { + // OTM multiplier is fixed at 5 + ParticipationMode::OTM => 5u8, + ParticipationMode::Classic(multiplier) => *multiplier, + } + } + } } pub mod extrinsic { use crate::{ - AcceptedFundingAsset, AccountIdOf, Balance, Config, MultiplierOf, PriceOf, ProjectDetailsOf, ProjectId, - TicketSize, + AcceptedFundingAsset, AccountIdOf, Balance, Config, MultiplierOf, ParticipationMode, PriceOf, ProjectDetailsOf, + ProjectId, TicketSize, }; use frame_system::pallet_prelude::BlockNumberFor; use polimec_common::credentials::{Cid, Did, InvestorType}; @@ -814,7 +833,7 @@ pub mod extrinsic { pub bidder: AccountIdOf, pub project_id: ProjectId, pub ct_amount: Balance, - pub multiplier: MultiplierOf, + pub mode: ParticipationMode, pub funding_asset: AcceptedFundingAsset, pub did: Did, pub investor_type: InvestorType, @@ -826,7 +845,7 @@ pub mod extrinsic { pub project_id: ProjectId, pub ct_amount: Balance, pub ct_usd_price: PriceOf, - pub multiplier: MultiplierOf, + pub mode: ParticipationMode, pub funding_asset: AcceptedFundingAsset, pub bid_id: u32, pub now: BlockNumberFor, @@ -840,7 +859,7 @@ pub mod extrinsic { pub contributor: AccountIdOf, pub project_id: ProjectId, pub ct_amount: Balance, - pub multiplier: MultiplierOf, + pub mode: ParticipationMode, pub funding_asset: AcceptedFundingAsset, pub did: Did, pub investor_type: InvestorType, @@ -852,7 +871,7 @@ pub mod extrinsic { pub project_id: ProjectId, pub project_details: &'a mut ProjectDetailsOf, pub buyable_tokens: Balance, - pub multiplier: MultiplierOf, + pub mode: ParticipationMode, pub funding_asset: AcceptedFundingAsset, pub investor_type: InvestorType, pub did: Did, diff --git a/pallets/proxy-bonding/Cargo.toml b/pallets/proxy-bonding/Cargo.toml index f860e13ca..f7b262e14 100644 --- a/pallets/proxy-bonding/Cargo.toml +++ b/pallets/proxy-bonding/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "proxy-bonding" +name = "pallet-proxy-bonding" authors.workspace = true documentation.workspace = true edition.workspace = true @@ -20,7 +20,7 @@ sp-runtime.workspace = true polimec-common.workspace = true parity-scale-codec.workspace = true scale-info.workspace = true -serde = { version = "1.0.204", features = ["derive"] } +serde = { workspace = true, features = ["derive"] } [dev-dependencies] sp-io.workspace = true @@ -44,14 +44,18 @@ std = [ "sp-io/std", "pallet-linear-release/std", "pallet-balances/std", - "pallet-assets/std" + "pallet-assets/std", + "serde/std" ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "sp-runtime/try-runtime", - "polimec-common/try-runtime" + "polimec-common/try-runtime", + "pallet-linear-release/try-runtime", + "pallet-assets/try-runtime", + "pallet-balances/try-runtime" ] runtime-benchmarks = [ @@ -59,5 +63,8 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", - "polimec-common/runtime-benchmarks" + "polimec-common/runtime-benchmarks", + "pallet-linear-release/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-balances/runtime-benchmarks" ] diff --git a/pallets/proxy-bonding/src/functions.rs b/pallets/proxy-bonding/src/functions.rs index c7c79a8cc..a91a470b9 100644 --- a/pallets/proxy-bonding/src/functions.rs +++ b/pallets/proxy-bonding/src/functions.rs @@ -1,4 +1,4 @@ -use crate::{AccountIdOf, AssetId, BalanceOf, Config, Error, Pallet, ReleaseType, Releases}; +use crate::{AccountIdOf, AssetId, BalanceOf, Config, Error, Pallet, PriceProviderOf, ReleaseType, Releases}; use frame_support::{ ensure, traits::{ @@ -21,7 +21,7 @@ impl Pallet { /// e.g. if the fee is 1%, native token PLMC, fee_asset USDT, bond_amount 1000 PLMC, PLMC price 0.5USD, USDT price 1USD, /// Then the calculated fee would be 1% * 1000 * 0.5 = 5USD, which is 5 USDT at a price of 1USD. pub fn calculate_fee(bond_amount: BalanceOf, fee_asset: AssetId) -> Result, DispatchError> { - let bonding_token_price = T::PriceProvider::get_decimals_aware_price( + let bonding_token_price = >::get_decimals_aware_price( T::BondingTokenId::get(), T::UsdDecimals::get(), T::BondingTokenDecimals::get(), @@ -30,7 +30,7 @@ impl Pallet { let fee_asset_decimals = >>::decimals(fee_asset); let fee_token_price = - T::PriceProvider::get_decimals_aware_price(fee_asset, T::UsdDecimals::get(), fee_asset_decimals) + >::get_decimals_aware_price(fee_asset, T::UsdDecimals::get(), fee_asset_decimals) .ok_or(Error::::PriceNotAvailable)?; let bonded_in_usd = bonding_token_price.saturating_mul_int(bond_amount); diff --git a/pallets/proxy-bonding/src/lib.rs b/pallets/proxy-bonding/src/lib.rs index a97c4b4af..d29b374fc 100644 --- a/pallets/proxy-bonding/src/lib.rs +++ b/pallets/proxy-bonding/src/lib.rs @@ -46,6 +46,7 @@ pub mod pallet { pub type BalanceOf = <::BondingToken as fungible::Inspect>>::Balance; pub type AccountIdOf = ::AccountId; pub type HoldReasonOf = <::BondingToken as fungible::InspectHold>>::Reason; + pub type PriceProviderOf = ::PriceProvider; /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] diff --git a/polimec-common/common/src/lib.rs b/polimec-common/common/src/lib.rs index 8533c4459..c7d3472ca 100644 --- a/polimec-common/common/src/lib.rs +++ b/polimec-common/common/src/lib.rs @@ -224,7 +224,8 @@ pub mod migration_types { pub const USD_DECIMALS: u8 = 6; pub const USD_UNIT: u128 = 10u128.pow(USD_DECIMALS as u32); - +pub const PLMC_DECIMALS: u8 = 10; +pub const PLMC_FOREIGN_ID: u32 = 3344; pub struct DummyXcmSender; impl SendXcm for DummyXcmSender { type Ticket = (); diff --git a/runtimes/polimec/Cargo.toml b/runtimes/polimec/Cargo.toml index fd5af08e6..cac98208b 100644 --- a/runtimes/polimec/Cargo.toml +++ b/runtimes/polimec/Cargo.toml @@ -38,6 +38,7 @@ shared-configuration.workspace = true polimec-common.workspace = true pallet-parachain-staking.workspace = true on-slash-vesting.workspace = true +pallet-proxy-bonding.workspace = true # Substrate frame-benchmarking = { workspace = true, optional = true } @@ -187,6 +188,7 @@ std = [ "xcm-builder/std", "xcm-executor/std", "xcm/std", + "pallet-proxy-bonding/std" ] runtime-benchmarks = [ @@ -232,6 +234,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "pallet-proxy-bonding/runtime-benchmarks" ] try-runtime = [ @@ -276,6 +279,7 @@ try-runtime = [ "polkadot-runtime-common/try-runtime", "shared-configuration/try-runtime", "sp-runtime/try-runtime", + "pallet-proxy-bonding/try-runtime" ] # A feature that should be enabled when the runtime should be built for on-chain diff --git a/runtimes/polimec/src/benchmark_helpers.rs b/runtimes/polimec/src/benchmark_helpers.rs index 1cf82cb63..26aa3e4a5 100644 --- a/runtimes/polimec/src/benchmark_helpers.rs +++ b/runtimes/polimec/src/benchmark_helpers.rs @@ -1,6 +1,7 @@ use crate::{Oracle, Runtime, RuntimeOrigin}; use alloc::vec; use pallet_funding::{traits::SetPrices, AcceptedFundingAsset}; +use polimec_common::PLMC_FOREIGN_ID; use sp_runtime::{BoundedVec, FixedU128}; pub struct SetOraclePrices; @@ -9,7 +10,7 @@ impl SetPrices for SetOraclePrices { let dot = (AcceptedFundingAsset::DOT.id(), FixedU128::from_rational(69, 1)); let usdc = (AcceptedFundingAsset::USDC.id(), FixedU128::from_rational(1, 1)); let usdt = (AcceptedFundingAsset::USDT.id(), FixedU128::from_rational(1, 1)); - let plmc = (pallet_funding::PLMC_FOREIGN_ID, FixedU128::from_rational(840, 100)); + let plmc = (PLMC_FOREIGN_ID, FixedU128::from_rational(840, 100)); let values: BoundedVec<(u32, FixedU128), ::MaxFeedValues> = vec![dot, usdc, usdt, plmc].try_into().expect("benchmarks can panic"); diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index db0447098..3518df340 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -34,6 +34,7 @@ use frame_support::{ TransformOrigin, }, weights::{ConstantMultiplier, Weight}, + PalletId, }; use frame_system::{EnsureRoot, EnsureRootWithSuccess, EnsureSigned, EnsureSignedBy}; use pallet_aura::Authorities; @@ -51,7 +52,7 @@ use polimec_common::credentials::{Did, EnsureInvestor}; use polkadot_runtime_common::{BlockHashCount, CurrencyToVote, SlowAdjustingFeeUpdate}; use shared_configuration::proxy; use sp_api::impl_runtime_apis; -use sp_core::{crypto::KeyTypeId, ConstU64, OpaqueMetadata}; +use sp_core::{crypto::KeyTypeId, ConstU64, ConstU8, OpaqueMetadata}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, traits::{ @@ -1078,6 +1079,31 @@ impl pallet_funding::Config for Runtime { type WeightInfo = weights::pallet_funding::WeightInfo; } +use polimec_common::{PLMC_DECIMALS, PLMC_FOREIGN_ID, USD_DECIMALS}; +parameter_types! { + // Fee is defined as 1.5% of the usd_amount. Since fee is applied to the plmc amount, and that is always 5 times + // less than the usd_amount (multiplier of 5), we multiply the 1.5 by 5 to get 7.5% + pub const FeePercentage: Perbill = Perbill::from_percent(5); + // TODO: add a real account here + pub FeeRecipient: AccountId = [0u8; 32].into(); + pub const RootId: PalletId = PalletId(*b"treasury"); +} +impl pallet_proxy_bonding::Config for Runtime { + type BondingToken = Balances; + type BondingTokenDecimals = ConstU8; + type BondingTokenId = ConstU32; + type FeePercentage = FeePercentage; + type FeeRecipient = FeeRecipient; + type FeeToken = ForeignAssets; + type Id = PalletId; + type PriceProvider = OraclePriceProvider; + type RootId = TreasuryId; + type RuntimeEvent = RuntimeEvent; + type RuntimeHoldReason = RuntimeHoldReason; + type Treasury = TreasuryAccount; + type UsdDecimals = ConstU8; +} + #[cfg(feature = "runtime-benchmarks")] parameter_types! { pub BenchmarkReason: RuntimeHoldReason = RuntimeHoldReason::Funding(pallet_funding::HoldReason::Participation); @@ -1183,6 +1209,7 @@ construct_runtime!( Funding: pallet_funding = 80, LinearRelease: pallet_linear_release = 81, + ProxyBonding: pallet_proxy_bonding = 82, } ); diff --git a/runtimes/shared-configuration/src/currency.rs b/runtimes/shared-configuration/src/currency.rs index 133647402..a8379e918 100644 --- a/runtimes/shared-configuration/src/currency.rs +++ b/runtimes/shared-configuration/src/currency.rs @@ -19,6 +19,7 @@ use frame_support::parameter_types; use pallet_funding::AcceptedFundingAsset; use pallet_oracle_ocw::types::AssetName; use parachains_common::AssetIdForTrustBackedAssets as AssetId; +use polimec_common::PLMC_FOREIGN_ID; use sp_runtime::{traits::Convert, FixedU128}; /// One PLMC @@ -88,7 +89,7 @@ impl Convert<(AssetName, FixedU128), (AssetId, Price)> for AssetPriceConverter { AssetName::DOT => (AcceptedFundingAsset::DOT.id(), price), AssetName::USDC => (AcceptedFundingAsset::USDC.id(), price), AssetName::USDT => (AcceptedFundingAsset::USDT.id(), price), - AssetName::PLMC => (pallet_funding::PLMC_FOREIGN_ID, price), + AssetName::PLMC => (PLMC_FOREIGN_ID, price), } } } diff --git a/runtimes/shared-configuration/src/funding.rs b/runtimes/shared-configuration/src/funding.rs index 10ae7350f..ee9280698 100644 --- a/runtimes/shared-configuration/src/funding.rs +++ b/runtimes/shared-configuration/src/funding.rs @@ -18,7 +18,7 @@ use crate::{Balance, BlockNumber}; use frame_support::{parameter_types, PalletId}; use pallet_funding::types::AcceptedFundingAsset; use parachains_common::AssetIdForTrustBackedAssets; -use polimec_common::USD_UNIT; +use polimec_common::{PLMC_FOREIGN_ID, USD_UNIT}; use sp_arithmetic::{FixedU128, Percent}; use sp_runtime::Perquintill; use sp_std::{collections::btree_map::BTreeMap, vec, vec::Vec}; @@ -63,7 +63,7 @@ parameter_types! { (AcceptedFundingAsset::DOT.id(), FixedU128::from_rational(69, 1)), // DOT (AcceptedFundingAsset::USDC.id(), FixedU128::from_rational(100, 100)), // USDC (AcceptedFundingAsset::USDT.id(), FixedU128::from_rational(100, 100)), // USDT - (pallet_funding::PLMC_FOREIGN_ID, FixedU128::from_rational(840, 100)), // PLMC + (PLMC_FOREIGN_ID, FixedU128::from_rational(840, 100)), // PLMC ]); pub FeeBrackets: Vec<(Percent, Balance)> = vec![ (Percent::from_percent(10), 1_000_000 * USD_UNIT),