diff --git a/libs/traits/src/lib.rs b/libs/traits/src/lib.rs index 4c9548df70..769cd64d4c 100644 --- a/libs/traits/src/lib.rs +++ b/libs/traits/src/lib.rs @@ -492,7 +492,7 @@ pub trait TransferAllowance { send: AccountId, receive: Self::Location, currency: Self::CurrencyId, - ) -> DispatchResult; + ) -> Result, DispatchError>; } /// Trait to retrieve information about currencies. diff --git a/libs/types/src/locations.rs b/libs/types/src/locations.rs index 36e7a314b2..4055b1c5be 100644 --- a/libs/types/src/locations.rs +++ b/libs/types/src/locations.rs @@ -10,14 +10,12 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. +use cfg_primitives::AccountId; use frame_support::RuntimeDebugNoBound; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, Hash}, - AccountId32, -}; +use sp_core::{crypto::AccountId32, H256}; +use sp_runtime::traits::{BlakeTwo256, Hash}; use xcm::{v3::MultiLocation, VersionedMultiLocation}; use crate::domain_address::DomainAddress; @@ -25,7 +23,7 @@ use crate::domain_address::DomainAddress; #[derive(Clone, RuntimeDebugNoBound, Encode, Decode, Eq, PartialEq, MaxEncodedLen, TypeInfo)] pub enum Location { /// Local chain account sending destination. - Local(AccountId32), + Local(AccountId), /// XCM MultiLocation sending destinations. /// Using hash value here as Multilocation is large -- v1 is 512 bytes, but /// next largest is only 40 bytes other values aren't hashed as we have @@ -36,8 +34,8 @@ pub enum Location { } impl From for Location { - fn from(a: AccountId32) -> Self { - Self::Local(a) + fn from(value: AccountId32) -> Self { + Self::Local(value) } } diff --git a/libs/types/src/tokens.rs b/libs/types/src/tokens.rs index 97b5c158bf..4c713318e9 100644 --- a/libs/types/src/tokens.rs +++ b/libs/types/src/tokens.rs @@ -321,6 +321,20 @@ impl From for DomainAddress { } } +#[derive( + Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen, +)] +pub enum FilterCurrency { + All, + Specific(CurrencyId), +} + +impl From for FilterCurrency { + fn from(value: CurrencyId) -> Self { + Self::Specific(value) + } +} + pub mod before { use cfg_primitives::{PoolId, TrancheId}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; diff --git a/pallets/transfer-allowlist/src/benchmarking.rs b/pallets/transfer-allowlist/src/benchmarking.rs index a314772d21..7cfa103ea2 100644 --- a/pallets/transfer-allowlist/src/benchmarking.rs +++ b/pallets/transfer-allowlist/src/benchmarking.rs @@ -12,7 +12,7 @@ #![cfg(feature = "runtime-benchmarks")] -use cfg_types::tokens::CurrencyId; +use cfg_types::tokens::{CurrencyId, FilterCurrency}; use frame_benchmarking::*; use frame_support::{ pallet_prelude::Get, @@ -28,7 +28,7 @@ use sp_runtime::{ use super::*; -const BENCHMARK_CURRENCY_ID: CurrencyId = CurrencyId::ForeignAsset(1); +const BENCHMARK_CURRENCY_ID: FilterCurrency = FilterCurrency::Specific(CurrencyId::ForeignAsset(1)); benchmarks! { where_clause { @@ -36,7 +36,7 @@ benchmarks! { ::AccountId: Into<::Location>, ::Location: From<::AccountId> + EncodeLike<::Location>, ::ReserveCurrency: Currency<::AccountId> + ReservableCurrency<::AccountId>, - T: pallet::Config, + T: pallet::Config, ::BlockNumber: AtLeast32BitUnsigned + Bounded + TypeInfo, <::ReserveCurrency as frame_support::traits::fungible::Inspect<::AccountId,>>::Balance: From } diff --git a/pallets/transfer-allowlist/src/lib.rs b/pallets/transfer-allowlist/src/lib.rs index e4f00360a2..10ebf7ce74 100644 --- a/pallets/transfer-allowlist/src/lib.rs +++ b/pallets/transfer-allowlist/src/lib.rs @@ -73,7 +73,7 @@ pub mod pallet { >>::Reason; /// The current storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] @@ -84,13 +84,7 @@ pub mod pallet { pub trait Config: frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; - type CurrencyId: AssetId + Parameter + Member + Copy + Default; - - // We need this to block restrictions on the native chain currency as - // currently it is impossible to place transfer restrictions - // on native currencies as we simply have no way of - // restricting pallet-balances... - type NativeCurrency: Get; + type CurrencyId: AssetId + Parameter + Member + Copy; /// Currency for holding/unholding with allowlist adding/removal, /// given that the allowlist will be in storage @@ -256,8 +250,6 @@ pub mod pallet { /// Transfer from sending account and currency not allowed to /// destination NoAllowanceForDestination, - /// Native currency can not be restricted with allowances - NativeCurrencyNotRestrictable, } #[pallet::event] @@ -332,11 +324,6 @@ pub mod pallet { ) -> DispatchResult { let account_id = ensure_signed(origin)?; - ensure!( - currency_id != T::NativeCurrency::get(), - Error::::NativeCurrencyNotRestrictable - ); - let allowance_details = match Self::get_account_currency_restriction_count_delay( &account_id, currency_id, @@ -766,24 +753,28 @@ pub mod pallet { send: T::AccountId, receive: Self::Location, currency: T::CurrencyId, - ) -> DispatchResult { + ) -> Result, DispatchError> { match Self::get_account_currency_restriction_count_delay(&send, currency) { Some(AllowanceMetadata { allowance_count: count, .. }) if count > 0 => { let current_block = >::block_number(); - match >::get((&send, ¤cy, receive)) { + match >::get(( + &send, + ¤cy, + receive.clone(), + )) { Some(AllowanceDetails { allowed_at, blocked_at, - }) if current_block >= allowed_at && current_block < blocked_at => Ok(()), + }) if current_block >= allowed_at && current_block < blocked_at => Ok(Some(receive)), _ => Err(DispatchError::from(Error::::NoAllowanceForDestination)), } } // In this case no allowances are set for the sending account & currency, // therefore no restrictions should be in place. - _ => Ok(()), + _ => Ok(None), } } } diff --git a/pallets/transfer-allowlist/src/mock.rs b/pallets/transfer-allowlist/src/mock.rs index b288c8c313..3e08c08ca4 100644 --- a/pallets/transfer-allowlist/src/mock.rs +++ b/pallets/transfer-allowlist/src/mock.rs @@ -10,7 +10,7 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -use cfg_types::tokens::CurrencyId; +use cfg_types::tokens::FilterCurrency; use frame_support::{ parameter_types, traits::{ConstU32, ConstU64}, @@ -122,16 +122,14 @@ impl pallet_balances::Config for Runtime { } parameter_types! { - pub const NativeCurrency: CurrencyId = CurrencyId::Native; pub const HoldId: () = (); } impl transfer_allowlist::Config for Runtime { - type CurrencyId = CurrencyId; + type CurrencyId = FilterCurrency; type Deposit = ConstU64<10>; type HoldId = HoldId; type Location = Location; - type NativeCurrency = NativeCurrency; type ReserveCurrency = Balances; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); diff --git a/pallets/transfer-allowlist/src/tests.rs b/pallets/transfer-allowlist/src/tests.rs index 7be3a58d50..dfd603a641 100644 --- a/pallets/transfer-allowlist/src/tests.rs +++ b/pallets/transfer-allowlist/src/tests.rs @@ -1,41 +1,11 @@ -use cfg_types::tokens::CurrencyId; +use cfg_types::tokens::{CurrencyId, FilterCurrency}; use frame_support::{assert_err, assert_noop, assert_ok}; use super::*; use crate::mock::*; -const TEST_CURRENCY_ID: CurrencyId = CurrencyId::ForeignAsset(1); +const TEST_CURRENCY_ID: FilterCurrency = FilterCurrency::Specific(CurrencyId::ForeignAsset(1)); -#[test] -fn add_transfer_fails_for_native() { - new_test_ext().execute_with(|| { - assert_noop!( - TransferAllowList::add_transfer_allowance( - RuntimeOrigin::signed(SENDER), - ::NativeCurrency::get(), - ACCOUNT_RECEIVER.into(), - ), - Error::::NativeCurrencyNotRestrictable - ); - assert_eq!( - TransferAllowList::get_account_currency_transfer_allowance(( - SENDER, - TEST_CURRENCY_ID, - ::Location::from(ACCOUNT_RECEIVER) - )), - None, - ); - assert_eq!( - TransferAllowList::get_account_currency_restriction_count_delay( - SENDER, - TEST_CURRENCY_ID - ), - None, - ); - - assert_eq!(Balances::reserved_balance(&SENDER), 0); - }) -} #[test] fn add_transfer_allowance_works() { new_test_ext().execute_with(|| { @@ -162,7 +132,7 @@ fn transfer_allowance_allows_correctly_with_allowance_set() { )); assert_eq!( TransferAllowList::allowance(SENDER.into(), ACCOUNT_RECEIVER.into(), TEST_CURRENCY_ID), - Ok(()) + Ok(Some(ACCOUNT_RECEIVER.into())) ) }) } @@ -213,7 +183,7 @@ fn transfer_allowance_blocks_correctly_when_after_blocked_at_block() { )); assert_eq!( TransferAllowList::allowance(SENDER.into(), ACCOUNT_RECEIVER.into(), TEST_CURRENCY_ID), - Ok(()) + Ok(Some(ACCOUNT_RECEIVER.into())) ) }) } diff --git a/runtime/altair/src/lib.rs b/runtime/altair/src/lib.rs index d2296bfed7..80fdd5c1bc 100644 --- a/runtime/altair/src/lib.rs +++ b/runtime/altair/src/lib.rs @@ -81,7 +81,7 @@ use runtime_common::{ oracle::{Feeder, OracleConverterBridge}, permissions::PoolAdminCheck, xcm::AccountIdToMultiLocation, - xcm_transactor, AllowanceDeposit, CurrencyED, HoldId, NativeCurrency, + xcm_transactor, AllowanceDeposit, CurrencyED, HoldId, }; use scale_info::TypeInfo; use sp_api::impl_runtime_apis; @@ -1742,11 +1742,10 @@ impl pallet_order_book::Config for Runtime { } impl pallet_transfer_allowlist::Config for Runtime { - type CurrencyId = CurrencyId; + type CurrencyId = FilterCurrency; type Deposit = AllowanceDeposit; type HoldId = HoldId; type Location = Location; - type NativeCurrency = NativeCurrency; type ReserveCurrency = Balances; type RuntimeEvent = RuntimeEvent; type WeightInfo = weights::pallet_transfer_allowlist::WeightInfo; @@ -1866,6 +1865,7 @@ pub type SignedExtra = ( frame_system::CheckNonce, frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, + runtime_common::transfer_filter::PreBalanceTransferExtension, ); /// Unchecked extrinsic type as expected by this runtime. @@ -1987,7 +1987,7 @@ mod __runtime_api_use { #[cfg(not(feature = "disable-runtime-api"))] use __runtime_api_use::*; -use cfg_types::locations::Location; +use cfg_types::{locations::Location, tokens::FilterCurrency}; use runtime_common::transfer_filter::PreNativeTransfer; #[cfg(not(feature = "disable-runtime-api"))] diff --git a/runtime/altair/src/migrations.rs b/runtime/altair/src/migrations.rs index 3b522fc7ab..7b7b485c11 100644 --- a/runtime/altair/src/migrations.rs +++ b/runtime/altair/src/migrations.rs @@ -53,6 +53,8 @@ pub type UpgradeAltair1034 = ( runtime_common::migrations::precompile_account_codes::Migration, // Migrates EpochExecution V1 to V2 runtime_common::migrations::epoch_execution::Migration, + // Probably not needed, as storage is likely not populated. Mirates currency used in allowlist + runtime_common::migrations::transfer_allowlist_currency::Migration, ); mod asset_registry { diff --git a/runtime/centrifuge/src/lib.rs b/runtime/centrifuge/src/lib.rs index 072a4de3f4..f27ae72b20 100644 --- a/runtime/centrifuge/src/lib.rs +++ b/runtime/centrifuge/src/lib.rs @@ -85,7 +85,7 @@ use runtime_common::{ oracle::{Feeder, OracleConverterBridge}, permissions::PoolAdminCheck, xcm::AccountIdToMultiLocation, - xcm_transactor, AllowanceDeposit, CurrencyED, HoldId, NativeCurrency, + xcm_transactor, AllowanceDeposit, CurrencyED, HoldId, }; use scale_info::TypeInfo; use sp_api::impl_runtime_apis; @@ -1853,11 +1853,10 @@ impl pallet_order_book::Config for Runtime { } impl pallet_transfer_allowlist::Config for Runtime { - type CurrencyId = CurrencyId; + type CurrencyId = FilterCurrency; type Deposit = AllowanceDeposit; type HoldId = HoldId; type Location = Location; - type NativeCurrency = NativeCurrency; type ReserveCurrency = Balances; type RuntimeEvent = RuntimeEvent; type WeightInfo = weights::pallet_transfer_allowlist::WeightInfo; @@ -1996,6 +1995,7 @@ pub type SignedExtra = ( frame_system::CheckNonce, frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, + runtime_common::transfer_filter::PreBalanceTransferExtension, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -2045,7 +2045,7 @@ mod __runtime_api_use { #[cfg(not(feature = "disable-runtime-api"))] use __runtime_api_use::*; -use cfg_types::locations::Location; +use cfg_types::{locations::Location, tokens::FilterCurrency}; use runtime_common::transfer_filter::PreNativeTransfer; #[cfg(not(feature = "disable-runtime-api"))] diff --git a/runtime/centrifuge/src/migrations.rs b/runtime/centrifuge/src/migrations.rs index c6a7cd8949..f6fa50f3b3 100644 --- a/runtime/centrifuge/src/migrations.rs +++ b/runtime/centrifuge/src/migrations.rs @@ -11,8 +11,13 @@ // GNU General Public License for more details. pub type UpgradeCentrifuge1025 = ( + // Burns tokens from other domains that are falsly not burned when they were transferred back + // to their domain burn_unburned::Migration, runtime_common::migrations::epoch_execution::Migration, + // Migrates the currency used in `pallet-transfer-allowlist` from our global currency to a + // special filter currency enum + runtime_common::migrations::transfer_allowlist_currency::Migration, ); // Copyright 2021 Centrifuge Foundation (centrifuge.io). diff --git a/runtime/common/src/migrations/mod.rs b/runtime/common/src/migrations/mod.rs index 4cf7e2373e..7e72e1f5c7 100644 --- a/runtime/common/src/migrations/mod.rs +++ b/runtime/common/src/migrations/mod.rs @@ -17,3 +17,5 @@ pub mod epoch_execution; pub mod nuke; pub mod orml_tokens; pub mod precompile_account_codes; + +pub mod transfer_allowlist_currency; diff --git a/runtime/common/src/migrations/transfer_allowlist_currency.rs b/runtime/common/src/migrations/transfer_allowlist_currency.rs new file mode 100644 index 0000000000..ff7fca6e79 --- /dev/null +++ b/runtime/common/src/migrations/transfer_allowlist_currency.rs @@ -0,0 +1,169 @@ +// Copyright 2023 Centrifuge Foundation (centrifuge.io). +// This file is part of Centrifuge chain project. + +// Centrifuge 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 (see http://www.gnu.org/licenses). + +// Centrifuge 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. + +use cfg_primitives::{AccountId, BlockNumber}; +use cfg_types::{ + locations::Location, + tokens::{CurrencyId, FilterCurrency}, +}; +use frame_support::{ + dispatch::GetStorageVersion, + pallet_prelude::{NMapKey, OptionQuery}, + storage::types::{StorageDoubleMap, StorageNMap}, + traits::{Get, OnRuntimeUpgrade, StorageInstance}, + weights::Weight, + Blake2_128Concat, Twox64Concat, +}; +use pallet_transfer_allowlist::{AllowanceDetails, AllowanceMetadata}; +#[cfg(feature = "try-runtime")] +use parity_scale_codec::{Decode, Encode}; +#[cfg(feature = "try-runtime")] +use sp_runtime::DispatchError; + +const LOG_PREFIX: &str = "TransferAllowlist-CurrencyMigration: "; + +struct DelayPrefix; +impl StorageInstance for DelayPrefix { + const STORAGE_PREFIX: &'static str = "AccountCurrencyTransferCountDelay"; + + fn pallet_prefix() -> &'static str { + "TransferAllowList" + } +} +type OldAccountCurrencyTransferCountDelay = StorageDoubleMap< + DelayPrefix, + Twox64Concat, + AccountId, + Twox64Concat, + CurrencyId, + AllowanceMetadata, + OptionQuery, +>; + +struct AllowancePrefix; +impl StorageInstance for AllowancePrefix { + const STORAGE_PREFIX: &'static str = "AccountCurrencyTransferAllowance"; + + fn pallet_prefix() -> &'static str { + "TransferAllowList" + } +} +type OldAccountCurrencyTransferAllowance = StorageNMap< + AllowancePrefix, + ( + NMapKey, + NMapKey, + NMapKey, + ), + AllowanceDetails, + OptionQuery, +>; + +pub struct Migration(sp_std::marker::PhantomData); +impl< + T: pallet_transfer_allowlist::Config< + AccountId = AccountId, + Location = Location, + CurrencyId = FilterCurrency, + BlockNumber = BlockNumber, + >, + > OnRuntimeUpgrade for Migration +{ + fn on_runtime_upgrade() -> Weight { + let weight = T::DbWeight::get().reads(1); + if pallet_transfer_allowlist::Pallet::::on_chain_storage_version() != 0 { + log::info!( + "{LOG_PREFIX} Skipping on_runtime_upgrade: executed on wrong storage version. Expected version 0" + ); + return weight; + } + + log::info!("{LOG_PREFIX} Migrating allowlist storage keys started..."); + + let mut counter = 0; + OldAccountCurrencyTransferAllowance::translate::, _>( + |(account, currency_id, location), allowance| { + pallet_transfer_allowlist::AccountCurrencyTransferAllowance::::insert( + (account, FilterCurrency::Specific(currency_id), location), + allowance, + ); + + counter += 1; + // We will remove the storage here, as we are inserting the new storage above + None + }, + ); + + OldAccountCurrencyTransferCountDelay::translate::, _>( + |account, currency_id, delay| { + pallet_transfer_allowlist::AccountCurrencyTransferCountDelay::::insert( + account, + FilterCurrency::Specific(currency_id), + delay, + ); + + counter += 1; + // We will remove the storage here, as we are inserting the new storage above + None + }, + ); + + pallet_transfer_allowlist::STORAGE_VERSION.put::>(); + + log::info!("{LOG_PREFIX} Migrating allowlist storage keys finished."); + + weight.saturating_add(T::DbWeight::get().reads_writes(counter, counter.saturating_mul(2))) + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, DispatchError> { + log::info!("{LOG_PREFIX} PRE UPGRADE: Starting..."); + + let count_allowance = OldAccountCurrencyTransferAllowance::iter().count() as u64; + log::info!("{LOG_PREFIX} Counted {count_allowance} keys in old allowance storage."); + + let count_delay = OldAccountCurrencyTransferCountDelay::iter().count() as u64; + log::info!("{LOG_PREFIX} Counted {count_delay} keys in old delay storage."); + + log::info!("{LOG_PREFIX} PRE UPGRADE: Finished."); + Ok((count_allowance, count_delay).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: sp_std::vec::Vec) -> Result<(), DispatchError> { + log::info!("{LOG_PREFIX} POST UPGRADE: Starting..."); + let (count_allowance_pre, count_delay_pre): (u64, u64) = + Decode::decode(&mut state.as_slice()) + .map_err(|_| DispatchError::Other("{LOG_PREFIX} Failed decoding state."))?; + + let count_allowance_after = + pallet_transfer_allowlist::AccountCurrencyTransferCountDelay::::iter().count() + as u64; + + if count_allowance_after != count_allowance_pre { + log::error!("{LOG_PREFIX} Delay migration failed. Got: {count_allowance_after}, Expected: {count_allowance_pre}"); + } + + let count_delay_after = + pallet_transfer_allowlist::AccountCurrencyTransferCountDelay::::iter().count() + as u64; + + if count_delay_after != count_delay_pre { + log::error!("{LOG_PREFIX} Delay migration failed. Got: {count_delay_after}, Expected: {count_delay_pre}"); + } + + log::info!("{LOG_PREFIX} POST UPGRADE: Finished."); + + Ok(()) + } +} diff --git a/runtime/common/src/transfer_filter.rs b/runtime/common/src/transfer_filter.rs index 322bb0f923..e5e5f5a2fa 100644 --- a/runtime/common/src/transfer_filter.rs +++ b/runtime/common/src/transfer_filter.rs @@ -12,13 +12,19 @@ use cfg_primitives::{AccountId, Balance}; use cfg_traits::{PreConditions, TransferAllowance}; -use cfg_types::{domain_address::DomainAddress, locations::Location, tokens::CurrencyId}; +use cfg_types::{ + domain_address::DomainAddress, + locations::Location, + tokens::{CurrencyId, FilterCurrency}, +}; +use frame_support::{dispatch::TypeInfo, traits::IsSubType, RuntimeDebugNoBound}; use pallet_restricted_tokens::TransferDetails; use pallet_restricted_xtokens::TransferEffects; -use parity_scale_codec::Encode; +use parity_scale_codec::{Decode, Encode}; use sp_core::Hasher; use sp_runtime::{ - traits::{BlakeTwo256, Convert}, + traits::{BlakeTwo256, Convert, DispatchInfoOf, SignedExtension, StaticLookup}, + transaction_validity::{InvalidTransaction, TransactionValidityError}, DispatchError, DispatchResult, TokenError, }; use xcm::v3::{MultiAsset, MultiLocation}; @@ -26,18 +32,25 @@ use xcm::v3::{MultiAsset, MultiLocation}; pub struct PreXcmTransfer(sp_std::marker::PhantomData<(T, C)>); impl< - T: TransferAllowance, + T: TransferAllowance, C: Convert>, > PreConditions> for PreXcmTransfer { type Result = DispatchResult; fn check(t: TransferEffects) -> Self::Result { - let currency_based_check = |sender, destination: MultiLocation, currency| { - T::allowance( - sender, - Location::XCM(BlakeTwo256::hash(&destination.encode())), - currency, + let currency_based_check = |sender: AccountId, destination: MultiLocation, currency| { + amalgamate_allowance( + T::allowance( + sender.clone(), + Location::XCM(BlakeTwo256::hash(&destination.encode())), + FilterCurrency::Specific(currency), + ), + T::allowance( + sender, + Location::XCM(BlakeTwo256::hash(&destination.encode())), + FilterCurrency::All, + ), ) }; @@ -121,24 +134,155 @@ impl< pub struct PreNativeTransfer(sp_std::marker::PhantomData); -impl> +impl> PreConditions> for PreNativeTransfer { type Result = bool; fn check(t: TransferDetails) -> Self::Result { - T::allowance(t.send, Location::Local(t.recv), t.id).is_ok() + amalgamate_allowance( + T::allowance( + t.send.clone(), + Location::Local(t.recv.clone()), + FilterCurrency::Specific(t.id), + ), + T::allowance( + t.send.clone(), + Location::Local(t.recv.clone()), + FilterCurrency::All, + ), + ) + .is_ok() } } pub struct PreLpTransfer(sp_std::marker::PhantomData); -impl> +impl> PreConditions<(AccountId, DomainAddress, CurrencyId)> for PreLpTransfer { type Result = DispatchResult; fn check(t: (AccountId, DomainAddress, CurrencyId)) -> Self::Result { let (sender, receiver, currency) = t; - T::allowance(sender, Location::Address(receiver), currency) + // NOTE: The order of the allowance check here is + amalgamate_allowance( + T::allowance( + sender.clone(), + Location::Address(receiver.clone()), + FilterCurrency::Specific(currency), + ), + T::allowance(sender, Location::Address(receiver), FilterCurrency::All), + ) + } +} + +#[derive( + Clone, Copy, PartialOrd, Ord, PartialEq, Eq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, +)] +#[scale_info(skip_type_params(T))] +pub struct PreBalanceTransferExtension(sp_std::marker::PhantomData); + +#[allow(clippy::new_without_default)] +impl PreBalanceTransferExtension { + pub fn new() -> Self { + Self(sp_std::marker::PhantomData) + } +} + +impl SignedExtension for PreBalanceTransferExtension +where + T: frame_system::Config + + pallet_balances::Config + + pallet_transfer_allowlist::Config + + Sync + + Send, + ::RuntimeCall: IsSubType>, +{ + type AccountId = T::AccountId; + type AdditionalSigned = (); + type Call = T::RuntimeCall; + type Pre = (); + + const IDENTIFIER: &'static str = "PreBalanceTransferExtension"; + + fn additional_signed(&self) -> Result { + Ok(()) + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + _: &DispatchInfoOf, + _: usize, + ) -> Result { + let recv: T::AccountId = if let Some(call) = + IsSubType::>::is_sub_type(call) + { + match call { + pallet_balances::Call::transfer { dest, .. } + | pallet_balances::Call::transfer_all { dest, .. } + | pallet_balances::Call::transfer_allow_death { dest, .. } + | pallet_balances::Call::transfer_keep_alive { dest, .. } => { + ::Lookup::lookup(dest.clone()) + .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Call))? + } + + // If the call is not a transfer we are fine with it to go through without futher + // checks + _ => return Ok(()), + } + } else { + return Ok(()); + }; + + amalgamate_allowance( + pallet_transfer_allowlist::pallet::Pallet::::allowance( + who.clone(), + Location::Local(recv.clone()), + FilterCurrency::All, + ), + pallet_transfer_allowlist::pallet::Pallet::::allowance( + who.clone(), + Location::Local(recv.clone()), + FilterCurrency::Specific(CurrencyId::Native), + ), + ) + .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Custom(255))) + } +} + +fn amalgamate_allowance( + first: Result, DispatchError>, + second: Result, DispatchError>, +) -> DispatchResult { + match (first, second) { + // There is an allowance set for `Specific(id)`, but NOT for the given recv + // There is an allowance set for `All`, but NOT for the given recv + (Err(e), Err(_)) => Err(e), + // There is an allowance set for `Specific(id)`, but NOT for the given recv + // There is an allowance set for `All`, for the given recv + (Err(_), Ok(Some(_))) => Ok(()), + // There is an allowance set for `Specific(id)`, for the given recv + // There is an allowance set for `All`, but NOT for the given recv + (Ok(Some(_)), Err(_)) => Ok(()), + // There is NO allowance set for `Specific(id)` + // There is an allowance set for `All`, but NOT for the given recv + (Ok(None), Err(e)) => Err(e), + // There is an allowance set for `Specific(id)`, but NOT for the given recv + // There is NO allowance set for `All` + (Err(e), Ok(None)) => Err(e), + // There is an allowance set for `Specific(id)`, for the given recv + // There is an allowance set for `All`, for the given recv + (Ok(Some(_)), Ok(Some(_))) => Ok(()), + // There is NO allowance set for `Specific(id)` + // There is NO allowance set for `All` + (Ok(None), Ok(None)) => Ok(()), + // There is an allowance set for `Specific(id)`, for the given recv + // There is NO allowance set for `All` + (Ok(Some(_)), Ok(None)) => Ok(()), + // There is NO allowance set for `Specific(id)` + // There is an allowance set for `All`, for the given recv + (Ok(None), Ok(Some(_))) => Ok(()), } } diff --git a/runtime/development/src/lib.rs b/runtime/development/src/lib.rs index 31e447063f..17771518ab 100644 --- a/runtime/development/src/lib.rs +++ b/runtime/development/src/lib.rs @@ -93,7 +93,7 @@ use runtime_common::{ oracle::{Feeder, OracleConverterBridge}, permissions::PoolAdminCheck, xcm::AccountIdToMultiLocation, - xcm_transactor, AllowanceDeposit, CurrencyED, HoldId, NativeCurrency, + xcm_transactor, AllowanceDeposit, CurrencyED, HoldId, }; use scale_info::TypeInfo; use sp_api::impl_runtime_apis; @@ -1814,11 +1814,10 @@ impl pallet_block_rewards::Config for Runtime { } impl pallet_transfer_allowlist::Config for Runtime { - type CurrencyId = CurrencyId; + type CurrencyId = FilterCurrency; type Deposit = AllowanceDeposit; type HoldId = HoldId; type Location = Location; - type NativeCurrency = NativeCurrency; type ReserveCurrency = Balances; type RuntimeEvent = RuntimeEvent; type WeightInfo = weights::pallet_transfer_allowlist::WeightInfo; @@ -2013,6 +2012,7 @@ pub type SignedExtra = ( frame_system::CheckNonce, frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, + runtime_common::transfer_filter::PreBalanceTransferExtension, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -2120,6 +2120,7 @@ mod __runtime_api_use { #[cfg(not(feature = "disable-runtime-api"))] use __runtime_api_use::*; +use cfg_types::tokens::FilterCurrency; use runtime_common::{remarks::Remark, transfer_filter::PreNativeTransfer}; #[cfg(not(feature = "disable-runtime-api"))] diff --git a/runtime/development/src/migrations.rs b/runtime/development/src/migrations.rs index 8ad8345057..b835785bac 100644 --- a/runtime/development/src/migrations.rs +++ b/runtime/development/src/migrations.rs @@ -10,5 +10,7 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -pub type UpgradeDevelopment1038 = - runtime_common::migrations::epoch_execution::Migration; +pub type UpgradeDevelopment1038 = ( + runtime_common::migrations::epoch_execution::Migration, + runtime_common::migrations::transfer_allowlist_currency::Migration, +); diff --git a/runtime/integration-tests/src/generic/cases/liquidity_pools.rs b/runtime/integration-tests/src/generic/cases/liquidity_pools.rs index 24fdcef0c2..5f51b242da 100644 --- a/runtime/integration-tests/src/generic/cases/liquidity_pools.rs +++ b/runtime/integration-tests/src/generic/cases/liquidity_pools.rs @@ -219,7 +219,7 @@ pub mod utils { } pub fn ausd(amount: Balance) -> Balance { - amount * dollar(currency_decimals::AUSD) + amount * decimals(currency_decimals::AUSD) } pub fn ausd_fee() -> Balance { @@ -227,14 +227,14 @@ pub mod utils { } pub fn cfg(amount: Balance) -> Balance { - amount * dollar(currency_decimals::NATIVE) + amount * decimals(currency_decimals::NATIVE) } pub fn cfg_fee() -> Balance { fee(currency_decimals::NATIVE) } - pub fn dollar(decimals: u32) -> Balance { + pub fn decimals(decimals: u32) -> Balance { 10u128.saturating_pow(decimals) } @@ -299,7 +299,7 @@ mod development { /// * Two tranches /// * AUSD as pool currency with max reserve 10k. pub fn create_ausd_pool(pool_id: u64) { - create_currency_pool::(pool_id, AUSD_CURRENCY_ID, dollar(currency_decimals::AUSD)) + create_currency_pool::(pool_id, AUSD_CURRENCY_ID, decimals(currency_decimals::AUSD)) } /// Creates a new pool for for the given id with the provided currency. @@ -1233,7 +1233,7 @@ mod development { assert_eq!( orml_tokens::Pallet::::free_balance(GLMR_CURRENCY_ID, &gateway_sender), // Ensure it only charged the 0.2 GLMR of fee - DEFAULT_BALANCE_GLMR - dollar(18).saturating_div(5) + DEFAULT_BALANCE_GLMR - decimals(18).saturating_div(5) ); }); } @@ -1487,7 +1487,7 @@ mod development { )); // Create pool - create_currency_pool::(pool_id, currency_id, 10_000 * dollar(12)); + create_currency_pool::(pool_id, currency_id, 10_000 * decimals(12)); // Should fail if asset is not payment currency assert_noop!( @@ -1722,7 +1722,7 @@ mod development { )); // Create pool - create_currency_pool::(pool_id, currency_id, 10_000 * dollar(12)); + create_currency_pool::(pool_id, currency_id, 10_000 * decimals(12)); // Should fail if asset is not payment currency assert_noop!( @@ -2002,7 +2002,7 @@ mod development { env.parachain_state_mut(|| { let pool_id = DEFAULT_POOL_ID; - let amount = 10 * dollar(12); + let amount = 10 * decimals(12); let investor: AccountId = AccountConverter::::convert( (DOMAIN_MOONBEAM, Keyring::Bob.into()), ); @@ -2066,7 +2066,7 @@ mod development { env.parachain_state_mut(|| { let pool_id = DEFAULT_POOL_ID; - let invest_amount: u128 = 10 * dollar(12); + let invest_amount: u128 = 10 * decimals(12); let decrease_amount = invest_amount / 3; let final_amount = invest_amount - decrease_amount; let investor: AccountId = AccountConverter::::convert( @@ -2160,7 +2160,7 @@ mod development { env.parachain_state_mut(|| { let pool_id = DEFAULT_POOL_ID; - let invest_amount = 10 * dollar(12); + let invest_amount = 10 * decimals(12); let investor: AccountId = AccountConverter::::convert( (DOMAIN_MOONBEAM, Keyring::Bob.into()), ); @@ -2275,7 +2275,7 @@ mod development { env.parachain_state_mut(|| { let pool_id = DEFAULT_POOL_ID; - let amount = 10 * dollar(12); + let amount = 10 * decimals(12); let investor: AccountId = AccountConverter::::convert( (DOMAIN_MOONBEAM, Keyring::Bob.into()), ); @@ -2449,7 +2449,7 @@ mod development { env.parachain_state_mut(|| { let pool_id = DEFAULT_POOL_ID; - let invest_amount = 10 * dollar(12); + let invest_amount = 10 * decimals(12); let investor: AccountId = AccountConverter::::convert( (DOMAIN_MOONBEAM, Keyring::Bob.into()), ); @@ -2734,7 +2734,7 @@ mod development { env.parachain_state_mut(|| { let pool_id = DEFAULT_POOL_ID; - let amount = 10 * dollar(12); + let amount = 10 * decimals(12); let investor: AccountId = AccountConverter::::convert( (DOMAIN_MOONBEAM, Keyring::Bob.into()), ); @@ -2798,7 +2798,7 @@ mod development { env.parachain_state_mut(|| { let pool_id = DEFAULT_POOL_ID; - let redeem_amount = 10 * dollar(12); + let redeem_amount = 10 * decimals(12); let decrease_amount = redeem_amount / 3; let final_amount = redeem_amount - decrease_amount; let investor: AccountId = AccountConverter::::convert( @@ -2929,7 +2929,7 @@ mod development { env.parachain_state_mut(|| { let pool_id = DEFAULT_POOL_ID; - let redeem_amount = 10 * dollar(12); + let redeem_amount = 10 * decimals(12); let investor: AccountId = AccountConverter::::convert( (DOMAIN_MOONBEAM, Keyring::Bob.into()), ); @@ -3041,7 +3041,7 @@ mod development { env.parachain_state_mut(|| { let pool_id = DEFAULT_POOL_ID; - let amount = 10 * dollar(12); + let amount = 10 * decimals(12); let investor: AccountId = AccountConverter::::convert( (DOMAIN_MOONBEAM, Keyring::Bob.into()), ); @@ -3216,7 +3216,7 @@ mod development { env.parachain_state_mut(|| { let pool_id = DEFAULT_POOL_ID; - let redeem_amount = 10 * dollar(12); + let redeem_amount = 10 * decimals(12); let investor: AccountId = AccountConverter::::convert( (DOMAIN_MOONBEAM, Keyring::Bob.into()), ); @@ -3479,7 +3479,7 @@ mod development { env.parachain_state_mut(|| { let pool_id = DEFAULT_POOL_ID; - let invest_amount: u128 = 10 * dollar(12); + let invest_amount: u128 = 10 * decimals(12); let decrease_amount = invest_amount + 1; let investor: AccountId = AccountConverter::::convert(( @@ -3534,7 +3534,7 @@ mod development { env.parachain_state_mut(|| { let pool_id = DEFAULT_POOL_ID; - let redeem_amount: u128 = 10 * dollar(12); + let redeem_amount: u128 = 10 * decimals(12); let decrease_amount = redeem_amount + 1; let investor: AccountId = AccountConverter::::convert(( @@ -3594,7 +3594,7 @@ mod development { env.parachain_state_mut(|| { let pool_id = DEFAULT_POOL_ID; - let amount: u128 = 10 * dollar(12); + let amount: u128 = 10 * decimals(12); let investor: AccountId = AccountConverter::::convert(( DOMAIN_MOONBEAM, @@ -3685,7 +3685,7 @@ mod development { env.parachain_state_mut(|| { let pool_id = DEFAULT_POOL_ID; - let amount: u128 = 10 * dollar(12); + let amount: u128 = 10 * decimals(12); let investor: AccountId = AccountConverter::::convert(( DOMAIN_MOONBEAM, @@ -3797,7 +3797,7 @@ mod development { let pool_currency = AUSD_CURRENCY_ID; let currency_decimals = currency_decimals::AUSD; let foreign_currency: CurrencyId = USDT_CURRENCY_ID; - let amount = 6 * dollar(18); + let amount = 6 * decimals(18); create_currency_pool::( pool_id, @@ -3892,7 +3892,7 @@ mod development { let pool_currency = AUSD_CURRENCY_ID; let currency_decimals = currency_decimals::AUSD; let foreign_currency: CurrencyId = USDT_CURRENCY_ID; - let amount = 6 * dollar(18); + let amount = 6 * decimals(18); create_currency_pool::( pool_id, @@ -3990,7 +3990,7 @@ mod development { let pool_currency = AUSD_CURRENCY_ID; let currency_decimals = currency_decimals::AUSD; let foreign_currency: CurrencyId = USDT_CURRENCY_ID; - let amount = 6 * dollar(18); + let amount = 6 * decimals(18); create_currency_pool::( pool_id, @@ -4059,7 +4059,7 @@ mod development { let pool_currency = AUSD_CURRENCY_ID; let currency_decimals = currency_decimals::AUSD; let foreign_currency: CurrencyId = USDT_CURRENCY_ID; - let amount = 6 * dollar(18); + let amount = 6 * decimals(18); create_currency_pool::( pool_id, @@ -4145,7 +4145,7 @@ mod development { let pool_currency: CurrencyId = AUSD_CURRENCY_ID; let foreign_currency: CurrencyId = USDT_CURRENCY_ID; let pool_currency_decimals = currency_decimals::AUSD; - let invest_amount_pool_denominated: u128 = 6 * dollar(18); + let invest_amount_pool_denominated: u128 = 6 * decimals(18); let sending_domain_locator = Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()); create_currency_pool::( @@ -4272,7 +4272,7 @@ mod development { let pool_currency: CurrencyId = AUSD_CURRENCY_ID; let foreign_currency: CurrencyId = USDT_CURRENCY_ID; let pool_currency_decimals = currency_decimals::AUSD; - let invest_amount_pool_denominated: u128 = 6 * dollar(18); + let invest_amount_pool_denominated: u128 = 6 * decimals(18); create_currency_pool::( pool_id, pool_currency, @@ -4466,7 +4466,7 @@ mod development { let pool_currency: CurrencyId = AUSD_CURRENCY_ID; let foreign_currency: CurrencyId = USDT_CURRENCY_ID; let pool_currency_decimals = currency_decimals::AUSD; - let invest_amount_pool_denominated: u128 = 10 * dollar(18); + let invest_amount_pool_denominated: u128 = 10 * decimals(18); create_currency_pool::( pool_id, pool_currency, @@ -4670,7 +4670,7 @@ mod development { let pool_currency: CurrencyId = AUSD_CURRENCY_ID; let foreign_currency: CurrencyId = USDT_CURRENCY_ID; let pool_currency_decimals = currency_decimals::AUSD; - let invest_amount_pool_denominated: u128 = 10 * dollar(18); + let invest_amount_pool_denominated: u128 = 10 * decimals(18); let swap_order_id = 1; create_currency_pool::( pool_id, @@ -4958,7 +4958,7 @@ mod development { let pool_currency: CurrencyId = AUSD_CURRENCY_ID; let foreign_currency: CurrencyId = USDT_CURRENCY_ID; let pool_currency_decimals = currency_decimals::AUSD; - let invest_amount_pool_denominated: u128 = 10 * dollar(18); + let invest_amount_pool_denominated: u128 = 10 * decimals(18); let swap_order_id = 1; create_currency_pool::( pool_id, @@ -5356,7 +5356,7 @@ mod development { let pool_currency: CurrencyId = AUSD_CURRENCY_ID; let foreign_currency: CurrencyId = USDT_CURRENCY_ID; let pool_currency_decimals = currency_decimals::AUSD; - let invest_amount_pool_denominated: u128 = 10 * dollar(18); + let invest_amount_pool_denominated: u128 = 10 * decimals(18); let swap_order_id = 1; create_currency_pool::( pool_id, @@ -5472,7 +5472,7 @@ mod development { let pool_currency: CurrencyId = AUSD_CURRENCY_ID; let foreign_currency: CurrencyId = USDT_CURRENCY_ID; let pool_currency_decimals = currency_decimals::AUSD; - let invest_amount_pool_denominated: u128 = 10 * dollar(18); + let invest_amount_pool_denominated: u128 = 10 * decimals(18); let swap_order_id = 1; create_currency_pool::( pool_id, @@ -5892,7 +5892,7 @@ mod development { let pool_currency: CurrencyId = AUSD_CURRENCY_ID; let foreign_currency: CurrencyId = USDT_CURRENCY_ID; let pool_currency_decimals = currency_decimals::AUSD; - let invest_amount_pool_denominated: u128 = 10 * dollar(18); + let invest_amount_pool_denominated: u128 = 10 * decimals(18); let swap_order_id = 1; create_currency_pool::( pool_id, @@ -7172,7 +7172,7 @@ mod development { transact_required_weight_at_most: Default::default(), overall_weight: Default::default(), fee_currency: currency_id, - fee_amount: dollar(18).saturating_div(5), + fee_amount: decimals(18).saturating_div(5), }, _marker: Default::default(), }, @@ -7210,7 +7210,7 @@ mod development { transact_required_weight_at_most: Default::default(), overall_weight: Default::default(), fee_currency: currency_id, - fee_amount: dollar(18).saturating_div(5), + fee_amount: decimals(18).saturating_div(5), }, _marker: Default::default(), }, @@ -7426,15 +7426,15 @@ mod altair { } pub fn air(amount: Balance) -> Balance { - amount * dollar(currency_decimals::NATIVE) + amount * decimals(currency_decimals::NATIVE) } pub fn ksm(amount: Balance) -> Balance { - amount * dollar(currency_decimals::KSM) + amount * decimals(currency_decimals::KSM) } - pub fn foreign(amount: Balance, decimals: u32) -> Balance { - amount * dollar(decimals) + pub fn foreign(amount: Balance, num_decimals: u32) -> Balance { + amount * decimals(num_decimals) } pub fn air_fee() -> Balance { @@ -8448,7 +8448,7 @@ mod centrifuge { /// Register CFG in the asset registry. /// It should be executed within an externalities environment. - pub fn register_cfg() { + pub fn register_cfg(para_id: u32) { let meta: AssetMetadata = AssetMetadata { decimals: 18, name: "Centrifuge".into(), @@ -8457,7 +8457,7 @@ mod centrifuge { location: Some(VersionedMultiLocation::V3(MultiLocation::new( 1, X2( - Parachain(T::FudgeHandle::PARA_ID), + Parachain(para_id), general_key(parachains::polkadot::centrifuge::CFG_KEY), ), ))), @@ -8544,19 +8544,19 @@ mod centrifuge { } pub fn dot(amount: Balance) -> Balance { - amount * dollar(10) + amount * decimals(10) } pub fn lp_eth_usdc(amount: Balance) -> Balance { - amount * dollar(6) + amount * decimals(6) } pub fn usdc(amount: Balance) -> Balance { - amount * dollar(6) + amount * decimals(6) } - pub fn foreign(amount: Balance, decimals: u32) -> Balance { - amount * dollar(decimals) + pub fn foreign(amount: Balance, num_decimals: u32) -> Balance { + amount * decimals(num_decimals) } pub fn transfer_dot_from_relay_chain(env: &mut FudgeEnv) { @@ -8742,7 +8742,7 @@ mod centrifuge { X1(general_key(parachains::polkadot::centrifuge::CFG_KEY)), ); - register_cfg::(); + register_cfg::(T::FudgeHandle::PARA_ID); assert_eq!( >::convert(cfg_location_inner), @@ -8878,6 +8878,8 @@ mod centrifuge { } mod restricted_transfers { + use cfg_types::tokens::{CurrencyId::Native, FilterCurrency}; + use super::*; use crate::generic::envs::runtime_env::RuntimeEnv; @@ -8901,12 +8903,212 @@ mod centrifuge { assert_ok!( pallet_transfer_allowlist::Pallet::::add_transfer_allowance( RawOrigin::Signed(account.into()).into(), - asset, + FilterCurrency::Specific(asset), location ) ); } + #[test] + fn _test() { + restrict_cfg_extrinsic::() + } + + fn restrict_cfg_extrinsic() { + let mut env = RuntimeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(TRANSFER_AMOUNT + 10))) + .add(orml_tokens::GenesisConfig:: { + balances: vec![( + Keyring::Alice.to_account_id(), + USDC, + T::ExistentialDeposit::get() + usdc(TRANSFER_AMOUNT), + )], + }) + .storage(), + ); + + let (pre_transfer_alice, pre_transfer_bob, pre_transfer_charlie) = env + .parachain_state_mut(|| { + // NOTE: The para-id is not relevant here + register_cfg::(2031); + + assert_ok!( + pallet_transfer_allowlist::Pallet::::add_transfer_allowance( + RawOrigin::Signed(Keyring::Alice.into()).into(), + FilterCurrency::All, + Location::Local(Keyring::Bob.to_account_id()) + ) + ); + + ( + pallet_balances::Pallet::::free_balance(&Keyring::Alice.to_account_id()), + pallet_balances::Pallet::::free_balance(&Keyring::Bob.to_account_id()), + pallet_balances::Pallet::::free_balance( + &Keyring::Charlie.to_account_id(), + ), + ) + }); + + let call = pallet_balances::Call::::transfer { + dest: Keyring::Charlie.into(), + value: cfg(TRANSFER_AMOUNT), + }; + env.submit_now(Keyring::Alice, call).unwrap(); + + let call = pallet_balances::Call::::transfer { + dest: Keyring::Bob.into(), + value: cfg(TRANSFER_AMOUNT), + }; + let fee = env.submit_now(Keyring::Alice, call).unwrap(); + + // Restrict also CFG local + env.parachain_state(|| { + let after_transfer_alice = + pallet_balances::Pallet::::free_balance(&Keyring::Alice.to_account_id()); + let after_transfer_bob = + pallet_balances::Pallet::::free_balance(&Keyring::Bob.to_account_id()); + let after_transfer_charlie = + pallet_balances::Pallet::::free_balance(&Keyring::Charlie.to_account_id()); + + assert_eq!( + after_transfer_alice, + pre_transfer_alice - cfg(TRANSFER_AMOUNT) - 2 * fee + ); + assert_eq!(after_transfer_bob, pre_transfer_bob + cfg(TRANSFER_AMOUNT)); + assert_eq!(after_transfer_charlie, pre_transfer_charlie); + }); + } + + fn restrict_all() { + let mut env = RuntimeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(TRANSFER_AMOUNT + 10))) + .add(orml_tokens::GenesisConfig:: { + balances: vec![( + Keyring::Alice.to_account_id(), + USDC, + T::ExistentialDeposit::get() + usdc(TRANSFER_AMOUNT), + )], + }) + .storage(), + ); + + // Set allowance + env.parachain_state_mut(|| { + assert_ok!( + pallet_transfer_allowlist::Pallet::::add_transfer_allowance( + RawOrigin::Signed(Keyring::Alice.into()).into(), + FilterCurrency::All, + Location::Local(Keyring::Bob.to_account_id()) + ) + ); + }); + + // Restrict USDC local + env.parachain_state_mut(|| { + register_usdc::(); + + let pre_transfer_alice = + orml_tokens::Pallet::::free_balance(USDC, &Keyring::Alice.to_account_id()); + let pre_transfer_bob = + orml_tokens::Pallet::::free_balance(USDC, &Keyring::Bob.to_account_id()); + let pre_transfer_charlie = + orml_tokens::Pallet::::free_balance(USDC, &Keyring::Charlie.to_account_id()); + + assert_noop!( + pallet_restricted_tokens::Pallet::::transfer( + RawOrigin::Signed(Keyring::Alice.into()).into(), + Keyring::Charlie.into(), + USDC, + lp_eth_usdc(TRANSFER_AMOUNT) + ), + pallet_restricted_tokens::Error::::PreConditionsNotMet + ); + + let after_transfer_alice = + orml_tokens::Pallet::::free_balance(USDC, &Keyring::Alice.to_account_id()); + let after_transfer_charlie = + orml_tokens::Pallet::::free_balance(USDC, &Keyring::Charlie.to_account_id()); + + assert_eq!(after_transfer_alice, pre_transfer_alice); + assert_eq!(after_transfer_charlie, pre_transfer_charlie); + + assert_ok!(pallet_restricted_tokens::Pallet::::transfer( + RawOrigin::Signed(Keyring::Alice.into()).into(), + Keyring::Bob.into(), + USDC, + usdc(TRANSFER_AMOUNT) + ),); + + let after_transfer_alice = + orml_tokens::Pallet::::free_balance(USDC, &Keyring::Alice.to_account_id()); + let after_transfer_bob = + orml_tokens::Pallet::::free_balance(USDC, &Keyring::Bob.to_account_id()); + let after_transfer_charlie = + orml_tokens::Pallet::::free_balance(USDC, &Keyring::Charlie.to_account_id()); + + assert_eq!( + after_transfer_alice, + pre_transfer_alice - usdc(TRANSFER_AMOUNT) + ); + assert_eq!(after_transfer_bob, pre_transfer_bob + usdc(TRANSFER_AMOUNT)); + assert_eq!(after_transfer_charlie, pre_transfer_charlie); + }); + + // Restrict also CFG local + env.parachain_state_mut(|| { + // NOTE: The para-id is not relevant here + register_cfg::(2031); + + let pre_transfer_alice = + pallet_balances::Pallet::::free_balance(&Keyring::Alice.to_account_id()); + let pre_transfer_bob = + pallet_balances::Pallet::::free_balance(&Keyring::Bob.to_account_id()); + let pre_transfer_charlie = + pallet_balances::Pallet::::free_balance(&Keyring::Charlie.to_account_id()); + + assert_noop!( + pallet_restricted_tokens::Pallet::::transfer( + RawOrigin::Signed(Keyring::Alice.into()).into(), + Keyring::Charlie.into(), + Native, + cfg(TRANSFER_AMOUNT) + ), + pallet_restricted_tokens::Error::::PreConditionsNotMet + ); + + let after_transfer_alice = + pallet_balances::Pallet::::free_balance(&Keyring::Alice.to_account_id()); + let after_transfer_charlie = + pallet_balances::Pallet::::free_balance(&Keyring::Charlie.to_account_id()); + + assert_eq!(after_transfer_alice, pre_transfer_alice); + assert_eq!(after_transfer_charlie, pre_transfer_charlie); + + assert_ok!(pallet_restricted_tokens::Pallet::::transfer( + RawOrigin::Signed(Keyring::Alice.into()).into(), + Keyring::Bob.into(), + Native, + cfg(TRANSFER_AMOUNT) + ),); + + let after_transfer_alice = + pallet_balances::Pallet::::free_balance(&Keyring::Alice.to_account_id()); + let after_transfer_bob = + pallet_balances::Pallet::::free_balance(&Keyring::Bob.to_account_id()); + let after_transfer_charlie = + pallet_balances::Pallet::::free_balance(&Keyring::Charlie.to_account_id()); + + assert_eq!( + after_transfer_alice, + pre_transfer_alice - cfg(TRANSFER_AMOUNT) + ); + assert_eq!(after_transfer_bob, pre_transfer_bob + cfg(TRANSFER_AMOUNT)); + assert_eq!(after_transfer_charlie, pre_transfer_charlie); + }); + } + fn restrict_lp_eth_usdc_transfer() { let mut env = RuntimeEnv::::from_parachain_storage( Genesis::default() @@ -9209,7 +9411,7 @@ mod centrifuge { assert_ok!( pallet_transfer_allowlist::Pallet::::add_transfer_allowance( RawOrigin::Signed(Keyring::Alice.into()).into(), - USDC, + FilterCurrency::Specific(USDC), Location::XCM(BlakeTwo256::hash( &MultiLocation::new( 1, @@ -9395,7 +9597,7 @@ mod centrifuge { assert_ok!( pallet_transfer_allowlist::Pallet::::add_transfer_allowance( RawOrigin::Signed(Keyring::Alice.into()).into(), - DOT_ASSET_ID, + FilterCurrency::Specific(DOT_ASSET_ID), allowed_xcm_location() ) ); @@ -9461,6 +9663,7 @@ mod centrifuge { crate::test_for_runtimes!([centrifuge], restrict_usdc_xcm_transfer); crate::test_for_runtimes!([centrifuge], restrict_dot_transfer); crate::test_for_runtimes!([centrifuge], restrict_dot_xcm_transfer); + crate::test_for_runtimes!([centrifuge], restrict_all); } mod transfers { diff --git a/runtime/integration-tests/src/generic/config.rs b/runtime/integration-tests/src/generic/config.rs index a4728f8c70..7befe67601 100644 --- a/runtime/integration-tests/src/generic/config.rs +++ b/runtime/integration-tests/src/generic/config.rs @@ -12,12 +12,12 @@ use cfg_types::{ locations::Location, oracles::OracleKey, permissions::{PermissionScope, Role}, - tokens::{CurrencyId, CustomMetadata, TrancheCurrency}, + tokens::{CurrencyId, CustomMetadata, FilterCurrency, TrancheCurrency}, }; use fp_self_contained::{SelfContainedCall, UncheckedExtrinsic}; use frame_support::{ dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo, RawOrigin}, - traits::{IsType, OriginTrait}, + traits::{IsSubType, IsType, OriginTrait}, Parameter, }; use liquidity_pools_gateway_routers::DomainRouter; @@ -114,7 +114,7 @@ pub trait Runtime: + pallet_proxy::Config + pallet_restricted_tokens::Config + pallet_restricted_xtokens::Config - + pallet_transfer_allowlist::Config + + pallet_transfer_allowlist::Config + pallet_liquidity_pools::Config< CurrencyId = CurrencyId, Balance = Balance, @@ -166,7 +166,8 @@ pub trait Runtime: + From> + From> + From> - + From>; + + From> + + IsSubType>; /// Just the RuntimeEvent type, but redefined with extra bounds. /// You can add `TryInto` and `From` bounds in order to convert pallet @@ -225,6 +226,7 @@ pub trait Runtime: frame_system::CheckNonce, frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, + runtime_common::transfer_filter::PreBalanceTransferExtension, ), >, >; diff --git a/runtime/integration-tests/src/generic/env.rs b/runtime/integration-tests/src/generic/env.rs index 3c0ef75cd9..cf962fef68 100644 --- a/runtime/integration-tests/src/generic/env.rs +++ b/runtime/integration-tests/src/generic/env.rs @@ -178,6 +178,7 @@ pub mod utils { frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(0), + runtime_common::transfer_filter::PreBalanceTransferExtension::::new(), ); let raw_payload = SignedPayload::new(runtime_call.clone(), signed_extra.clone()).unwrap(); diff --git a/runtime/integration-tests/src/generic/envs/runtime_env.rs b/runtime/integration-tests/src/generic/envs/runtime_env.rs index 4064452b46..4c27719dec 100644 --- a/runtime/integration-tests/src/generic/envs/runtime_env.rs +++ b/runtime/integration-tests/src/generic/envs/runtime_env.rs @@ -14,7 +14,11 @@ use sp_api::runtime_decl_for_core::CoreV4; use sp_block_builder::runtime_decl_for_block_builder::BlockBuilderV6; use sp_consensus_aura::{Slot, AURA_ENGINE_ID}; use sp_core::{sr25519::Public, H256}; -use sp_runtime::{traits::Extrinsic, Digest, DigestItem, DispatchError, Storage}; +use sp_runtime::{ + traits::Extrinsic, + transaction_validity::{InvalidTransaction, TransactionValidityError}, + Digest, DigestItem, DispatchError, Storage, +}; use sp_timestamp::Timestamp; use crate::{ @@ -92,7 +96,18 @@ impl Env for RuntimeEnv { utils::create_extrinsic::(who, call, nonce) }); - self.parachain_state_mut(|| T::Api::apply_extrinsic(extrinsic).unwrap())?; + self.parachain_state_mut(|| { + let res = T::Api::apply_extrinsic(extrinsic); + // NOTE: This is our custom error that we are having in the + // `PreBalanceTransferExtension` SignedExtension, so we need to + // catch that here. + if let Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(255))) = res { + Ok(Ok(())) + } else { + res + } + .unwrap() + })?; let fee = self .find_event(|e| match e { @@ -101,7 +116,9 @@ impl Env for RuntimeEnv { } _ => None, }) - .unwrap(); + // NOTE: This is actually not always correct. Even if there is not fee event, there is a + // fee substracted if the `pre_dispatch()` errors out. + .unwrap_or_default(); Ok(fee) } diff --git a/runtime/integration-tests/src/utils/extrinsics.rs b/runtime/integration-tests/src/utils/extrinsics.rs index 7e56c97ccb..1c84165317 100644 --- a/runtime/integration-tests/src/utils/extrinsics.rs +++ b/runtime/integration-tests/src/utils/extrinsics.rs @@ -108,6 +108,7 @@ fn signed_extra_centrifuge(nonce: cfg_primitives::Index) -> CentrifugeSignedExtr frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(0), + runtime_common::transfer_filter::PreBalanceTransferExtension::::new(), ) } @@ -129,6 +130,7 @@ fn sign_centrifuge( (), (), (), + (), ); let raw_payload = SignedPayload::from_raw(call.clone(), extra.clone(), additional); let signature = MultiSignature::Sr25519(raw_payload.using_encoded(|payload| who.sign(payload)));