diff --git a/Cargo.lock b/Cargo.lock index 04f18e77b..83ec0bd13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1025,7 +1025,7 @@ dependencies = [ [[package]] name = "bifrost-cli" -version = "0.14.0" +version = "0.15.0" dependencies = [ "bifrost-primitives", "bifrost-service", @@ -1443,6 +1443,7 @@ dependencies = [ "strum 0.26.3", "strum_macros 0.26.4", "substrate-wasm-builder", + "xcm-fee-payment-runtime-api", "zenlink-protocol", "zenlink-protocol-runtime-api", "zenlink-stable-amm", @@ -1491,6 +1492,7 @@ dependencies = [ "bifrost-fee-share", "bifrost-flexible-fee", "bifrost-flexible-fee-rpc-runtime-api", + "bifrost-parachain-staking", "bifrost-primitives", "bifrost-runtime-common", "bifrost-salp", @@ -1551,7 +1553,6 @@ dependencies = [ "pallet-balances", "pallet-base-fee", "pallet-bounties", - "pallet-collator-selection", "pallet-collective", "pallet-conviction-voting", "pallet-democracy", @@ -1624,6 +1625,7 @@ dependencies = [ "strum 0.26.3", "strum_macros 0.26.4", "substrate-wasm-builder", + "xcm-fee-payment-runtime-api", "zenlink-protocol", "zenlink-protocol-runtime-api", ] @@ -1731,7 +1733,6 @@ dependencies = [ "orml-tokens", "orml-traits", "orml-xcm-support", - "pallet-collator-selection", "pallet-collective", "pallet-democracy", "pallet-membership", @@ -3962,9 +3963,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.124" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "273dcfd3acd4e1e276af13ed2a43eea7001318823e7a726a6b3ed39b4acc0b82" +checksum = "28403c86fc49e3401fdf45499ba37fad6493d9329449d6449d7f0e10f4654d28" dependencies = [ "cc", "cxxbridge-flags", @@ -3974,9 +3975,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.124" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b2766fbd92be34e9ed143898fce6c572dc009de39506ed6903e5a05b68914e" +checksum = "78da94fef01786dc3e0c76eafcd187abcaa9972c78e05ff4041e24fdf059c285" dependencies = [ "cc", "codespan-reporting", @@ -3989,15 +3990,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.124" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "839fcd5e43464614ffaa989eaf1c139ef1f0c51672a1ed08023307fa1b909ccd" +checksum = "e2a6f5e1dfb4b34292ad4ea1facbfdaa1824705b231610087b00b17008641809" [[package]] name = "cxxbridge-macro" -version = "1.0.124" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2c1c1776b986979be68bb2285da855f8d8a35851a769fca8740df7c3d07877" +checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index d5a5891c1..a27ef19be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -179,7 +179,6 @@ pallet-authority-discovery = { git = "https://github.com/parityt pallet-authorship = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.13.0", default-features = false } pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.13.0", default-features = false } pallet-bounties = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.13.0", default-features = false } -pallet-collator-selection = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.13.0", default-features = false } pallet-collective = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.13.0", default-features = false } pallet-conviction-voting = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.13.0", default-features = false } pallet-democracy = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.13.0", default-features = false } @@ -253,6 +252,7 @@ substrate-fixed = { git = "https://github.com/encoint xcm = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.13.0", package = "staging-xcm", default-features = false } xcm-builder = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.13.0", package = "staging-xcm-builder", default-features = false } xcm-executor = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.13.0", package = "staging-xcm-executor", default-features = false } +xcm-fee-payment-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.13.0", default-features = false } # polkadot-sdk (client) cumulus-client-cli = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.13.0" } diff --git a/node/cli/Cargo.toml b/node/cli/Cargo.toml index 11b753212..8411d2a18 100644 --- a/node/cli/Cargo.toml +++ b/node/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bifrost-cli" -version = "0.14.0" +version = "0.15.0" authors = ["Liebi Technologies "] description = "Bifrost Parachain Node" build = "build.rs" diff --git a/pallets/asset-registry/src/benchmarking.rs b/pallets/asset-registry/src/benchmarking.rs index 5c5555516..87eed10a7 100644 --- a/pallets/asset-registry/src/benchmarking.rs +++ b/pallets/asset-registry/src/benchmarking.rs @@ -21,13 +21,18 @@ use super::*; use crate::Pallet as AssetRegistry; use bifrost_primitives::CurrencyId; -use frame_benchmarking::{benchmarks, v1::BenchmarkError}; +use frame_benchmarking::v2::*; use frame_support::{assert_ok, traits::UnfilteredDispatchable}; use sp_runtime::traits::UniqueSaturatedFrom; -benchmarks! { - register_token_metadata { - let origin = T::RegisterOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn register_token_metadata() -> Result<(), BenchmarkError> { + let origin = + T::RegisterOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let metadata = AssetMetadata { name: b"Bifrost Native Coin".to_vec(), symbol: b"BNC".to_vec(), @@ -35,16 +40,21 @@ benchmarks! { minimal_balance: BalanceOf::::unique_saturated_from(0u128), }; - let call = Call::::register_token_metadata { - metadata: Box::new(metadata.clone()) - }; - }: {call.dispatch_bypass_filter(origin)?} - verify { - assert_eq!(CurrencyMetadatas::::get(Token2(0)), Some(metadata.clone())) + let call = Call::::register_token_metadata { metadata: Box::new(metadata.clone()) }; + + #[block] + { + call.dispatch_bypass_filter(origin)?; + } + + assert_eq!(CurrencyMetadatas::::get(Token2(0)), Some(metadata.clone())); + Ok(()) } - register_vtoken_metadata { - let origin = T::RegisterOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + #[benchmark] + fn register_vtoken_metadata() -> Result<(), BenchmarkError> { + let origin = + T::RegisterOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let metadata = AssetMetadata { name: b"Bifrost Native Coin".to_vec(), symbol: b"BNC".to_vec(), @@ -62,27 +72,28 @@ benchmarks! { Box::new(metadata.clone()) )); - let call = Call::::register_vtoken_metadata { - token_id: 0 - }; - }: {call.dispatch_bypass_filter(origin)?} - verify { - assert_eq!( - CurrencyMetadatas::::get(CurrencyId::VToken2(0)), - Some(v_metadata.clone()) - ) + let call = Call::::register_vtoken_metadata { token_id: 0 }; + + #[block] + { + call.dispatch_bypass_filter(origin)?; + } + + assert_eq!(CurrencyMetadatas::::get(CurrencyId::VToken2(0)), Some(v_metadata.clone())); + Ok(()) } - register_location { - let origin = T::RegisterOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + #[benchmark] + fn register_location() -> Result<(), BenchmarkError> { + let origin = + T::RegisterOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let metadata = AssetMetadata { name: b"Bifrost Native Coin".to_vec(), symbol: b"BNC".to_vec(), decimals: 12, minimal_balance: BalanceOf::::unique_saturated_from(0u128), }; - let versioned_location = VersionedLocation::V4(Location::new(1, [Parachain(2001)])); - + let versioned_location = VersionedLocation::V4(Location::new(1, [Parachain(2001)])); let location: xcm::v4::Location = versioned_location.clone().try_into().unwrap(); assert_ok!(AssetRegistry::::register_token_metadata( @@ -92,24 +103,28 @@ benchmarks! { let call = Call::::register_location { currency_id: Token2(0), - location:Box::new(versioned_location.clone()), - weight:Weight::from_parts(2000_000_000, u64::MAX), + location: Box::new(versioned_location.clone()), + weight: Weight::from_parts(2000_000_000, u64::MAX), }; - }: {call.dispatch_bypass_filter(origin)?} - verify { - assert_eq!( - LocationToCurrencyIds::::get(location.clone()), - Some(Token2(0)) - ); + + #[block] + { + call.dispatch_bypass_filter(origin)?; + } + + assert_eq!(LocationToCurrencyIds::::get(location.clone()), Some(Token2(0))); + assert_eq!(CurrencyIdToLocations::::get(Token2(0)), Some(location)); assert_eq!( - CurrencyIdToLocations::::get(Token2(0)), - Some(location) + CurrencyIdToWeights::::get(Token2(0)), + Some(Weight::from_parts(2000_000_000, u64::MAX)) ); - assert_eq!(CurrencyIdToWeights::::get(Token2(0)), Some(Weight::from_parts(2000_000_000, u64::MAX))); + Ok(()) } - force_set_location { - let origin = T::RegisterOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + #[benchmark] + fn force_set_location() -> Result<(), BenchmarkError> { + let origin = + T::RegisterOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let metadata = AssetMetadata { name: b"Bifrost Native Coin".to_vec(), symbol: b"BNC".to_vec(), @@ -118,8 +133,6 @@ benchmarks! { }; let versioned_location = VersionedLocation::V4(Location::new(1, [Parachain(2001)])); - let location: xcm::v3::Location = versioned_location.clone().try_into().unwrap(); - assert_ok!(AssetRegistry::::register_token_metadata( origin.clone(), Box::new(metadata.clone()) @@ -127,13 +140,22 @@ benchmarks! { let call = Call::::force_set_location { currency_id: Token2(0), - location:Box::new(versioned_location.clone()), - weight:Weight::from_parts(2000_000_000, u64::MAX), + location: Box::new(versioned_location.clone()), + weight: Weight::from_parts(2000_000_000, u64::MAX), }; - }: {call.dispatch_bypass_filter(origin)?} - update_currency_metadata { - let origin = T::RegisterOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + #[block] + { + call.dispatch_bypass_filter(origin)?; + } + + Ok(()) + } + + #[benchmark] + fn update_currency_metadata() -> Result<(), BenchmarkError> { + let origin = + T::RegisterOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; assert_ok!(AssetRegistry::::register_token_metadata( origin.clone(), @@ -149,12 +171,15 @@ benchmarks! { currency_id: CurrencyId::Token2(0), asset_name: Some(b"Token Name".to_vec()), asset_symbol: Some(b"TN".to_vec()), - asset_decimals : Some(12), - asset_minimal_balance : Some(BalanceOf::::unique_saturated_from(1000u128)), + asset_decimals: Some(12), + asset_minimal_balance: Some(BalanceOf::::unique_saturated_from(1000u128)), }; - }: {call.dispatch_bypass_filter(origin)?} - verify { + #[block] + { + call.dispatch_bypass_filter(origin)?; + } + assert_eq!( CurrencyMetadatas::::get(CurrencyId::Token2(0)), Some(AssetMetadata { @@ -164,12 +189,16 @@ benchmarks! { minimal_balance: BalanceOf::::unique_saturated_from(1000u128), }) ); + Ok(()) } - impl_benchmark_test_suite!( - AssetRegistry, - crate::mock::ExtBuilder::default().build(), - crate::mock::Runtime -); - + // This line generates test cases for benchmarking, and could be run by: + // `cargo test -p pallet-example-basic --all-features`, you will see one line per case: + // `test benchmarking::bench_sort_vector ... ok` + // `test benchmarking::bench_accumulate_dummy ... ok` + // `test benchmarking::bench_set_dummy_benchmark ... ok` in the result. + // + // The line generates three steps per benchmark, with repeat=1 and the three steps are + // [low, mid, high] of the range. + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext_benchmark(), crate::mock::Runtime); } diff --git a/pallets/asset-registry/src/lib.rs b/pallets/asset-registry/src/lib.rs index edf1ca0e4..63e760815 100644 --- a/pallets/asset-registry/src/lib.rs +++ b/pallets/asset-registry/src/lib.rs @@ -33,23 +33,19 @@ use frame_support::{ ensure, pallet_prelude::*, traits::{Currency, EnsureOrigin}, - weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, + weights::Weight, }; use frame_system::pallet_prelude::*; use scale_info::{prelude::string::String, TypeInfo}; use sp_runtime::{ traits::{One, UniqueSaturatedFrom}, - ArithmeticError, FixedPointNumber, FixedU128, RuntimeDebug, + ArithmeticError, RuntimeDebug, }; use sp_std::{boxed::Box, vec::Vec}; use xcm::{ - opaque::lts::XcmContext, - v3::MultiLocation, - v4::{prelude::*, Asset, Location}, + v4::{prelude::*, Location}, VersionedLocation, }; -use xcm_builder::TakeRevenue; -use xcm_executor::{traits::WeightTrader, AssetsInHolding}; pub mod migrations; mod mock; @@ -423,13 +419,15 @@ impl Pallet { Ok(()) } + + pub fn asset_ids() -> Vec { + LocationToCurrencyIds::::iter_keys().map(|key| AssetId(key)).collect() + } } -pub struct AssetIdMaps(sp_std::marker::PhantomData); +pub struct AssetIdMaps(PhantomData); -impl CurrencyIdMapping>> - for AssetIdMaps -{ +impl CurrencyIdMapping>> for AssetIdMaps { fn get_asset_metadata(asset_ids: AssetIds) -> Option>> { AssetMetadatas::::get(asset_ids) } @@ -442,13 +440,12 @@ impl CurrencyIdMapping::iter_keys().collect() } - fn get_location(currency_id: CurrencyId) -> Option { - CurrencyIdToLocations::::get(currency_id).map(|location| location.try_into().ok())? + fn get_location(currency_id: &CurrencyId) -> Option { + CurrencyIdToLocations::::get(currency_id) } - fn get_currency_id(multi_location: Location) -> Option { - let v4_location = Location::try_from(multi_location).ok()?; - LocationToCurrencyIds::::get(v4_location) + fn get_currency_id(location: &Location) -> Option { + LocationToCurrencyIds::::get(location) } } @@ -586,129 +583,3 @@ impl CurrencyIdRegister for AssetIdMaps { ) } } - -/// Simple fee calculator that requires payment in a single fungible at a fixed rate. -/// -/// The constant `FixedRate` type parameter should be the concrete fungible ID and the amount of it -/// required for one second of weight. -pub struct FixedRateOfAsset, R: TakeRevenue> { - weight: u64, - amount: u128, - ed_ratio: FixedU128, - location: Option, - _marker: PhantomData<(T, FixedRate, R)>, -} - -impl, R: TakeRevenue> WeightTrader - for FixedRateOfAsset -where - BalanceOf: Into, -{ - fn new() -> Self { - Self { - weight: 0, - amount: 0, - ed_ratio: Default::default(), - location: None, - _marker: PhantomData, - } - } - - fn buy_weight( - &mut self, - weight: Weight, - payment: AssetsInHolding, - _context: &XcmContext, - ) -> Result { - log::trace!(target: "asset-registry::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment); - - // only support first fungible assets now. - let asset_id = payment - .fungible - .iter() - .next() - .map_or(Err(XcmError::TooExpensive), |v| Ok(v.0))?; - - let AssetId(ref location) = asset_id.clone(); - log::debug!(target: "asset-registry::weight", "buy_weight location: {:?}", location); - - let v4_location = - Location::try_from(location.clone()).map_err(|_| XcmError::InvalidLocation)?; - - if let Some(currency_id) = LocationToCurrencyIds::::get(v4_location) { - if let Some(currency_metadatas) = CurrencyMetadatas::::get(currency_id) { - // The integration tests can ensure the ed is non-zero. - let ed_ratio = FixedU128::saturating_from_rational( - currency_metadatas.minimal_balance.into(), - T::Currency::minimum_balance().into(), - ); - // The WEIGHT_REF_TIME_PER_SECOND is non-zero. - let weight_ratio = FixedU128::saturating_from_rational( - weight.ref_time(), - WEIGHT_REF_TIME_PER_SECOND, - ); - let amount = - ed_ratio.saturating_mul_int(weight_ratio.saturating_mul_int(FixedRate::get())); - - let required = Asset { id: asset_id.clone(), fun: Fungible(amount) }; - - log::trace!( - target: "asset-registry::weight", "buy_weight payment: {:?}, required: {:?}, fixed_rate: {:?}, ed_ratio: {:?}, weight_ratio: {:?}", - payment, required, FixedRate::get(), ed_ratio, weight_ratio - ); - let unused = - payment.clone().checked_sub(required).map_err(|_| XcmError::TooExpensive)?; - self.weight = self.weight.saturating_add(weight.ref_time()); - self.amount = self.amount.saturating_add(amount); - self.ed_ratio = ed_ratio; - self.location = Some(location.clone()); - return Ok(unused); - } - }; - - log::trace!(target: "asset-registry::weight", "no concrete fungible asset"); - Err(XcmError::TooExpensive) - } - - fn refund_weight(&mut self, weight: Weight, _context: &XcmContext) -> Option { - log::trace!( - target: "asset-registry::weight", "refund_weight weight: {:?}, weight: {:?}, amount: {:?}, ed_ratio: {:?}, location: {:?}", - weight, self.weight, self.amount, self.ed_ratio, self.location - ); - let weight = weight.min(Weight::from_parts(self.weight, 0)); - let weight_ratio = - FixedU128::saturating_from_rational(weight.ref_time(), WEIGHT_REF_TIME_PER_SECOND); - let amount = self - .ed_ratio - .saturating_mul_int(weight_ratio.saturating_mul_int(FixedRate::get())); - - self.weight = self.weight.saturating_sub(weight.ref_time()); - self.amount = self.amount.saturating_sub(amount); - - log::trace!(target: "asset-registry::weight", "refund_weight amount: {:?}", amount); - if amount > 0 && self.location.is_some() { - Some(Asset { - fun: Fungible(amount), - id: AssetId( - self.location.clone().expect("checked is non-empty; qed").try_into().unwrap(), - ), - }) - } else { - None - } - } -} - -impl, R: TakeRevenue> Drop for FixedRateOfAsset { - fn drop(&mut self) { - log::trace!(target: "asset-registry::weight", "take revenue, weight: {:?}, amount: {:?}, location: {:?}", self.weight, self.amount, self.location); - if self.amount > 0 && self.location.is_some() { - R::take_revenue(Asset { - fun: Fungible(self.amount), - id: AssetId( - self.location.clone().expect("checked is non-empty; qed").try_into().unwrap(), - ), - }); - } - } -} diff --git a/pallets/asset-registry/src/mock.rs b/pallets/asset-registry/src/mock.rs index a948e290c..67344b21b 100644 --- a/pallets/asset-registry/src/mock.rs +++ b/pallets/asset-registry/src/mock.rs @@ -107,3 +107,8 @@ impl ExtBuilder { ext } } + +#[cfg(feature = "runtime-benchmarks")] +pub fn new_test_ext_benchmark() -> sp_io::TestExternalities { + ExtBuilder::default().build() +} diff --git a/pallets/flexible-fee/src/lib.rs b/pallets/flexible-fee/src/lib.rs index 48a065209..074b0e18d 100644 --- a/pallets/flexible-fee/src/lib.rs +++ b/pallets/flexible-fee/src/lib.rs @@ -159,7 +159,7 @@ pub mod pallet { } /// The current storage version, we set to 2 our new version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(3); /// Universal fee currency order list for all users #[pallet::storage] @@ -188,12 +188,19 @@ pub mod pallet { #[pallet::error] pub enum Error { + /// The account does not have enough balance to perform the operation. NotEnoughBalance, + /// An error occurred during currency conversion. ConversionError, + /// No weight or fee information is available for the requested operation. WeightAndFeeNotExist, + /// The message cannot be weighed, possibly due to insufficient information. UnweighableMessage, + /// The XCM execution has failed. XcmExecutionFailed, + /// The specified currency is not supported by the system. CurrencyNotSupport, + /// The maximum number of currencies that can be handled has been reached. MaxCurrenciesReached, } @@ -212,6 +219,9 @@ pub mod pallet { let who = ensure_signed(origin)?; if let Some(fee_currency) = ¤cy_id { + // VBNC is not supported. + ensure!(fee_currency != &VBNC, Error::::CurrencyNotSupport); + UserDefaultFeeCurrency::::insert(&who, fee_currency); } else { UserDefaultFeeCurrency::::remove(&who); diff --git a/pallets/flexible-fee/src/migrations/mod.rs b/pallets/flexible-fee/src/migrations/mod.rs index 504be9301..0ec4683cc 100644 --- a/pallets/flexible-fee/src/migrations/mod.rs +++ b/pallets/flexible-fee/src/migrations/mod.rs @@ -18,3 +18,6 @@ /// Version 2. pub mod v2; + +/// Version 3. +pub mod v3; diff --git a/pallets/flexible-fee/src/migrations/v3.rs b/pallets/flexible-fee/src/migrations/v3.rs new file mode 100644 index 000000000..f381014f2 --- /dev/null +++ b/pallets/flexible-fee/src/migrations/v3.rs @@ -0,0 +1,118 @@ +// This file is part of Bifrost. + +// Copyright (C) Liebi Technologies PTE. LTD. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::*; +use bifrost_primitives::BNC; +use frame_support::traits::OnRuntimeUpgrade; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + +const LOG_TARGET: &str = "flexible-fee::migration"; + +pub struct MigrateToV3(sp_std::marker::PhantomData); +impl OnRuntimeUpgrade for MigrateToV3 { + fn on_runtime_upgrade() -> frame_support::weights::Weight { + // Check the storage version + let onchain_version = Pallet::::on_chain_storage_version(); + if onchain_version < 3 { + log::info!(target: LOG_TARGET, "Start to migrate flexible-fee storage..."); + + let mut count: u64 = 0; + + // Traversal UserDefaultFeeCurrency storage + UserDefaultFeeCurrency::::iter().for_each(|(account_id, currency_id)| { + // If currency_id is vbnc, change it to bnc + if currency_id == VBNC { + count += 1; + UserDefaultFeeCurrency::::insert(account_id, BNC); + } + }); + + // Update the storage version + StorageVersion::new(3).put::>(); + + // Return the consumed weight + Weight::from(T::DbWeight::get().reads_writes(count + 1, count + 1)) + } else { + // We don't do anything here. + Weight::zero() + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + let total_count = UserDefaultFeeCurrency::::iter().count(); + + let mut vbnc_count: u64 = 0; + UserDefaultFeeCurrency::::iter().for_each(|(_, currency_id)| { + if currency_id == VBNC { + vbnc_count += 1; + } + }); + + // print out the pre-migrate storage count + log::info!( + target: LOG_TARGET, + "UserDefaultFeeCurrency pre-migrate storage total count: {:?}", + total_count + ); + log::info!( + target: LOG_TARGET, + "UserDefaultFeeCurrency pre-migrate storage vbnc count: {:?}", + vbnc_count + ); + Ok((total_count as u64).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(cnt: Vec) -> Result<(), TryRuntimeError> { + let old_total_count: u64 = Decode::decode(&mut cnt.as_slice()) + .expect("the state parameter should be something that was generated by pre_upgrade"); + + let new_total_count = UserDefaultFeeCurrency::::iter().count(); + + let mut new_vbnc_count: u64 = 0; + UserDefaultFeeCurrency::::iter().for_each(|(_, currency_id)| { + if currency_id == VBNC { + new_vbnc_count += 1; + } + }); + + // print out the post-migrate storage count + log::info!( + target: LOG_TARGET, + "UserDefaultFeeCurrency post-migrate storage total count: {:?}", + new_total_count + ); + log::info!( + target: LOG_TARGET, + "UserDefaultFeeCurrency post-migrate storage vbnc count: {:?}", + new_vbnc_count + ); + + ensure!( + new_total_count as u64 == old_total_count, + "Post-migration storage total count does not match pre-migration total count" + ); + ensure!( + new_vbnc_count == 0, + "Post-migration storage vbnc count does not match pre-migration vbnc count" + ); + Ok(()) + } +} diff --git a/pallets/flexible-fee/src/tests.rs b/pallets/flexible-fee/src/tests.rs index ce373e01e..e7ffb47f1 100644 --- a/pallets/flexible-fee/src/tests.rs +++ b/pallets/flexible-fee/src/tests.rs @@ -21,10 +21,11 @@ #![cfg(test)] use crate::{ impls::on_charge_transaction::PaymentInfo, mock::*, BlockNumberFor, BoundedVec, Config, - DispatchError::BadOrigin, UserDefaultFeeCurrency, + DispatchError::BadOrigin, Error, UserDefaultFeeCurrency, }; use bifrost_primitives::{ - AccountFeeCurrency, BalanceCmp, CurrencyId, TryConvertFrom, BNC, DOT, KSM, MANTA, VDOT, WETH, + AccountFeeCurrency, BalanceCmp, CurrencyId, TryConvertFrom, BNC, DOT, KSM, MANTA, VBNC, VDOT, + WETH, }; use frame_support::{ assert_noop, assert_ok, @@ -151,6 +152,19 @@ fn set_user_default_fee_currency_should_work() { }); } +#[test] +fn set_user_default_fee_currency_should_fail_with_error_currency() { + new_test_ext().execute_with(|| { + let origin_signed_alice = RuntimeOrigin::signed(ALICE); + assert_noop!( + FlexibleFee::set_user_default_fee_currency(origin_signed_alice.clone(), Some(VBNC)), + Error::::CurrencyNotSupport + ); + + assert_eq!(UserDefaultFeeCurrency::::get(ALICE).is_none(), true); + }); +} + #[test] fn set_default_fee_currency_list_should_work() { new_test_ext().execute_with(|| { diff --git a/pallets/prices/src/lib.rs b/pallets/prices/src/lib.rs index a65516974..5ca2071f5 100644 --- a/pallets/prices/src/lib.rs +++ b/pallets/prices/src/lib.rs @@ -35,7 +35,6 @@ pub use pallet::*; use pallet_traits::*; use sp_runtime::{traits::CheckedDiv, FixedU128}; use sp_std::vec::Vec; -use xcm::v3::MultiLocation; #[cfg(test)] mod mock; @@ -79,11 +78,7 @@ pub mod pallet { type RelayCurrency: Get; /// Convert Location to `T::CurrencyId`. - type CurrencyIdConvert: CurrencyIdMapping< - CurrencyId, - MultiLocation, - AssetMetadata>, - >; + type CurrencyIdConvert: CurrencyIdMapping>>; /// Weight information type WeightInfo: WeightInfo; diff --git a/pallets/slp/src/lib.rs b/pallets/slp/src/lib.rs index 4ca0f422a..f6c62046a 100644 --- a/pallets/slp/src/lib.rs +++ b/pallets/slp/src/lib.rs @@ -167,11 +167,7 @@ pub mod pallet { >; // asset registry to get asset metadata - type AssetIdMaps: CurrencyIdMapping< - CurrencyId, - MultiLocation, - AssetMetadata>, - >; + type AssetIdMaps: CurrencyIdMapping>>; #[pallet::constant] type TreasuryAccount: Get; diff --git a/pallets/slpx/src/lib.rs b/pallets/slpx/src/lib.rs index 1e3fdb129..3d3f0d399 100644 --- a/pallets/slpx/src/lib.rs +++ b/pallets/slpx/src/lib.rs @@ -110,11 +110,7 @@ pub mod pallet { /// Send Xcm type XcmSender: SendXcm; /// Convert Location to `T::CurrencyId`. - type CurrencyIdConvert: CurrencyIdMapping< - CurrencyId, - xcm::v3::MultiLocation, - AssetMetadata>, - >; + type CurrencyIdConvert: CurrencyIdMapping>>; /// TreasuryAccount #[pallet::constant] type TreasuryAccount: Get>; diff --git a/pallets/slpx/src/mock.rs b/pallets/slpx/src/mock.rs index 12c3aa264..605c72a8c 100644 --- a/pallets/slpx/src/mock.rs +++ b/pallets/slpx/src/mock.rs @@ -248,13 +248,13 @@ parameter_types! { pub struct CurrencyIdConvert(sp_std::marker::PhantomData); impl> Convert> for CurrencyIdConvert { fn convert(id: CurrencyId) -> Option { - AssetIdMaps::::get_location(id) + AssetIdMaps::::get_location(&id) } } impl> Convert> for CurrencyIdConvert { fn convert(location: Location) -> Option { - AssetIdMaps::::get_currency_id(location) + AssetIdMaps::::get_currency_id(&location) } } diff --git a/pallets/xcm-interface/src/lib.rs b/pallets/xcm-interface/src/lib.rs index 1a9b609ca..1dbc6bba5 100644 --- a/pallets/xcm-interface/src/lib.rs +++ b/pallets/xcm-interface/src/lib.rs @@ -73,11 +73,7 @@ pub mod pallet { type AccountIdToLocation: Convert; /// Convert Location to `T::CurrencyId`. - type CurrencyIdConvert: CurrencyIdMapping< - CurrencyId, - xcm::v3::MultiLocation, - AssetMetadata>, - >; + type CurrencyIdConvert: CurrencyIdMapping>>; #[pallet::constant] type ParachainId: Get; @@ -170,8 +166,8 @@ pub mod pallet { to: H160, ) -> DispatchResult { let who = ensure_signed(origin.clone())?; - let asset_location = - T::CurrencyIdConvert::get_location(currency_id).ok_or(Error::::FailToConvert)?; + let asset_location = T::CurrencyIdConvert::get_location(¤cy_id) + .ok_or(Error::::FailToConvert)?; let asset: Asset = Asset { id: AssetId(asset_location), diff --git a/primitives/src/currency.rs b/primitives/src/currency.rs index e6d1e67f4..6f8f76b42 100644 --- a/primitives/src/currency.rs +++ b/primitives/src/currency.rs @@ -90,6 +90,14 @@ pub const LDOT: CurrencyId = CurrencyId::Lend(0); pub const LKSM: CurrencyId = CurrencyId::Lend(1); pub const LUSDT: CurrencyId = CurrencyId::Lend(2); pub const LVDOT: CurrencyId = CurrencyId::Lend(3); +pub const BLP_BNC_VBNC: CurrencyId = CurrencyId::BLP(2); +pub const LP_BNC_VBNC: CurrencyId = CurrencyId::LPToken(TokenSymbol::ASG, 0, TokenSymbol::BNC, 1); +pub const KUSAMA_VBNC_ASSET_INDEX: AssetId = + AssetId { chain_id: 2001, asset_type: 2, asset_index: 257 }; +pub const KUSAMA_VBNC_LP_ASSET_INDEX: AssetId = + AssetId { chain_id: 2001, asset_type: 2, asset_index: 1103806596608 }; +pub const KUSAMA_BNC_ASSET_INDEX: AssetId = + AssetId { chain_id: 2001, asset_type: 0, asset_index: 0 }; macro_rules! create_currency_id { ($(#[$meta:meta])* diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index e7d7e5d69..b29a588bf 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -34,6 +34,7 @@ use sp_runtime::{ BoundedVec, DispatchError, DispatchResult, TypeId, }; use sp_std::{cmp::Ordering, fmt::Debug, vec::Vec}; +use xcm::prelude::Location; pub trait TokenInfo { fn name(&self) -> Option<&str>; @@ -143,15 +144,15 @@ pub trait SlpxOperator { } /// A mapping between CurrencyId and AssetMetadata. -pub trait CurrencyIdMapping { +pub trait CurrencyIdMapping { /// Returns the AssetMetadata associated with a given `AssetIds`. fn get_asset_metadata(asset_ids: AssetIds) -> Option; /// Returns the AssetMetadata associated with a given `CurrencyId`. fn get_currency_metadata(currency_id: CurrencyId) -> Option; /// Returns the Location associated with a given CurrencyId. - fn get_location(currency_id: CurrencyId) -> Option; + fn get_location(currency_id: &CurrencyId) -> Option; /// Returns the CurrencyId associated with a given Location. - fn get_currency_id(multi_location: xcm::v4::Location) -> Option; + fn get_currency_id(location: &Location) -> Option; /// Returns all currencies in currencyMetadata. fn get_all_currency() -> Vec; } diff --git a/primitives/src/xcm.rs b/primitives/src/xcm.rs index efcb265a8..4bdfd8700 100644 --- a/primitives/src/xcm.rs +++ b/primitives/src/xcm.rs @@ -25,7 +25,7 @@ use sp_runtime::traits::Convert; use sp_std::marker::PhantomData; use xcm::{ latest::Asset, - prelude::{AccountId32, Ethereum, Fungible, GlobalConsensus, Parachain}, + prelude::{AccountId32, Ethereum, Fungible, GeneralKey, GlobalConsensus, Parachain}, v4::{AssetId, InteriorLocation, Location, NetworkId, Parent}, }; @@ -58,6 +58,13 @@ parameter_types! { pub SelfLocation: Location = Location::here(); pub AssetHubLocation: Location = Location::new(1, Parachain(AssetHubChainId::get())); pub EthereumLocation: Location = Location::new(2, [GlobalConsensus(Ethereum { chain_id: EthereumChainId::get() })]); + pub LocalBncLocation: Location = Location::new(0, [GeneralKey { + length: 2, + data: [ + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 + ], + }]); pub const KusamaNetwork: NetworkId = NetworkId::Kusama; pub const PolkadotNetwork: NetworkId = NetworkId::Polkadot; diff --git a/runtime/bifrost-kusama/Cargo.toml b/runtime/bifrost-kusama/Cargo.toml index 20bca7d5d..020f2abd5 100644 --- a/runtime/bifrost-kusama/Cargo.toml +++ b/runtime/bifrost-kusama/Cargo.toml @@ -92,6 +92,7 @@ polkadot-runtime-common = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } +xcm-fee-payment-runtime-api = { workspace = true } # orml orml-tokens = { workspace = true } @@ -223,6 +224,7 @@ std = [ "xcm/std", "xcm-builder/std", "xcm-executor/std", + "xcm-fee-payment-runtime-api/std", "orml-tokens/std", "orml-traits/std", diff --git a/runtime/bifrost-kusama/src/lib.rs b/runtime/bifrost-kusama/src/lib.rs index 9aa6c9f05..8db4a7d52 100644 --- a/runtime/bifrost-kusama/src/lib.rs +++ b/runtime/bifrost-kusama/src/lib.rs @@ -26,6 +26,9 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +use bifrost_primitives::{ + BLP_BNC_VBNC, KUSAMA_VBNC_ASSET_INDEX, KUSAMA_VBNC_LP_ASSET_INDEX, LP_BNC_VBNC, VBNC, +}; use bifrost_slp::{DerivativeAccountProvider, QueryResponseManager}; use core::convert::TryInto; // A few exports that help ease life for downstream crates. @@ -34,9 +37,9 @@ use bifrost_primitives::{ BifrostCrowdloanId, BifrostVsbondAccount, BuybackPalletId, CommissionPalletId, FarmingBoostPalletId, FarmingGaugeRewardIssuerPalletId, FarmingKeeperPalletId, FarmingRewardIssuerPalletId, FeeSharePalletId, FlexibleFeePalletId, IncentivePoolAccount, - LendMarketPalletId, MerkleDirtributorPalletId, OraclePalletId, ParachainStakingPalletId, - SlpEntrancePalletId, SlpExitPalletId, SystemMakerPalletId, SystemStakingPalletId, - TreasuryPalletId, VBNCConvertPalletId, + LendMarketPalletId, LocalBncLocation, MerkleDirtributorPalletId, OraclePalletId, + ParachainStakingPalletId, SlpEntrancePalletId, SlpExitPalletId, SystemMakerPalletId, + SystemStakingPalletId, TreasuryPalletId, VBNCConvertPalletId, }; pub use frame_support::{ construct_runtime, match_types, parameter_types, @@ -106,6 +109,7 @@ use frame_support::{ Currency, EitherOf, EitherOfDiverse, Get, Imbalance, InsideBoth, LinearStoragePrice, LockIdentifier, OnUnbalanced, }, + weights::WeightToFee as _, }; use frame_system::{EnsureRoot, EnsureRootWithSuccess, EnsureSigned}; use hex_literal::hex; @@ -130,16 +134,23 @@ use governance::{ // xcm config pub mod xcm_config; -use bifrost_primitives::MoonriverChainId; +use bifrost_primitives::{MoonriverChainId, OraclePriceProvider}; use bifrost_runtime_common::currency_converter::CurrencyIdConvert; use pallet_xcm::{EnsureResponse, QueryStatus}; use sp_runtime::traits::{IdentityLookup, Verify}; -use xcm::{v3::MultiLocation, v4::prelude::*}; +use xcm::{ + v3::MultiLocation, v4::prelude::*, IntoVersion, VersionedAssetId, VersionedAssets, + VersionedLocation, VersionedXcm, +}; pub use xcm_config::{ AccountId32Aliases, BifrostTreasuryAccount, ExistentialDeposits, MultiCurrency, Sibling, SiblingParachainConvertsVia, XcmConfig, XcmRouter, }; use xcm_executor::{traits::QueryHandler, XcmExecutor}; +use xcm_fee_payment_runtime_api::{ + dry_run::{CallDryRunEffects, Error as XcmDryRunApiError, XcmDryRunEffects}, + fees::Error as XcmPaymentApiError, +}; impl_opaque_keys! { pub struct SessionKeys { @@ -153,7 +164,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("bifrost"), impl_name: create_runtime_str!("bifrost"), authoring_version: 1, - spec_version: 14000, + spec_version: 15000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -220,11 +231,142 @@ parameter_types! { pub const StableAssetPalletId: PalletId = PalletId(*b"bf/stabl"); } +pub struct CallFilter; +impl Contains for CallFilter { + fn contains(call: &RuntimeCall) -> bool { + let is_core_call = matches!( + call, + RuntimeCall::System(_) | RuntimeCall::Timestamp(_) | RuntimeCall::ParachainSystem(_) + ); + if is_core_call { + // always allow core call + return true; + } + + // disable transfer + let is_transfer = matches!(call, RuntimeCall::Tokens(_) | RuntimeCall::Balances(_)); + if is_transfer { + let is_disabled = match call { + RuntimeCall::Tokens(orml_tokens::Call::transfer { + dest: _, + currency_id, + amount: _, + }) => + VBNC == *currency_id || + BLP_BNC_VBNC == *currency_id || + LP_BNC_VBNC == *currency_id, + RuntimeCall::Tokens(orml_tokens::Call::transfer_all { + dest: _, + currency_id, + keep_alive: _, + }) => + VBNC == *currency_id || + BLP_BNC_VBNC == *currency_id || + LP_BNC_VBNC == *currency_id, + RuntimeCall::Tokens(orml_tokens::Call::transfer_keep_alive { + dest: _, + currency_id, + amount: _, + }) => + VBNC == *currency_id || + BLP_BNC_VBNC == *currency_id || + LP_BNC_VBNC == *currency_id, + + RuntimeCall::StablePool(bifrost_stable_pool::Call::add_liquidity { + pool_id, + amounts: _, + min_mint_amount: _, + }) => *pool_id == 2, + RuntimeCall::StablePool(bifrost_stable_pool::Call::swap { + pool_id, + i: _, + j: _, + dx: _, + min_dy: _, + }) => *pool_id == 2, + RuntimeCall::StablePool(bifrost_stable_pool::Call::redeem_proportion { + pool_id, + amount: _, + min_redeem_amounts: _, + }) => *pool_id == 2, + RuntimeCall::StablePool(bifrost_stable_pool::Call::redeem_single { + pool_id, + amount: _, + i: _, + min_redeem_amount: _, + asset_length: _, + }) => *pool_id == 2, + RuntimeCall::StablePool(bifrost_stable_pool::Call::redeem_multi { + pool_id, + amounts: _, + max_redeem_amount: _, + }) => *pool_id == 2, + + RuntimeCall::ZenlinkProtocol(zenlink_protocol::Call::transfer { + asset_id, + recipient: _, + amount: _, + }) => *asset_id == KUSAMA_VBNC_ASSET_INDEX || *asset_id == KUSAMA_VBNC_LP_ASSET_INDEX, + RuntimeCall::ZenlinkProtocol(zenlink_protocol::Call::add_liquidity { + asset_0, + asset_1, + amount_0_desired: _, + amount_1_desired: _, + amount_0_min: _, + amount_1_min: _, + deadline: _, + }) => *asset_0 == KUSAMA_VBNC_ASSET_INDEX || *asset_1 == KUSAMA_VBNC_ASSET_INDEX, + RuntimeCall::ZenlinkProtocol(zenlink_protocol::Call::remove_liquidity { + asset_0, + asset_1, + liquidity: _, + amount_0_min: _, + amount_1_min: _, + recipient: _, + deadline: _, + }) => *asset_0 == KUSAMA_VBNC_ASSET_INDEX || *asset_1 == KUSAMA_VBNC_ASSET_INDEX, + RuntimeCall::ZenlinkProtocol( + zenlink_protocol::Call::swap_exact_assets_for_assets { + amount_in: _, + amount_out_min: _, + path, + recipient: _, + deadline: _, + }, + ) => path.contains(&KUSAMA_VBNC_ASSET_INDEX), + RuntimeCall::ZenlinkProtocol( + zenlink_protocol::Call::swap_assets_for_exact_assets { + amount_out: _, + amount_in_max: _, + path, + recipient: _, + deadline: _, + }, + ) => path.contains(&KUSAMA_VBNC_ASSET_INDEX), + RuntimeCall::ZenlinkProtocol(zenlink_protocol::Call::bootstrap_claim { + recipient: _, + asset_0, + asset_1, + deadline: _, + }) => *asset_0 == KUSAMA_VBNC_ASSET_INDEX || *asset_1 == KUSAMA_VBNC_ASSET_INDEX, + + _ => false, + }; + + if is_disabled { + // no switched off call + return false; + } + } + true + } +} + impl frame_system::Config for Runtime { type AccountData = pallet_balances::AccountData; /// The identifier used to distinguish between accounts. type AccountId = AccountId; - type BaseCallFilter = InsideBoth; + type BaseCallFilter = InsideBoth; /// Maximum number of block number to block hash mappings to keep (oldest pruned first). type BlockHashCount = BlockHashCount; type BlockLength = RuntimeBlockLength; @@ -1900,6 +2042,7 @@ pub mod migrations { >, // permanent migration, do not remove pallet_xcm::migration::MigrateToLatestXcmVersion, + bifrost_flexible_fee::migrations::v3::MigrateToV3, ); } @@ -2062,6 +2205,49 @@ impl_runtime_apis! { } } + impl xcm_fee_payment_runtime_api::fees::XcmPaymentApi for Runtime { + fn query_acceptable_payment_assets(xcm_version: xcm::Version) -> Result, XcmPaymentApiError> { + let acceptable_assets = AssetRegistry::asset_ids(); + PolkadotXcm::query_acceptable_payment_assets(xcm_version, acceptable_assets) + } + + fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result { + let asset = asset + .into_version(4) + .map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?; + let bnc_asset = VersionedAssetId::V4(LocalBncLocation::get().into()); + + if asset == bnc_asset { + // for native token + Ok(WeightToFee::weight_to_fee(&weight)) + } else { + let native_fee = WeightToFee::weight_to_fee(&weight); + let asset_location = &asset.try_as::().map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?.0; + let asset_currency = AssetIdMaps::::get_currency_id(&asset_location).ok_or(XcmPaymentApiError::AssetNotFound)?; + let asset_fee = Prices::get_oracle_amount_by_currency_and_amount_in(&bifrost_primitives::BNC, native_fee, &asset_currency).ok_or(XcmPaymentApiError::AssetNotFound)?.0; + Ok(asset_fee) + } + } + + fn query_xcm_weight(message: VersionedXcm<()>) -> Result { + PolkadotXcm::query_xcm_weight(message) + } + + fn query_delivery_fees(destination: VersionedLocation, message: VersionedXcm<()>) -> Result { + PolkadotXcm::query_delivery_fees(destination, message) + } + } + + impl xcm_fee_payment_runtime_api::dry_run::DryRunApi for Runtime { + fn dry_run_call(origin: OriginCaller, call: RuntimeCall) -> Result, XcmDryRunApiError> { + PolkadotXcm::dry_run_call::(origin, call) + } + + fn dry_run_xcm(origin_location: VersionedLocation, xcm: VersionedXcm) -> Result, XcmDryRunApiError> { + PolkadotXcm::dry_run_xcm::(origin_location, xcm) + } + } + impl bifrost_flexible_fee_rpc_runtime_api::FlexibleFeeRuntimeApi for Runtime { fn get_fee_token_and_amount(who: AccountId, fee: Balance,utx: ::Extrinsic) -> (CurrencyId, Balance) { let call = utx.function; diff --git a/runtime/bifrost-kusama/src/xcm_config.rs b/runtime/bifrost-kusama/src/xcm_config.rs index 90b13211a..e4100406b 100644 --- a/runtime/bifrost-kusama/src/xcm_config.rs +++ b/runtime/bifrost-kusama/src/xcm_config.rs @@ -17,17 +17,15 @@ // along with this program. If not, see . use super::*; -use bifrost_asset_registry::{AssetIdMaps, FixedRateOfAsset}; +use bifrost_asset_registry::AssetIdMaps; use bifrost_primitives::{ - AccountId, AccountIdToLocation, AssetHubChainId, AssetHubLocation, AssetPrefixFrom, CurrencyId, - CurrencyIdMapping, EthereumLocation, KaruraChainId, KusamaNetwork, KusamaUniversalLocation, - NativeAssetFrom, PhalaChainId, SelfLocation, TokenSymbol, + AccountId, AccountIdToLocation, AssetHubLocation, AssetPrefixFrom, CurrencyId, + CurrencyIdMapping, EthereumLocation, KusamaNetwork, KusamaUniversalLocation, NativeAssetFrom, + SelfLocation, TokenSymbol, }; pub use cumulus_primitives_core::ParaId; use frame_support::{parameter_types, sp_runtime::traits::Convert, traits::Get}; -use parity_scale_codec::Encode; pub use polkadot_parachain_primitives::primitives::Sibling; -use sp_std::convert::TryFrom; pub use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin, @@ -42,6 +40,7 @@ use bifrost_currencies::BasicCurrencyAdapter; use bifrost_runtime_common::{ currency_adapter::{BifrostDropAssets, DepositToAlternative, MultiCurrencyAdapter}, currency_converter::CurrencyIdConvert, + xcm_weight_trader::XcmWeightTrader, }; use cumulus_primitives_core::AggregateMessageOrigin; use frame_support::traits::TransformOrigin; @@ -51,7 +50,6 @@ use orml_xcm_support::{IsNativeConcrete, MultiNativeAsset}; use pallet_xcm::XcmPassthrough; use parachains_common::message_queue::{NarrowOriginToSibling, ParaIdToSibling}; use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery; -use sp_core::bounded::BoundedVec; use xcm::v4::{prelude::*, Location}; use xcm_builder::{FrameTransactionalProcessor, TrailingSetTopicAsId, WithComputedOrigin}; @@ -66,7 +64,7 @@ parameter_types! { // XTokens pallet supports maximum number of assets to be transferred at a time pub const MaxAssetsForTransfer: usize = 2; // One XCM operation is 200_000_000 weight, cross-chain transfer ~= 2x of transfer = 3_000_000_000 - pub const UnitWeightCost: Weight = Weight::from_parts(200_000_000, 0); + pub const UnitWeightCost: Weight = Weight::from_parts(50_000_000, 0); // Maximum number of instructions that can be executed in one XCM message pub const MaxInstructions: u32 = 100; } @@ -137,128 +135,6 @@ pub type BifrostAssetTransactor = MultiCurrencyAdapter< DepositToAlternative, >; -parameter_types! { - pub KsmPerSecond: (AssetId, u128, u128) = (Location::parent().into(), ksm_per_second::(),0); - pub VksmPerSecond: (AssetId, u128,u128) = ( - Location::new( - 0, - [Junction::from(BoundedVec::try_from(CurrencyId::VToken(TokenSymbol::KSM).encode()).unwrap())], - ).into(), - ksm_per_second::(), - 0 - ); - pub VsksmPerSecond: (AssetId, u128,u128) = ( - Location::new( - 1, - [Parachain(SelfParaId::get()), Junction::from(BoundedVec::try_from(CurrencyId::VSToken(TokenSymbol::KSM).encode()).unwrap())] - ).into(), - ksm_per_second::(), - 0 - ); - pub VsksmNewPerSecond: (AssetId, u128,u128) = ( - Location::new( - 0, - [Junction::from(BoundedVec::try_from(CurrencyId::VSToken(TokenSymbol::KSM).encode()).unwrap())] - ).into(), - ksm_per_second::(), - 0 - ); - pub BncPerSecond: (AssetId, u128,u128) = ( - Location::new( - 1, - [Parachain(SelfParaId::get()), Junction::from(BoundedVec::try_from(NativeCurrencyId::get().encode()).unwrap())] - ).into(), - // BNC:KSM = 80:1 - ksm_per_second::() * 80, - 0 - ); - pub BncNewPerSecond: (AssetId, u128,u128) = ( - Location::new( - 0, - [Junction::from(BoundedVec::try_from(NativeCurrencyId::get().encode()).unwrap())] - ).into(), - // BNC:KSM = 80:1 - ksm_per_second::() * 80, - 0 - ); - - pub ZlkPerSecond: (AssetId, u128,u128) = ( - Location::new( - 1, - [Parachain(SelfParaId::get()), Junction::from(BoundedVec::try_from(CurrencyId::Token(TokenSymbol::ZLK).encode()).unwrap())] - ).into(), - // ZLK:KSM = 150:1 - //ZLK has a decimal of 18, while KSM is 12. - ksm_per_second::() * 150 * 1_000_000, - 0 - ); - pub ZlkNewPerSecond: (AssetId, u128,u128) = ( - Location::new( - 0, - [Junction::from(BoundedVec::try_from(CurrencyId::Token(TokenSymbol::ZLK).encode()).unwrap())] - ).into(), - // ZLK:KSM = 150:1 - //ZLK has a decimal of 18, while KSM is 12. - ksm_per_second::() * 150 * 1_000_000, - 0 - ); - pub KarPerSecond: (AssetId, u128,u128) = ( - Location::new( - 1, - [Parachain(KaruraChainId::get()), Junction::from(BoundedVec::try_from(vec![0,128u8]).unwrap())] - ).into(), - // KAR:KSM = 100:1 - ksm_per_second::() * 100, - 0 - ); - pub KusdPerSecond: (AssetId, u128,u128) = ( - Location::new( - 1, - [Parachain(KaruraChainId::get()), Junction::from(BoundedVec::try_from(vec![0,129u8]).unwrap())] - ).into(), - // kUSD:KSM = 400:1 - ksm_per_second::() * 400, - 0 - ); - pub PhaPerSecond: (AssetId, u128,u128) = ( - Location::new( - 1, - [Parachain(PhalaChainId::get())], - ).into(), - // PHA:KSM = 400:1 - ksm_per_second::() * 400, - 0 - ); - pub RmrkPerSecond: (AssetId, u128,u128) = ( - Location::new( - 1, - [Parachain(AssetHubChainId::get()), GeneralIndex(50)] - ).into(), - // rmrk:KSM = 10:1 - ksm_per_second::() * 10 / 100, //rmrk currency decimal as 10 - 0 - ); - pub RmrkNewPerSecond: (AssetId, u128,u128) = ( - Location::new( - 1, - [Parachain(AssetHubChainId::get()), PalletInstance(50), GeneralIndex(8)] - ).into(), - // rmrk:KSM = 10:1 - ksm_per_second::() * 10 / 100, //rmrk currency decimal as 10 - 0 - ); - pub MovrPerSecond: (AssetId, u128,u128) = ( - Location::new( - 1, - [Parachain(MoonriverChainId::get()), PalletInstance(10)] - ).into(), - // MOVR:KSM = 2.67:1 - ksm_per_second::() * 267 * 10_000, //movr currency decimal as 18 - 0 - ); - pub BasePerSecond: u128 = ksm_per_second::(); -} - pub struct ToTreasury; impl TakeRevenue for ToTreasury { fn take_revenue(revenue: Asset) { @@ -272,24 +148,6 @@ impl TakeRevenue for ToTreasury { } } -pub type Trader = ( - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfAsset, -); - /// A call filter for the XCM Transact instruction. This is a temporary measure until we properly /// account for proof size weights. /// @@ -398,7 +256,9 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type ResponseHandler = PolkadotXcm; type SubscriptionService = PolkadotXcm; - type Trader = Trader; + type Trader = XcmWeightTrader, ToTreasury>; + // TODO: Implement XcmWeigher, using real Weight, currently per instruction Weight = + // Weight::from_parts(50_000_000, 0) type Weigher = FixedWeightBounds; type XcmSender = XcmRouter; type PalletInstancesInfo = AllPalletsWithSystem; diff --git a/runtime/bifrost-polkadot/Cargo.toml b/runtime/bifrost-polkadot/Cargo.toml index 0888dda20..fc9f224d1 100644 --- a/runtime/bifrost-polkadot/Cargo.toml +++ b/runtime/bifrost-polkadot/Cargo.toml @@ -80,7 +80,6 @@ cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-timestamp = { workspace = true } cumulus-primitives-utility = { workspace = true } -pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } parachains-common = { workspace = true } @@ -92,6 +91,7 @@ polkadot-runtime-common = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } +xcm-fee-payment-runtime-api = { workspace = true } # orml orml-tokens = { workspace = true } @@ -163,6 +163,7 @@ bifrost-vstoken-conversion = { workspace = true } bifrost-vtoken-minting = { workspace = true } bifrost-vtoken-voting = { workspace = true, features = [ "polkadot" ] } bifrost-xcm-interface = { workspace = true } +bifrost-parachain-staking = { workspace = true } lend-market = { workspace = true } lend-market-rpc-runtime-api = { workspace = true } pallet-prices = { workspace = true } @@ -191,7 +192,6 @@ std = [ "pallet-aura/std", "pallet-balances/std", "pallet-bounties/std", - "pallet-collator-selection/std", "pallet-collective/std", "pallet-conviction-voting/std", "pallet-democracy/std", @@ -241,6 +241,7 @@ std = [ "xcm-builder/std", "xcm-executor/std", "xcm/std", + "xcm-fee-payment-runtime-api/std", "orml-tokens/std", "orml-traits/std", @@ -305,7 +306,9 @@ std = [ "pallet-evm-accounts/std", "pallet-evm-accounts-rpc-runtime-api/std", "bifrost-clouds-convert/std", + "bifrost-vtoken-minting-rpc-runtime-api/std", + "bifrost-parachain-staking/std", "substrate-wasm-builder" ] @@ -349,6 +352,7 @@ runtime-benchmarks = [ "bifrost-channel-commission/runtime-benchmarks", "bifrost-clouds-convert/runtime-benchmarks", "bifrost-buy-back/runtime-benchmarks", + "bifrost-parachain-staking/runtime-benchmarks", ] try-runtime = [ @@ -396,7 +400,6 @@ try-runtime = [ "pallet-authorship/try-runtime", "pallet-balances/try-runtime", "pallet-bounties/try-runtime", - "pallet-collator-selection/try-runtime", "pallet-collective/try-runtime", "pallet-conviction-voting/try-runtime", "pallet-democracy/try-runtime", @@ -427,6 +430,7 @@ try-runtime = [ "pallet-evm/try-runtime", "pallet-evm-chain-id/try-runtime", "pallet-dynamic-fee/try-runtime", + "bifrost-parachain-staking/try-runtime", ] # Enable the metadata hash generation in the wasm builder. diff --git a/runtime/bifrost-polkadot/src/lib.rs b/runtime/bifrost-polkadot/src/lib.rs index 090fd2c31..ae4ad4bdf 100644 --- a/runtime/bifrost-polkadot/src/lib.rs +++ b/runtime/bifrost-polkadot/src/lib.rs @@ -30,13 +30,15 @@ use bifrost_slp::{DerivativeAccountProvider, QueryResponseManager}; use core::convert::TryInto; use pallet_traits::evm::InspectEvmAccounts; // A few exports that help ease life for downstream crates. +pub use bifrost_parachain_staking::{InflationInfo, Range}; use bifrost_primitives::{ BifrostCrowdloanId, BifrostVsbondAccount, BuyBackAccount, BuybackPalletId, CloudsPalletId, CommissionPalletId, FarmingBoostPalletId, FarmingGaugeRewardIssuerPalletId, FarmingKeeperPalletId, FarmingRewardIssuerPalletId, FeeSharePalletId, FlexibleFeePalletId, IncentivePalletId, IncentivePoolAccount, LendMarketPalletId, LiquidityAccount, - MerkleDirtributorPalletId, OraclePalletId, SlpEntrancePalletId, SlpExitPalletId, - SystemMakerPalletId, SystemStakingPalletId, TreasuryPalletId, + LocalBncLocation, MerkleDirtributorPalletId, OraclePalletId, ParachainStakingPalletId, + SlpEntrancePalletId, SlpExitPalletId, SystemMakerPalletId, SystemStakingPalletId, + TreasuryPalletId, }; use cumulus_pallet_parachain_system::{RelayNumberStrictlyIncreases, RelaychainDataProvider}; pub use frame_support::{ @@ -78,7 +80,7 @@ mod evm; mod migration; pub mod weights; use bb_bnc::traits::BbBNCInterface; -use bifrost_asset_registry::{AssetIdMaps, FixedRateOfAsset}; +use bifrost_asset_registry::AssetIdMaps; pub use bifrost_primitives::{ traits::{ CheckSubAccount, FarmingInfo, VtokenMintingInterface, VtokenMintingOperator, @@ -128,16 +130,26 @@ use sp_runtime::{ transaction_validity::TransactionValidityError, }; use static_assertions::const_assert; -use xcm::{v3::MultiLocation, v4::prelude::*}; +use xcm::{ + v3::MultiLocation, v4::prelude::*, VersionedAssetId, VersionedAssets, VersionedLocation, + VersionedXcm, +}; pub use xcm_config::{BifrostTreasuryAccount, MultiCurrency}; use xcm_executor::{traits::QueryHandler, XcmExecutor}; pub mod governance; use crate::xcm_config::XcmRouter; +use bifrost_primitives::OraclePriceProvider; +use frame_support::weights::WeightToFee as _; use governance::{ custom_origins, CoreAdminOrCouncil, LiquidStaking, SALPAdmin, Spender, TechAdmin, TechAdminOrCouncil, }; +use xcm::IntoVersion; +use xcm_fee_payment_runtime_api::{ + dry_run::{CallDryRunEffects, Error as XcmDryRunApiError, XcmDryRunEffects}, + fees::Error as XcmPaymentApiError, +}; use bifrost_primitives::MoonbeamChainId; #[cfg(feature = "runtime-benchmarks")] @@ -176,7 +188,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("bifrost_polkadot"), impl_name: create_runtime_str!("bifrost_polkadot"), authoring_version: 0, - spec_version: 14000, + spec_version: 15000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -334,6 +346,7 @@ pub enum ProxyType { Governance = 2, CancelProxy = 3, IdentityJudgement = 4, + Staking = 5, } impl Default for ProxyType { @@ -368,8 +381,12 @@ impl InstanceFilter for ProxyType { // Specifically omitting Vesting `vested_transfer`, and `force_vested_transfer` RuntimeCall::Utility(..) | RuntimeCall::Proxy(..) | - RuntimeCall::Multisig(..) + RuntimeCall::Multisig(..) | + RuntimeCall::ParachainStaking(..) ), + ProxyType::Staking => { + matches!(c, RuntimeCall::ParachainStaking(..) | RuntimeCall::Utility(..)) + }, ProxyType::Governance => matches!( c, RuntimeCall::Democracy(..) | @@ -817,6 +834,75 @@ impl parachain_info::Config for Runtime {} impl cumulus_pallet_aura_ext::Config for Runtime {} +parameter_types! { + /// Minimum round length is 2 minutes (10 * 12 second block times) + pub const MinBlocksPerRound: u32 = 10; + /// Rounds before the collator leaving the candidates request can be executed + pub const LeaveCandidatesDelay: u32 = 84; + /// Rounds before the candidate bond increase/decrease can be executed + pub const CandidateBondLessDelay: u32 = 84; + /// Rounds before the delegator exit can be executed + pub const LeaveDelegatorsDelay: u32 = 84; + /// Rounds before the delegator revocation can be executed + pub const RevokeDelegationDelay: u32 = 84; + /// Rounds before the delegator bond increase/decrease can be executed + pub const DelegationBondLessDelay: u32 = 84; + /// Rounds before the reward is paid + pub const RewardPaymentDelay: u32 = 2; + /// Minimum collators selected per round, default at genesis and minimum forever after + pub const MinSelectedCandidates: u32 = prod_or_fast!(16,6); + /// Maximum top delegations per candidate + pub const MaxTopDelegationsPerCandidate: u32 = 300; + /// Maximum bottom delegations per candidate + pub const MaxBottomDelegationsPerCandidate: u32 = 50; + /// Maximum delegations per delegator + pub const MaxDelegationsPerDelegator: u32 = 100; + /// Minimum stake required to become a collator + pub MinCollatorStk: u128 = 5000 * BNCS; + /// Minimum stake required to be reserved to be a candidate + pub MinCandidateStk: u128 = 5000 * BNCS; + /// Minimum stake required to be reserved to be a delegator + pub MinDelegatorStk: u128 = 50 * BNCS; + pub AllowInflation: bool = false; + pub ToMigrateInvulnables: Vec = prod_or_fast!(vec![ + hex!["5c7e9ccd1045cac7f8c5c77a79c87f44019d1dda4f5032713bda89c5d73cb36b"].into(), + hex!["606b0aad375ae1715fbe6a07315136a8e9c1c84a91230f6a0c296c2953581335"].into(), + hex!["b6ba81e73bd39203e006fc99cc1e41976745de2ea2007bf62ed7c9a48ccc5b1d"].into(), + hex!["ce42cea2dd0d4ac87ccdd5f0f2e1010955467f5a37587cf6af8ee2b4ba781034"].into(), + ],vec![]); + pub PaymentInRound: u128 = 180 * BNCS; + pub InitSeedStk: u128 = 5000 * BNCS; +} +impl bifrost_parachain_staking::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type MonetaryGovernanceOrigin = + EitherOfDiverse; + type MinBlocksPerRound = MinBlocksPerRound; + type LeaveCandidatesDelay = LeaveCandidatesDelay; + type CandidateBondLessDelay = CandidateBondLessDelay; + type LeaveDelegatorsDelay = LeaveDelegatorsDelay; + type RevokeDelegationDelay = RevokeDelegationDelay; + type DelegationBondLessDelay = DelegationBondLessDelay; + type RewardPaymentDelay = RewardPaymentDelay; + type MinSelectedCandidates = MinSelectedCandidates; + type MaxTopDelegationsPerCandidate = MaxTopDelegationsPerCandidate; + type MaxBottomDelegationsPerCandidate = MaxBottomDelegationsPerCandidate; + type MaxDelegationsPerDelegator = MaxDelegationsPerDelegator; + type MinCollatorStk = MinCollatorStk; + type MinCandidateStk = MinCandidateStk; + type MinDelegation = MinDelegatorStk; + type MinDelegatorStk = MinDelegatorStk; + type AllowInflation = AllowInflation; + type PaymentInRound = PaymentInRound; + type ToMigrateInvulnables = ToMigrateInvulnables; + type PalletId = ParachainStakingPalletId; + type InitSeedStk = InitSeedStk; + type OnCollatorPayout = (); + type OnNewRound = (); + type WeightInfo = bifrost_parachain_staking::weights::SubstrateWeight; +} + parameter_types! { pub const Period: u32 = 6 * HOURS; pub const Offset: u32 = 0; @@ -825,20 +911,20 @@ parameter_types! { impl pallet_session::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Keys = opaque::SessionKeys; - type NextSessionRotation = pallet_session::PeriodicSessions; + type NextSessionRotation = ParachainStaking; // Essentially just Aura, but lets be pedantic. type SessionHandler = ::KeyTypeIdProviders; - type SessionManager = CollatorSelection; - type ShouldEndSession = pallet_session::PeriodicSessions; + type SessionManager = ParachainStaking; + type ShouldEndSession = ParachainStaking; type ValidatorId = ::AccountId; // we don't have stash and controller, thus we don't need the convert as well. - type ValidatorIdOf = pallet_collator_selection::IdentityCollator; + type ValidatorIdOf = ConvertInto; type WeightInfo = pallet_session::weights::SubstrateWeight; } impl pallet_authorship::Config for Runtime { - type EventHandler = CollatorSelection; + type EventHandler = ParachainStaking; type FindAuthor = pallet_session::FindAccountFromAuthorIndex; } @@ -850,28 +936,6 @@ impl pallet_aura::Config for Runtime { type SlotDuration = ConstU64; } -parameter_types! { - pub const PotId: PalletId = PalletId(*b"PotStake"); - pub const SessionLength: BlockNumber = 6 * HOURS; - pub const MaxInvulnerables: u32 = 100; -} - -impl pallet_collator_selection::Config for Runtime { - type Currency = Balances; - type RuntimeEvent = RuntimeEvent; - // should be a multiple of session or things will get inconsistent - type KickThreshold = Period; - type MaxCandidates = MaxCandidates; - type MaxInvulnerables = MaxInvulnerables; - type PotId = PotId; - type UpdateOrigin = EnsureRoot; - type ValidatorId = ::AccountId; - type ValidatorIdOf = pallet_collator_selection::IdentityCollator; - type ValidatorRegistration = Session; - type WeightInfo = (); - type MinEligibleCollators = ConstU32<5>; -} - // culumus runtime end parameter_types! { @@ -1100,7 +1164,7 @@ impl bifrost_slp::Config for Runtime { type SubstrateResponseManager = SubstrateResponseManager; type MaxTypeEntryPerBlock = MaxTypeEntryPerBlock; type MaxRefundPerBlock = MaxRefundPerBlock; - type ParachainStaking = (); + type ParachainStaking = ParachainStaking; type XcmTransfer = XTokens; type MaxLengthLimit = MaxLengthLimit; type XcmWeightAndFeeHandler = XcmInterface; @@ -1658,10 +1722,10 @@ construct_runtime! { // Collator support. the order of these 4 are important and shall not change. Authorship: pallet_authorship = 20, - CollatorSelection: pallet_collator_selection = 21, Session: pallet_session = 22, Aura: pallet_aura = 23, AuraExt: cumulus_pallet_aura_ext = 24, + ParachainStaking: bifrost_parachain_staking = 25, // Governance stuff Democracy: pallet_democracy = 30, @@ -1818,7 +1882,7 @@ impl cumulus_pallet_xcmp_queue::migration::v5::V5Config for Runtime { pub type Migrations = migrations::Unreleased; parameter_types! { - pub const SystemMakerName: &'static str = "SystemMaker"; + pub const CollatorSelectionName: &'static str = "CollatorSelection"; } /// The runtime migrations per release. @@ -1827,7 +1891,12 @@ pub mod migrations { use super::*; /// Unreleased migrations. Add new ones here: - pub type Unreleased = pallet_xcm::migration::MigrateToLatestXcmVersion; + pub type Unreleased = ( + // permanent migration, do not remove + pallet_xcm::migration::MigrateToLatestXcmVersion, + bifrost_parachain_staking::migrations::InitGenesisMigration, + frame_support::migrations::RemovePallet, + ); } /// Executive: handles dispatch to the various modules. @@ -2300,6 +2369,49 @@ impl fp_rpc::EthereumRuntimeRPCApi for Runtime { } } + impl xcm_fee_payment_runtime_api::fees::XcmPaymentApi for Runtime { + fn query_acceptable_payment_assets(xcm_version: xcm::Version) -> Result, XcmPaymentApiError> { + let acceptable_assets = AssetRegistry::asset_ids(); + PolkadotXcm::query_acceptable_payment_assets(xcm_version, acceptable_assets) + } + + fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result { + let asset = asset + .into_version(4) + .map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?; + let bnc_asset = VersionedAssetId::V4(LocalBncLocation::get().into()); + + if asset == bnc_asset { + // for native token + Ok(WeightToFee::weight_to_fee(&weight)) + } else { + let native_fee = WeightToFee::weight_to_fee(&weight); + let asset_location = &asset.try_as::().map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?.0; + let asset_currency = AssetIdMaps::::get_currency_id(&asset_location).ok_or(XcmPaymentApiError::AssetNotFound)?; + let asset_fee = Prices::get_oracle_amount_by_currency_and_amount_in(&bifrost_primitives::BNC, native_fee, &asset_currency).ok_or(XcmPaymentApiError::AssetNotFound)?.0; + Ok(asset_fee) + } + } + + fn query_xcm_weight(message: VersionedXcm<()>) -> Result { + PolkadotXcm::query_xcm_weight(message) + } + + fn query_delivery_fees(destination: VersionedLocation, message: VersionedXcm<()>) -> Result { + PolkadotXcm::query_delivery_fees(destination, message) + } + } + + impl xcm_fee_payment_runtime_api::dry_run::DryRunApi for Runtime { + fn dry_run_call(origin: OriginCaller, call: RuntimeCall) -> Result, XcmDryRunApiError> { + PolkadotXcm::dry_run_call::(origin, call) + } + + fn dry_run_xcm(origin_location: VersionedLocation, xcm: VersionedXcm) -> Result, XcmDryRunApiError> { + PolkadotXcm::dry_run_xcm::(origin_location, xcm) + } + } + impl bifrost_flexible_fee_rpc_runtime_api::FlexibleFeeRuntimeApi for Runtime { fn get_fee_token_and_amount(who: AccountId, fee: Balance,utx: ::Extrinsic) -> (CurrencyId, Balance) { let call = utx.0.function; diff --git a/runtime/bifrost-polkadot/src/xcm_config.rs b/runtime/bifrost-polkadot/src/xcm_config.rs index 69ab3fa92..7bb1b2760 100644 --- a/runtime/bifrost-polkadot/src/xcm_config.rs +++ b/runtime/bifrost-polkadot/src/xcm_config.rs @@ -24,8 +24,9 @@ use bifrost_primitives::{ CurrencyId, CurrencyIdMapping, EthereumLocation, NativeAssetFrom, PolkadotNetwork, PolkadotUniversalLocation, SelfLocation, TokenSymbol, DOT_TOKEN_ID, }; -use bifrost_runtime_common::currency_adapter::{ - BifrostDropAssets, DepositToAlternative, MultiCurrencyAdapter, +use bifrost_runtime_common::{ + currency_adapter::{BifrostDropAssets, DepositToAlternative, MultiCurrencyAdapter}, + xcm_weight_trader::XcmWeightTrader, }; use cumulus_primitives_core::AggregateMessageOrigin; pub use cumulus_primitives_core::ParaId; @@ -37,11 +38,8 @@ pub use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key use orml_xcm_support::{IsNativeConcrete, MultiNativeAsset}; use pallet_xcm::XcmPassthrough; use parachains_common::message_queue::{NarrowOriginToSibling, ParaIdToSibling}; -use parity_scale_codec::Encode; pub use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery; -use sp_core::bounded::BoundedVec; -use sp_std::convert::TryFrom; use xcm::v4::{Asset, AssetId, Location}; pub use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, @@ -66,7 +64,7 @@ parameter_types! { // XTokens pallet supports maximum number of assets to be transferred at a time pub const MaxAssetsForTransfer: usize = 2; // One XCM operation is 200_000_000 weight, cross-chain transfer ~= 2x of transfer = 3_000_000_000 - pub const UnitWeightCost: Weight = Weight::from_parts(200_000_000, 0); + pub const UnitWeightCost: Weight = Weight::from_parts(50_000_000, 0); // Maximum number of instructions that can be executed in one XCM message pub const MaxInstructions: u32 = 100; } @@ -139,47 +137,6 @@ pub type BifrostAssetTransactor = MultiCurrencyAdapter< DepositToAlternative, >; -parameter_types! { - pub DotPerSecond: (AssetId,u128, u128) = (Location::parent().into(), dot_per_second::(),0); - pub BncPerSecond: (AssetId,u128, u128) = ( - Location::new( - 1, - [xcm::v4::Junction::Parachain(SelfParaId::get()), xcm::v4::Junction::from(BoundedVec::try_from(NativeCurrencyId::get().encode()).unwrap())], - ).into(), - // BNC:DOT = 80:1 - dot_per_second::() * 80, - 0 - ); - pub BncNewPerSecond: (AssetId,u128, u128) = ( - Location::new( - 0, - [xcm::v4::Junction::from(BoundedVec::try_from(NativeCurrencyId::get().encode()).unwrap())] - ).into(), - // BNC:DOT = 80:1 - dot_per_second::() * 80, - 0 - ); - pub ZlkPerSecond: (AssetId, u128,u128) = ( - Location::new( - 1, - [xcm::v4::Junction::Parachain(SelfParaId::get()), xcm::v4::Junction::from(BoundedVec::try_from(CurrencyId::Token(TokenSymbol::ZLK).encode()).unwrap())] - ).into(), - // ZLK:KSM = 150:1 - dot_per_second::() * 150 * 1_000_000, - 0 - ); - pub ZlkNewPerSecond: (AssetId, u128,u128) = ( - Location::new( - 0, - [xcm::v4::Junction::from(BoundedVec::try_from(CurrencyId::Token(TokenSymbol::ZLK).encode()).unwrap())] - ).into(), - // ZLK:KSM = 150:1 - dot_per_second::() * 150 * 1_000_000, - 0 - ); - pub BasePerSecond: u128 = dot_per_second::(); -} - pub struct ToTreasury; impl TakeRevenue for ToTreasury { fn take_revenue(revenue: Asset) { @@ -193,13 +150,6 @@ impl TakeRevenue for ToTreasury { } } -pub type Trader = ( - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfAsset, -); - /// A call filter for the XCM Transact instruction. This is a temporary measure until we properly /// account for proof size weights. /// @@ -305,7 +255,9 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type ResponseHandler = PolkadotXcm; type SubscriptionService = PolkadotXcm; - type Trader = Trader; + type Trader = XcmWeightTrader, ToTreasury>; + // TODO: Implement XcmWeigher, using real Weight, currently per instruction Weight = + // Weight::from_parts(50_000_000, 0) type Weigher = FixedWeightBounds; type XcmSender = XcmRouter; type PalletInstancesInfo = AllPalletsWithSystem; diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index 7937f5f93..c4907fdf9 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -51,7 +51,6 @@ cumulus-primitives-utility = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-pallet-xcm = { workspace = true } parachain-info = { workspace = true } -pallet-collator-selection = { workspace = true } # Polkadot dependencies polkadot-parachain-primitives = { workspace = true } diff --git a/runtime/common/src/currency_converter.rs b/runtime/common/src/currency_converter.rs index fd2276e04..eb7190d41 100644 --- a/runtime/common/src/currency_converter.rs +++ b/runtime/common/src/currency_converter.rs @@ -21,7 +21,7 @@ use bifrost_primitives::{CurrencyId, CurrencyIdMapping}; use cumulus_primitives_core::ParaId; use frame_support::traits::Get; use sp_runtime::traits::Convert; -use sp_std::{marker::PhantomData, prelude::*}; +use sp_std::marker::PhantomData; use xcm::{ latest::{AssetId, Location}, prelude::Fungible, @@ -34,7 +34,7 @@ impl, R: bifrost_asset_registry::Config> Convert { fn convert(id: CurrencyId) -> Option { - AssetIdMaps::::get_location(id) + AssetIdMaps::::get_location(&id) } } /// Convert Location to CurrencyId @@ -42,7 +42,7 @@ impl, R: bifrost_asset_registry::Config> Convert { fn convert(location: Location) -> Option { - AssetIdMaps::::get_currency_id(location.clone()) + AssetIdMaps::::get_currency_id(&location) } } /// Convert Asset to CurrencyId diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index c7fccfce8..9181be760 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -32,6 +32,7 @@ use sp_runtime::{traits::Bounded, FixedPointNumber, Perquintill}; pub mod constants; pub mod currency_adapter; pub mod currency_converter; +pub mod xcm_weight_trader; #[cfg(test)] mod tests; diff --git a/runtime/common/src/xcm_weight_trader.rs b/runtime/common/src/xcm_weight_trader.rs new file mode 100644 index 000000000..42bc9ba6e --- /dev/null +++ b/runtime/common/src/xcm_weight_trader.rs @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use bifrost_asset_registry::AssetMetadata; +use bifrost_primitives::{ + Balance, CurrencyId, CurrencyIdMapping, LocalBncLocation, OraclePriceProvider, BNC, +}; +use frame_support::weights::Weight; +use sp_std::marker::PhantomData; +use xcm::{ + latest::{Asset, AssetId, Location, XcmContext}, + prelude::{Fungible, XcmError}, +}; +use xcm_builder::TakeRevenue; +use xcm_executor::{traits::WeightTrader, AssetsInHolding}; + +pub struct XcmWeightTrader< + WeightToFee: frame_support::weights::WeightToFee, + Price: OraclePriceProvider, + CM: CurrencyIdMapping>, + R: TakeRevenue, +>(Weight, Option, PhantomData<(WeightToFee, Price, CM, R)>); + +impl< + WeightToFee: frame_support::weights::WeightToFee, + Price: OraclePriceProvider, + CM: CurrencyIdMapping>, + R: TakeRevenue, + > XcmWeightTrader +{ + fn compute_amount_to_charge( + weight: &Weight, + asset_location: &Location, + ) -> Result { + if *asset_location == LocalBncLocation::get() { + Ok(WeightToFee::weight_to_fee(weight)) + } else { + let bnc_amount = WeightToFee::weight_to_fee(weight); + let asset_currency_id = + CM::get_currency_id(asset_location).ok_or(XcmError::AssetNotFound)?; + let asset_amount = Price::get_oracle_amount_by_currency_and_amount_in( + &BNC, + bnc_amount, + &asset_currency_id, + ) + .ok_or(XcmError::Overflow)? + .0; + Ok(asset_amount) + } + } +} + +impl< + WeightToFee: frame_support::weights::WeightToFee, + Price: OraclePriceProvider, + CM: CurrencyIdMapping>, + R: TakeRevenue, + > WeightTrader for XcmWeightTrader +{ + fn new() -> Self { + Self(Weight::zero(), None, PhantomData) + } + + fn buy_weight( + &mut self, + weight: Weight, + payment: AssetsInHolding, + _context: &XcmContext, + ) -> Result { + log::trace!(target: "xcm-weight-trader", "buy_weight weight: {:?}, payment: {:?}", weight, payment); + + // only support first fungible assets now. + let first_asset = + payment.clone().fungible_assets_iter().next().ok_or(XcmError::AssetNotFound)?; + + match (first_asset.id, first_asset.fun) { + (AssetId(location), Fungible(_)) => { + log::trace!(target: "xcm::weight", "buy_weight location: {:?}", location); + let amount = Self::compute_amount_to_charge(&weight, &location)?; + + // We don't need to proceed if the amount is 0 + // For cases (specially tests) where the asset is very cheap with respect + // to the weight needed + if amount == 0 { + return Ok(payment); + } + + let required = Asset { fun: Fungible(amount), id: AssetId(location) }; + let unused = + payment.checked_sub(required.clone()).map_err(|_| XcmError::TooExpensive)?; + + self.0 = weight; + self.1 = Some(required); + + Ok(unused) + }, + _ => Err(XcmError::AssetNotFound), + } + } + + fn refund_weight(&mut self, actual_weight: Weight, context: &XcmContext) -> Option { + log::trace!( + target: "xcm-weight-trader", + "refund_weight weight: {:?}, context: {:?}, available weight: {:?}, asset: {:?}", + actual_weight, + context, + self.0, + self.1 + ); + + if let Some(Asset { fun: Fungible(initial_amount), id: AssetId(location) }) = self.1.take() + { + if actual_weight == self.0 { + self.1 = Some(Asset { fun: Fungible(initial_amount), id: AssetId(location) }); + None + } else { + let weight = actual_weight.min(self.0); + let amount = + Self::compute_amount_to_charge(&weight, &location).unwrap_or(Balance::MAX); + let final_amount = amount.min(initial_amount); + let amount_to_refund = initial_amount.saturating_sub(final_amount); + self.0 -= weight; + self.1 = Some(Asset { fun: Fungible(final_amount), id: AssetId(location.clone()) }); + log::trace!( + target: "xcm-weight-trader", + "refund_weight amount to refund: {:?}", + amount_to_refund + ); + Some(Asset { fun: Fungible(amount_to_refund), id: AssetId(location) }) + } + } else { + None + } + } +} + +impl< + WeightToFee: frame_support::weights::WeightToFee, + Price: OraclePriceProvider, + CM: CurrencyIdMapping>, + R: TakeRevenue, + > Drop for XcmWeightTrader +{ + fn drop(&mut self) { + log::trace!(target: "xcm-weight-trader", "take revenue, weight: {:?}, asset: {:?}", self.0, self.1); + if let Some(asset) = self.1.take() { + R::take_revenue(asset); + } + } +}