From 92b67f36da59df4a5a039ccf2bd7107fd275f71a Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Tue, 9 Aug 2022 01:38:42 +0800 Subject: [PATCH 1/2] Support LCDOT for XCM --- modules/asset-registry/src/lib.rs | 21 ++++ runtime/acala/src/xcm_config.rs | 12 +- .../relaychain/kusama_cross_chain_transfer.rs | 4 +- .../polkadot_cross_chain_transfer.rs | 104 +++++++++++++++++- 4 files changed, 134 insertions(+), 7 deletions(-) diff --git a/modules/asset-registry/src/lib.rs b/modules/asset-registry/src/lib.rs index 73ce30a783..c7d1e6410a 100644 --- a/modules/asset-registry/src/lib.rs +++ b/modules/asset-registry/src/lib.rs @@ -582,6 +582,27 @@ 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)) => { + // The default rate for all leases is the same. + // LCDOT:DOT = 1.3:1 + let rate = FixedU128::saturating_from_rational(13, 10); + log::debug!(target: "asset-registry::weight", "LiquidCrowdloan: {}, rate:{:?}", lease, rate); + Some(rate) + } + _ => 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..0e7c54e284 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,99 @@ 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 rate = FixedU128::saturating_from_rational(13, 10); // LCDOT:DOT = 1.3:1 + let lcdot_fee = rate.saturating_mul_int(native_per_second_as_fee(4)); + 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!(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 - lcdot_fee ); + assert_eq!(Tokens::free_balance(LCDOT, &bifrost_reserve_account()), 4 * dollar); }); } From 8aed1c1d8e0e8450a243cd02a14073468d4b52a6 Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Tue, 9 Aug 2022 08:22:16 +0800 Subject: [PATCH 2/2] Apply review suggestions --- modules/asset-registry/src/lib.rs | 16 +++++++++++----- .../relaychain/polkadot_cross_chain_transfer.rs | 14 +++++++++++--- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/modules/asset-registry/src/lib.rs b/modules/asset-registry/src/lib.rs index c7d1e6410a..7953a2fcfe 100644 --- a/modules/asset-registry/src/lib.rs +++ b/modules/asset-registry/src/lib.rs @@ -592,11 +592,17 @@ where let currency = key_to_currency(location); match currency { Some(CurrencyId::LiquidCrowdloan(lease)) => { - // The default rate for all leases is the same. - // LCDOT:DOT = 1.3:1 - let rate = FixedU128::saturating_from_rational(13, 10); - log::debug!(target: "asset-registry::weight", "LiquidCrowdloan: {}, rate:{:?}", lease, rate); - Some(rate) + 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, } 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 0e7c54e284..d730cc45c1 100644 --- a/runtime/integration-tests/src/relaychain/polkadot_cross_chain_transfer.rs +++ b/runtime/integration-tests/src/relaychain/polkadot_cross_chain_transfer.rs @@ -107,8 +107,6 @@ fn liquid_crowdloan_xtokens_works() { let foreign_asset = CurrencyId::ForeignAsset(0); let dollar = dollar(KAR); let minimal_balance = Balances::minimum_balance() / 10; // 10% - let rate = FixedU128::saturating_from_rational(13, 10); // LCDOT:DOT = 1.3:1 - let lcdot_fee = rate.saturating_mul_int(native_per_second_as_fee(4)); let foreign_fee = foreign_per_second_as_fee(4, minimal_balance); MockBifrost::execute_with(|| { @@ -131,6 +129,16 @@ fn liquid_crowdloan_xtokens_works() { }); 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( @@ -187,7 +195,7 @@ fn liquid_crowdloan_xtokens_works() { Acala::execute_with(|| { assert_eq!( Tokens::free_balance(LCDOT, &AccountId::from(BOB)), - 6 * dollar - lcdot_fee + 6 * dollar - foreign_fee ); assert_eq!(Tokens::free_balance(LCDOT, &bifrost_reserve_account()), 4 * dollar); });