diff --git a/Cargo.lock b/Cargo.lock index 0c0ba8f306..0c13c4c6b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8668,7 +8668,7 @@ dependencies = [ [[package]] name = "pallet-xcm-rate-limiter" -version = "0.1.3" +version = "0.1.4" dependencies = [ "cumulus-pallet-xcmp-queue", "frame-benchmarking", @@ -11225,7 +11225,7 @@ dependencies = [ [[package]] name = "runtime-integration-tests" -version = "1.17.0" +version = "1.17.1" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index e62fe929cc..e149e86aca 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runtime-integration-tests" -version = "1.17.0" +version = "1.17.1" description = "Integration tests" authors = ["GalacticCouncil"] edition = "2021" diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs index 1a143bf74a..db957efcef 100644 --- a/integration-tests/src/lib.rs +++ b/integration-tests/src/lib.rs @@ -22,4 +22,19 @@ mod staking; mod transact_call_filter; mod vesting; mod xcm_defer; +mod xcm_rate_limiter; + +#[macro_export] +macro_rules! assert_balance { + ( $who:expr, $asset:expr, $amount:expr) => {{ + assert_eq!(Currencies::free_balance($asset, &$who), $amount); + }}; +} + +#[macro_export] +macro_rules! assert_reserved_balance { + ( $who:expr, $asset:expr, $amount:expr) => {{ + assert_eq!(Currencies::reserved_balance($asset, &$who), $amount); + }}; +} mod xyk; diff --git a/integration-tests/src/polkadot_test_net.rs b/integration-tests/src/polkadot_test_net.rs index e10bdceba1..060c9ed0fa 100644 --- a/integration-tests/src/polkadot_test_net.rs +++ b/integration-tests/src/polkadot_test_net.rs @@ -685,20 +685,6 @@ pub fn init_omnipool() { set_zero_reward_for_referrals(HDX); } -#[macro_export] -macro_rules! assert_balance { - ( $who:expr, $asset:expr, $amount:expr) => {{ - assert_eq!(Currencies::free_balance($asset, &$who), $amount); - }}; -} - -#[macro_export] -macro_rules! assert_reserved_balance { - ( $who:expr, $asset:expr, $amount:expr) => {{ - assert_eq!(Currencies::reserved_balance($asset, &$who), $amount); - }}; -} - pub fn set_zero_reward_for_referrals(asset_id: AssetId) { assert_ok!(Referrals::set_reward_percentage( RawOrigin::Root.into(), diff --git a/integration-tests/src/xcm_rate_limiter.rs b/integration-tests/src/xcm_rate_limiter.rs new file mode 100644 index 0000000000..a1c21763f2 --- /dev/null +++ b/integration-tests/src/xcm_rate_limiter.rs @@ -0,0 +1,262 @@ +#![cfg(test)] + +use crate::polkadot_test_net::*; + +use frame_support::{assert_ok, pallet_prelude::Weight}; +use orml_traits::currency::MultiCurrency; +use pallet_asset_registry::AssetType; +use polkadot_xcm::prelude::*; +use xcm_emulator::TestExt; + +/// Returns the message hash in the `XcmpMessageSent` event at the `n`th last event (1-indexed, so if the second to last +/// event has the hash, pass `2`); +fn get_message_hash_from_event(n: usize) -> Option<[u8; 32]> { + use cumulus_pallet_xcmp_queue::Event; + use hydradx_runtime::RuntimeEvent; + let RuntimeEvent::XcmpQueue(Event::XcmpMessageSent { message_hash }) = &last_hydra_events(n)[0] else { + panic!("expecting to find message sent event"); + }; + Some(*message_hash) +} + +#[test] +fn xcm_rate_limiter_should_limit_aca_when_limit_is_exceeded() { + // Arrange + TestNet::reset(); + + Hydra::execute_with(|| { + assert_ok!(hydradx_runtime::AssetRegistry::set_location( + hydradx_runtime::RuntimeOrigin::root(), + ACA, + hydradx_runtime::AssetLocation(MultiLocation::new(1, X2(Parachain(ACALA_PARA_ID), GeneralIndex(0)))) + )); + + // set an xcm rate limit + assert_ok!(hydradx_runtime::AssetRegistry::update( + hydradx_runtime::RuntimeOrigin::root(), + ACA, + b"ACA".to_vec(), + AssetType::Token, + None, + Some(50 * UNITS), + )); + + assert_eq!(hydradx_runtime::Tokens::free_balance(ACA, &AccountId::from(BOB)), 0); + + //Set it to same as the relay block number should be in XcmDeferFilter + //since we use different RelayChainBlockNumberProvider in runtime-benchmark feature + //where we return frame_system current time + frame_system::Pallet::::set_block_number(4); + }); + + let amount = 100 * UNITS; + let mut message_hash = None; + Acala::execute_with(|| { + // Act + assert_ok!(hydradx_runtime::XTokens::transfer( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + 0, + amount, + Box::new( + MultiLocation::new( + 1, + X2( + Junction::Parachain(HYDRA_PARA_ID), + Junction::AccountId32 { id: BOB, network: None } + ) + ) + .into() + ), + WeightLimit::Limited(Weight::from_parts(399_600_000_000, 0)) + )); + + message_hash = get_message_hash_from_event(2); + + // Assert + assert_eq!( + hydradx_runtime::Balances::free_balance(AccountId::from(ALICE)), + ALICE_INITIAL_NATIVE_BALANCE - amount + ); + }); + + let relay_block = PolkadotRelay::execute_with(polkadot_runtime::System::block_number); + + Hydra::execute_with(|| { + expect_hydra_events(vec![ + cumulus_pallet_xcmp_queue::Event::XcmDeferred { + sender: ACALA_PARA_ID.into(), + sent_at: relay_block, + deferred_to: hydradx_runtime::DeferDuration::get() + relay_block - 1, + message_hash, + index: (hydradx_runtime::DeferDuration::get() + relay_block - 1, 0), + position: 0, + } + .into(), + pallet_relaychain_info::Event::CurrentBlockNumbers { + parachain_block_number: 5, + relaychain_block_number: relay_block + 1, + } + .into(), + ]); + assert_eq!(hydradx_runtime::Tokens::free_balance(ACA, &AccountId::from(BOB)), 0); + }); +} + +#[test] +fn xcm_rate_limiter_should_not_limit_aca_when_limit_is_not_exceeded() { + // Arrange + TestNet::reset(); + + Hydra::execute_with(|| { + assert_ok!(hydradx_runtime::AssetRegistry::set_location( + hydradx_runtime::RuntimeOrigin::root(), + ACA, + hydradx_runtime::AssetLocation(MultiLocation::new(1, X2(Parachain(ACALA_PARA_ID), GeneralIndex(0)))) + )); + + // set an xcm rate limit + assert_ok!(hydradx_runtime::AssetRegistry::update( + hydradx_runtime::RuntimeOrigin::root(), + ACA, + b"ACA".to_vec(), + AssetType::Token, + None, + Some(101 * UNITS), + )); + }); + + let amount = 100 * UNITS; + Acala::execute_with(|| { + // Act + assert_ok!(hydradx_runtime::XTokens::transfer( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + 0, + amount, + Box::new( + MultiLocation::new( + 1, + X2( + Junction::Parachain(HYDRA_PARA_ID), + Junction::AccountId32 { id: BOB, network: None } + ) + ) + .into() + ), + WeightLimit::Limited(Weight::from_parts(399_600_000_000, 0)) + )); + + // Assert + assert_eq!( + hydradx_runtime::Balances::free_balance(AccountId::from(ALICE)), + ALICE_INITIAL_NATIVE_BALANCE - amount + ); + }); + + Hydra::execute_with(|| { + let fee = hydradx_runtime::Tokens::free_balance(ACA, &hydradx_runtime::Treasury::account_id()); + assert_eq!( + hydradx_runtime::Tokens::free_balance(ACA, &AccountId::from(BOB)), + amount - fee + ); + }); +} + +#[test] +fn deferred_messages_should_be_executable_by_root() { + // Arrange + TestNet::reset(); + + Hydra::execute_with(|| { + assert_ok!(hydradx_runtime::AssetRegistry::set_location( + hydradx_runtime::RuntimeOrigin::root(), + ACA, + hydradx_runtime::AssetLocation(MultiLocation::new(1, X2(Parachain(ACALA_PARA_ID), GeneralIndex(0)))) + )); + + // set an xcm rate limit + assert_ok!(hydradx_runtime::AssetRegistry::update( + hydradx_runtime::RuntimeOrigin::root(), + ACA, + b"ACA".to_vec(), + AssetType::Token, + None, + Some(50 * UNITS), + )); + + assert_eq!(hydradx_runtime::Tokens::free_balance(ACA, &AccountId::from(BOB)), 0); + + //Set it to same as the relay block number should be in XcmDeferFilter + //since we use different RelayChainBlockNumberProvider in runtime-benchmark feature + //where we return frame_system current time + frame_system::Pallet::::set_block_number(4); + }); + + let amount = 100 * UNITS; + let mut message_hash = None; + let max_weight = Weight::from_parts(399_600_000_000, 0); + Acala::execute_with(|| { + // Act + assert_ok!(hydradx_runtime::XTokens::transfer( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + 0, + amount, + Box::new( + MultiLocation::new( + 1, + X2( + Junction::Parachain(HYDRA_PARA_ID), + Junction::AccountId32 { id: BOB, network: None } + ) + ) + .into() + ), + WeightLimit::Limited(max_weight), + )); + + message_hash = get_message_hash_from_event(2); + + // Assert + assert_eq!( + hydradx_runtime::Balances::free_balance(AccountId::from(ALICE)), + ALICE_INITIAL_NATIVE_BALANCE - amount + ); + }); + + let relay_block = PolkadotRelay::execute_with(polkadot_runtime::System::block_number); + + Hydra::execute_with(|| { + expect_hydra_events(vec![ + cumulus_pallet_xcmp_queue::Event::XcmDeferred { + sender: ACALA_PARA_ID.into(), + sent_at: relay_block, + deferred_to: hydradx_runtime::DeferDuration::get() + relay_block - 1, + message_hash, + index: (hydradx_runtime::DeferDuration::get() + relay_block - 1, 0), + position: 0, + } + .into(), + pallet_relaychain_info::Event::CurrentBlockNumbers { + parachain_block_number: 5, + relaychain_block_number: relay_block + 1, + } + .into(), + ]); + assert_eq!(hydradx_runtime::Tokens::free_balance(ACA, &AccountId::from(BOB)), 0); + + set_relaychain_block_number(hydradx_runtime::DeferDuration::get() + relay_block); + + assert_eq!(hydradx_runtime::Tokens::free_balance(ACA, &AccountId::from(BOB)), 0); + assert_ok!(hydradx_runtime::XcmpQueue::service_deferred( + hydradx_runtime::RuntimeOrigin::root(), + hydradx_runtime::ReservedXcmpWeight::get(), + ACALA_PARA_ID.into(), + hydradx_runtime::MaxDeferredBuckets::get(), + )); + + let fee = hydradx_runtime::Tokens::free_balance(ACA, &hydradx_runtime::Treasury::account_id()); + assert_eq!( + hydradx_runtime::Tokens::free_balance(ACA, &AccountId::from(BOB)), + amount - fee + ); + }); +} diff --git a/pallets/xcm-rate-limiter/Cargo.toml b/pallets/xcm-rate-limiter/Cargo.toml index e0580fb65c..c562299274 100644 --- a/pallets/xcm-rate-limiter/Cargo.toml +++ b/pallets/xcm-rate-limiter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-xcm-rate-limiter" -version = "0.1.3" +version = "0.1.4" authors = ["GalacticCouncil "] edition = "2021" license = "Apache-2.0" diff --git a/pallets/xcm-rate-limiter/src/lib.rs b/pallets/xcm-rate-limiter/src/lib.rs index 590ea45f36..b2ad6fe393 100644 --- a/pallets/xcm-rate-limiter/src/lib.rs +++ b/pallets/xcm-rate-limiter/src/lib.rs @@ -163,16 +163,6 @@ pub mod pallet { impl Pallet {} } -fn get_loc_and_amount(m: &MultiAsset) -> Option<(MultiLocation, u128)> { - match m.id { - AssetId::Concrete(location) => match m.fun { - Fungibility::Fungible(amount) => Some((location, amount)), - _ => None, - }, - _ => None, - } -} - impl Pallet { fn get_locations_and_amounts(instruction: &Instruction) -> Vec<(MultiLocation, u128)> { use Instruction::*; @@ -186,6 +176,16 @@ impl Pallet { } } +fn get_loc_and_amount(m: &MultiAsset) -> Option<(MultiLocation, u128)> { + match m.id { + AssetId::Concrete(location) => match m.fun { + Fungibility::Fungible(amount) => Some((location, amount)), + _ => None, + }, + _ => None, + } +} + impl XcmDeferFilter for Pallet { fn deferred_by( _para: polkadot_parachain::primitives::Id, diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index 3b40ec1028..981f1ef365 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -174,6 +174,7 @@ construct_runtime!( LBP: pallet_lbp = 73, XYK: pallet_xyk = 74, Referrals: pallet_referrals = 75, + XcmRateLimiter: pallet_xcm_rate_limiter = 76, // ORML related modules Tokens: orml_tokens = 77, diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index 06cf48708a..004b97d4d8 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -18,8 +18,9 @@ use frame_system::EnsureRoot; use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key}; use orml_xcm_support::{DepositToAlternative, IsNativeConcrete, MultiNativeAsset}; use pallet_xcm::XcmPassthrough; -use polkadot_parachain::primitives::Sibling; +use polkadot_parachain::primitives::{RelayChainBlockNumber, Sibling}; use polkadot_xcm::v3::{prelude::*, Weight as XcmWeight}; +use primitives::Price; use scale_info::TypeInfo; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, @@ -161,7 +162,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type MaxDeferredBuckets = MaxDeferredBuckets; type MaxBucketsProcessed = MaxBucketsProcessed; type RelayChainBlockNumberProvider = RelayChainBlockNumberProvider; - type XcmDeferFilter = (); + type XcmDeferFilter = XcmRateLimiter; } impl cumulus_pallet_dmp_queue::Config for Runtime { @@ -240,6 +241,39 @@ impl pallet_xcm::Config for Runtime { type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); } + +#[test] +fn defer_duration_configuration() { + use sp_runtime::{traits::One, FixedPointNumber, FixedU128}; + /// Calculate the configuration value for the defer duration based on the desired defer duration and + /// the threshold percentage when to start deferring. + /// - `defer_by`: the desired defer duration when reaching the rate limit + /// - `a``: the fraction of the rate limit where we start deferring, e.g. 0.9 + fn defer_duration(defer_by: u32, a: FixedU128) -> u32 { + assert!(a < FixedU128::one()); + // defer_by * a / (1 - a) + (FixedU128::one() / (FixedU128::one() - a)).saturating_mul_int(a.saturating_mul_int(defer_by)) + } + assert_eq!( + defer_duration(600 * 4, FixedU128::from_rational(9, 10)), + DeferDuration::get() + ); +} +parameter_types! { + pub DeferDuration: RelayChainBlockNumber = 600 * 36; // 36 hours + pub MaxDeferDuration: RelayChainBlockNumber = 600 * 24 * 10; // 10 days +} + +impl pallet_xcm_rate_limiter::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type AssetId = AssetId; + type DeferDuration = DeferDuration; + type MaxDeferDuration = MaxDeferDuration; + type RelayBlockNumberProvider = RelayChainBlockNumberProvider; + type CurrencyIdConvert = CurrencyIdConvert; + type RateLimitFor = pallet_asset_registry::XcmRateLimitsInRegistry; +} + pub struct CurrencyIdConvert; use primitives::constants::chain::CORE_ASSET_ID;