diff --git a/Cargo.lock b/Cargo.lock index aa0bafbe8..a17c304ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7772,6 +7772,7 @@ dependencies = [ "polimec-common", "scale-info", "serde", + "sp-core", "sp-io", "sp-runtime", ] diff --git a/integration-tests/src/constants.rs b/integration-tests/src/constants.rs index f57e21e64..500b27a61 100644 --- a/integration-tests/src/constants.rs +++ b/integration-tests/src/constants.rs @@ -445,7 +445,7 @@ pub mod polimec { let dot = (AcceptedFundingAsset::DOT.id(), prices.dot); let usdc = (AcceptedFundingAsset::USDC.id(), prices.usdc); let usdt = (AcceptedFundingAsset::USDT.id(), prices.usdt); - let plmc = (pallet_funding::PLMC_FOREIGN_ID, prices.plmc); + let plmc = (polimec_common::PLMC_FOREIGN_ID, prices.plmc); let values: BoundedVec<(u32, FixedU128), ::MaxFeedValues> = vec![dot, usdc, usdt, plmc].try_into().expect("benchmarks can panic"); @@ -501,8 +501,10 @@ pub mod polimec { funded_accounts.extend(accounts::init_balances().iter().cloned().map(|k| (k, INITIAL_DEPOSIT))); funded_accounts.extend(collators::initial_authorities().iter().cloned().map(|(acc, _)| (acc, 20_005 * PLMC))); - funded_accounts.push((TreasuryAccount::get(), 20_005 * PLMC)); + funded_accounts.push((TreasuryAccount::get(), 20_000_000 * PLMC)); funded_accounts.push((BlockchainOperationTreasury::get(), 20_005 * PLMC)); + /// Treasury account needs PLMC for the One Token Model participations + funded_accounts.push((polimec_runtime::FeeRecipient::get(), INITIAL_DEPOSIT)); let genesis_config = polimec_runtime::RuntimeGenesisConfig { system: Default::default(), @@ -510,9 +512,9 @@ pub mod polimec { contribution_tokens: Default::default(), foreign_assets: polimec_runtime::ForeignAssetsConfig { assets: vec![ - (dot_asset_id, alice_account.clone(), true, 0_0_010_000_000u128), - (usdt_asset_id, alice_account.clone(), true, 0_0_010_000_000u128), - (usdc_asset_id, alice_account.clone(), true, 0_0_010_000_000u128), + (dot_asset_id, alice_account.clone(), true, 100_000_000), + (usdt_asset_id, alice_account.clone(), true, 70_000), + (usdc_asset_id, alice_account.clone(), true, 70_000), ], metadata: vec![ (dot_asset_id, "Local DOT".as_bytes().to_vec(), "DOT".as_bytes().to_vec(), 10), @@ -520,9 +522,9 @@ pub mod polimec { (usdc_asset_id, "Local USDC".as_bytes().to_vec(), "USDC".as_bytes().to_vec(), 6), ], accounts: vec![ - (dot_asset_id, TreasuryAccount::get(), 0_0_010_000_000u128), - (usdt_asset_id, TreasuryAccount::get(), 0_0_010_000_000u128), - (usdc_asset_id, TreasuryAccount::get(), 0_0_010_000_000u128), + // (dot_asset_id, TreasuryAccount::get(), 100_000_000), + // (usdt_asset_id, TreasuryAccount::get(), 70_000), + // (usdc_asset_id, TreasuryAccount::get(), 70_000), ], }, parachain_info: polimec_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), ..Default::default() }, diff --git a/integration-tests/src/tests/credentials.rs b/integration-tests/src/tests/credentials.rs index 81d9b49a6..a2138725a 100644 --- a/integration-tests/src/tests/credentials.rs +++ b/integration-tests/src/tests/credentials.rs @@ -17,6 +17,7 @@ use crate::*; use frame_support::{assert_err, assert_ok, dispatch::GetDispatchInfo, traits::tokens::currency::VestingSchedule}; use macros::generate_accounts; +use pallet_funding::ParticipationMode::{Classic, OTM}; use polimec_common::credentials::{Did, InvestorType}; use polimec_common_test_utils::{get_fake_jwt, get_mock_jwt_with_cid, get_test_jwt}; use polimec_runtime::PLMC; diff --git a/integration-tests/src/tests/defaults.rs b/integration-tests/src/tests/defaults.rs index 3153edce3..550e805f0 100644 --- a/integration-tests/src/tests/defaults.rs +++ b/integration-tests/src/tests/defaults.rs @@ -17,8 +17,8 @@ use crate::PolimecRuntime; use frame_support::BoundedVec; pub use pallet_funding::instantiator::{BidParams, ContributionParams, UserToUSDBalance}; use pallet_funding::{ - AcceptedFundingAsset, BiddingTicketSizes, ContributingTicketSizes, CurrencyMetadata, PriceProviderOf, - ProjectMetadata, ProjectMetadataOf, TicketSize, + AcceptedFundingAsset, BiddingTicketSizes, ContributingTicketSizes, CurrencyMetadata, ParticipationMode, + PriceProviderOf, ProjectMetadata, ProjectMetadataOf, TicketSize, }; use sp_arithmetic::{FixedPointNumber, Percent}; @@ -26,6 +26,7 @@ use macros::generate_accounts; use polimec_common::{ProvideAssetPrice, USD_DECIMALS, USD_UNIT}; use polimec_runtime::{AccountId, PLMC}; use sp_runtime::{traits::ConstU32, Perquintill}; +use ParticipationMode::{Classic, OTM}; pub const IPFS_CID: &str = "QmeuJ24ffwLAZppQcgcggJs3n689bewednYkuc8Bx5Gngz"; pub const CT_DECIMALS: u8 = 18; @@ -54,11 +55,11 @@ pub fn ipfs_hash() -> BoundedVec> { pub fn default_weights() -> Vec { vec![20u8, 15u8, 10u8, 25u8, 30u8] } -pub fn default_bidder_multipliers() -> Vec { - vec![1u8, 6u8, 10u8, 8u8, 3u8] +pub fn default_bidder_modes() -> Vec { + vec![Classic(1u8), Classic(6u8), OTM, OTM, Classic(3u8)] } -pub fn default_contributor_multipliers() -> Vec { - vec![1u8, 1u8, 1u8, 1u8, 1u8] +pub fn default_contributor_modes() -> Vec { + vec![Classic(1u8), Classic(1u8), OTM, OTM, Classic(3u8)] } pub fn default_project_metadata(issuer: AccountId) -> ProjectMetadataOf { @@ -113,7 +114,7 @@ pub fn default_bids() -> Vec> { default_metadata.minimum_price, default_weights(), default_bidders(), - default_bidder_multipliers(), + default_bidder_modes(), ) } @@ -134,7 +135,7 @@ pub fn default_community_contributions() -> Vec Vec Vec { diff --git a/integration-tests/src/tests/mod.rs b/integration-tests/src/tests/mod.rs index d45335565..26c716055 100644 --- a/integration-tests/src/tests/mod.rs +++ b/integration-tests/src/tests/mod.rs @@ -21,6 +21,7 @@ mod e2e; mod evaluator_slash_sideffects; mod governance; mod oracle; +mod otm_edge_cases; mod reserve_backed_transfers; mod vest; mod xcm_config; diff --git a/integration-tests/src/tests/otm_edge_cases.rs b/integration-tests/src/tests/otm_edge_cases.rs new file mode 100644 index 000000000..0bc178c1e --- /dev/null +++ b/integration-tests/src/tests/otm_edge_cases.rs @@ -0,0 +1,185 @@ +use crate::{ + constants::PricesBuilder, + tests::defaults::{default_evaluations, default_project_metadata, ipfs_hash, IntegrationInstantiator}, + *, +}; +use frame_support::traits::fungibles::Inspect; +use macros::generate_accounts; +use pallet_funding::{AcceptedFundingAsset, ParticipationMode, PriceProviderOf}; +use polimec_common::{credentials::InvestorType, ProvideAssetPrice, USD_UNIT}; +use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt_with_cid}; +use polimec_runtime::OraclePriceProvider; +use sp_arithmetic::{FixedPointNumber, FixedU128}; +use sp_core::bounded_vec; +use sp_runtime::TokenError; +generate_accounts!(ISSUER, BOBERT); + +#[test] +fn cannot_have_otm_fee_below_min_amount() { + let mut inst = IntegrationInstantiator::new(None); + let issuer: PolimecAccountId = ISSUER.into(); + let bobert: PolimecAccountId = BOBERT.into(); + + let prices = PricesBuilder::new() + .plmc(FixedU128::from_float(0.17f64)) + .usdt(FixedU128::from_float(0.9999f64)) + .usdc(FixedU128::from_float(1.0001f64)) + .dot(FixedU128::from_float(4.0f64)) + .build(); + + polimec::set_prices(prices); + + PolimecNet::execute_with(|| { + let mut project_metadata = default_project_metadata(issuer.clone()); + project_metadata.participation_currencies = + bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC, AcceptedFundingAsset::DOT,]; + + let usdt_price = ::PriceProvider::get_decimals_aware_price( + AcceptedFundingAsset::USDT.id(), + 6, + 6, + ) + .unwrap(); + let usdc_price = ::PriceProvider::get_decimals_aware_price( + AcceptedFundingAsset::USDC.id(), + 6, + 6, + ) + .unwrap(); + let dot_price = ::PriceProvider::get_decimals_aware_price( + AcceptedFundingAsset::USDC.id(), + 6, + 10, + ) + .unwrap(); + + let project_id = inst.create_community_contributing_project( + project_metadata.clone(), + issuer.clone(), + None, + default_evaluations(), + vec![], + ); + + let plmc_ed = inst.get_ed(); + + let min_usd_contribution = USD_UNIT; + + let mut min_usdt_contribution = usdt_price.reciprocal().unwrap().saturating_mul_int(min_usd_contribution); + while usdt_price.saturating_mul_int(min_usdt_contribution) < min_usd_contribution { + min_usdt_contribution += 1; + } + + let mut min_usdc_contribution = usdc_price.reciprocal().unwrap().saturating_mul_int(min_usd_contribution); + while usdc_price.saturating_mul_int(min_usdc_contribution) < min_usd_contribution { + min_usdc_contribution += 1; + } + + let mut min_dot_contribution = dot_price.reciprocal().unwrap().saturating_mul_int(min_usd_contribution); + while dot_price.saturating_mul_int(min_dot_contribution) < min_usd_contribution { + min_dot_contribution += 1; + } + + let min_usdt_contribution_otm_fee = polimec_runtime::FeePercentage::get() * min_usdt_contribution; + let min_usdc_contribution_otm_fee = polimec_runtime::FeePercentage::get() * min_usdc_contribution; + let min_dot_contribution_otm_fee = polimec_runtime::FeePercentage::get() * min_dot_contribution; + + let usdt_min_balance = inst.execute(|| PolimecForeignAssets::minimum_balance(AcceptedFundingAsset::USDT.id())); + let usdc_min_balance = inst.execute(|| PolimecForeignAssets::minimum_balance(AcceptedFundingAsset::USDC.id())); + let dot_min_balance = inst.execute(|| PolimecForeignAssets::minimum_balance(AcceptedFundingAsset::DOT.id())); + + assert!(min_usdt_contribution_otm_fee > usdt_min_balance); + assert!(min_usdc_contribution_otm_fee > usdc_min_balance); + assert!(min_dot_contribution_otm_fee > dot_min_balance); + + let ct_for_min_usdt_contribution = + PolimecFunding::funding_asset_to_ct_amount(project_id, AcceptedFundingAsset::USDT, min_usdt_contribution); + let ct_for_min_usdc_contribution = + PolimecFunding::funding_asset_to_ct_amount(project_id, AcceptedFundingAsset::USDC, min_usdc_contribution); + let ct_for_min_dot_contribution = + PolimecFunding::funding_asset_to_ct_amount(project_id, AcceptedFundingAsset::DOT, min_dot_contribution); + + let jwt = get_mock_jwt_with_cid( + bobert.clone(), + InvestorType::Retail, + generate_did_from_account(bobert.clone()), + ipfs_hash(), + ); + + inst.mint_plmc_to(vec![(bobert.clone(), plmc_ed).into()]); + inst.mint_funding_asset_to(vec![ + (bobert.clone(), min_usdt_contribution + min_usdt_contribution_otm_fee, AcceptedFundingAsset::USDT.id()) + .into(), + (bobert.clone(), min_usdc_contribution + min_usdc_contribution_otm_fee, AcceptedFundingAsset::USDC.id()) + .into(), + (bobert.clone(), min_dot_contribution + min_dot_contribution_otm_fee, AcceptedFundingAsset::DOT.id()) + .into(), + ]); + + let contribute_is_ok_with = |asset, ct_amount| { + assert_ok!(PolimecFunding::contribute( + PolimecOrigin::signed(bobert.clone()), + jwt.clone(), + project_id, + ct_amount, + ParticipationMode::OTM, + asset + )); + }; + contribute_is_ok_with(AcceptedFundingAsset::USDT, ct_for_min_usdt_contribution); + contribute_is_ok_with(AcceptedFundingAsset::USDC, ct_for_min_usdc_contribution); + contribute_is_ok_with(AcceptedFundingAsset::DOT, ct_for_min_dot_contribution); + }); +} + +#[test] +fn after_otm_fee_user_goes_under_ed() { + let mut inst = IntegrationInstantiator::new(None); + let issuer: PolimecAccountId = ISSUER.into(); + let bobert: PolimecAccountId = BOBERT.into(); + + polimec::set_prices(PricesBuilder::default()); + PolimecNet::execute_with(|| { + let mut project_metadata = default_project_metadata(issuer.clone()); + + let project_id = inst.create_community_contributing_project( + project_metadata.clone(), + issuer.clone(), + None, + default_evaluations(), + vec![], + ); + + // default price of usdt = 1usd + let usdt_contribution = 100 * USD_UNIT; + let contribution_otm_fee = polimec_runtime::FeePercentage::get() * usdt_contribution; + + let usdt_min_balance = inst.execute(|| PolimecForeignAssets::minimum_balance(AcceptedFundingAsset::USDT.id())); + assert!(contribution_otm_fee > usdt_min_balance); + + let ct_for_contribution = + PolimecFunding::funding_asset_to_ct_amount(project_id, AcceptedFundingAsset::USDT, usdt_contribution); + let jwt = get_mock_jwt_with_cid( + bobert.clone(), + InvestorType::Retail, + generate_did_from_account(bobert.clone()), + ipfs_hash(), + ); + + let usdt_mint = usdt_contribution + contribution_otm_fee; + inst.mint_funding_asset_to(vec![(bobert.clone(), usdt_mint, AcceptedFundingAsset::USDT.id()).into()]); + + assert_ok!(PolimecFunding::contribute( + PolimecOrigin::signed(bobert.clone()), + jwt.clone(), + project_id, + ct_for_contribution, + ParticipationMode::OTM, + AcceptedFundingAsset::USDT, + )); + + // Somehow still exists because sufficients = 1 + assert!(PolimecSystem::account_exists(&bobert)); + dbg!(PolimecSystem::account(&bobert)); + }); +} diff --git a/nodes/parachain/src/chain_spec/common.rs b/nodes/parachain/src/chain_spec/common.rs index 827d680be..6959af592 100644 --- a/nodes/parachain/src/chain_spec/common.rs +++ b/nodes/parachain/src/chain_spec/common.rs @@ -11,10 +11,12 @@ use polimec_runtime::{ inflation::{perbill_annual_to_perbill_round, BLOCKS_PER_YEAR}, InflationInfo, Range, }, - AccountId, AuraId as AuthorityId, Balance, OracleProvidersMembershipConfig, Runtime, PLMC, + AccountId, AuraId as AuthorityId, Balance, BlockchainOperationTreasury, ContributionTreasuryAccount, + ExistentialDeposit, FeeRecipient, OracleProvidersMembershipConfig, Runtime, TreasuryAccount, PLMC, }; use sp_core::{crypto::UncheckedInto, sr25519}; use sp_runtime::{traits::AccountIdConversion, Perbill, Percent}; + pub type ChainSpec = sc_service::GenericChainSpec; /// The default XCM version to set in genesis config. @@ -86,13 +88,14 @@ pub fn genesis_config(genesis_config_params: GenesisConfigParams) -> serde_json: id, } = genesis_config_params; + let ed = ExistentialDeposit::get(); let system_accounts = vec![ - ( - ::ContributionTreasury::get(), - ::NativeCurrency::minimum_balance(), - ), + (ContributionTreasuryAccount::get(), ed), + (FeeRecipient::get(), ed), // Need this to have enough for staking rewards - (::PayMaster::get(), 10_000_000 * PLMC), + (BlockchainOperationTreasury::get(), 10_000_000 * PLMC), + // Need this to have enough for proxy bonding + (TreasuryAccount::get(), 10_000_000 * PLMC), ]; endowed_accounts.append(&mut system_accounts.clone()); diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs index 59c4f1ebb..fafbe93ba 100644 --- a/pallets/funding/src/benchmarking.rs +++ b/pallets/funding/src/benchmarking.rs @@ -20,6 +20,8 @@ use super::*; use crate::{instantiator::*, traits::SetPrices}; +use ParticipationMode::{Classic, OTM}; + use frame_benchmarking::v2::*; use frame_support::{ assert_ok, @@ -126,7 +128,7 @@ where default_project_metadata.minimum_price, default_weights(), default_bidders::(), - default_bidder_multipliers(), + default_bidder_modes(), ) } @@ -145,7 +147,7 @@ where default_project.minimum_price, default_weights(), default_bidders::(), - default_bidder_multipliers(), + default_bidder_modes(), ) } @@ -170,7 +172,7 @@ where default_project_metadata.minimum_price, default_weights(), default_community_contributors::(), - default_community_contributor_multipliers(), + default_community_contributor_modes(), ) } @@ -195,7 +197,7 @@ where 10u128.into(), default_weights(), default_remainder_contributors::(), - default_remainder_contributor_multipliers(), + default_remainder_contributor_modes(), ) } @@ -241,14 +243,14 @@ pub fn default_remainder_contributors() -> Vec> { ] } -pub fn default_bidder_multipliers() -> Vec { - vec![10u8, 3u8, 1u8, 7u8, 4u8] +pub fn default_bidder_modes() -> Vec { + vec![Classic(10u8), Classic(3u8), OTM, OTM, Classic(4u8)] } -pub fn default_community_contributor_multipliers() -> Vec { - vec![2u8, 1u8, 3u8, 1u8, 1u8] +pub fn default_community_contributor_modes() -> Vec { + vec![Classic(2u8), Classic(1u8), Classic(3u8), OTM, OTM] } -pub fn default_remainder_contributor_multipliers() -> Vec { - vec![1u8, 11u8, 1u8, 1u8, 1u8] +pub fn default_remainder_contributor_modes() -> Vec { + vec![Classic(1u8), OTM, Classic(1u8), OTM, Classic(1u8)] } /// Grab an account, seeded by a name and index. @@ -665,7 +667,12 @@ mod benchmarks { let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); - let existing_bid = BidParams::new(bidder.clone(), (50 * CT_UNIT).into(), 5u8, AcceptedFundingAsset::USDT); + let existing_bid = BidParams::new( + bidder.clone(), + (50 * CT_UNIT).into(), + ParticipationMode::Classic(5u8), + AcceptedFundingAsset::USDT, + ); let existing_bids = vec![existing_bid; x as usize]; let existing_bids_post_bucketing = @@ -708,7 +715,12 @@ mod benchmarks { 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::new(new_bidder, current_bucket.amount_left, 1u8, AcceptedFundingAsset::USDT); + let bid_params = BidParams::new( + new_bidder, + 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()], @@ -733,7 +745,8 @@ mod benchmarks { ct_amount = bucket_size * (y as u128); usdt_for_filler_bidder = usdt_for_new_bidder; } - let extrinsic_bid = BidParams::new(bidder.clone(), ct_amount, 1u8, AcceptedFundingAsset::USDT); + let extrinsic_bid = + BidParams::new(bidder.clone(), ct_amount, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT); let original_extrinsic_bid = extrinsic_bid.clone(); let current_bucket = Buckets::::get(project_id).unwrap(); // we need to call this after bidding `x` amount of times, to get the latest bucket from storage @@ -786,7 +799,7 @@ mod benchmarks { jwt, project_id, original_extrinsic_bid.amount, - original_extrinsic_bid.multiplier, + original_extrinsic_bid.mode, original_extrinsic_bid.asset, ); @@ -803,7 +816,7 @@ mod benchmarks { original_ct_usd_price: Some(price), funding_asset: Some(AcceptedFundingAsset::USDT), funding_asset_amount_locked: None, - multiplier: Some(bid_params.multiplier), + mode: Some(bid_params.mode), plmc_bond: None, when: None, }; @@ -862,11 +875,11 @@ mod benchmarks { Event::::Bid { project_id, ct_amount, - multiplier, .. + mode, .. }, project_id == project_id, ct_amount == bid_params.amount, - multiplier == bid_params.multiplier + mode == bid_params.mode }; assert!(maybe_event.is_some(), "Event not found"); } @@ -917,7 +930,7 @@ mod benchmarks { BidParams::::new( account::>("bidder", 0, i), (min_bid_amount * CT_UNIT).into(), - 1u8, + ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, ) }) @@ -929,7 +942,7 @@ mod benchmarks { let last_rejected_bid = BidParams::::new( account::>("bidder", 0, 420), auction_allocation - (min_bid_amount * CT_UNIT * (y as u128 - 1u128)), - 1u8, + ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, ); all_bids.push(last_rejected_bid.clone()); @@ -939,7 +952,7 @@ mod benchmarks { let allocation_bid = BidParams::::new( account::>("bidder", 0, y), auction_allocation, - 1u8, + ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, ); all_bids.push(allocation_bid); @@ -954,7 +967,7 @@ mod benchmarks { BidParams::::new( account::>("bidder", 0, i), (min_bid_amount * CT_UNIT).into(), - 1u8, + ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, ) }) @@ -1501,7 +1514,14 @@ mod benchmarks { .map(|_| UserToUSDBalance::new(participant.clone(), (100 * USD_UNIT).into())) .collect_vec(); let participant_bids = (0..max_bids) - .map(|_| BidParams::new(participant.clone(), (500 * CT_UNIT).into(), 1u8, AcceptedFundingAsset::USDT)) + .map(|_| { + BidParams::new( + participant.clone(), + (500 * CT_UNIT).into(), + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ) + }) .collect_vec(); let participant_contributions = (0..max_contributions) .map(|_| { @@ -1843,7 +1863,14 @@ mod benchmarks { .map(|_| UserToUSDBalance::new(participant.clone(), (100 * USD_UNIT).into())) .collect_vec(); let participant_bids = (0..max_bids) - .map(|_| BidParams::new(participant.clone(), (500 * CT_UNIT).into(), 1u8, AcceptedFundingAsset::USDT)) + .map(|_| { + BidParams::new( + participant.clone(), + (500 * CT_UNIT).into(), + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ) + }) .collect_vec(); let participant_contributions = (0..max_contributions) .map(|_| { @@ -1934,7 +1961,14 @@ mod benchmarks { .map(|_| UserToUSDBalance::new(participant.clone(), (100 * USD_UNIT).into())) .collect_vec(); let participant_bids = (0..max_bids) - .map(|_| BidParams::new(participant.clone(), (500 * CT_UNIT).into(), 1u8, AcceptedFundingAsset::USDT)) + .map(|_| { + BidParams::new( + participant.clone(), + (500 * CT_UNIT).into(), + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ) + }) .collect_vec(); let participant_contributions = (0..max_contributions) .map(|_| { diff --git a/pallets/funding/src/functions/4_contribution.rs b/pallets/funding/src/functions/4_contribution.rs index 8035377ee..5de666c18 100644 --- a/pallets/funding/src/functions/4_contribution.rs +++ b/pallets/funding/src/functions/4_contribution.rs @@ -126,7 +126,7 @@ impl Pallet { contributor: contributor.clone(), ct_amount: buyable_tokens, usd_contribution_amount: ticket_size, - mode: mode.clone(), + mode, funding_asset, funding_asset_amount, plmc_bond, diff --git a/pallets/funding/src/functions/6_settlement.rs b/pallets/funding/src/functions/6_settlement.rs index 86b3e938f..32bacb598 100644 --- a/pallets/funding/src/functions/6_settlement.rs +++ b/pallets/funding/src/functions/6_settlement.rs @@ -5,7 +5,7 @@ use frame_support::{ dispatch::DispatchResult, ensure, traits::{ - fungible::MutateHold as FungibleMutateHold, + fungible::{Inspect, MutateHold as FungibleMutateHold}, fungibles::Mutate as FungiblesMutate, tokens::{Fortitude, Precision, Preservation, Restriction}, Get, @@ -175,22 +175,22 @@ impl Pallet { Self::release_funding_asset(project_id, &bid.bidder, refunded_funding_asset_amount, bid.funding_asset)?; if bid.mode == ParticipationMode::OTM { - >::refund_fee( - project_id, - HoldReason::Participation.into(), - &bid.bidder, - refunded_plmc, - bid.funding_asset.id(), - )?; + if refunded_plmc > T::NativeCurrency::minimum_balance() { + >::refund_fee( + project_id, + &bid.bidder, + refunded_plmc, + bid.funding_asset.id(), + )?; + } } else { Self::release_participation_bond_for(&bid.bidder, refunded_plmc)?; } - if funding_success && bid.status != BidStatus::Rejected { let ct_vesting_duration = Self::set_plmc_bond_release_with_mode( bid.bidder.clone(), - bid.plmc_bond, + bid.plmc_bond.saturating_sub(refunded_plmc), bid.mode, project_details.funding_end_block.ok_or(Error::::ImpossibleState)?, )?; @@ -276,7 +276,6 @@ impl Pallet { if contribution.mode == ParticipationMode::OTM { >::refund_fee( project_id, - HoldReason::Participation.into(), &contribution.contributor, contribution.plmc_bond, contribution.funding_asset.id(), @@ -284,7 +283,6 @@ impl Pallet { } else { Self::release_participation_bond_for(&contribution.contributor, contribution.plmc_bond)?; } - } else { let ct_vesting_duration = Self::set_plmc_bond_release_with_mode( contribution.contributor.clone(), diff --git a/pallets/funding/src/functions/misc.rs b/pallets/funding/src/functions/misc.rs index 45d365294..fe7f141ca 100644 --- a/pallets/funding/src/functions/misc.rs +++ b/pallets/funding/src/functions/misc.rs @@ -32,7 +32,7 @@ impl Pallet { 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)?; + let usd_bond = multiplier.calculate_usd_bonding_requirement::(ticket_size).ok_or(Error::::BadMath)?; plmc_usd_price .reciprocal() .ok_or(Error::::BadMath)? diff --git a/pallets/funding/src/instantiator/calculations.rs b/pallets/funding/src/instantiator/calculations.rs index 2eb3cef00..bfeade94b 100644 --- a/pallets/funding/src/instantiator/calculations.rs +++ b/pallets/funding/src/instantiator/calculations.rs @@ -1,6 +1,6 @@ #[allow(clippy::wildcard_imports)] use super::*; -use crate::{Multiplier, MultiplierOf, ParticipationMode}; +use crate::{MultiplierOf, ParticipationMode}; use core::cmp::Ordering; use itertools::GroupBy; use polimec_common::{ProvideAssetPrice, USD_DECIMALS}; @@ -15,6 +15,17 @@ impl< T::ExistentialDeposit::get() } + pub fn get_funding_asset_ed(&mut self, asset_id: AssetIdOf) -> Balance { + self.execute(|| T::FundingCurrency::minimum_balance(asset_id)) + } + + pub fn get_funding_asset_unit(&mut self, asset_id: AssetIdOf) -> Balance { + self.execute(|| { + let decimals = T::FundingCurrency::decimals(asset_id); + 10u128.pow(decimals as u32) + }) + } + pub fn get_ct_account_deposit(&self) -> Balance { ::ContributionTokenCurrency::deposit_required(One::one()) } @@ -58,12 +69,7 @@ impl< while !amount_to_bid.is_zero() { let bid_amount = if amount_to_bid <= bucket.amount_left { amount_to_bid } else { bucket.amount_left }; output.push(( - BidParams { - bidder: bid.bidder.clone(), - amount: bid_amount, - multiplier: bid.multiplier, - asset: bid.asset, - }, + BidParams { bidder: bid.bidder.clone(), amount: bid_amount, mode: bid.mode, asset: bid.asset }, bucket.current_price, )); bucket.update(bid_amount); @@ -79,19 +85,17 @@ impl< ct_price: PriceOf, with_ed: bool, ) -> Vec> { - let plmc_usd_price = self.execute(|| { - >::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS).unwrap() - }); - let mut output = Vec::new(); for bid in bids { let usd_ticket_size = ct_price.saturating_mul_int(bid.amount); - let usd_bond = bid.multiplier.calculate_bonding_requirement::(usd_ticket_size).unwrap(); - let mut plmc_bond = plmc_usd_price.reciprocal().unwrap().saturating_mul_int(usd_bond); + let mut plmc_required = Balance::zero(); + if let ParticipationMode::Classic(multiplier) = bid.mode { + self.add_required_plmc_to(&mut plmc_required, usd_ticket_size, multiplier) + } if with_ed { - plmc_bond = plmc_bond.saturating_add(self.get_ed()); + plmc_required = plmc_required.saturating_add(self.get_ed()); } - output.push(UserToPLMCBalance::new(bid.bidder.clone(), plmc_bond)); + output.push(UserToPLMCBalance::new(bid.bidder.clone(), plmc_required)); } output } @@ -105,18 +109,17 @@ impl< with_ed: bool, ) -> Vec> { let mut output = Vec::new(); - let plmc_usd_price = self.execute(|| { - >::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) { let usd_ticket_size = price.saturating_mul_int(bid.amount); - let usd_bond = bid.multiplier.calculate_bonding_requirement::(usd_ticket_size).unwrap(); - let mut plmc_bond = plmc_usd_price.reciprocal().unwrap().saturating_mul_int(usd_bond); + let mut plmc_required = Balance::zero(); + if let ParticipationMode::Classic(multiplier) = bid.mode { + self.add_required_plmc_to(&mut plmc_required, usd_ticket_size, multiplier) + } if with_ed { - plmc_bond = plmc_bond.saturating_add(self.get_ed()); + plmc_required = plmc_required.saturating_add(self.get_ed()); } - output.push(UserToPLMCBalance::::new(bid.bidder.clone(), plmc_bond)); + output.push(UserToPLMCBalance::::new(bid.bidder.clone(), plmc_required)); } output.merge_accounts(MergeOperation::Add) @@ -138,18 +141,16 @@ impl< .collect(); grouped_by_price_bids.reverse(); - let plmc_usd_price = self.execute(|| { - >::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; for (price_charged, bids) in grouped_by_price_bids { for bid in bids { let charged_usd_ticket_size = price_charged.saturating_mul_int(bid.amount); - let charged_usd_bond = - bid.multiplier.calculate_bonding_requirement::(charged_usd_ticket_size).unwrap(); - let charged_plmc_bond = plmc_usd_price.reciprocal().unwrap().saturating_mul_int(charged_usd_bond); + let mut charged_plmc_bond = Balance::zero(); + if let ParticipationMode::Classic(multiplier) = bid.mode { + self.add_required_plmc_to(&mut charged_plmc_bond, charged_usd_ticket_size, multiplier); + } if remaining_cts <= Zero::zero() { output.push(UserToPLMCBalance::new(bid.bidder, charged_plmc_bond)); @@ -163,9 +164,10 @@ impl< if weighted_average_price > price_charged { price_charged } else { weighted_average_price }; let actual_usd_ticket_size = final_price.saturating_mul_int(bought_cts); - let actual_usd_bond = - bid.multiplier.calculate_bonding_requirement::(actual_usd_ticket_size).unwrap(); - let actual_plmc_bond = plmc_usd_price.reciprocal().unwrap().saturating_mul_int(actual_usd_bond); + let mut actual_plmc_bond = Balance::zero(); + if let ParticipationMode::Classic(multiplier) = bid.mode { + self.add_required_plmc_to(&mut actual_plmc_bond, actual_usd_ticket_size, multiplier); + } let returned_plmc_bond = charged_plmc_bond - actual_plmc_bond; @@ -204,14 +206,12 @@ impl< ) -> Vec> { let mut output = Vec::new(); for bid in bids { - 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(|| { - >::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) - .unwrap() - }); let usd_ticket_size = ct_price.saturating_mul_int(bid.amount); - let funding_asset_spent = funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(usd_ticket_size); + let mut funding_asset_spent = Balance::zero(); + self.add_required_funding_asset_to(&mut funding_asset_spent, usd_ticket_size, bid.asset); + if bid.mode == ParticipationMode::OTM { + self.add_otm_fee_to(&mut funding_asset_spent, usd_ticket_size, bid.asset); + } output.push(UserToFundingAsset::new(bid.bidder.clone(), funding_asset_spent, bid.asset.id())); } output @@ -227,15 +227,13 @@ impl< let mut output = Vec::new(); for (bid, price) in self.get_actual_price_charged_for_bucketed_bids(bids, project_metadata, maybe_bucket) { - 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(|| { - >::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) - .ok_or(Error::::PriceNotFound) - .unwrap() - }); let usd_ticket_size = price.saturating_mul_int(bid.amount); - let funding_asset_spent = funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(usd_ticket_size); + let mut funding_asset_spent = Balance::zero(); + self.add_required_funding_asset_to(&mut funding_asset_spent, usd_ticket_size, bid.asset); + if bid.mode == ParticipationMode::OTM { + self.add_otm_fee_to(&mut funding_asset_spent, usd_ticket_size, bid.asset); + } + output.push(UserToFundingAsset::::new(bid.bidder.clone(), funding_asset_spent, bid.asset.id())); } @@ -263,21 +261,12 @@ impl< for (price_charged, bids) in grouped_by_price_bids { for bid in bids { - 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(|| { - >::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); - - let charged_funding_asset = - funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(charged_usd_ticket_size); + let mut charged_usd_ticket_size = price_charged.saturating_mul_int(bid.amount); + let mut charged_funding_asset = Balance::zero(); + self.add_required_funding_asset_to(&mut charged_funding_asset, charged_usd_ticket_size, bid.asset); + if bid.mode == ParticipationMode::OTM { + self.add_otm_fee_to(&mut charged_usd_ticket_size, bid.amount, bid.asset); + } if remaining_cts <= Zero::zero() { output.push(UserToFundingAsset::new(bid.bidder, charged_funding_asset, bid.asset.id())); @@ -291,8 +280,11 @@ impl< if weighted_average_price > price_charged { price_charged } else { weighted_average_price }; let actual_usd_ticket_size = final_price.saturating_mul_int(bought_cts); - let actual_funding_asset_spent = - funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(actual_usd_ticket_size); + let mut actual_funding_asset_spent = Balance::zero(); + self.add_required_funding_asset_to(&mut actual_funding_asset_spent, actual_usd_ticket_size, bid.asset); + if bid.mode == ParticipationMode::OTM { + self.add_otm_fee_to(&mut actual_funding_asset_spent, actual_usd_ticket_size, bid.asset); + } let returned_foreign_asset = charged_funding_asset - actual_funding_asset_spent; @@ -336,7 +328,7 @@ impl< filtered_bids.push(BidParams { bidder: bid.bidder.clone(), amount: total_cts_left, - multiplier: bid.multiplier, + mode: bid.mode, asset: bid.asset, }); total_cts_left = Zero::zero(); @@ -351,17 +343,14 @@ impl< token_usd_price: PriceOf, with_ed: bool, ) -> Vec> { - let plmc_usd_price = self.execute(|| { - >::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); - // 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); + let mut plmc_bond = 0u128; + if let ParticipationMode::Classic(multiplier) = cont.mode { + let usd_ticket_size = token_usd_price.saturating_mul_int(cont.amount); + self.add_required_plmc_to(&mut plmc_bond, usd_ticket_size, multiplier); + } + if with_ed { plmc_bond = plmc_bond.saturating_add(self.get_ed()); } @@ -425,20 +414,60 @@ impl< ) -> Vec> { let mut output = Vec::new(); for cont in contributions { - 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(|| { - >::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) - .ok_or(Error::::PriceNotFound) - .unwrap() - }); let usd_ticket_size = token_usd_price.saturating_mul_int(cont.amount); - let funding_asset_spent = funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(usd_ticket_size); + let mut funding_asset_spent = Balance::zero(); + self.add_required_funding_asset_to(&mut funding_asset_spent, usd_ticket_size, cont.asset); + if cont.mode == ParticipationMode::OTM { + self.add_otm_fee_to(&mut funding_asset_spent, usd_ticket_size, cont.asset); + } output.push(UserToFundingAsset::new(cont.contributor, funding_asset_spent, cont.asset.id())); } output } + pub fn add_otm_fee_to( + &mut self, + balance: &mut Balance, + usd_ticket_size: Balance, + funding_asset: AcceptedFundingAsset, + ) { + let multiplier: MultiplierOf = ParticipationMode::OTM.multiplier().try_into().ok().unwrap(); + let plmc_usd_price = self.execute(|| { + >::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS).unwrap() + }); + let usd_bond = multiplier.calculate_usd_bonding_requirement::(usd_ticket_size).unwrap(); + let plmc_bond = plmc_usd_price.reciprocal().unwrap().saturating_mul_int(usd_bond); + let otm_fee = + self.execute(|| >::calculate_fee(plmc_bond, funding_asset.id())).unwrap(); + *balance += otm_fee; + } + + pub fn add_required_plmc_to(&mut self, balance: &mut Balance, usd_ticket_size: Balance, multiplier: u8) { + let multiplier: MultiplierOf = multiplier.try_into().ok().unwrap(); + let usd_bond = multiplier.calculate_usd_bonding_requirement::(usd_ticket_size).unwrap(); + let plmc_usd_price = self.execute(|| { + >::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS).unwrap() + }); + let plmc_bond = plmc_usd_price.reciprocal().unwrap().saturating_mul_int(usd_bond); + *balance += plmc_bond; + } + + pub fn add_required_funding_asset_to( + &mut self, + balance: &mut Balance, + usd_ticket_size: Balance, + funding_asset: AcceptedFundingAsset, + ) { + let funding_asset_id = funding_asset.id(); + let funding_asset_decimals = self.execute(|| T::FundingCurrency::decimals(funding_asset_id)); + let funding_asset_usd_price = self.execute(|| { + >::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) + .unwrap() + }); + let funding_asset_bond = funding_asset_usd_price.reciprocal().unwrap().saturating_mul_int(usd_ticket_size); + *balance += funding_asset_bond; + } + pub fn generic_map_merge_reduce( &self, mappings: Vec>, @@ -547,16 +576,16 @@ impl< min_price: PriceOf, weights: Vec, bidders: Vec>, - multipliers: Vec, + modes: Vec, ) -> Vec> { assert_eq!(weights.len(), bidders.len(), "Should have enough weights for all the bidders"); - zip(zip(weights, bidders), multipliers) - .map(|((weight, bidder), multiplier)| { + zip(zip(weights, bidders), modes) + .map(|((weight, bidder), mode)| { let ticket_size = Percent::from_percent(weight) * usd_amount; let token_amount = min_price.reciprocal().unwrap().saturating_mul_int(ticket_size); - BidParams::new(bidder, token_amount, multiplier, AcceptedFundingAsset::USDT) + BidParams::new(bidder, token_amount, mode, AcceptedFundingAsset::USDT) }) .collect() } @@ -567,17 +596,17 @@ impl< percent_funding: u8, weights: Vec, bidders: Vec>, - multipliers: Vec, + modes: Vec, ) -> Vec> { let total_allocation_size = project_metadata.total_allocation_size; let total_ct_bid = Percent::from_percent(percent_funding) * total_allocation_size; assert_eq!(weights.len(), bidders.len(), "Should have enough weights for all the bidders"); - zip(zip(weights, bidders), multipliers) - .map(|((weight, bidder), multiplier)| { + zip(zip(weights, bidders), modes) + .map(|((weight, bidder), mode)| { let token_amount = Percent::from_percent(weight) * total_ct_bid; - BidParams::new(bidder, token_amount, multiplier, AcceptedFundingAsset::USDT) + BidParams::new(bidder, token_amount, mode, AcceptedFundingAsset::USDT) }) .collect() } @@ -588,19 +617,14 @@ impl< final_price: PriceOf, weights: Vec, contributors: Vec>, - multipliers: Vec, + modes: Vec, ) -> Vec> { - zip(zip(weights, contributors), multipliers) - .map(|((weight, bidder), multiplier)| { + zip(zip(weights, contributors), modes) + .map(|((weight, bidder), mode)| { let ticket_size = Percent::from_percent(weight) * usd_amount; let token_amount = final_price.reciprocal().unwrap().saturating_mul_int(ticket_size); - ContributionParams::new( - bidder, - token_amount, - ParticipationMode::Classic(multiplier), - AcceptedFundingAsset::USDT, - ) + ContributionParams::new(bidder, token_amount, mode, AcceptedFundingAsset::USDT) }) .collect() } @@ -611,22 +635,17 @@ impl< percent_funding: u8, weights: Vec, contributors: Vec>, - multipliers: Vec, + modes: Vec, ) -> Vec> { let total_allocation_size = project_metadata.total_allocation_size; let total_ct_bought = Percent::from_percent(percent_funding) * total_allocation_size; assert_eq!(weights.len(), contributors.len(), "Should have enough weights for all the bidders"); - zip(zip(weights, contributors), multipliers) - .map(|((weight, contributor), multiplier)| { + zip(zip(weights, contributors), modes) + .map(|((weight, contributor), mode)| { let token_amount = Percent::from_percent(weight) * total_ct_bought; - ContributionParams::new( - contributor, - token_amount, - ParticipationMode::Classic(multiplier), - AcceptedFundingAsset::USDT, - ) + ContributionParams::new(contributor, token_amount, mode, AcceptedFundingAsset::USDT) }) .collect() } diff --git a/pallets/funding/src/instantiator/chain_interactions.rs b/pallets/funding/src/instantiator/chain_interactions.rs index 4119d26ec..8328f77d4 100644 --- a/pallets/funding/src/instantiator/chain_interactions.rs +++ b/pallets/funding/src/instantiator/chain_interactions.rs @@ -145,7 +145,9 @@ impl< pub fn mint_plmc_to(&mut self, mapping: Vec>) { self.execute(|| { for UserToPLMCBalance { account, plmc_amount } in mapping { - ::NativeCurrency::mint_into(&account, plmc_amount).expect("Minting should work"); + if plmc_amount > Zero::zero() { + ::NativeCurrency::mint_into(&account, plmc_amount).expect("Minting should work"); + } } }); } diff --git a/pallets/funding/src/instantiator/tests.rs b/pallets/funding/src/instantiator/tests.rs index 91bd80edd..51a88b987 100644 --- a/pallets/funding/src/instantiator/tests.rs +++ b/pallets/funding/src/instantiator/tests.rs @@ -16,12 +16,12 @@ use sp_arithmetic::Percent; fn dry_run_wap() { let mut inst = tests::MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - const ADAM: u32 = 60; - const TOM: u32 = 61; - const SOFIA: u32 = 62; - const FRED: u32 = 63; - const ANNA: u32 = 64; - const DAMIAN: u32 = 65; + const ADAM: AccountIdOf = 60; + const TOM: AccountIdOf = 61; + const SOFIA: AccountIdOf = 62; + const FRED: AccountIdOf = 63; + const ANNA: AccountIdOf = 64; + const DAMIAN: AccountIdOf = 65; let accounts = vec![ADAM, TOM, SOFIA, FRED, ANNA, DAMIAN]; @@ -50,7 +50,7 @@ fn dry_run_wap() { phantom: Default::default(), }, participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), - funding_destination_account: 0u32, + funding_destination_account: 0, policy_ipfs_cid: Some(metadata_hash), }; @@ -98,12 +98,12 @@ fn dry_run_wap() { fn find_bucket_for_wap() { let mut inst = tests::MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - const ADAM: u32 = 60; - const TOM: u32 = 61; - const SOFIA: u32 = 62; - const FRED: u32 = 63; - const ANNA: u32 = 64; - const DAMIAN: u32 = 65; + const ADAM: AccountIdOf = 60; + const TOM: AccountIdOf = 61; + const SOFIA: AccountIdOf = 62; + const FRED: AccountIdOf = 63; + const ANNA: AccountIdOf = 64; + const DAMIAN: AccountIdOf = 65; let accounts = vec![ADAM, TOM, SOFIA, FRED, ANNA, DAMIAN]; @@ -132,7 +132,7 @@ fn find_bucket_for_wap() { phantom: Default::default(), }, participation_currencies: vec![AcceptedFundingAsset::USDT].try_into().unwrap(), - funding_destination_account: 0u32, + funding_destination_account: 0, policy_ipfs_cid: Some(metadata_hash), }; diff --git a/pallets/funding/src/instantiator/types.rs b/pallets/funding/src/instantiator/types.rs index cfe33f5a6..36dafc4ff 100644 --- a/pallets/funding/src/instantiator/types.rs +++ b/pallets/funding/src/instantiator/types.rs @@ -246,57 +246,32 @@ pub struct BidParams { pub asset: AcceptedFundingAsset, } impl BidParams { - pub fn new(bidder: AccountIdOf, amount: Balance, multiplier: u8, asset: AcceptedFundingAsset) -> Self { - Self { bidder, amount, multiplier: multiplier.try_into().map_err(|_| ()).unwrap(), asset } + pub fn new(bidder: AccountIdOf, amount: Balance, mode: ParticipationMode, asset: AcceptedFundingAsset) -> Self { + Self { bidder, amount, mode, asset } } pub fn new_with_defaults(bidder: AccountIdOf, amount: Balance) -> Self { - Self { - bidder, - amount, - multiplier: 1u8.try_into().unwrap_or_else(|_| panic!("multiplier could not be created from 1u8")), - asset: AcceptedFundingAsset::USDT, - } + Self { bidder, amount, mode: ParticipationMode::Classic(1u8), asset: AcceptedFundingAsset::USDT } } } impl From<(AccountIdOf, Balance)> for BidParams { fn from((bidder, amount): (AccountIdOf, Balance)) -> Self { - Self { - bidder, - amount, - multiplier: 1u8.try_into().unwrap_or_else(|_| panic!("multiplier could not be created from 1u8")), - asset: AcceptedFundingAsset::USDT, - } + Self { bidder, amount, mode: ParticipationMode::Classic(1u8), asset: AcceptedFundingAsset::USDT } } } -impl From<(AccountIdOf, Balance, u8)> for BidParams { - fn from((bidder, amount, multiplier): (AccountIdOf, Balance, u8)) -> Self { - Self { - bidder, - amount, - multiplier: multiplier.try_into().unwrap_or_else(|_| panic!("Failed to create multiplier")), - asset: AcceptedFundingAsset::USDT, - } +impl From<(AccountIdOf, Balance, ParticipationMode)> for BidParams { + fn from((bidder, amount, mode): (AccountIdOf, Balance, ParticipationMode)) -> Self { + Self { bidder, amount, mode, asset: AcceptedFundingAsset::USDT } } } -impl From<(AccountIdOf, Balance, u8, AcceptedFundingAsset)> for BidParams { - fn from((bidder, amount, multiplier, asset): (AccountIdOf, Balance, u8, AcceptedFundingAsset)) -> Self { - Self { - bidder, - amount, - multiplier: multiplier.try_into().unwrap_or_else(|_| panic!("Failed to create multiplier")), - asset, - } +impl From<(AccountIdOf, Balance, ParticipationMode, AcceptedFundingAsset)> for BidParams { + fn from((bidder, amount, mode, asset): (AccountIdOf, Balance, ParticipationMode, AcceptedFundingAsset)) -> Self { + Self { bidder, amount, mode, asset } } } impl From<(AccountIdOf, Balance, AcceptedFundingAsset)> for BidParams { fn from((bidder, amount, asset): (AccountIdOf, Balance, AcceptedFundingAsset)) -> Self { - Self { - bidder, - amount, - multiplier: 1u8.try_into().unwrap_or_else(|_| panic!("multiplier could not be created from 1u8")), - asset, - } + Self { bidder, amount, mode: ParticipationMode::Classic(1u8), asset } } } diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs index cb2ae6659..e7bafb374 100644 --- a/pallets/funding/src/mock.rs +++ b/pallets/funding/src/mock.rs @@ -437,7 +437,8 @@ impl Config for TestRuntime { } parameter_types! { - pub const FeePercentage: Perbill = Perbill::from_percent(5); + // Means a USD Ticket fee of 1.5%, since the FeePercentage is applied on the PLMC bond with multiplier 5. + pub FeePercentage: Perbill = Perbill::from_rational(75u32, 100u32); pub const FeeRecipient: AccountId = 80085; pub const RootId: PalletId = PalletId(*b"treasury"); } @@ -484,7 +485,7 @@ 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 + // Treasury account needs PLMC for the One Token Model participations (ProxyBondingTreasuryAccount::get(), 1_000_000 * PLMC), (FeeRecipient::get(), ed), ], @@ -496,19 +497,19 @@ pub fn new_test_ext() -> sp_io::TestExternalities { ::PalletId::get().into_account_truncating(), // asset is sufficient, i.e. participants can hold only this asset to participate with OTM true, - 10, + 70_000, ), ( AcceptedFundingAsset::USDC.id(), ::PalletId::get().into_account_truncating(), true, - 10, + 70_000, ), ( AcceptedFundingAsset::DOT.id(), ::PalletId::get().into_account_truncating(), true, - 10, + 100_000_000, ), ], metadata: vec![ diff --git a/pallets/funding/src/tests/2_evaluation.rs b/pallets/funding/src/tests/2_evaluation.rs index 98c8e9f53..df16a6ede 100644 --- a/pallets/funding/src/tests/2_evaluation.rs +++ b/pallets/funding/src/tests/2_evaluation.rs @@ -764,7 +764,7 @@ mod evaluate_extrinsic { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let project_metadata = default_project_metadata(ISSUER_1); let evaluations = (0u32..::MaxEvaluationsPerProject::get()) - .map(|i| UserToUSDBalance::::new(i as u32 + 420u32, (100u128 * CT_UNIT).into())) + .map(|i| UserToUSDBalance::::new(i as u64 + 420, (100u128 * CT_UNIT).into())) .collect_vec(); let failing_evaluation = UserToUSDBalance::new(EVALUATOR_1, 1000 * CT_UNIT); diff --git a/pallets/funding/src/tests/3_auction.rs b/pallets/funding/src/tests/3_auction.rs index 48c9b5521..002966ebe 100644 --- a/pallets/funding/src/tests/3_auction.rs +++ b/pallets/funding/src/tests/3_auction.rs @@ -223,7 +223,7 @@ mod round_flow { ), project_id, min_professional_bid_ct, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1u8), funding_asset, ))); @@ -262,7 +262,7 @@ mod round_flow { let max_bids_per_project: u32 = ::MaxBidsPerProject::get(); let big_bid: BidParams = (BIDDER_1, total_allocation).into(); let small_bids: Vec> = - (0..max_bids_per_project - 1).map(|i| (i + BIDDER_1, min_bid_ct).into()).collect(); + (0..max_bids_per_project - 1).map(|i| (i as u64 + BIDDER_1, 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); @@ -362,7 +362,12 @@ mod bid_extrinsic { let mut evaluations = default_evaluations(); let evaluator_bidder = 69; let evaluation_amount = 420 * USD_UNIT; - let evaluator_bid = BidParams::new(evaluator_bidder, 600 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + let evaluator_bid = BidParams::new( + evaluator_bidder, + 600 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ); evaluations.push((evaluator_bidder, evaluation_amount).into()); let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); @@ -436,9 +441,12 @@ mod bid_extrinsic { let evaluations = default_evaluations(); - let usdt_bid = BidParams::new(BIDDER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); - let usdc_bid = BidParams::new(BIDDER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDC); - let dot_bid = BidParams::new(BIDDER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::DOT); + let usdt_bid = + BidParams::new(BIDDER_1, 10_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT); + let usdc_bid = + BidParams::new(BIDDER_1, 10_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDC); + let dot_bid = + BidParams::new(BIDDER_1, 10_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::DOT); let plmc_fundings = inst.calculate_auction_plmc_charged_with_given_price( &vec![usdt_bid.clone(), usdc_bid.clone(), dot_bid.clone()], @@ -514,20 +522,12 @@ mod bid_extrinsic { project_policy, ); let amount = 1000 * CT_UNIT; - let multiplier = Multiplier::force_new(u8_multiplier); + let mode = ParticipationMode::Classic(u8_multiplier); if u8_multiplier > 0 { - let bid = BidParams:: { - bidder: bidder.clone(), - amount, - multiplier, - asset: AcceptedFundingAsset::USDT, - }; - let min_price = inst.get_project_metadata(project_id).minimum_price; - let necessary_plmc = - inst.calculate_auction_plmc_charged_with_given_price(&vec![bid.clone()], min_price, true); - let necessary_usdt = - inst.calculate_auction_funding_asset_charged_with_given_price(&vec![bid.clone()], min_price); + // We cannot use helper functions because some multipliers are invalid + let necessary_plmc = vec![(bidder, 1_000_000 * PLMC).into()]; + let necessary_usdt = vec![(bidder, 1_000_000 * USDT_UNIT).into()]; inst.mint_plmc_to(necessary_plmc.clone()); inst.mint_funding_asset_to(necessary_usdt.clone()); @@ -538,7 +538,7 @@ mod bid_extrinsic { jwt, project_id, amount, - multiplier, + mode, AcceptedFundingAsset::USDT, ) }) @@ -608,7 +608,7 @@ mod bid_extrinsic { 40u8, vec![100], vec![BIDDER_1], - vec![8u8], + vec![ParticipationMode::Classic(8u8)], ); // Note: 5% of total CTs is one bucket, i.e 10% of the auction allocation @@ -620,7 +620,7 @@ mod bid_extrinsic { 23u8, vec![100], vec![BIDDER_2], - vec![7u8], + vec![ParticipationMode::Classic(7u8)], ); let all_bids = vec![bid_40_percent[0].clone(), bid_23_percent[0].clone()]; @@ -696,7 +696,8 @@ mod bid_extrinsic { let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, default_evaluations()); - let bid = BidParams::new(BIDDER_4, 500 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + let bid = + BidParams::new(BIDDER_4, 500 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT); let plmc_required = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( &vec![bid.clone()], project_metadata.clone(), @@ -738,7 +739,7 @@ mod bid_extrinsic { ), project_id, bid.amount, - bid.multiplier, + bid.mode, bid.asset )); }); @@ -777,7 +778,8 @@ mod bid_extrinsic { let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, default_evaluations()); - let bid = BidParams::new(BIDDER_4, 500 * CT_UNIT, 5u8, AcceptedFundingAsset::USDT); + let bid = + BidParams::new(BIDDER_4, 500 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::USDT); let plmc_required = inst.calculate_auction_plmc_charged_from_all_bids_made_or_with_bucket( &vec![bid.clone()], project_metadata.clone(), @@ -819,7 +821,7 @@ mod bid_extrinsic { ), project_id, bid.amount, - bid.multiplier, + bid.mode, bid.asset )); }); @@ -832,12 +834,10 @@ mod bid_extrinsic { 90u8, default_weights(), default_community_contributors(), - default_multipliers(), + default_modes(), ); - let plmc_required = inst.calculate_contributed_plmc_spent(contributions.clone(), wap, false); - let plmc_existential_deposits = plmc_required.accounts().existential_deposits(); + let plmc_required = inst.calculate_contributed_plmc_spent(contributions.clone(), wap, true); inst.mint_plmc_to(plmc_required.clone()); - inst.mint_plmc_to(plmc_existential_deposits.clone()); let usdt_required = inst.calculate_contributed_funding_asset_spent(contributions.clone(), wap); inst.mint_funding_asset_to(usdt_required.clone()); @@ -886,6 +886,308 @@ mod bid_extrinsic { assert_eq!(bid_held_balance, Zero::zero()); assert_eq!(frozen_balance, frozen_amount); } + + #[test] + fn one_token_mode_bid_funding_success() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let issuer = ISSUER_1; + + let mut project_metadata = default_project_metadata(issuer); + project_metadata.mainnet_token_max_supply = 50_000 * CT_UNIT; + project_metadata.total_allocation_size = 10_000 * CT_UNIT; + project_metadata.minimum_price = ::PriceProvider::calculate_decimals_aware_price( + PriceOf::::from_float(1.0), + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + + let evaluations = + inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); + + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); + let otm_multiplier: MultiplierOf = + ParticipationMode::OTM.multiplier().try_into().ok().unwrap(); + let otm_duration = otm_multiplier.calculate_vesting_duration::(); + + const USDT_ID: u32 = AcceptedFundingAsset::USDT.id(); + const USDT_PARTICIPATION: u128 = 5000 * USDT_UNIT; + + let otm_usdt_fee: u128 = (FeePercentage::get() / ParticipationMode::OTM.multiplier()) * USDT_PARTICIPATION; + + let usdt_ed = inst.get_funding_asset_ed(AcceptedFundingAsset::USDT.id()); + let required_usdt = UserToFundingAsset::new(BIDDER_1, USDT_PARTICIPATION + otm_usdt_fee + usdt_ed, USDT_ID); + inst.mint_funding_asset_to(vec![required_usdt.clone()]); + + let ct_participation = inst.execute(|| { + >::funding_asset_to_ct_amount( + project_id, + AcceptedFundingAsset::USDT, + USDT_PARTICIPATION, + ) + }); + // USDT has the same decimals and price as our baseline USD + let expected_plmc_bond = + >::calculate_plmc_bond(USDT_PARTICIPATION, otm_multiplier).unwrap(); + + let otm_escrow_account = + ::RootId::get().into_sub_account_truncating(project_id); + let otm_treasury_account = ::Treasury::get(); + let otm_fee_recipient_account = ::FeeRecipient::get(); + let funding_project_escrow = PolimecFunding::fund_account_id(project_id); + + assert!(funding_project_escrow != otm_escrow_account); + + let pre_participation_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); + let pre_participation_otm_escrow_held_plmc = + inst.get_reserved_plmc_balance_for(otm_escrow_account, HoldReason::Participation.into()); + let pre_participation_otm_escrow_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_escrow_account); + let pre_participation_otm_fee_recipient_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_fee_recipient_account); + let pre_participation_buyer_usdt = inst.get_free_funding_asset_balance_for(USDT_ID, BIDDER_1); + + inst.execute(|| { + assert_ok!(PolimecFunding::bid( + RuntimeOrigin::signed(BIDDER_1), + get_mock_jwt_with_cid( + BIDDER_1, + InvestorType::Professional, + generate_did_from_account(BIDDER_1), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + ct_participation, + ParticipationMode::OTM, + AcceptedFundingAsset::USDT + )); + }); + + let post_participation_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); + let post_participation_otm_escrow_held_plmc = + inst.get_reserved_plmc_balance_for(otm_escrow_account, HoldReason::Participation.into()); + let post_participation_otm_escrow_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_escrow_account); + let post_participation_otm_fee_recipient_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_fee_recipient_account); + let post_participation_buyer_usdt = inst.get_free_funding_asset_balance_for(USDT_ID, 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!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(_))); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); + inst.settle_project(project_id, true); + + inst.execute(|| { + assert_ok!(>::transfer_fees_to_recipient( + RuntimeOrigin::signed(BIDDER_1), + project_id, + HoldReason::Participation.into(), + USDT_ID + )); + assert_noop!( + >::transfer_bonds_back_to_treasury( + RuntimeOrigin::signed(BIDDER_1), + project_id, + HoldReason::Participation.into() + ), + pallet_proxy_bonding::Error::::TooEarlyToUnlock + ); + }); + let now = inst.current_block(); + inst.jump_to_block(otm_duration + now); + inst.execute(|| { + assert_ok!(>::transfer_bonds_back_to_treasury( + RuntimeOrigin::signed(BIDDER_1), + project_id, + HoldReason::Participation.into() + )); + }); + + let post_settlement_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); + let post_settlement_otm_escrow_held_plmc = inst.get_free_plmc_balance_for(otm_escrow_account); + let post_settlement_otm_escrow_usdt = inst.get_free_funding_asset_balance_for(USDT_ID, otm_escrow_account); + let post_settlement_otm_fee_recipient_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_fee_recipient_account); + let post_settlement_buyer_usdt = inst.get_free_funding_asset_balance_for(USDT_ID, BIDDER_1); + let issuer_funding_account = inst.get_free_funding_asset_balance_for(USDT_ID, issuer); + + assert_eq!(post_settlement_treasury_free_plmc, post_participation_treasury_free_plmc + expected_plmc_bond); + assert_eq!(post_settlement_otm_escrow_held_plmc, inst.get_ed()); + assert_eq!(post_settlement_otm_escrow_usdt, Zero::zero()); + assert_close_enough!(post_settlement_otm_fee_recipient_usdt, otm_usdt_fee, Perquintill::from_float(0.999)); + assert_close_enough!(post_settlement_buyer_usdt, usdt_ed, Perquintill::from_float(0.999)); + assert_close_enough!(issuer_funding_account, USDT_PARTICIPATION, Perquintill::from_float(0.999)); + } + + #[test] + fn one_token_mode_bid_funding_failed() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let issuer = ISSUER_1; + + let mut project_metadata = default_project_metadata(issuer); + project_metadata.mainnet_token_max_supply = 50_000 * CT_UNIT; + project_metadata.total_allocation_size = 20_000 * CT_UNIT; + project_metadata.minimum_price = ::PriceProvider::calculate_decimals_aware_price( + PriceOf::::from_float(1.0), + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + + let evaluations = + inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); + + let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); + let otm_multiplier: MultiplierOf = + ParticipationMode::OTM.multiplier().try_into().ok().unwrap(); + + const USDT_ID: u32 = AcceptedFundingAsset::USDT.id(); + const USDT_PARTICIPATION: u128 = 5000 * USDT_UNIT; + + let otm_usdt_fee: u128 = (FeePercentage::get() / ParticipationMode::OTM.multiplier()) * USDT_PARTICIPATION; + let usdt_ed = inst.get_funding_asset_ed(AcceptedFundingAsset::USDT.id()); + let required_usdt = UserToFundingAsset::new(BIDDER_1, USDT_PARTICIPATION + otm_usdt_fee + usdt_ed, USDT_ID); + inst.mint_funding_asset_to(vec![required_usdt.clone()]); + + let ct_participation = inst.execute(|| { + >::funding_asset_to_ct_amount( + project_id, + AcceptedFundingAsset::USDT, + USDT_PARTICIPATION, + ) + }); + // USDT has the same decimals and price as our baseline USD + let expected_plmc_bond = + >::calculate_plmc_bond(USDT_PARTICIPATION, otm_multiplier).unwrap(); + + let otm_escrow_account = + ::RootId::get().into_sub_account_truncating(project_id); + let otm_treasury_account = ::Treasury::get(); + let otm_fee_recipient_account = ::FeeRecipient::get(); + let funding_project_escrow = PolimecFunding::fund_account_id(project_id); + + assert!(funding_project_escrow != otm_escrow_account); + + let pre_participation_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); + let pre_participation_otm_escrow_held_plmc = + inst.get_reserved_plmc_balance_for(otm_escrow_account, HoldReason::Participation.into()); + let pre_participation_otm_escrow_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_escrow_account); + let pre_participation_otm_fee_recipient_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_fee_recipient_account); + let pre_participation_buyer_usdt = inst.get_free_funding_asset_balance_for(USDT_ID, BIDDER_1); + + inst.execute(|| { + assert_ok!(PolimecFunding::bid( + RuntimeOrigin::signed(BIDDER_1), + get_mock_jwt_with_cid( + BIDDER_1, + InvestorType::Institutional, + generate_did_from_account(BIDDER_1), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + ct_participation, + ParticipationMode::OTM, + AcceptedFundingAsset::USDT + )); + }); + + let post_participation_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); + let post_participation_otm_escrow_held_plmc = + inst.get_reserved_plmc_balance_for(otm_escrow_account, HoldReason::Participation.into()); + let post_participation_otm_escrow_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_escrow_account); + let post_participation_otm_fee_recipient_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_fee_recipient_account); + let post_participation_buyer_usdt = inst.get_free_funding_asset_balance_for(USDT_ID, 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!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); + inst.settle_project(project_id, true); + + inst.execute(|| { + assert_noop!( + >::transfer_fees_to_recipient( + RuntimeOrigin::signed(BIDDER_1), + project_id, + HoldReason::Participation.into(), + USDT_ID + ), + pallet_proxy_bonding::Error::::FeeToRecipientDisallowed + ); + + assert_ok!(>::transfer_bonds_back_to_treasury( + RuntimeOrigin::signed(BIDDER_1), + project_id, + HoldReason::Participation.into() + )); + }); + + let post_settlement_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); + let post_settlement_otm_escrow_held_plmc = inst.get_free_plmc_balance_for(otm_escrow_account); + let post_settlement_otm_escrow_usdt = inst.get_free_funding_asset_balance_for(USDT_ID, otm_escrow_account); + let post_settlement_otm_fee_recipient_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_fee_recipient_account); + let post_settlement_buyer_usdt = inst.get_free_funding_asset_balance_for(USDT_ID, BIDDER_1); + let issuer_funding_account = inst.get_free_funding_asset_balance_for(USDT_ID, issuer); + + assert_eq!(post_settlement_treasury_free_plmc, post_participation_treasury_free_plmc + expected_plmc_bond); + assert_eq!(post_settlement_otm_escrow_held_plmc, inst.get_ed()); + assert_eq!(post_settlement_otm_escrow_usdt, Zero::zero()); + assert_eq!(post_settlement_otm_fee_recipient_usdt, Zero::zero()); + assert_eq!(post_settlement_buyer_usdt, usdt_ed + USDT_PARTICIPATION + otm_usdt_fee); + assert_eq!(issuer_funding_account, Zero::zero()); + } } #[cfg(test)] @@ -900,7 +1202,12 @@ mod bid_extrinsic { let mut evaluations = default_evaluations(); let evaluator_bidder = 69; let evaluation_amount = 420 * USD_UNIT; - let evaluator_bid = BidParams::new(evaluator_bidder, 600 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + let evaluator_bid = BidParams::new( + evaluator_bidder, + 600 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ); evaluations.push((evaluator_bidder, evaluation_amount).into()); let project_id = inst.create_auctioning_project(project_metadata.clone(), issuer, None, evaluations); @@ -929,7 +1236,12 @@ mod bid_extrinsic { let evaluator_bidder = 69; let evaluation_amount = 420 * USD_UNIT; - let evaluator_bid = BidParams::new(evaluator_bidder, 600 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + let evaluator_bid = BidParams::new( + evaluator_bidder, + 600 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ); evaluations_1.push((evaluator_bidder, evaluation_amount).into()); let _project_id_1 = @@ -971,7 +1283,7 @@ mod bid_extrinsic { ), project_id_2, evaluator_bid.amount, - evaluator_bid.multiplier, + evaluator_bid.mode, evaluator_bid.asset ), Error::::ParticipantNotEnoughFunds @@ -993,7 +1305,7 @@ mod bid_extrinsic { bidder: BIDDER_2, project_id: 0, ct_amount: 1, - mode: 1u8.try_into().unwrap(), + mode: ParticipationMode::Classic(1u8), funding_asset: AcceptedFundingAsset::USDT, did, investor_type, @@ -1014,8 +1326,7 @@ mod bid_extrinsic { let evaluations = inst.generate_successful_evaluations(project_metadata.clone(), vec![EVALUATOR_1], vec![100u8]); let max_bids_per_project: u32 = ::MaxBidsPerProject::get(); - let bids = - (0u32..max_bids_per_project - 1).map(|i| (i as u32 + 420u32, 5000 * CT_UNIT).into()).collect_vec(); + 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); @@ -1037,8 +1348,12 @@ mod bid_extrinsic { 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::::new(BIDDER_1, remaining_ct + 5000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + let failing_bid = BidParams::::new( + BIDDER_1, + 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(), @@ -1067,7 +1382,7 @@ mod bid_extrinsic { ), project_id, failing_bid.amount, - failing_bid.multiplier, + failing_bid.mode, failing_bid.asset ), Error::::TooManyProjectParticipations @@ -1086,7 +1401,7 @@ mod bid_extrinsic { ), project_id, remaining_ct, - failing_bid.multiplier, + failing_bid.mode, failing_bid.asset )); }); @@ -1102,7 +1417,7 @@ mod bid_extrinsic { ), project_id, 5000 * CT_UNIT, - failing_bid.multiplier, + failing_bid.mode, failing_bid.asset ), Error::::TooManyProjectParticipations @@ -1142,8 +1457,12 @@ mod bid_extrinsic { 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::::new(BIDDER_1, remaining_ct + 5000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT); + let failing_bid = BidParams::::new( + BIDDER_1, + 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(), @@ -1170,7 +1489,7 @@ mod bid_extrinsic { ), project_id, failing_bid.amount, - failing_bid.multiplier, + failing_bid.mode, failing_bid.asset ), Error::::TooManyUserParticipations @@ -1189,7 +1508,7 @@ mod bid_extrinsic { ), project_id, remaining_ct, - failing_bid.multiplier, + failing_bid.mode, failing_bid.asset )); }); @@ -1205,7 +1524,7 @@ mod bid_extrinsic { ), project_id, 5000 * CT_UNIT, - failing_bid.multiplier, + failing_bid.mode, failing_bid.asset ), Error::::TooManyUserParticipations @@ -1243,7 +1562,7 @@ mod bid_extrinsic { bidder: BIDDER_1, project_id, ct_amount: 799 * CT_UNIT, - mode: 1u8.try_into().unwrap(), + mode: ParticipationMode::Classic(1u8), funding_asset: AcceptedFundingAsset::USDT, did: generate_did_from_account(BIDDER_1), investor_type: InvestorType::Professional, @@ -1259,7 +1578,7 @@ mod bid_extrinsic { bidder: BIDDER_2, project_id, ct_amount: 1999 * CT_UNIT, - mode: 1u8.try_into().unwrap(), + mode: ParticipationMode::Classic(1u8), funding_asset: AcceptedFundingAsset::USDT, did: generate_did_from_account(BIDDER_1), investor_type: InvestorType::Institutional, @@ -1326,7 +1645,7 @@ mod bid_extrinsic { bidder: BIDDER_2, project_id, ct_amount: smallest_ct_amount_at_8k_usd, - mode: 1u8.try_into().unwrap(), + mode: ParticipationMode::Classic(1u8), funding_asset: AcceptedFundingAsset::USDT, did: generate_did_from_account(BIDDER_1), investor_type: InvestorType::Professional, @@ -1345,7 +1664,7 @@ mod bid_extrinsic { bidder: BIDDER_3, project_id, ct_amount: smallest_ct_amount_at_20k_usd, - mode: 1u8.try_into().unwrap(), + mode: ParticipationMode::Classic(1u8), funding_asset: AcceptedFundingAsset::USDT, did: generate_did_from_account(BIDDER_1), investor_type: InvestorType::Institutional, @@ -1407,7 +1726,7 @@ mod bid_extrinsic { bidder_1_jwt, project_id, 8000 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, )); }); @@ -1418,7 +1737,7 @@ mod bid_extrinsic { bidder_2_jwt_same_did.clone(), project_id, 3000 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT ), Error::::TooHigh @@ -1431,7 +1750,7 @@ mod bid_extrinsic { bidder_2_jwt_same_did, project_id, 2000 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, )); }); @@ -1455,7 +1774,7 @@ mod bid_extrinsic { bidder_3_jwt, project_id, 40_000 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, )); }); @@ -1466,7 +1785,7 @@ mod bid_extrinsic { bidder_4_jwt_same_did.clone(), project_id, 11_000 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, ), Error::::TooHigh @@ -1479,7 +1798,7 @@ mod bid_extrinsic { bidder_4_jwt_same_did, project_id, 10_000 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, )); }); @@ -1496,7 +1815,7 @@ mod bid_extrinsic { bidder: ISSUER_1, project_id, ct_amount: 5000 * CT_UNIT, - mode: 1u8.try_into().unwrap(), + mode: ParticipationMode::Classic(1u8), funding_asset: AcceptedFundingAsset::USDT, did: generate_did_from_account(ISSUER_1), investor_type: InvestorType::Professional, @@ -1512,7 +1831,12 @@ mod bid_extrinsic { let project_metadata = default_project_metadata(ISSUER_1); let project_id = inst.create_auctioning_project(project_metadata.clone(), ISSUER_1, None, default_evaluations()); - let bids = vec![BidParams::::new(BIDDER_1, 10_000, 1u8, AcceptedFundingAsset::USDC)]; + let bids = vec![BidParams::::new( + BIDDER_1, + 10_000, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDC, + )]; let did = generate_did_from_account(bids[0].bidder); let investor_type = InvestorType::Institutional; @@ -1522,7 +1846,7 @@ mod bid_extrinsic { bidder: bids[0].bidder, project_id, ct_amount: bids[0].amount, - mode: bids[0].multiplier, + mode: bids[0].mode, funding_asset: bids[0].asset, did, investor_type, @@ -1551,7 +1875,7 @@ mod bid_extrinsic { ), project_id, 5000 * CT_UNIT, - 1u8.try_into().unwrap(), + ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT ), Error::::PolicyMismatch @@ -1574,12 +1898,12 @@ mod end_auction_extrinsic { // From the knowledge hub: https://hub.polimec.org/learn/calculation-example#auction-round-calculation-example let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - const ADAM: u32 = 60; - const TOM: u32 = 61; - const SOFIA: u32 = 62; - const FRED: u32 = 63; - const ANNA: u32 = 64; - const DAMIAN: u32 = 65; + const ADAM: AccountIdOf = 60; + const TOM: AccountIdOf = 61; + const SOFIA: AccountIdOf = 62; + const FRED: AccountIdOf = 63; + const ANNA: AccountIdOf = 64; + const DAMIAN: AccountIdOf = 65; let accounts = vec![ADAM, TOM, SOFIA, FRED, ANNA, DAMIAN]; @@ -1688,11 +2012,16 @@ mod end_auction_extrinsic { // We use multiplier > 1 so after settlement, only the refunds defined above are done. The rest will be done // through the linear release pallet - let bid_1 = BidParams::new(BIDDER_1, 5000 * CT_UNIT, 5u8, AcceptedFundingAsset::USDT); - let bid_2 = BidParams::new(BIDDER_2, 40_000 * CT_UNIT, 5u8, AcceptedFundingAsset::USDC); - let bid_3 = BidParams::new(BIDDER_1, 10_000 * CT_UNIT, 5u8, AcceptedFundingAsset::DOT); - let bid_4 = BidParams::new(BIDDER_3, 6000 * CT_UNIT, 5u8, AcceptedFundingAsset::USDT); - let bid_5 = BidParams::new(BIDDER_4, 2000 * CT_UNIT, 5u8, AcceptedFundingAsset::DOT); + let bid_1 = + BidParams::new(BIDDER_1, 5000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::USDT); + let bid_2 = + BidParams::new(BIDDER_2, 40_000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::USDC); + let bid_3 = + BidParams::new(BIDDER_1, 10_000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::DOT); + let bid_4 = + BidParams::new(BIDDER_3, 6000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::USDT); + let bid_5 = + BidParams::new(BIDDER_4, 2000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::DOT); // post bucketing, the bids look like this: // (BIDDER_1, 5k) - (BIDDER_2, 40k) - (BIDDER_1, 5k) - (BIDDER_1, 5k) - (BIDDER_3 - 5k) - (BIDDER_3 - 1k) - (BIDDER_4 - 2k) // | -------------------- 10USD ----------------------|---- 11 USD ---|---- 12 USD ----|----------- 13 USD -------------| @@ -1801,12 +2130,12 @@ mod end_auction_extrinsic { // From the knowledge hub: https://hub.polimec.org/learn/calculation-example#auction-round-calculation-example let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - const ADAM: u32 = 60; - const TOM: u32 = 61; - const SOFIA: u32 = 62; - const FRED: u32 = 63; - const ANNA: u32 = 64; - const DAMIAN: u32 = 65; + const ADAM: AccountIdOf = 60; + const TOM: AccountIdOf = 61; + const SOFIA: AccountIdOf = 62; + const FRED: AccountIdOf = 63; + const ANNA: AccountIdOf = 64; + const DAMIAN: AccountIdOf = 65; let accounts = vec![ADAM, TOM, SOFIA, FRED, ANNA, DAMIAN]; let mut project_metadata = default_project_metadata(ISSUER_1); @@ -1840,12 +2169,12 @@ mod end_auction_extrinsic { let project_id = inst.create_auctioning_project(project_metadata, ISSUER_1, None, default_evaluations()); let bids = vec![ - (ADAM, 10_000 * CT_UNIT, 1, AcceptedFundingAsset::USDT).into(), - (TOM, 20_000 * CT_UNIT, 1, AcceptedFundingAsset::USDC).into(), - (SOFIA, 20_000 * CT_UNIT, 1, AcceptedFundingAsset::DOT).into(), - (FRED, 10_000 * CT_UNIT, 1, AcceptedFundingAsset::USDT).into(), - (ANNA, 5_000 * CT_UNIT, 1, AcceptedFundingAsset::USDC).into(), - (DAMIAN, 5_000 * CT_UNIT, 1, AcceptedFundingAsset::DOT).into(), + (ADAM, 10_000 * CT_UNIT, ParticipationMode::Classic(1), AcceptedFundingAsset::USDT).into(), + (TOM, 20_000 * CT_UNIT, ParticipationMode::Classic(1), AcceptedFundingAsset::USDC).into(), + (SOFIA, 20_000 * CT_UNIT, ParticipationMode::Classic(1), AcceptedFundingAsset::DOT).into(), + (FRED, 10_000 * CT_UNIT, ParticipationMode::Classic(1), AcceptedFundingAsset::USDT).into(), + (ANNA, 5_000 * CT_UNIT, ParticipationMode::Classic(1), AcceptedFundingAsset::USDC).into(), + (DAMIAN, 5_000 * CT_UNIT, ParticipationMode::Classic(1), AcceptedFundingAsset::DOT).into(), ]; inst.bid_for_users(project_id, bids).unwrap(); diff --git a/pallets/funding/src/tests/4_contribution.rs b/pallets/funding/src/tests/4_contribution.rs index 983ed0c1e..562e481c1 100644 --- a/pallets/funding/src/tests/4_contribution.rs +++ b/pallets/funding/src/tests/4_contribution.rs @@ -608,22 +608,13 @@ mod contribute_extrinsic { project_policy, ); let amount = 1000 * CT_UNIT; - let wap = inst.get_project_details(project_id).weighted_average_price.unwrap(); if u8_multiplier > 0 { - let contribution = ContributionParams:: { - contributor: contributor.clone(), - amount, - mode: ParticipationMode::Classic(u8_multiplier), - asset: AcceptedFundingAsset::USDT, - }; - - let necessary_plmc = inst.calculate_contributed_plmc_spent(vec![contribution.clone()], wap, false); - let plmc_existential_amounts = necessary_plmc.accounts().existential_deposits(); - let necessary_usdt = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], wap); + // We can't calculate exactly the amounts needed because the multipliers can be invalid + let necessary_plmc = vec![(contributor, 1_000_000 * PLMC).into()]; + let necessary_usdt = vec![(contributor, 1_000_000 * USDT_UNIT).into()]; inst.mint_plmc_to(necessary_plmc.clone()); - inst.mint_plmc_to(plmc_existential_amounts.clone()); inst.mint_funding_asset_to(necessary_usdt.clone()); } inst.execute(|| { @@ -662,7 +653,7 @@ mod contribute_extrinsic { 50, default_weights(), default_bidders(), - default_multipliers(), + default_modes(), ); let project_id = inst.create_community_contributing_project(project_metadata.clone(), ISSUER_1, None, evaluations, bids); @@ -739,8 +730,18 @@ mod contribute_extrinsic { evaluations.push((BIDDER_4, 1337 * USD_UNIT).into()); let successful_bids = vec![ - BidParams::new(BIDDER_1, 400_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - BidParams::new(BIDDER_2, 100_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + BidParams::new( + BIDDER_1, + 400_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), + BidParams::new( + BIDDER_2, + 100_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), ]; // This bids should fill the first bucket. @@ -1005,12 +1006,17 @@ mod contribute_extrinsic { fn participant_was_evaluator_and_bidder() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); let issuer = ISSUER_1; - let participant = 42069u32; + let participant = 42069; let project_metadata = default_project_metadata(issuer); let mut evaluations = default_evaluations(); evaluations.push((participant, 100 * USD_UNIT).into()); let mut bids = default_bids(); - bids.push(BidParams::new(participant, 1000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT)); + bids.push(BidParams::new( + participant, + 1000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + )); let community_contributions = default_community_contributions(); let mut remainder_contributions = default_remainder_contributions(); remainder_contributions.push(ContributionParams::new( @@ -1030,6 +1036,307 @@ mod contribute_extrinsic { remainder_contributions, ); } + + #[test] + fn one_token_mode_contribution_funding_success() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let issuer = ISSUER_1; + + let mut project_metadata = default_project_metadata(issuer); + project_metadata.mainnet_token_max_supply = 50_000 * CT_UNIT; + project_metadata.total_allocation_size = 5_000 * CT_UNIT; + project_metadata.minimum_price = ::PriceProvider::calculate_decimals_aware_price( + PriceOf::::from_float(1.0), + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + + let evaluations = + inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); + + let project_id = + inst.create_community_contributing_project(project_metadata.clone(), issuer, None, evaluations, vec![]); + let otm_multiplier: MultiplierOf = + ParticipationMode::OTM.multiplier().try_into().ok().unwrap(); + let otm_duration = otm_multiplier.calculate_vesting_duration::(); + + const USDT_ID: u32 = AcceptedFundingAsset::USDT.id(); + const USDT_PARTICIPATION: u128 = 3000 * USDT_UNIT; + + let otm_usdt_fee: u128 = (FeePercentage::get() / ParticipationMode::OTM.multiplier()) * USDT_PARTICIPATION; + let usdt_ed = inst.get_funding_asset_ed(AcceptedFundingAsset::USDT.id()); + let required_usdt = UserToFundingAsset::new(BUYER_1, USDT_PARTICIPATION + otm_usdt_fee + usdt_ed, USDT_ID); + inst.mint_funding_asset_to(vec![required_usdt.clone()]); + + let ct_participation = inst.execute(|| { + >::funding_asset_to_ct_amount( + project_id, + AcceptedFundingAsset::USDT, + USDT_PARTICIPATION, + ) + }); + // USDT has the same decimals and price as our baseline USD + let expected_plmc_bond = + >::calculate_plmc_bond(USDT_PARTICIPATION, otm_multiplier).unwrap(); + + let otm_escrow_account = + ::RootId::get().into_sub_account_truncating(project_id); + let otm_treasury_account = ::Treasury::get(); + let otm_fee_recipient_account = ::FeeRecipient::get(); + let funding_project_escrow = PolimecFunding::fund_account_id(project_id); + + assert!(funding_project_escrow != otm_escrow_account); + + let pre_participation_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); + let pre_participation_otm_escrow_held_plmc = + inst.get_reserved_plmc_balance_for(otm_escrow_account, HoldReason::Participation.into()); + let pre_participation_otm_escrow_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_escrow_account); + let pre_participation_otm_fee_recipient_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_fee_recipient_account); + let pre_participation_buyer_usdt = inst.get_free_funding_asset_balance_for(USDT_ID, BUYER_1); + + inst.execute(|| { + assert_ok!(PolimecFunding::contribute( + RuntimeOrigin::signed(BUYER_1), + get_mock_jwt_with_cid( + BUYER_1, + InvestorType::Retail, + generate_did_from_account(BUYER_1), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + ct_participation, + ParticipationMode::OTM, + AcceptedFundingAsset::USDT + )); + }); + + let post_participation_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); + let post_participation_otm_escrow_held_plmc = + inst.get_reserved_plmc_balance_for(otm_escrow_account, HoldReason::Participation.into()); + let post_participation_otm_escrow_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_escrow_account); + let post_participation_otm_fee_recipient_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_fee_recipient_account); + let post_participation_buyer_usdt = inst.get_free_funding_asset_balance_for(USDT_ID, BUYER_1); + + assert_eq!( + post_participation_treasury_free_plmc, + pre_participation_treasury_free_plmc - expected_plmc_bond - inst.get_ed() + ); + assert_eq!( + post_participation_otm_escrow_held_plmc, + pre_participation_otm_escrow_held_plmc + expected_plmc_bond + ); + assert_close_enough!( + post_participation_otm_escrow_usdt, + pre_participation_otm_escrow_usdt + otm_usdt_fee, + Perquintill::from_float(0.999) + ); + assert_close_enough!( + post_participation_otm_fee_recipient_usdt, + pre_participation_otm_fee_recipient_usdt, + Perquintill::from_float(0.999) + ); + assert_close_enough!( + post_participation_buyer_usdt, + pre_participation_buyer_usdt - USDT_PARTICIPATION - otm_usdt_fee, + Perquintill::from_float(0.999) + ); + + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingSuccessful); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Success)); + inst.settle_project(project_id, true); + + inst.execute(|| { + assert_ok!(>::transfer_fees_to_recipient( + RuntimeOrigin::signed(BUYER_1), + project_id, + HoldReason::Participation.into(), + USDT_ID + )); + assert_noop!( + >::transfer_bonds_back_to_treasury( + RuntimeOrigin::signed(BUYER_1), + project_id, + HoldReason::Participation.into() + ), + pallet_proxy_bonding::Error::::TooEarlyToUnlock + ); + }); + let now = inst.current_block(); + inst.jump_to_block(otm_duration + now); + inst.execute(|| { + assert_ok!(>::transfer_bonds_back_to_treasury( + RuntimeOrigin::signed(BUYER_1), + project_id, + HoldReason::Participation.into() + )); + }); + + let post_settlement_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); + let post_settlement_otm_escrow_held_plmc = inst.get_free_plmc_balance_for(otm_escrow_account); + let post_settlement_otm_escrow_usdt = inst.get_free_funding_asset_balance_for(USDT_ID, otm_escrow_account); + let post_settlement_otm_fee_recipient_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_fee_recipient_account); + let post_settlement_buyer_usdt = inst.get_free_funding_asset_balance_for(USDT_ID, BUYER_1); + let issuer_funding_account = inst.get_free_funding_asset_balance_for(USDT_ID, issuer); + + assert_eq!(post_settlement_treasury_free_plmc, post_participation_treasury_free_plmc + expected_plmc_bond); + assert_eq!(post_settlement_otm_escrow_held_plmc, inst.get_ed()); + assert_eq!(post_settlement_otm_escrow_usdt, Zero::zero()); + assert_close_enough!(post_settlement_otm_fee_recipient_usdt, otm_usdt_fee, Perquintill::from_float(0.999)); + assert_close_enough!(post_settlement_buyer_usdt, usdt_ed, Perquintill::from_float(0.999)); + assert_eq!(issuer_funding_account, USDT_PARTICIPATION); + } + + #[test] + fn one_token_mode_contribution_funding_failed() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let issuer = ISSUER_1; + + let mut project_metadata = default_project_metadata(issuer); + project_metadata.mainnet_token_max_supply = 50_000 * CT_UNIT; + project_metadata.total_allocation_size = 20_000 * CT_UNIT; + project_metadata.minimum_price = ::PriceProvider::calculate_decimals_aware_price( + PriceOf::::from_float(1.0), + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + + let evaluations = + inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); + + let project_id = + inst.create_community_contributing_project(project_metadata.clone(), issuer, None, evaluations, vec![]); + let otm_multiplier: MultiplierOf = + ParticipationMode::OTM.multiplier().try_into().ok().unwrap(); + + const USDT_ID: u32 = AcceptedFundingAsset::USDT.id(); + const USDT_PARTICIPATION: u128 = 3000 * USDT_UNIT; + + let otm_usdt_fee: u128 = (FeePercentage::get() / ParticipationMode::OTM.multiplier()) * USDT_PARTICIPATION; + let usdt_ed = inst.get_funding_asset_ed(AcceptedFundingAsset::USDT.id()); + let required_usdt = UserToFundingAsset::new(BUYER_1, USDT_PARTICIPATION + otm_usdt_fee + usdt_ed, USDT_ID); + inst.mint_funding_asset_to(vec![required_usdt.clone()]); + + let ct_participation = inst.execute(|| { + >::funding_asset_to_ct_amount( + project_id, + AcceptedFundingAsset::USDT, + USDT_PARTICIPATION, + ) + }); + // USDT has the same decimals and price as our baseline USD + let expected_plmc_bond = + >::calculate_plmc_bond(USDT_PARTICIPATION, otm_multiplier).unwrap(); + + let otm_escrow_account = + ::RootId::get().into_sub_account_truncating(project_id); + let otm_treasury_account = ::Treasury::get(); + let otm_fee_recipient_account = ::FeeRecipient::get(); + let funding_project_escrow = PolimecFunding::fund_account_id(project_id); + + assert!(funding_project_escrow != otm_escrow_account); + + let pre_participation_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); + let pre_participation_otm_escrow_held_plmc = + inst.get_reserved_plmc_balance_for(otm_escrow_account, HoldReason::Participation.into()); + let pre_participation_otm_escrow_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_escrow_account); + let pre_participation_otm_fee_recipient_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_fee_recipient_account); + let pre_participation_buyer_usdt = inst.get_free_funding_asset_balance_for(USDT_ID, BUYER_1); + + inst.execute(|| { + assert_ok!(PolimecFunding::contribute( + RuntimeOrigin::signed(BUYER_1), + get_mock_jwt_with_cid( + BUYER_1, + InvestorType::Retail, + generate_did_from_account(BUYER_1), + project_metadata.clone().policy_ipfs_cid.unwrap() + ), + project_id, + ct_participation, + ParticipationMode::OTM, + AcceptedFundingAsset::USDT + )); + }); + + let post_participation_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); + let post_participation_otm_escrow_held_plmc = + inst.get_reserved_plmc_balance_for(otm_escrow_account, HoldReason::Participation.into()); + let post_participation_otm_escrow_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_escrow_account); + let post_participation_otm_fee_recipient_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_fee_recipient_account); + let post_participation_buyer_usdt = inst.get_free_funding_asset_balance_for(USDT_ID, BUYER_1); + + assert_eq!( + post_participation_treasury_free_plmc, + pre_participation_treasury_free_plmc - expected_plmc_bond - inst.get_ed() + ); + assert_eq!( + post_participation_otm_escrow_held_plmc, + pre_participation_otm_escrow_held_plmc + expected_plmc_bond + ); + assert_close_enough!( + post_participation_otm_escrow_usdt, + pre_participation_otm_escrow_usdt + otm_usdt_fee, + Perquintill::from_float(0.999) + ); + assert_close_enough!( + post_participation_otm_fee_recipient_usdt, + pre_participation_otm_fee_recipient_usdt, + Perquintill::from_float(0.999) + ); + assert_close_enough!( + post_participation_buyer_usdt, + pre_participation_buyer_usdt - USDT_PARTICIPATION - otm_usdt_fee, + Perquintill::from_float(0.999) + ); + + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::FundingFailed); + assert_eq!(inst.go_to_next_state(project_id), ProjectStatus::SettlementStarted(FundingOutcome::Failure)); + inst.settle_project(project_id, true); + + inst.execute(|| { + assert_noop!( + >::transfer_fees_to_recipient( + RuntimeOrigin::signed(BUYER_1), + project_id, + HoldReason::Participation.into(), + USDT_ID + ), + pallet_proxy_bonding::Error::::FeeToRecipientDisallowed + ); + + assert_ok!(>::transfer_bonds_back_to_treasury( + RuntimeOrigin::signed(BUYER_1), + project_id, + HoldReason::Participation.into() + )); + }); + + let post_settlement_treasury_free_plmc = inst.get_free_plmc_balance_for(otm_treasury_account); + let post_settlement_otm_escrow_held_plmc = inst.get_free_plmc_balance_for(otm_escrow_account); + let post_settlement_otm_escrow_usdt = inst.get_free_funding_asset_balance_for(USDT_ID, otm_escrow_account); + let post_settlement_otm_fee_recipient_usdt = + inst.get_free_funding_asset_balance_for(USDT_ID, otm_fee_recipient_account); + let post_settlement_buyer_usdt = inst.get_free_funding_asset_balance_for(USDT_ID, BUYER_1); + let issuer_funding_account = inst.get_free_funding_asset_balance_for(USDT_ID, issuer); + + assert_eq!(post_settlement_treasury_free_plmc, post_participation_treasury_free_plmc + expected_plmc_bond); + assert_eq!(post_settlement_otm_escrow_held_plmc, inst.get_ed()); + assert_eq!(post_settlement_otm_escrow_usdt, Zero::zero()); + assert_eq!(post_settlement_otm_fee_recipient_usdt, Zero::zero()); + assert_eq!(post_settlement_buyer_usdt, usdt_ed + USDT_PARTICIPATION + otm_usdt_fee); + assert_eq!(issuer_funding_account, Zero::zero()); + } } #[cfg(test)] @@ -1155,10 +1462,20 @@ mod contribute_extrinsic { let mut evaluations = default_evaluations(); evaluations.push((BIDDER_2, 1337 * USD_UNIT).into()); let bids = vec![ - BidParams::new(BIDDER_1, 400_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - BidParams::new(BIDDER_2, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + BidParams::new( + BIDDER_1, + 400_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), + BidParams::new(BIDDER_2, 50_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT), // Partially accepted bid. Only the 50k of the second bid will be accepted. - BidParams::new(BIDDER_3, 100_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + BidParams::new( + BIDDER_3, + 100_000 * CT_UNIT, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDT, + ), ]; let project_id = inst.create_community_contributing_project( diff --git a/pallets/funding/src/tests/6_settlement.rs b/pallets/funding/src/tests/6_settlement.rs index a0727710b..1b65ee0b3 100644 --- a/pallets/funding/src/tests/6_settlement.rs +++ b/pallets/funding/src/tests/6_settlement.rs @@ -171,14 +171,14 @@ mod settle_evaluation_extrinsic { 50, default_weights(), default_bidders(), - default_multipliers(), + default_modes(), ), inst.generate_contributions_from_total_ct_percent( project_metadata.clone(), 50, default_weights(), default_community_contributors(), - default_community_contributor_multipliers(), + default_community_contributor_modes(), ), vec![], ); @@ -255,7 +255,7 @@ mod settle_evaluation_extrinsic { RuntimeOrigin::signed(evaluator), project_id, evaluator, - evaluator - 21 // The First evaluation index is 0, the first evaluator account is 21 + (evaluator - 21) as u32 // The First evaluation index is 0, the first evaluator account is 21 ))); let ct_rewarded = inst.get_ct_asset_balance_for(project_id, evaluator); assert_close_enough!(ct_rewarded, expected_reward, Perquintill::from_float(0.9999)); @@ -395,9 +395,14 @@ mod settle_bid_extrinsic { bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; let auction_allocation = project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - let partial_amount_bid_params = - BidParams::new(BIDDER_1, auction_allocation, 3u8, AcceptedFundingAsset::USDT); - let lower_price_bid_params = BidParams::new(BIDDER_2, 2000 * CT_UNIT, 5u8, AcceptedFundingAsset::DOT); + let partial_amount_bid_params = BidParams::new( + BIDDER_1, + auction_allocation, + ParticipationMode::Classic(3u8), + AcceptedFundingAsset::USDT, + ); + let lower_price_bid_params = + BidParams::new(BIDDER_2, 2000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::DOT); let bids = vec![partial_amount_bid_params.clone(), lower_price_bid_params.clone()]; let project_id = inst.create_finished_project( @@ -504,7 +509,9 @@ mod settle_bid_extrinsic { inst.assert_migration(project_id, BIDDER_2, 2000 * CT_UNIT, 1, ParticipationType::Bid, true); // Multiplier 5 should be unbonded no earlier than after 8.67 weeks (i.e. 436'867 blocks) - let vesting_time = lower_price_bid_params.multiplier.calculate_vesting_duration::(); + let multiplier: MultiplierOf = + lower_price_bid_params.mode.multiplier().try_into().ok().unwrap(); + let vesting_time = multiplier.calculate_vesting_duration::(); // Sanity check, 5 blocks should not be enough inst.advance_time(5u64); @@ -530,8 +537,12 @@ mod settle_bid_extrinsic { bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; let auction_allocation = project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - let no_refund_bid_params = - BidParams::new(BIDDER_1, auction_allocation / 2, 16u8, AcceptedFundingAsset::USDT); + let no_refund_bid_params = BidParams::new( + BIDDER_1, + auction_allocation / 2, + ParticipationMode::Classic(16u8), + AcceptedFundingAsset::USDT, + ); let project_id = inst.create_finished_project( project_metadata.clone(), @@ -572,8 +583,8 @@ mod settle_bid_extrinsic { inst.assert_migration(project_id, BIDDER_1, auction_allocation / 2, 0, ParticipationType::Bid, true); let hold_reason: RuntimeHoldReason = HoldReason::Participation.into(); - - let vesting_time = no_refund_bid_params.multiplier.calculate_vesting_duration::(); + let multiplier: MultiplierOf = no_refund_bid_params.mode.multiplier().try_into().ok().unwrap(); + let vesting_time = multiplier.calculate_vesting_duration::(); // Sanity check, 5 blocks should not be enough inst.advance_time(5u64); @@ -597,9 +608,14 @@ mod settle_bid_extrinsic { project_metadata.auction_round_allocation_percentage = Percent::from_percent(10); let auction_allocation = project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - let partial_amount_bid_params = - BidParams::new(BIDDER_1, auction_allocation, 1u8, AcceptedFundingAsset::USDC); - let lower_price_bid_params = BidParams::new(BIDDER_2, 2000 * CT_UNIT, 5u8, AcceptedFundingAsset::DOT); + let partial_amount_bid_params = BidParams::new( + BIDDER_1, + auction_allocation, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::USDC, + ); + let lower_price_bid_params = + BidParams::new(BIDDER_2, 2000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::DOT); let bids = vec![partial_amount_bid_params.clone(), lower_price_bid_params.clone()]; let project_id = inst.create_finished_project( @@ -695,7 +711,8 @@ mod settle_bid_extrinsic { let mut project_metadata = default_project_metadata(ISSUER_1); project_metadata.participation_currencies = bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; - let no_refund_bid_params = BidParams::new(BIDDER_1, 500 * CT_UNIT, 16u8, AcceptedFundingAsset::USDT); + let no_refund_bid_params = + BidParams::new(BIDDER_1, 500 * CT_UNIT, ParticipationMode::Classic(16u8), AcceptedFundingAsset::USDT); let project_id = inst.create_finished_project( project_metadata.clone(), @@ -753,8 +770,18 @@ mod settle_bid_extrinsic { bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; let auction_allocation = project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - let rejected_bid_params = BidParams::new(BIDDER_1, auction_allocation, 4u8, AcceptedFundingAsset::USDT); - let accepted_bid_params = BidParams::new(BIDDER_2, auction_allocation, 1u8, AcceptedFundingAsset::DOT); + let rejected_bid_params = BidParams::new( + BIDDER_1, + auction_allocation, + ParticipationMode::Classic(4u8), + AcceptedFundingAsset::USDT, + ); + let accepted_bid_params = BidParams::new( + BIDDER_2, + auction_allocation, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::DOT, + ); let bids = vec![rejected_bid_params.clone(), accepted_bid_params.clone()]; let project_id = inst.create_community_contributing_project( @@ -810,8 +837,18 @@ mod settle_bid_extrinsic { bounded_vec![AcceptedFundingAsset::USDT, AcceptedFundingAsset::DOT]; let auction_allocation = project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - let rejected_bid_params = BidParams::new(BIDDER_1, auction_allocation, 4u8, AcceptedFundingAsset::USDT); - let accepted_bid_params = BidParams::new(BIDDER_2, auction_allocation, 1u8, AcceptedFundingAsset::DOT); + let rejected_bid_params = BidParams::new( + BIDDER_1, + auction_allocation, + ParticipationMode::Classic(4u8), + AcceptedFundingAsset::USDT, + ); + let accepted_bid_params = BidParams::new( + BIDDER_2, + auction_allocation, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::DOT, + ); let bids = vec![rejected_bid_params.clone(), accepted_bid_params.clone()]; let project_id = inst.create_finished_project( @@ -871,8 +908,18 @@ mod settle_bid_extrinsic { project_metadata.auction_round_allocation_percentage = Percent::from_percent(10); let auction_allocation = project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; - let rejected_bid_params = BidParams::new(BIDDER_1, auction_allocation, 4u8, AcceptedFundingAsset::USDT); - let accepted_bid_params = BidParams::new(BIDDER_2, auction_allocation, 1u8, AcceptedFundingAsset::DOT); + let rejected_bid_params = BidParams::new( + BIDDER_1, + auction_allocation, + ParticipationMode::Classic(4u8), + AcceptedFundingAsset::USDT, + ); + let accepted_bid_params = BidParams::new( + BIDDER_2, + auction_allocation, + ParticipationMode::Classic(1u8), + AcceptedFundingAsset::DOT, + ); let bids = vec![rejected_bid_params.clone(), accepted_bid_params.clone()]; let project_id = inst.create_finished_project( @@ -1066,14 +1113,14 @@ mod settle_contribution_extrinsic { 10, default_weights(), default_bidders(), - default_multipliers(), + default_modes(), ); let mut community_contributions = inst.generate_contributions_from_total_ct_percent( project_metadata.clone(), 10, default_weights(), default_community_contributors(), - default_community_contributor_multipliers(), + default_community_contributor_modes(), ); let contribution_mul_1 = ContributionParams::::new( diff --git a/pallets/funding/src/tests/misc.rs b/pallets/funding/src/tests/misc.rs index 6594d36ba..7d91f5840 100644 --- a/pallets/funding/src/tests/misc.rs +++ b/pallets/funding/src/tests/misc.rs @@ -66,23 +66,23 @@ mod helper_functions { #[test] fn calculate_evaluation_plmc_spent() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); - const EVALUATOR_1: AccountIdOf = 1u32; + const EVALUATOR_1: AccountIdOf = 1; const USD_AMOUNT_1: Balance = 150_000 * USD_UNIT; const EXPECTED_PLMC_AMOUNT_1: f64 = 17_857.1428571428f64; - const EVALUATOR_2: AccountIdOf = 2u32; + const EVALUATOR_2: AccountIdOf = 2; const USD_AMOUNT_2: Balance = 50_000 * USD_UNIT; const EXPECTED_PLMC_AMOUNT_2: f64 = 5_952.3809523809f64; - const EVALUATOR_3: AccountIdOf = 3u32; + const EVALUATOR_3: AccountIdOf = 3; const USD_AMOUNT_3: Balance = 75_000 * USD_UNIT; const EXPECTED_PLMC_AMOUNT_3: f64 = 8_928.5714285714f64; - const EVALUATOR_4: AccountIdOf = 4u32; + const EVALUATOR_4: AccountIdOf = 4; const USD_AMOUNT_4: Balance = 100 * USD_UNIT; const EXPECTED_PLMC_AMOUNT_4: f64 = 11.9047619047f64; - const EVALUATOR_5: AccountIdOf = 5u32; + const EVALUATOR_5: AccountIdOf = 5; // 123.7 USD const USD_AMOUNT_5: Balance = 1237 * USD_UNIT / 10; @@ -139,11 +139,11 @@ mod helper_functions { const CT_AMOUNT_4: u128 = 6000 * CT_UNIT; const CT_AMOUNT_5: u128 = 2000 * CT_UNIT; - let bid_1 = BidParams::new(BIDDER_1, CT_AMOUNT_1, 1u8, AcceptedFundingAsset::USDT); - let bid_2 = BidParams::new(BIDDER_2, CT_AMOUNT_2, 1u8, AcceptedFundingAsset::USDT); - let bid_3 = BidParams::new(BIDDER_1, CT_AMOUNT_3, 1u8, AcceptedFundingAsset::USDT); - let bid_4 = BidParams::new(BIDDER_3, CT_AMOUNT_4, 1u8, AcceptedFundingAsset::USDT); - let bid_5 = BidParams::new(BIDDER_4, CT_AMOUNT_5, 1u8, AcceptedFundingAsset::USDT); + let bid_1 = BidParams::new(BIDDER_1, CT_AMOUNT_1, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT); + let bid_2 = BidParams::new(BIDDER_2, CT_AMOUNT_2, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT); + let bid_3 = BidParams::new(BIDDER_1, CT_AMOUNT_3, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT); + let bid_4 = BidParams::new(BIDDER_3, CT_AMOUNT_4, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT); + let bid_5 = BidParams::new(BIDDER_4, CT_AMOUNT_5, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT); // post bucketing, the bids look like this: // (BIDDER_1, 5k) - (BIDDER_2, 40k) - (BIDDER_1, 5k) - (BIDDER_1, 5k) - (BIDDER_3 - 5k) - (BIDDER_3 - 1k) - (BIDDER_4 - 2k) @@ -246,31 +246,31 @@ mod helper_functions { const PLMC_PRICE: f64 = 8.4f64; const CT_PRICE: f64 = 16.32f64; - const CONTRIBUTOR_1: AccountIdOf = 1u32; + const CONTRIBUTOR_1: AccountIdOf = 1; const TOKEN_AMOUNT_1: u128 = 120 * CT_UNIT; const MULTIPLIER_1: u8 = 1u8; const _TICKET_SIZE_USD_1: u128 = 1_958_4_000_000_000_u128; const EXPECTED_PLMC_AMOUNT_1: f64 = 233.1_428_571_428f64; - const CONTRIBUTOR_2: AccountIdOf = 2u32; + const CONTRIBUTOR_2: AccountIdOf = 2; const TOKEN_AMOUNT_2: u128 = 5023 * CT_UNIT; const MULTIPLIER_2: u8 = 2u8; const _TICKET_SIZE_USD_2: u128 = 81_975_3_600_000_000_u128; const EXPECTED_PLMC_AMOUNT_2: f64 = 4_879.4_857_142_857f64; - const CONTRIBUTOR_3: AccountIdOf = 3u32; + const CONTRIBUTOR_3: AccountIdOf = 3; const TOKEN_AMOUNT_3: u128 = 20_000 * CT_UNIT; const MULTIPLIER_3: u8 = 17u8; const _TICKET_SIZE_USD_3: u128 = 326_400_0_000_000_000_u128; const EXPECTED_PLMC_AMOUNT_3: f64 = 2_285.7_142_857_142f64; - const CONTRIBUTOR_4: AccountIdOf = 4u32; + const CONTRIBUTOR_4: AccountIdOf = 4; const TOKEN_AMOUNT_4: u128 = 1_000_000 * CT_UNIT; const MULTIPLIER_4: u8 = 25u8; const _TICKET_SIZE_4: u128 = 16_320_000_0_000_000_000_u128; const EXPECTED_PLMC_AMOUNT_4: f64 = 77_714.2_857_142_857f64; - const CONTRIBUTOR_5: AccountIdOf = 5u32; + const CONTRIBUTOR_5: AccountIdOf = 5; // 0.1233 CTs const TOKEN_AMOUNT_5: u128 = 1_233 * CT_UNIT / 10_000; const MULTIPLIER_5: u8 = 10u8; diff --git a/pallets/funding/src/tests/mod.rs b/pallets/funding/src/tests/mod.rs index 9aacdbac2..a1032b8a7 100644 --- a/pallets/funding/src/tests/mod.rs +++ b/pallets/funding/src/tests/mod.rs @@ -18,6 +18,7 @@ use sp_arithmetic::{traits::Zero, Percent, Perquintill}; use sp_runtime::TokenError; use sp_std::cell::RefCell; use std::iter::zip; +use ParticipationMode::{Classic, OTM}; #[path = "1_application.rs"] mod application; @@ -198,20 +199,20 @@ pub mod defaults { pub fn default_bids() -> Vec> { vec![ - BidParams::new(BIDDER_1, 400_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - BidParams::new(BIDDER_2, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_1, 400_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_2, 50_000 * CT_UNIT, ParticipationMode::OTM, AcceptedFundingAsset::USDT), ] } pub fn knowledge_hub_bids() -> Vec> { // This should reflect the bidding currency, which currently is USDT vec![ - BidParams::new(BIDDER_1, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - BidParams::new(BIDDER_2, 20_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - BidParams::new(BIDDER_3, 20_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - BidParams::new(BIDDER_4, 10_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - BidParams::new(BIDDER_5, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - BidParams::new(BIDDER_6, 5_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_1, 10_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_2, 20_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_3, 20_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_4, 10_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_5, 5_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_6, 5_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT), ] } @@ -226,27 +227,17 @@ pub mod defaults { ContributionParams::new( BUYER_2, 130_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - ), - ContributionParams::new( - BUYER_3, - 30_000 * CT_UNIT, - ParticipationMode::Classic(1u8), + ParticipationMode::Classic(5u8), AcceptedFundingAsset::USDT, ), + ContributionParams::new(BUYER_3, 30_000 * CT_UNIT, ParticipationMode::OTM, 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), + ParticipationMode::Classic(3u8), AcceptedFundingAsset::USDT, ), + ContributionParams::new(BUYER_5, 10_000 * CT_UNIT, ParticipationMode::OTM, AcceptedFundingAsset::USDT), ] } @@ -264,12 +255,7 @@ pub mod defaults { ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT, ), - ContributionParams::new( - BIDDER_1, - 30_000 * CT_UNIT, - ParticipationMode::Classic(1u8), - AcceptedFundingAsset::USDT, - ), + ContributionParams::new(BIDDER_1, 30_000 * CT_UNIT, ParticipationMode::OTM, AcceptedFundingAsset::USDT), ] } @@ -340,17 +326,17 @@ pub mod defaults { pub fn default_bidders() -> Vec { vec![BIDDER_1, BIDDER_2, BIDDER_3, BIDDER_4, BIDDER_5] } - pub fn default_multipliers() -> Vec { - vec![1u8, 1u8, 1u8, 1u8, 1u8] + pub fn default_modes() -> Vec { + vec![Classic(1u8), Classic(1u8), Classic(1u8), OTM, OTM] } - pub fn default_bidder_multipliers() -> Vec { - vec![10u8, 3u8, 8u8, 1u8, 4u8] + pub fn default_bidder_modes() -> Vec { + vec![Classic(10u8), OTM, Classic(8u8), OTM, Classic(4u8)] } - pub fn default_community_contributor_multipliers() -> Vec { - vec![1u8, 1u8, 1u8, 1u8, 1u8] + pub fn default_community_contributor_modes() -> Vec { + vec![Classic(1u8), Classic(1u8), OTM, Classic(1u8), OTM] } - pub fn default_remainder_contributor_multipliers() -> Vec { - vec![1u8, 1u8, 1u8, 1u8, 1u8] + pub fn default_remainder_contributor_modes() -> Vec { + vec![Classic(1u8), Classic(1u8), Classic(1u8), OTM, Classic(1u8)] } pub fn default_community_contributors() -> Vec { @@ -385,14 +371,14 @@ pub mod defaults { min_price, default_weights(), default_bidders(), - default_multipliers(), + default_modes(), ); let contributions = instantiator.generate_contributions_from_total_usd( Percent::from_percent(50u8) * usd_to_reach, min_price, default_weights(), default_community_contributors(), - default_multipliers(), + default_modes(), ); instantiator.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids, contributions, vec![]) } @@ -406,7 +392,7 @@ pub mod defaults { percent, default_weights(), default_bidders(), - default_bidder_multipliers(), + default_bidder_modes(), ) } @@ -419,7 +405,7 @@ pub mod defaults { percent, default_weights(), default_community_contributors(), - default_community_contributor_multipliers(), + default_community_contributor_modes(), ) } @@ -432,7 +418,7 @@ pub mod defaults { percent, default_weights(), default_remainder_contributors(), - default_remainder_contributor_multipliers(), + default_remainder_contributor_modes(), ) } } @@ -452,14 +438,14 @@ pub fn create_project_with_funding_percentage( min_price, default_weights(), default_bidders(), - default_multipliers(), + default_modes(), ); let contributions = inst.generate_contributions_from_total_usd( Percent::from_percent(50u8) * percentage_funded_usd, min_price, default_weights(), default_community_contributors(), - default_multipliers(), + default_modes(), ); let project_id = inst.create_finished_project(project_metadata, ISSUER_1, None, evaluations, bids, contributions, vec![]); @@ -482,7 +468,7 @@ pub fn create_finished_project_with_usd_raised( usd_raised: Balance, usd_target: Balance, ) -> (MockInstantiator, ProjectId) { - let issuer = inst.get_new_nonce() as u32; + let issuer = inst.get_new_nonce(); let mut project_metadata = default_project_metadata(issuer); project_metadata.total_allocation_size = project_metadata.minimum_price.reciprocal().unwrap().saturating_mul_int(usd_target); @@ -505,7 +491,7 @@ pub fn create_finished_project_with_usd_raised( let evaluations = default_evaluations(); - let bids = inst.generate_bids_that_take_price_to(project_metadata.clone(), required_price, 420, |acc| acc + 1u32); + let bids = inst.generate_bids_that_take_price_to(project_metadata.clone(), required_price, 420, |acc| acc + 1); let project_id = inst.create_community_contributing_project(project_metadata, issuer, None, evaluations, bids); @@ -520,7 +506,7 @@ pub fn create_finished_project_with_usd_raised( wap, default_weights(), default_community_contributors(), - default_multipliers(), + default_modes(), ); let plmc_required = inst.calculate_contributed_plmc_spent(community_contributions.clone(), required_price, true); let usdt_required = inst.calculate_contributed_funding_asset_spent(community_contributions.clone(), required_price); diff --git a/pallets/funding/src/tests/runtime_api.rs b/pallets/funding/src/tests/runtime_api.rs index a2d10a05d..213d615b4 100644 --- a/pallets/funding/src/tests/runtime_api.rs +++ b/pallets/funding/src/tests/runtime_api.rs @@ -363,7 +363,7 @@ fn funding_asset_to_ct_amount() { PriceProviderOf::::calculate_decimals_aware_price(new_price, USD_DECIMALS, CT_DECIMALS).unwrap(); let bids = - inst.generate_bids_that_take_price_to(project_metadata_2.clone(), decimal_aware_price, 420u32, |acc| acc + 1); + inst.generate_bids_that_take_price_to(project_metadata_2.clone(), decimal_aware_price, 420, |acc| acc + 1); let project_id_2 = inst.create_community_contributing_project( project_metadata_2.clone(), ISSUER_2, @@ -403,7 +403,7 @@ fn funding_asset_to_ct_amount() { let bids = inst.generate_bids_from_bucket( project_metadata_3.clone(), bucket, - 420u32, + 420, |acc| acc + 1, AcceptedFundingAsset::USDT, ); @@ -478,9 +478,9 @@ fn get_next_vesting_schedule_merge_candidates() { UserToUSDBalance::new(BIDDER_1, 320_000 * USD_UNIT), ]; let bids = vec![ - BidParams::new(BIDDER_1, 50_000 * CT_UNIT, 10u8, AcceptedFundingAsset::USDT), - BidParams::new(BIDDER_1, 400_000 * CT_UNIT, 5u8, AcceptedFundingAsset::USDT), - BidParams::new(BIDDER_2, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_1, 50_000 * CT_UNIT, ParticipationMode::Classic(10u8), AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_1, 400_000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_2, 50_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT), ]; let remaining_contributions = vec![ ContributionParams::new(BIDDER_1, 1_000 * CT_UNIT, ParticipationMode::Classic(5u8), AcceptedFundingAsset::USDT), @@ -564,8 +564,8 @@ fn all_project_participations_by_did() { UserToUSDBalance::new(EVALUATOR_3, 320_000 * USD_UNIT), ]; let bids = vec![ - BidParams::new(BIDDER_1, 400_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), - BidParams::new(BIDDER_2, 50_000 * CT_UNIT, 1u8, AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_1, 400_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT), + BidParams::new(BIDDER_2, 50_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT), ]; let community_contributions = vec![ ContributionParams::new(BUYER_1, 50_000 * CT_UNIT, ParticipationMode::Classic(1u8), AcceptedFundingAsset::USDT), @@ -657,15 +657,8 @@ fn all_project_participations_by_did() { for bid in bids[1..].to_vec() { let jwt = get_mock_jwt_with_cid(bid.bidder, InvestorType::Institutional, did_user.clone(), cid.clone()); inst.execute(|| { - PolimecFunding::bid( - RuntimeOrigin::signed(bid.bidder), - jwt, - project_id, - bid.amount, - bid.multiplier, - bid.asset, - ) - .unwrap(); + PolimecFunding::bid(RuntimeOrigin::signed(bid.bidder), jwt, project_id, bid.amount, bid.mode, bid.asset) + .unwrap(); }); } diff --git a/pallets/funding/src/traits.rs b/pallets/funding/src/traits.rs index eb1ab30d0..9ecc77e60 100644 --- a/pallets/funding/src/traits.rs +++ b/pallets/funding/src/traits.rs @@ -20,7 +20,7 @@ use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::DispatchError; pub trait BondingRequirementCalculation { - fn calculate_bonding_requirement(&self, ticket_size: Balance) -> Option; + fn calculate_usd_bonding_requirement(&self, ticket_size: Balance) -> Option; } pub trait VestingDurationCalculation { diff --git a/pallets/funding/src/types.rs b/pallets/funding/src/types.rs index d2d016714..813aad077 100644 --- a/pallets/funding/src/types.rs +++ b/pallets/funding/src/types.rs @@ -66,7 +66,7 @@ pub mod config { } impl BondingRequirementCalculation for Multiplier { - fn calculate_bonding_requirement(&self, ticket_size: Balance) -> Option { + fn calculate_usd_bonding_requirement(&self, ticket_size: Balance) -> Option { let balance_multiplier = Balance::from(self.0); ticket_size.checked_div(balance_multiplier) } @@ -823,8 +823,8 @@ pub mod inner { pub mod extrinsic { use crate::{ - AcceptedFundingAsset, AccountIdOf, Balance, Config, MultiplierOf, ParticipationMode, PriceOf, ProjectDetailsOf, - ProjectId, TicketSize, + AcceptedFundingAsset, AccountIdOf, Balance, Config, ParticipationMode, PriceOf, ProjectDetailsOf, ProjectId, + TicketSize, }; use frame_system::pallet_prelude::BlockNumberFor; use polimec_common::credentials::{Cid, Did, InvestorType}; diff --git a/pallets/on-slash-vesting/src/mock.rs b/pallets/on-slash-vesting/src/mock.rs index c227403b0..3bb36f7c9 100644 --- a/pallets/on-slash-vesting/src/mock.rs +++ b/pallets/on-slash-vesting/src/mock.rs @@ -7,7 +7,7 @@ use frame_support::{ use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; -use sp_runtime::traits::{ConvertInto, Identity}; +use sp_runtime::traits::ConvertInto; frame_support::construct_runtime!( pub enum TestRuntime { @@ -82,11 +82,6 @@ pub struct ExtBuilder { } impl ExtBuilder { - pub fn existential_deposit(mut self, existential_deposit: u128) -> Self { - self.existential_deposit = existential_deposit; - self - } - pub fn build(self) -> sp_io::TestExternalities { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); diff --git a/pallets/on-slash-vesting/src/test.rs b/pallets/on-slash-vesting/src/test.rs index f64498142..a20c4b49b 100644 --- a/pallets/on-slash-vesting/src/test.rs +++ b/pallets/on-slash-vesting/src/test.rs @@ -2,7 +2,7 @@ extern crate alloc; use super::{mock::*, *}; use frame_support::{ assert_ok, - traits::tokens::fungible::{BalancedHold, Inspect, Mutate, MutateHold}, + traits::tokens::fungible::{BalancedHold, Mutate, MutateHold}, }; use mock::{Balances as PalletBalances, System as PalletSystem, Vesting as PalletVesting}; use pallet_balances::AccountData; @@ -25,7 +25,7 @@ fn one_schedule() { assert_eq!(PalletBalances::usable_balance(1), 20); // Slash 30 - >::slash(&MockRuntimeHoldReason::Reason, &1u64, 30u128); + let _ = >::slash(&MockRuntimeHoldReason::Reason, &1u64, 30u128); >::on_slash(&1, 30); // After calling on_slash, the previously unlocked 20 should be available again @@ -65,7 +65,7 @@ fn multiple_schedules() { assert_ok!(PalletVesting::vest(RuntimeOrigin::signed(1))); assert_eq!(PalletBalances::usable_balance(1), 200); - >::slash(&MockRuntimeHoldReason::Reason, &1u64, 65u128); + let _ = >::slash(&MockRuntimeHoldReason::Reason, &1u64, 65u128); >::on_slash(&1, 65); let schedules = >::get(1).unwrap().to_vec(); diff --git a/pallets/proxy-bonding/Cargo.toml b/pallets/proxy-bonding/Cargo.toml index f7b262e14..b025c7855 100644 --- a/pallets/proxy-bonding/Cargo.toml +++ b/pallets/proxy-bonding/Cargo.toml @@ -21,6 +21,7 @@ polimec-common.workspace = true parity-scale-codec.workspace = true scale-info.workspace = true serde = { workspace = true, features = ["derive"] } +sp-core.workspace = true [dev-dependencies] sp-io.workspace = true diff --git a/pallets/proxy-bonding/src/functions.rs b/pallets/proxy-bonding/src/functions.rs index a91a470b9..b357deb02 100644 --- a/pallets/proxy-bonding/src/functions.rs +++ b/pallets/proxy-bonding/src/functions.rs @@ -1,13 +1,10 @@ use crate::{AccountIdOf, AssetId, BalanceOf, Config, Error, Pallet, PriceProviderOf, ReleaseType, Releases}; -use frame_support::{ - ensure, - traits::{ - fungible, - fungible::{Inspect, Mutate, MutateHold}, - fungibles, - fungibles::Mutate as FungiblesMutate, - tokens::{Fortitude, Precision, Preservation}, - }, +use frame_support::traits::{ + fungible, + fungible::{Inspect, Mutate, MutateHold}, + fungibles, + fungibles::Mutate as FungiblesMutate, + tokens::{Fortitude, Precision, Preservation}, }; use frame_system::pallet_prelude::BlockNumberFor; use polimec_common::ProvideAssetPrice; @@ -91,19 +88,16 @@ impl Pallet { Releases::::insert(derivation_path, hold_reason, release_type); } - /// Refund the fee paid by a user to lock up some treasury tokens. It is this function's caller responsibility to ensure that the fee is not refunded twice. + /// Refund the fee paid by a user to lock up some treasury tokens. It is this function's caller responsibility to + /// ensure that the fee should be refunded, and is not refunded twice pub fn refund_fee( derivation_path: u32, - hold_reason: T::RuntimeHoldReason, account: &T::AccountId, bond_amount: BalanceOf, fee_asset: AssetId, ) -> Result<(), DispatchError> { let bonding_account: AccountIdOf = T::RootId::get().into_sub_account_truncating(derivation_path); let fee_in_fee_asset = Self::calculate_fee(bond_amount, fee_asset)?; - let release_type = Releases::::get(derivation_path, hold_reason).ok_or(Error::::ReleaseTypeNotSet)?; - - ensure!(release_type == ReleaseType::Refunded, Error::::FeeRefundDisallowed); // We know this fee token account is existing thanks to the provider reference of the ED of the native asset, so we can fully move all the funds. // FYI same cannot be said of the `account`. We assume they only hold the fee token so their fee asset balance must not go below the min_balance. diff --git a/pallets/proxy-bonding/src/tests.rs b/pallets/proxy-bonding/src/tests.rs index 7be5ecae0..abdab0837 100644 --- a/pallets/proxy-bonding/src/tests.rs +++ b/pallets/proxy-bonding/src/tests.rs @@ -149,7 +149,7 @@ fn refunded_outcome() { Error::::FeeToRecipientDisallowed ); - assert_ok!(ProxyBonding::refund_fee(derivation_path, hold_reason.clone(), user, bond_amount, fee_asset)); + assert_ok!(ProxyBonding::refund_fee(derivation_path, &user, bond_amount, fee_asset)); assert_eq!(>::balance(fee_asset, &user), 100 + expected_fee); }); } diff --git a/runtimes/polimec/Cargo.toml b/runtimes/polimec/Cargo.toml index cac98208b..388523a5a 100644 --- a/runtimes/polimec/Cargo.toml +++ b/runtimes/polimec/Cargo.toml @@ -25,8 +25,8 @@ scale-info = { workspace= true, default-features = false, features = [ "derive", ] } -# Uncomment this to see variables instead of in the console output -# sp-debug-derive = { workspace = true, features = ["force-debug"]} +# Uncomment this and the std feature below to see variables instead of in the console output +#sp-debug-derive = { workspace = true, features = ["force-debug"]} # Polimec specific @@ -175,7 +175,7 @@ std = [ "sp-block-builder/std", "sp-consensus-aura/std", "sp-core/std", - # "sp-debug-derive/std", +# "sp-debug-derive/std", "sp-genesis-builder/std", "sp-inherents/std", "sp-offchain/std", diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index 07a92449d..d12ffb134 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -1086,7 +1086,7 @@ parameter_types! { pub FeePercentage: Perbill = Perbill::from_rational(75u32, 1000u32); // TODO: add a real account here pub FeeRecipient: AccountId = [0u8; 32].into(); - pub const RootId: PalletId = PalletId(*b"treasury"); + pub RootId: PalletId = PalletId(*b"treasury"); } impl pallet_proxy_bonding::Config for Runtime { type BondingToken = Balances;