Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

XCM: Allow reclaim of assets dropped from holding #3727

Merged
merged 16 commits into from
Aug 28, 2021
2 changes: 1 addition & 1 deletion runtime/common/src/claims.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1044,7 +1044,7 @@ mod tests {
}

#[test]
fn invalid_attest_transactions_are_recognised() {
fn invalid_attest_transactions_are_recognized() {
new_test_ext().execute_with(|| {
let p = PrevalidateAttests::<Test>::new();
let c = Call::Claims(ClaimsCall::attest(StatementKind::Regular.to_text().to_vec()));
Expand Down
4 changes: 3 additions & 1 deletion runtime/kusama/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1296,7 +1296,9 @@ impl xcm_executor::Config for XcmConfig {
type Weigher = FixedWeightBounds<BaseXcmWeight, Call, MaxInstructions>;
// The weight trader piggybacks on the existing transaction-fee conversion logic.
type Trader = UsingComponents<WeightToFee, KsmLocation, AccountId, Balances, ToAuthor<Runtime>>;
type ResponseHandler = ();
type ResponseHandler = XcmPallet;
type AssetTrap = XcmPallet;
type AssetClaims = XcmPallet;
}

parameter_types! {
Expand Down
4 changes: 3 additions & 1 deletion runtime/rococo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,9 @@ impl xcm_executor::Config for XcmConfig {
type Barrier = Barrier;
type Weigher = FixedWeightBounds<BaseXcmWeight, Call, MaxInstructions>;
type Trader = UsingComponents<WeightToFee, RocLocation, AccountId, Balances, ToAuthor<Runtime>>;
type ResponseHandler = ();
type ResponseHandler = XcmPallet;
type AssetTrap = XcmPallet;
type AssetClaims = XcmPallet;
}

parameter_types! {
Expand Down
2 changes: 2 additions & 0 deletions runtime/test-runtime/src/xcm_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,6 @@ impl xcm_executor::Config for XcmConfig {
type Weigher = FixedWeightBounds<super::BaseXcmWeight, super::Call, MaxInstructions>;
type Trader = DummyWeightTrader;
type ResponseHandler = super::Xcm;
type AssetTrap = super::Xcm;
type AssetClaims = super::Xcm;
}
4 changes: 3 additions & 1 deletion runtime/westend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -938,7 +938,9 @@ impl xcm_executor::Config for XcmConfig {
type Barrier = Barrier;
type Weigher = FixedWeightBounds<BaseXcmWeight, Call, MaxInstructions>;
type Trader = UsingComponents<WeightToFee, WndLocation, AccountId, Balances, ToAuthor<Runtime>>;
type ResponseHandler = ();
type ResponseHandler = XcmPallet;
type AssetTrap = XcmPallet;
type AssetClaims = XcmPallet;
}

/// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior location
Expand Down
3 changes: 2 additions & 1 deletion xcm/pallet-xcm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ log = { version = "0.4.14", default-features = false }

sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }

Expand All @@ -20,7 +21,6 @@ xcm-executor = { path = "../xcm-executor", default-features = false }
[dev-dependencies]
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" }
polkadot-runtime-parachains = { path = "../../runtime/parachains" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
xcm-builder = { path = "../xcm-builder" }
polkadot-parachain = { path = "../../parachain" }
Expand All @@ -31,6 +31,7 @@ std = [
"codec/std",
"serde",
"sp-std/std",
"sp-core/std",
"sp-runtime/std",
"frame-support/std",
"frame-system/std",
Expand Down
62 changes: 58 additions & 4 deletions xcm/pallet-xcm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,12 @@ pub mod pallet {
pallet_prelude::*,
};
use frame_system::{pallet_prelude::*, Config as SysConfig};
use sp_runtime::traits::{AccountIdConversion, BlockNumberProvider};
use xcm_executor::traits::{InvertLocation, OnResponse, WeightBounds};
use sp_core::H256;
use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, BlockNumberProvider, Hash};
use xcm_executor::{
traits::{ClaimAssets, DropAssets, InvertLocation, OnResponse, WeightBounds},
Assets,
};

#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
Expand Down Expand Up @@ -170,6 +174,10 @@ pub mod pallet {
///
/// \[ id \]
ResponseTaken(QueryId),
/// Some assets have been placed in an asset trap.
///
/// \[ hash, origin, assets \]
AssetsTrapped(H256, MultiLocation, VersionedMultiAssets),
}

#[pallet::origin]
Expand Down Expand Up @@ -236,6 +244,14 @@ pub mod pallet {
pub(super) type Queries<T: Config> =
StorageMap<_, Blake2_128Concat, QueryId, QueryStatus<T::BlockNumber>, OptionQuery>;

/// The existing asset traps.
///
/// Key is the blake2 256 hash of (origin, versioned `MultiAssets`) pair. Value is the number of
/// times this pair has been trapped (usually just 1 if it exists at all).
#[pallet::storage]
#[pallet::getter(fn asset_trap)]
pub(super) type AssetTraps<T: Config> = StorageMap<_, Identity, H256, u32, ValueQuery>;

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}

Expand Down Expand Up @@ -553,16 +569,54 @@ pub mod pallet {
}
}

impl<T: Config> DropAssets for Pallet<T> {
fn drop_assets(origin: &MultiLocation, assets: Assets) -> Weight {
if assets.is_empty() {
return 0
}
let versioned = VersionedMultiAssets::from(MultiAssets::from(assets));
let hash = BlakeTwo256::hash_of(&(&origin, &versioned));
AssetTraps::<T>::mutate(hash, |n| *n += 1);
Self::deposit_event(Event::AssetsTrapped(hash, origin.clone(), versioned));
gavofyork marked this conversation as resolved.
Show resolved Hide resolved
// TODO: Put the real weight in there.
0
}
}

impl<T: Config> ClaimAssets for Pallet<T> {
fn claim_assets(
origin: &MultiLocation,
ticket: &MultiLocation,
assets: &MultiAssets,
) -> bool {
let mut versioned = VersionedMultiAssets::from(assets.clone());
match (ticket.parents, &ticket.interior) {
(0, X1(GeneralIndex(i))) =>
versioned = match versioned.into_version(*i as u32) {
Ok(v) => v,
Err(()) => return false,
},
(0, Here) => (),
_ => return false,
};
let hash = BlakeTwo256::hash_of(&(origin, versioned));
match AssetTraps::<T>::get(hash) {
0 => return false,
1 => AssetTraps::<T>::remove(hash),
n => AssetTraps::<T>::insert(hash, n - 1),
}
return true
}
}

impl<T: Config> OnResponse for Pallet<T> {
/// Returns `true` if we are expecting a response from `origin` for query `query_id`.
fn expecting_response(origin: &MultiLocation, query_id: QueryId) -> bool {
if let Some(QueryStatus::Pending { responder, .. }) = Queries::<T>::get(query_id) {
return MultiLocation::try_from(responder).map_or(false, |r| origin == &r)
}
false
}

/// Handler for receiving a `response` from `origin` relating to `query_id`.
fn on_response(
origin: &MultiLocation,
query_id: QueryId,
Expand Down
2 changes: 2 additions & 0 deletions xcm/pallet-xcm/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ impl xcm_executor::Config for XcmConfig {
type Weigher = FixedWeightBounds<BaseXcmWeight, Call, MaxInstructions>;
type Trader = FixedRateOfFungible<CurrencyPerSecond, ()>;
type ResponseHandler = XcmPallet;
type AssetTrap = XcmPallet;
type AssetClaims = XcmPallet;
}

pub type LocalOriginToLocation = SignedToAccountId32<Origin, AccountId, AnyNetwork>;
Expand Down
83 changes: 81 additions & 2 deletions xcm/pallet-xcm/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.

use crate::{mock::*, QueryStatus};
use crate::{mock::*, AssetTraps, QueryStatus};
use frame_support::{assert_noop, assert_ok, traits::Currency};
use polkadot_parachain::primitives::{AccountIdConversion, Id as ParaId};
use sp_runtime::traits::{BlakeTwo256, Hash};
use std::convert::TryInto;
use xcm::{latest::prelude::*, VersionedXcm};
use xcm::{latest::prelude::*, VersionedMultiAssets, VersionedXcm};
use xcm_executor::XcmExecutor;

const ALICE: AccountId = AccountId::new([0u8; 32]);
Expand Down Expand Up @@ -295,3 +296,81 @@ fn execute_withdraw_to_deposit_works() {
);
});
}

/// Test drop/claim assets.
#[test]
fn trapped_assets_can_be_claimed() {
let balances = vec![(ALICE, INITIAL_BALANCE), (BOB, INITIAL_BALANCE)];
new_test_ext_with_balances(balances).execute_with(|| {
let weight = 6 * BaseXcmWeight::get();
let dest: MultiLocation =
Junction::AccountId32 { network: NetworkId::Any, id: BOB.into() }.into();

assert_ok!(XcmPallet::execute(
Origin::signed(ALICE),
Box::new(VersionedXcm::from(Xcm(vec![
WithdrawAsset((Here, SEND_AMOUNT).into()),
buy_execution((Here, SEND_AMOUNT)),
// Don't propagated the error into the result.
SetErrorHandler(Xcm(vec![ClearError])),
// This will make an error.
Trap(0),
// This would succeed, but we never get to it.
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest.clone() },
]))),
weight
));
let source: MultiLocation =
Junction::AccountId32 { network: NetworkId::Any, id: ALICE.into() }.into();
let trapped = AssetTraps::<Test>::iter().collect::<Vec<_>>();
let vma = VersionedMultiAssets::from(MultiAssets::from((Here, SEND_AMOUNT)));
let hash = BlakeTwo256::hash_of(&(source.clone(), vma.clone()));
assert_eq!(
last_events(2),
vec![
Event::XcmPallet(crate::Event::AssetsTrapped(hash.clone(), source, vma)),
Event::XcmPallet(crate::Event::Attempted(Outcome::Complete(
5 * BaseXcmWeight::get()
)))
]
);
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT);
assert_eq!(Balances::total_balance(&BOB), INITIAL_BALANCE);

let expected = vec![(hash, 1u32)];
assert_eq!(trapped, expected);

let weight = 3 * BaseXcmWeight::get();
assert_ok!(XcmPallet::execute(
Origin::signed(ALICE),
Box::new(VersionedXcm::from(Xcm(vec![
ClaimAsset { assets: (Here, SEND_AMOUNT).into(), ticket: Here.into() },
buy_execution((Here, SEND_AMOUNT)),
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest.clone() },
]))),
weight
));

assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT);
assert_eq!(Balances::total_balance(&BOB), INITIAL_BALANCE + SEND_AMOUNT);
assert_eq!(AssetTraps::<Test>::iter().collect::<Vec<_>>(), vec![]);

let weight = 3 * BaseXcmWeight::get();
assert_ok!(XcmPallet::execute(
Origin::signed(ALICE),
Box::new(VersionedXcm::from(Xcm(vec![
ClaimAsset { assets: (Here, SEND_AMOUNT).into(), ticket: Here.into() },
buy_execution((Here, SEND_AMOUNT)),
DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest },
]))),
weight
));
assert_eq!(
last_event(),
Event::XcmPallet(crate::Event::Attempted(Outcome::Incomplete(
BaseXcmWeight::get(),
XcmError::UnknownClaim
)))
);
});
}
10 changes: 10 additions & 0 deletions xcm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,16 @@ pub enum VersionedMultiAssets {
V1(v1::MultiAssets),
}

impl VersionedMultiAssets {
pub fn into_version(self, n: u32) -> Result<Self, ()> {
Ok(match n {
0 => Self::V0(self.try_into()?),
1 | 2 => Self::V1(self.try_into()?),
_ => return Err(()),
})
}
}

impl From<Vec<v0::MultiAsset>> for VersionedMultiAssets {
fn from(x: Vec<v0::MultiAsset>) -> Self {
VersionedMultiAssets::V0(x)
Expand Down
Loading