diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index cd30e130f..258f285bb 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -153,6 +153,7 @@ std = [ "system-parachains-constants/std", "xcm-builder/std", "xcm-executor/std", + "xcm-fee-payment-runtime-api/std", "xcm/std", ] development-settings = [ "polimec-runtime/development-settings" ] @@ -196,5 +197,6 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm-fee-payment-runtime-api/runtime-benchmarks", ] diff --git a/integration-tests/src/tests/runtime_apis.rs b/integration-tests/src/tests/runtime_apis.rs index 3dc28154f..c097b2158 100644 --- a/integration-tests/src/tests/runtime_apis.rs +++ b/integration-tests/src/tests/runtime_apis.rs @@ -47,14 +47,12 @@ mod xcm_payment_api { VersionedAssetId::V4(AssetId(Location { parents: 0, interior: Here })), ) .unwrap(); - dbg!(plmc_fee); let dot_fee = PolimecRuntime::query_weight_to_asset_fee( compute_weight, VersionedAssetId::V4(AssetId(Location { parents: 1, interior: Here })), ) .unwrap(); - dbg!(dot_fee); let usdt_fee = PolimecRuntime::query_weight_to_asset_fee( compute_weight, @@ -64,7 +62,6 @@ mod xcm_payment_api { })), ) .unwrap(); - dbg!(usdt_fee); // PLMC and dot have the same decimals, so a simple conversion is enough assert_eq!(dot_fee, plmc_fee / 20); diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs index dc1a0462e..30350b3c7 100644 --- a/pallets/funding/src/mock.rs +++ b/pallets/funding/src/mock.rs @@ -578,6 +578,13 @@ sp_api::mock_impl_runtime_apis! { PolimecFunding::get_next_vesting_schedule_merge_candidates(account, hold_reason, end_max_delta) } + fn calculate_otm_fee(funding_asset: AcceptedFundingAsset, funding_asset_amount: Balance) -> Option { + PolimecFunding::calculate_otm_fee(funding_asset, funding_asset_amount) + } + fn get_funding_asset_min_max_amounts(project_id: ProjectId, did: Did, funding_asset: AcceptedFundingAsset, investor_type: InvestorType) -> Option<(Balance, Balance)> { + PolimecFunding::get_funding_asset_min_max_amounts(project_id, did, funding_asset, investor_type) + } + } } diff --git a/pallets/funding/src/runtime_api.rs b/pallets/funding/src/runtime_api.rs index aeeefa5ab..ac369bb74 100644 --- a/pallets/funding/src/runtime_api.rs +++ b/pallets/funding/src/runtime_api.rs @@ -1,10 +1,11 @@ +use crate::traits::BondingRequirementCalculation; #[allow(clippy::wildcard_imports)] use crate::*; use alloc::collections::BTreeMap; use frame_support::traits::fungibles::{metadata::Inspect as MetadataInspect, Inspect, InspectEnumerable}; use itertools::Itertools; use parity_scale_codec::{Decode, Encode}; -use polimec_common::{ProvideAssetPrice, USD_DECIMALS}; +use polimec_common::{credentials::InvestorType, ProvideAssetPrice, USD_DECIMALS}; use scale_info::TypeInfo; use sp_runtime::traits::Zero; #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)] @@ -52,7 +53,7 @@ sp_api::decl_runtime_apis! { fn projects_by_did(did: Did) -> Vec; } - #[api_version(2)] + #[api_version(3)] pub trait ExtrinsicHelpers { /// Get the current price of a contribution token (either current bucket in the auction, or WAP in contribution phase), /// and calculate the amount of tokens that can be bought with the given amount USDT/USDC/DOT. @@ -61,6 +62,12 @@ sp_api::decl_runtime_apis! { /// Get the indexes of vesting schedules that are good candidates to be merged. /// Schedules that have not yet started are de-facto bad candidates. fn get_next_vesting_schedule_merge_candidates(account_id: AccountIdOf, hold_reason: ::RuntimeHoldReason, end_max_delta: Balance) -> Option<(u32, u32)>; + + /// Calculate the OTM fee for a project, using a given asset and amount. + fn calculate_otm_fee(funding_asset: AcceptedFundingAsset, funding_asset_amount: Balance) -> Option; + + /// Gets the minimum and maximum amount of FundingAsset a user can input in the UI. + fn get_funding_asset_min_max_amounts(project_id: ProjectId, did: Did, funding_asset: AcceptedFundingAsset, investor_type: InvestorType) -> Option<(Balance, Balance)>; } } @@ -207,6 +214,100 @@ impl Pallet { None } + pub fn calculate_otm_fee(funding_asset: AcceptedFundingAsset, funding_asset_amount: Balance) -> Option { + let plmc_price = >::get_decimals_aware_price(PLMC_FOREIGN_ID, USD_DECIMALS, PLMC_DECIMALS) + .expect("Price not found"); + let funding_asset_id = funding_asset.id(); + let funding_asset_decimals = T::FundingCurrency::decimals(funding_asset_id); + let funding_asset_usd_price = + >::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals) + .expect("Price not found"); + let usd_amount = funding_asset_usd_price.saturating_mul_int(funding_asset_amount); + let otm_multiplier: MultiplierOf = ParticipationMode::OTM.multiplier().try_into().ok()?; + let required_usd_bond = otm_multiplier.calculate_usd_bonding_requirement::(usd_amount)?; + let plmc_bond = plmc_price.reciprocal()?.saturating_mul_int(required_usd_bond); + pallet_proxy_bonding::Pallet::::calculate_fee(plmc_bond, funding_asset_id).ok() + } + + pub fn get_funding_asset_min_max_amounts( + project_id: ProjectId, + did: Did, + funding_asset: AcceptedFundingAsset, + investor_type: InvestorType, + ) -> Option<(Balance, Balance)> { + let project_details = ProjectsDetails::::get(project_id)?; + let project_metadata = ProjectsMetadata::::get(project_id)?; + let funding_asset_id = funding_asset.id(); + let funding_asset_price = >::get_decimals_aware_price( + funding_asset_id, + USD_DECIMALS, + T::FundingCurrency::decimals(funding_asset_id), + )?; + + let (min_usd_ticket, maybe_max_usd_ticket, already_spent_usd, total_cts_usd_amount) = + match project_details.status { + ProjectStatus::AuctionRound => { + let ticket_sizes = match investor_type { + InvestorType::Institutional => project_metadata.bidding_ticket_sizes.institutional, + InvestorType::Professional => project_metadata.bidding_ticket_sizes.professional, + _ => return None, + }; + let already_spent_usd = AuctionBoughtUSD::::get((project_id, did)); + let mut max_contribution_tokens = + project_metadata.auction_round_allocation_percentage * project_metadata.total_allocation_size; + + let mut total_cts_usd_amount = 0; + + let mut current_bucket = Buckets::::get(project_id)?; + while max_contribution_tokens > 0u128 { + let bucket_price = current_bucket.current_price; + let ct_to_buy = max_contribution_tokens.min(current_bucket.amount_left); + let usd_spent = bucket_price.saturating_mul_int(ct_to_buy); + + max_contribution_tokens -= ct_to_buy; + total_cts_usd_amount += usd_spent; + current_bucket.update(ct_to_buy); + } + + ( + ticket_sizes.usd_minimum_per_participation, + ticket_sizes.usd_maximum_per_did, + already_spent_usd, + total_cts_usd_amount, + ) + }, + ProjectStatus::CommunityRound(..) => { + let ticket_sizes = match investor_type { + InvestorType::Institutional => project_metadata.contributing_ticket_sizes.institutional, + InvestorType::Professional => project_metadata.contributing_ticket_sizes.professional, + InvestorType::Retail => project_metadata.contributing_ticket_sizes.retail, + }; + let already_spent_usd = ContributionBoughtUSD::::get((project_id, did)); + let max_contribution_tokens = project_details.remaining_contribution_tokens; + let price = project_details.weighted_average_price?; + let total_cts_usd_amount = price.saturating_mul_int(max_contribution_tokens); + ( + ticket_sizes.usd_minimum_per_participation, + ticket_sizes.usd_maximum_per_did, + already_spent_usd, + total_cts_usd_amount, + ) + }, + _ => return None, + }; + + let max_usd_ticket = if let Some(issuer_set_max_usd_ticket) = maybe_max_usd_ticket { + total_cts_usd_amount.min(issuer_set_max_usd_ticket.saturating_sub(already_spent_usd)) + } else { + total_cts_usd_amount + }; + + let funding_asset_min_ticket = funding_asset_price.reciprocal()?.saturating_mul_int(min_usd_ticket); + let funding_asset_max_ticket = funding_asset_price.reciprocal()?.saturating_mul_int(max_usd_ticket); + + Some((funding_asset_min_ticket, funding_asset_max_ticket)) + } + pub fn all_project_participations_by_did(project_id: ProjectId, did: Did) -> Vec> { let evaluations = Evaluations::::iter_prefix((project_id,)) .filter(|((_account_id, _evaluation_id), evaluation)| evaluation.did == did) diff --git a/pallets/funding/src/tests/runtime_api.rs b/pallets/funding/src/tests/runtime_api.rs index 75b252674..6e768e076 100644 --- a/pallets/funding/src/tests/runtime_api.rs +++ b/pallets/funding/src/tests/runtime_api.rs @@ -1,5 +1,10 @@ use super::*; -use crate::runtime_api::{ExtrinsicHelpers, Leaderboards, ProjectInformation, UserInformation}; +use crate::{ + runtime_api::{ExtrinsicHelpers, Leaderboards, ProjectInformation, UserInformation}, + BidStatus::Accepted, +}; +use frame_support::traits::fungibles::{metadata::Inspect, Mutate}; +use sp_runtime::bounded_vec; #[test] fn top_evaluations() { @@ -545,6 +550,245 @@ fn get_next_vesting_schedule_merge_candidates() { }); } +#[test] +fn calculate_otm_fee() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.participation_currencies = bounded_vec![AcceptedFundingAsset::DOT]; + + let dot_id = AcceptedFundingAsset::DOT.id(); + let dot_decimals = inst.execute(|| ForeignAssets::decimals(dot_id)); + let dot_unit = 10u128.pow(dot_decimals as u32); + let dot_ticket = 10_000 * dot_unit; + let dot_ed = inst.get_funding_asset_ed(dot_id); + + let block_hash = inst.execute(|| System::block_hash(System::block_number())); + let calculated_fee = inst.execute(|| { + TestRuntime::calculate_otm_fee(&TestRuntime, block_hash, AcceptedFundingAsset::DOT, dot_ticket) + .unwrap() + .unwrap() + }); + + let project_id = inst.create_auctioning_project(project_metadata, ISSUER_1, None, default_evaluations()); + + let ct_amount = inst + .execute(|| { + TestRuntime::funding_asset_to_ct_amount( + &TestRuntime, + block_hash, + project_id, + AcceptedFundingAsset::DOT, + dot_ticket, + ) + }) + .unwrap(); + + inst.execute(|| ForeignAssets::set_balance(dot_id, &BIDDER_1, dot_ticket + calculated_fee + dot_ed)); + + let jwt = get_mock_jwt_with_cid( + BIDDER_1, + InvestorType::Professional, + generate_did_from_account(BIDDER_1), + default_project_metadata(ISSUER_1).policy_ipfs_cid.unwrap(), + ); + + inst.execute(|| { + PolimecFunding::bid( + RuntimeOrigin::signed(BIDDER_1), + jwt, + project_id, + ct_amount, + ParticipationMode::OTM, + AcceptedFundingAsset::DOT, + ) + .unwrap() + }); + + let balance = inst.get_free_funding_asset_balance_for(dot_id, BIDDER_1); + inst.execute(|| { + assert_close_enough!(balance, dot_ed, Perquintill::from_float(0.9999)); + }); +} + +#[test] +fn get_funding_asset_min_max_amounts() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + ConstPriceProvider::set_price(AcceptedFundingAsset::USDT.id(), PriceOf::::from_float(1.0f64)); + ConstPriceProvider::set_price(AcceptedFundingAsset::USDC.id(), PriceOf::::from_float(1.0f64)); + ConstPriceProvider::set_price(AcceptedFundingAsset::DOT.id(), PriceOf::::from_float(10.0f64)); + ConstPriceProvider::set_price(PLMC_FOREIGN_ID, PriceOf::::from_float(0.5f64)); + const DOT_UNIT: u128 = 10u128.pow(10u32); + + // We test the following cases: + // Bidder Professional where max is the ct max because it's lower than the ticket max. DOT + // Bidder Institutional where max is the ticket max (first bid). USDT + // Contributor Retail where the max is the ticket max (first contribution). DOT + // Contributor Institutional where max is the ct max because there is no ticket max. USDT + // Contributor Professional where max is the ticket max (4500 USD already contributed). USDC + + let mut project_metadata = default_project_metadata(ISSUER_1); + let min_price = PriceProviderOf::::calculate_decimals_aware_price( + PriceOf::::from_float(1.0f64), + USD_DECIMALS, + CT_DECIMALS, + ) + .unwrap(); + project_metadata.minimum_price = min_price; + project_metadata.total_allocation_size = 5_000_000 * CT_UNIT; + project_metadata.bidding_ticket_sizes = BiddingTicketSizes { + professional: TicketSize { + usd_minimum_per_participation: 5_000 * USD_UNIT, + usd_maximum_per_did: Some(10_000_000 * USD_UNIT), + }, + institutional: TicketSize { + usd_minimum_per_participation: 10_000 * USD_UNIT, + usd_maximum_per_did: Some(1_000_000 * USD_UNIT), + }, + phantom: Default::default(), + }; + project_metadata.contributing_ticket_sizes = ContributingTicketSizes { + retail: TicketSize { + usd_minimum_per_participation: 50 * USD_UNIT, + usd_maximum_per_did: Some(10_000 * USD_UNIT), + }, + professional: TicketSize { + usd_minimum_per_participation: 100 * USD_UNIT, + usd_maximum_per_did: Some(100_000 * USD_UNIT), + }, + institutional: TicketSize { usd_minimum_per_participation: 5000 * USD_UNIT, usd_maximum_per_did: None }, + phantom: Default::default(), + }; + project_metadata.participation_currencies = + bounded_vec![AcceptedFundingAsset::DOT, AcceptedFundingAsset::USDT, AcceptedFundingAsset::USDC]; + + const BIDDING_USD_MAX: u128 = 2_500_000; + const CONTRIBUTING_USD_MAX: u128 = 5_000_000; + + const BIDDER_PROFESSIONAL_DOT_MIN: u128 = 500 * DOT_UNIT; + const BIDDER_PROFESSIONAL_DOT_MAX: u128 = (BIDDING_USD_MAX / 10) * DOT_UNIT; + + const BIDDER_INSTITUTIONAL_USDT_MIN: u128 = 10_000 * USD_UNIT; + const BIDDER_INSTITUTIONAL_USDT_MAX: u128 = 1_000_000 * USD_UNIT; + + const CONTRIBUTOR_RETAIL_DOT_MIN: u128 = 5 * DOT_UNIT; + const CONTRIBUTOR_RETAIL_DOT_MAX: u128 = 1_000 * DOT_UNIT; + + const CONTRIBUTOR_INSTITUTIONAL_USDT_MIN: u128 = 5000 * USD_UNIT; + const CONTRIBUTOR_INSTITUTIONAL_USDT_MAX: u128 = CONTRIBUTING_USD_MAX * USD_UNIT; + + const CONTRIBUTOR_PROFESSIONAL_USDC_MIN: u128 = 100 * USD_UNIT; + const CONTRIBUTOR_PROFESSIONAL_USDC_MAX: u128 = (100_000 - 6000) * USD_UNIT; + + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + + let evaluations = + inst.generate_successful_evaluations(project_metadata.clone(), default_evaluators(), default_weights()); + let project_id = inst.create_auctioning_project(project_metadata, ISSUER_1, None, evaluations); + + let block_hash = inst.execute(|| System::block_hash(System::block_number())); + + let (min, max) = inst + .execute(|| { + TestRuntime::get_funding_asset_min_max_amounts( + &TestRuntime, + block_hash, + project_id, + generate_did_from_account(BIDDER_1), + AcceptedFundingAsset::DOT, + InvestorType::Professional, + ) + }) + .unwrap() + .unwrap(); + assert_eq!(min, BIDDER_PROFESSIONAL_DOT_MIN); + assert_eq!(max, BIDDER_PROFESSIONAL_DOT_MAX); + + let (min, max) = inst + .execute(|| { + TestRuntime::get_funding_asset_min_max_amounts( + &TestRuntime, + block_hash, + project_id, + generate_did_from_account(BIDDER_1), + AcceptedFundingAsset::USDT, + InvestorType::Institutional, + ) + }) + .unwrap() + .unwrap(); + assert_eq!(min, BIDDER_INSTITUTIONAL_USDT_MIN); + assert_eq!(max, BIDDER_INSTITUTIONAL_USDT_MAX); + + assert!(matches!(inst.go_to_next_state(project_id), ProjectStatus::CommunityRound(..))); + + let (min, max) = inst + .execute(|| { + TestRuntime::get_funding_asset_min_max_amounts( + &TestRuntime, + block_hash, + project_id, + generate_did_from_account(BUYER_1), + AcceptedFundingAsset::DOT, + InvestorType::Retail, + ) + }) + .unwrap() + .unwrap(); + assert_eq!(min, CONTRIBUTOR_RETAIL_DOT_MIN); + assert_eq!(max, CONTRIBUTOR_RETAIL_DOT_MAX); + + let (min, max) = inst + .execute(|| { + TestRuntime::get_funding_asset_min_max_amounts( + &TestRuntime, + block_hash, + project_id, + generate_did_from_account(BUYER_1), + AcceptedFundingAsset::USDT, + InvestorType::Institutional, + ) + }) + .unwrap() + .unwrap(); + assert_eq!(min, CONTRIBUTOR_INSTITUTIONAL_USDT_MIN); + assert_eq!(max, CONTRIBUTOR_INSTITUTIONAL_USDT_MAX); + + // This test requires the buyer to have contributed 4500 USD before calling the API + let required_ct = inst + .execute(|| { + TestRuntime::funding_asset_to_ct_amount( + &TestRuntime, + block_hash, + project_id, + AcceptedFundingAsset::USDC, + 6000 * USD_UNIT, + ) + }) + .unwrap(); + let contribution = + ContributionParams::new(BUYER_1, required_ct, ParticipationMode::OTM, AcceptedFundingAsset::USDC); + let usdc_to_mint = inst.calculate_contributed_funding_asset_spent(vec![contribution.clone()], min_price); + inst.mint_funding_asset_ed_if_required(usdc_to_mint.to_account_asset_map()); + inst.mint_funding_asset_to(usdc_to_mint); + inst.contribute_for_users(project_id, vec![contribution]).unwrap(); + + let (min, max) = inst + .execute(|| { + TestRuntime::get_funding_asset_min_max_amounts( + &TestRuntime, + block_hash, + project_id, + generate_did_from_account(BUYER_1), + AcceptedFundingAsset::USDC, + InvestorType::Professional, + ) + }) + .unwrap() + .unwrap(); + assert_eq!(min, CONTRIBUTOR_PROFESSIONAL_USDC_MIN); + assert_eq!(max, CONTRIBUTOR_PROFESSIONAL_USDC_MAX); +} + #[test] fn all_project_participations_by_did() { let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); diff --git a/polimec-common/common/Cargo.toml b/polimec-common/common/Cargo.toml index b83536f05..0e393e403 100644 --- a/polimec-common/common/Cargo.toml +++ b/polimec-common/common/Cargo.toml @@ -26,7 +26,7 @@ pallet-timestamp.workspace = true sp-std.workspace = true sp-runtime.workspace = true xcm.workspace = true -xcm-builder.workspace = true +xcm-builder = { workspace = true, optional = true } [features] @@ -42,6 +42,7 @@ std = [ "serde/std", "sp-runtime/std", "sp-std/std", + "xcm-builder?/std", "xcm/std", ] runtime-benchmarks = [ @@ -50,6 +51,8 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "xcm-builder", + "xcm-builder?/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", diff --git a/polimec-common/common/src/lib.rs b/polimec-common/common/src/lib.rs index caaaeddcf..dfab96af1 100644 --- a/polimec-common/common/src/lib.rs +++ b/polimec-common/common/src/lib.rs @@ -227,24 +227,31 @@ pub const USD_DECIMALS: u8 = 6; pub const USD_UNIT: u128 = 10u128.pow(USD_DECIMALS as u32); pub const PLMC_DECIMALS: u8 = 10; pub const PLMC_FOREIGN_ID: u32 = 3344; -pub struct DummyXcmSender; -impl SendXcm for DummyXcmSender { - type Ticket = (); - fn validate(_: &mut Option, _: &mut Option) -> SendResult { - Ok(((), Assets::new())) - } +#[cfg(feature = "runtime-benchmarks")] +mod dummy_xcm_sender { + use super::*; + pub struct DummyXcmSender; + impl SendXcm for DummyXcmSender { + type Ticket = (); + + fn validate(_: &mut Option, _: &mut Option) -> SendResult { + Ok(((), Assets::new())) + } - /// Actually carry out the delivery operation for a previously validated message sending. - fn deliver(_ticket: Self::Ticket) -> Result { - Ok([0u8; 32]) + /// Actually carry out the delivery operation for a previously validated message sending. + fn deliver(_ticket: Self::Ticket) -> Result { + Ok([0u8; 32]) + } } -} -impl xcm_builder::InspectMessageQueues for DummyXcmSender { - fn get_messages() -> Vec<(VersionedLocation, Vec>)> { - vec![] + impl xcm_builder::InspectMessageQueues for DummyXcmSender { + fn get_messages() -> Vec<(VersionedLocation, Vec>)> { + vec![] + } } } +#[cfg(feature = "runtime-benchmarks")] +pub use dummy_xcm_sender::*; pub trait ProvideAssetPrice { type AssetId; diff --git a/runtimes/polimec/Cargo.toml b/runtimes/polimec/Cargo.toml index 1afb2dc1c..18cb1aebf 100644 --- a/runtimes/polimec/Cargo.toml +++ b/runtimes/polimec/Cargo.toml @@ -190,6 +190,7 @@ std = [ "substrate-wasm-builder", "xcm-builder/std", "xcm-executor/std", + "xcm-fee-payment-runtime-api/std", "xcm/std", ] @@ -239,6 +240,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm-fee-payment-runtime-api/runtime-benchmarks", ] try-runtime = [ diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index 9e53d3a48..c36a1ca5b 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -53,7 +53,7 @@ use parachains_common::{ }; use parity_scale_codec::Encode; use polimec_common::{ - credentials::{Did, EnsureInvestor}, + credentials::{Did, EnsureInvestor, InvestorType}, ProvideAssetPrice, USD_UNIT, }; use polkadot_runtime_common::{BlockHashCount, CurrencyToVote, SlowAdjustingFeeUpdate}; @@ -1164,7 +1164,7 @@ impl Convert for ConvertMultilocationToAssetId { MultiLocation { parents: 1, interior: Here } => 10, MultiLocation { parents: 1, interior: X3(Parachain(1000), PalletInstance(50), GeneralIndex(1337)) } => 1337, MultiLocation { parents: 1, interior: X3(Parachain(1000), PalletInstance(50), GeneralIndex(1984)) } => 1984, - // asset 0 should be invalid. + // Since the asset with `AssetId` 0 does not exist and has no price, it is invalid. _ => 0, } } @@ -1540,6 +1540,12 @@ impl_runtime_apis! { fn get_next_vesting_schedule_merge_candidates(account: AccountId, hold_reason: RuntimeHoldReason, end_max_delta: Balance) -> Option<(u32, u32)> { Funding::get_next_vesting_schedule_merge_candidates(account, hold_reason, end_max_delta) } + fn calculate_otm_fee(funding_asset: AcceptedFundingAsset, funding_asset_amount: Balance) -> Option { + Funding::calculate_otm_fee(funding_asset, funding_asset_amount) + } + fn get_funding_asset_min_max_amounts(project_id: ProjectId, did: Did, funding_asset: AcceptedFundingAsset, investor_type: InvestorType) -> Option<(Balance, Balance)> { + Funding::get_funding_asset_min_max_amounts(project_id, did, funding_asset, investor_type) + } } #[cfg(feature = "try-runtime")] diff --git a/scripts/zombienet/polimec-paseo-local.toml b/scripts/zombienet/polimec-paseo-local.toml index 9fbe60c16..4ba85965c 100644 --- a/scripts/zombienet/polimec-paseo-local.toml +++ b/scripts/zombienet/polimec-paseo-local.toml @@ -1,4 +1,3 @@ - [settings] timeout = 1000 provider = "native"