diff --git a/modules/asset-registry/src/lib.rs b/modules/asset-registry/src/lib.rs index 73ce30a783..7953a2fcfe 100644 --- a/modules/asset-registry/src/lib.rs +++ b/modules/asset-registry/src/lib.rs @@ -582,6 +582,33 @@ where } } +pub struct BuyWeightRateOfLiquidCrowdloan(sp_std::marker::PhantomData); + +impl BuyWeightRate for BuyWeightRateOfLiquidCrowdloan +where + BalanceOf: Into, +{ + fn calculate_rate(location: MultiLocation) -> Option { + let currency = key_to_currency(location); + match currency { + Some(CurrencyId::LiquidCrowdloan(lease)) => { + if let Some(asset_metadata) = + Pallet::::asset_metadatas(AssetIds::NativeAssetId(CurrencyId::LiquidCrowdloan(lease))) + { + let minimum_balance = asset_metadata.minimal_balance.into(); + let rate = + FixedU128::saturating_from_rational(minimum_balance, T::Currency::minimum_balance().into()); + log::debug!(target: "asset-registry::weight", "LiquidCrowdloan: {}, MinimumBalance: {}, rate:{:?}", lease, minimum_balance, rate); + Some(rate) + } else { + None + } + } + _ => None, + } + } +} + pub struct BuyWeightRateOfStableAsset(sp_std::marker::PhantomData); impl BuyWeightRate for BuyWeightRateOfStableAsset diff --git a/runtime/acala/src/xcm_config.rs b/runtime/acala/src/xcm_config.rs index e2bd9fa4fd..1fc35df07f 100644 --- a/runtime/acala/src/xcm_config.rs +++ b/runtime/acala/src/xcm_config.rs @@ -29,7 +29,9 @@ pub use frame_support::{ traits::{Everything, Get, Nothing}, weights::Weight, }; -use module_asset_registry::{BuyWeightRateOfErc20, BuyWeightRateOfForeignAsset, BuyWeightRateOfStableAsset}; +use module_asset_registry::{ + BuyWeightRateOfErc20, BuyWeightRateOfForeignAsset, BuyWeightRateOfLiquidCrowdloan, BuyWeightRateOfStableAsset, +}; use module_support::HomaSubAccountXcm; use module_transaction_payment::BuyWeightRateOfTransactionFeePool; use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key, MultiCurrency}; @@ -147,6 +149,7 @@ pub type Trader = ( FixedRateOfAsset>, FixedRateOfAsset>, FixedRateOfAsset>, + FixedRateOfAsset>, FixedRateOfFungible, FixedRateOfFungible, FixedRateOfFungible, @@ -244,7 +247,7 @@ pub struct CurrencyIdConvert; impl Convert> for CurrencyIdConvert { fn convert(id: CurrencyId) -> Option { use primitives::TokenSymbol::*; - use CurrencyId::{Erc20, ForeignAsset, StableAssetPoolToken, Token}; + use CurrencyId::{Erc20, ForeignAsset, LiquidCrowdloan, StableAssetPoolToken, Token}; match id { Token(DOT) => Some(MultiLocation::parent()), Token(ACA) | Token(AUSD) | Token(LDOT) | Token(TAP) => { @@ -253,6 +256,7 @@ impl Convert> for CurrencyIdConvert { Erc20(address) if !is_system_contract(address) => { Some(native_currency_location(ParachainInfo::get().into(), id.encode())) } + LiquidCrowdloan(_lease) => Some(native_currency_location(ParachainInfo::get().into(), id.encode())), StableAssetPoolToken(_pool_id) => Some(native_currency_location(ParachainInfo::get().into(), id.encode())), ForeignAsset(foreign_asset_id) => AssetIdMaps::::get_multi_location(foreign_asset_id), _ => None, @@ -262,7 +266,7 @@ impl Convert> for CurrencyIdConvert { impl Convert> for CurrencyIdConvert { fn convert(location: MultiLocation) -> Option { use primitives::TokenSymbol::*; - use CurrencyId::{Erc20, StableAssetPoolToken, Token}; + use CurrencyId::{Erc20, LiquidCrowdloan, StableAssetPoolToken, Token}; if location == MultiLocation::parent() { return Some(Token(DOT)); @@ -285,6 +289,7 @@ impl Convert> for CurrencyIdConvert { match currency_id { Token(ACA) | Token(AUSD) | Token(LDOT) | Token(TAP) => Some(currency_id), Erc20(address) if !is_system_contract(address) => Some(currency_id), + LiquidCrowdloan(_lease) => Some(currency_id), StableAssetPoolToken(_pool_id) => Some(currency_id), _ => None, } @@ -306,6 +311,7 @@ impl Convert> for CurrencyIdConvert { match currency_id { Token(ACA) | Token(AUSD) | Token(LDOT) | Token(TAP) => Some(currency_id), Erc20(address) if !is_system_contract(address) => Some(currency_id), + LiquidCrowdloan(_lease) => Some(currency_id), StableAssetPoolToken(_pool_id) => Some(currency_id), _ => None, } diff --git a/runtime/integration-tests/src/relaychain/kusama_cross_chain_transfer.rs b/runtime/integration-tests/src/relaychain/kusama_cross_chain_transfer.rs index a76b3da84d..2e533c56b1 100644 --- a/runtime/integration-tests/src/relaychain/kusama_cross_chain_transfer.rs +++ b/runtime/integration-tests/src/relaychain/kusama_cross_chain_transfer.rs @@ -845,7 +845,7 @@ fn subscribe_version_notify_works() { #[test] fn unspent_xcm_fee_is_returned_correctly() { - let parachain_account: AccountId = polkadot_parachain::primitives::Id::from(2000).into_account_truncating(); + let parachain_account: AccountId = polkadot_parachain::primitives::Id::from(KARURA_ID).into_account_truncating(); let homa_lite_sub_account: AccountId = hex_literal::hex!["d7b8926b326dd349355a9a7cca6606c1e0eb6fd2b506066b518c7155ff0d8297"].into(); let dollar_r = dollar(RELAY_CHAIN_CURRENCY); @@ -993,7 +993,7 @@ fn trapped_asset() -> MultiAsset { }; KusamaNet::execute_with(|| { - let location = MultiLocation::new(0, X1(Parachain(2000))); + let location = MultiLocation::new(0, X1(Parachain(KARURA_ID))); let versioned = xcm::VersionedMultiAssets::from(MultiAssets::from(vec![asset.clone()])); let hash = BlakeTwo256::hash_of(&(&location, &versioned)); kusama_runtime::System::assert_has_event(kusama_runtime::Event::XcmPallet(pallet_xcm::Event::AssetsTrapped( diff --git a/runtime/integration-tests/src/relaychain/polkadot_cross_chain_transfer.rs b/runtime/integration-tests/src/relaychain/polkadot_cross_chain_transfer.rs index 31b7800a40..d730cc45c1 100644 --- a/runtime/integration-tests/src/relaychain/polkadot_cross_chain_transfer.rs +++ b/runtime/integration-tests/src/relaychain/polkadot_cross_chain_transfer.rs @@ -18,6 +18,7 @@ //! Cross-chain transfer tests within Polkadot network. +use crate::relaychain::fee_test::*; use crate::relaychain::polkadot_test_net::*; use crate::setup::*; @@ -25,6 +26,13 @@ use frame_support::assert_ok; use orml_traits::MultiCurrency; use xcm_emulator::TestExt; +pub const ACALA_ID: u32 = 2000; +pub const MOCK_BIFROST_ID: u32 = 2001; + +fn bifrost_reserve_account() -> AccountId { + polkadot_parachain::primitives::Sibling::from(MOCK_BIFROST_ID).into_account_truncating() +} + #[test] fn token_per_second_works() { let aca_per_second = acala_runtime::aca_per_second(); @@ -39,7 +47,7 @@ fn transfer_from_relay_chain() { PolkadotNet::execute_with(|| { assert_ok!(polkadot_runtime::XcmPallet::reserve_transfer_assets( polkadot_runtime::Origin::signed(ALICE.into()), - Box::new(Parachain(2000).into().into()), + Box::new(Parachain(ACALA_ID).into().into()), Box::new( Junction::AccountId32 { id: BOB, @@ -88,7 +96,107 @@ fn transfer_to_relay_chain() { ); assert_eq!( 5 * dollar(DOT), - polkadot_runtime::Balances::free_balance(&ParaId::from(2000).into_account_truncating()) + polkadot_runtime::Balances::free_balance(&ParaId::from(ACALA_ID).into_account_truncating()) + ); + }); +} + +#[test] +fn liquid_crowdloan_xtokens_works() { + TestNet::reset(); + let foreign_asset = CurrencyId::ForeignAsset(0); + let dollar = dollar(KAR); + let minimal_balance = Balances::minimum_balance() / 10; // 10% + let foreign_fee = foreign_per_second_as_fee(4, minimal_balance); + + MockBifrost::execute_with(|| { + assert_ok!(AssetRegistry::register_foreign_asset( + Origin::root(), + Box::new( + MultiLocation::new( + 1, + X2(Parachain(ACALA_ID), GeneralKey(LCDOT.encode().try_into().unwrap())) + ) + .into() + ), + Box::new(AssetMetadata { + name: b"Liquid Crowdloan Token".to_vec(), + symbol: b"LCDOT".to_vec(), + decimals: 12, + minimal_balance + }) + )); + }); + + Acala::execute_with(|| { + assert_ok!(AssetRegistry::register_native_asset( + Origin::root(), + LCDOT, + Box::new(AssetMetadata { + name: b"Liquid Crowdloan Token".to_vec(), + symbol: b"LCDOT".to_vec(), + decimals: 12, + minimal_balance + }) + )); + assert_ok!(Tokens::deposit(LCDOT, &AccountId::from(BOB), 10 * dollar)); + + assert_ok!(XTokens::transfer( + Origin::signed(BOB.into()), + LCDOT, + 5 * dollar, + Box::new( + MultiLocation::new( + 1, + X2( + Parachain(MOCK_BIFROST_ID), + Junction::AccountId32 { + network: NetworkId::Any, + id: ALICE.into(), + } + ) + ) + .into() + ), + 8_000_000_000, + )); + + assert_eq!(Tokens::free_balance(LCDOT, &AccountId::from(BOB)), 5 * dollar); + assert_eq!(Tokens::free_balance(LCDOT, &bifrost_reserve_account()), 5 * dollar); + }); + + MockBifrost::execute_with(|| { + assert_eq!( + Tokens::free_balance(foreign_asset, &AccountId::from(ALICE)), + 5 * dollar - foreign_fee + ); + + assert_ok!(XTokens::transfer( + Origin::signed(ALICE.into()), + foreign_asset, + dollar, + Box::new( + MultiLocation::new( + 1, + X2( + Parachain(ACALA_ID), + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + } + ) + ) + .into() + ), + 8_000_000_000, + )); + }); + + Acala::execute_with(|| { + assert_eq!( + Tokens::free_balance(LCDOT, &AccountId::from(BOB)), + 6 * dollar - foreign_fee ); + assert_eq!(Tokens::free_balance(LCDOT, &bifrost_reserve_account()), 4 * dollar); }); }