mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 05:11:02 +00:00
Add claim_assets extrinsic to pallet-xcm (#3403)
If an XCM execution fails or ends with leftover assets, these will be trapped. In order to claim them, a custom XCM has to be executed, with the `ClaimAsset` instruction. However, arbitrary XCM execution is not allowed everywhere yet and XCM itself is still not easy enough to use for users out there with trapped assets. This new extrinsic in `pallet-xcm` will allow these users to easily claim their assets, without concerning themselves with writing arbitrary XCMs. Part of fixing https://github.com/paritytech/polkadot-sdk/issues/3495 --------- Co-authored-by: command-bot <> Co-authored-by: Adrian Catangiu <adrian@parity.io>
This commit is contained in:
committed by
GitHub
parent
c0e52a9ed6
commit
650886683d
@@ -79,6 +79,13 @@ pub trait Config: crate::Config {
|
||||
fn set_up_complex_asset_transfer() -> Option<(Assets, u32, Location, Box<dyn FnOnce()>)> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Gets an asset that can be handled by the AssetTransactor.
|
||||
///
|
||||
/// Used only in benchmarks.
|
||||
///
|
||||
/// Used, for example, in the benchmark for `claim_assets`.
|
||||
fn get_asset() -> Asset;
|
||||
}
|
||||
|
||||
benchmarks! {
|
||||
@@ -341,11 +348,23 @@ benchmarks! {
|
||||
u32::MAX,
|
||||
).unwrap()).collect::<Vec<_>>();
|
||||
crate::Pallet::<T>::expect_response(query_id, Response::PalletsInfo(infos.try_into().unwrap()));
|
||||
|
||||
}: {
|
||||
<crate::Pallet::<T> as QueryHandler>::take_response(query_id);
|
||||
}
|
||||
|
||||
claim_assets {
|
||||
let claim_origin = RawOrigin::Signed(whitelisted_caller());
|
||||
let claim_location = T::ExecuteXcmOrigin::try_origin(claim_origin.clone().into()).map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
|
||||
let asset: Asset = T::get_asset();
|
||||
// Trap assets for claiming later
|
||||
crate::Pallet::<T>::drop_assets(
|
||||
&claim_location,
|
||||
asset.clone().into(),
|
||||
&XcmContext { origin: None, message_id: [0u8; 32], topic: None }
|
||||
);
|
||||
let versioned_assets = VersionedAssets::V4(asset.into());
|
||||
}: _<RuntimeOrigin<T>>(claim_origin.into(), Box::new(versioned_assets), Box::new(VersionedLocation::V4(claim_location)))
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
Pallet,
|
||||
crate::mock::new_test_ext_with_balances(Vec::new()),
|
||||
|
||||
@@ -85,6 +85,7 @@ pub trait WeightInfo {
|
||||
fn migrate_and_notify_old_targets() -> Weight;
|
||||
fn new_query() -> Weight;
|
||||
fn take_response() -> Weight;
|
||||
fn claim_assets() -> Weight;
|
||||
}
|
||||
|
||||
/// fallback implementation
|
||||
@@ -165,6 +166,10 @@ impl WeightInfo for TestWeightInfo {
|
||||
fn take_response() -> Weight {
|
||||
Weight::from_parts(100_000_000, 0)
|
||||
}
|
||||
|
||||
fn claim_assets() -> Weight {
|
||||
Weight::from_parts(100_000_000, 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[frame_support::pallet]
|
||||
@@ -1386,6 +1391,64 @@ pub mod pallet {
|
||||
weight_limit,
|
||||
)
|
||||
}
|
||||
|
||||
/// Claims assets trapped on this pallet because of leftover assets during XCM execution.
|
||||
///
|
||||
/// - `origin`: Anyone can call this extrinsic.
|
||||
/// - `assets`: The exact assets that were trapped. Use the version to specify what version
|
||||
/// was the latest when they were trapped.
|
||||
/// - `beneficiary`: The location/account where the claimed assets will be deposited.
|
||||
#[pallet::call_index(12)]
|
||||
#[pallet::weight({
|
||||
let assets_version = assets.identify_version();
|
||||
let maybe_assets: Result<Assets, ()> = (*assets.clone()).try_into();
|
||||
let maybe_beneficiary: Result<Location, ()> = (*beneficiary.clone()).try_into();
|
||||
match (maybe_assets, maybe_beneficiary) {
|
||||
(Ok(assets), Ok(beneficiary)) => {
|
||||
let ticket: Location = GeneralIndex(assets_version as u128).into();
|
||||
let mut message = Xcm(vec![
|
||||
ClaimAsset { assets: assets.clone(), ticket },
|
||||
DepositAsset { assets: AllCounted(assets.len() as u32).into(), beneficiary },
|
||||
]);
|
||||
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::claim_assets().saturating_add(w))
|
||||
}
|
||||
_ => Weight::MAX
|
||||
}
|
||||
})]
|
||||
pub fn claim_assets(
|
||||
origin: OriginFor<T>,
|
||||
assets: Box<VersionedAssets>,
|
||||
beneficiary: Box<VersionedLocation>,
|
||||
) -> DispatchResult {
|
||||
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
|
||||
log::debug!(target: "xcm::pallet_xcm::claim_assets", "origin: {:?}, assets: {:?}, beneficiary: {:?}", origin_location, assets, beneficiary);
|
||||
// Extract version from `assets`.
|
||||
let assets_version = assets.identify_version();
|
||||
let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
|
||||
let number_of_assets = assets.len() as u32;
|
||||
let beneficiary: Location =
|
||||
(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
|
||||
let ticket: Location = GeneralIndex(assets_version as u128).into();
|
||||
let mut message = Xcm(vec![
|
||||
ClaimAsset { assets, ticket },
|
||||
DepositAsset { assets: AllCounted(number_of_assets).into(), beneficiary },
|
||||
]);
|
||||
let weight =
|
||||
T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
|
||||
let mut hash = message.using_encoded(sp_io::hashing::blake2_256);
|
||||
let outcome = T::XcmExecutor::prepare_and_execute(
|
||||
origin_location,
|
||||
message,
|
||||
&mut hash,
|
||||
weight,
|
||||
weight,
|
||||
);
|
||||
outcome.ensure_complete().map_err(|error| {
|
||||
log::error!(target: "xcm::pallet_xcm::claim_assets", "XCM execution failed with error: {:?}", error);
|
||||
Error::<T>::LocalExecutionIncomplete
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ impl SendXcm for TestSendXcm {
|
||||
msg: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<(Location, Xcm<()>)> {
|
||||
if FAIL_SEND_XCM.with(|q| *q.borrow()) {
|
||||
return Err(SendError::Transport("Intentional send failure used in tests"))
|
||||
return Err(SendError::Transport("Intentional send failure used in tests"));
|
||||
}
|
||||
let pair = (dest.take().unwrap(), msg.take().unwrap());
|
||||
Ok((pair, Assets::new()))
|
||||
@@ -654,6 +654,10 @@ impl super::benchmarking::Config for Test {
|
||||
});
|
||||
Some((assets, fee_index as u32, dest, verify))
|
||||
}
|
||||
|
||||
fn get_asset() -> Asset {
|
||||
Asset { id: AssetId(Location::here()), fun: Fungible(ExistentialDeposit::get()) }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn last_event() -> RuntimeEvent {
|
||||
|
||||
@@ -467,6 +467,57 @@ fn trapped_assets_can_be_claimed() {
|
||||
});
|
||||
}
|
||||
|
||||
// Like `trapped_assets_can_be_claimed` but using the `claim_assets` extrinsic.
|
||||
#[test]
|
||||
fn claim_assets_works() {
|
||||
let balances = vec![(ALICE, INITIAL_BALANCE)];
|
||||
new_test_ext_with_balances(balances).execute_with(|| {
|
||||
// First trap some assets.
|
||||
let trapping_program =
|
||||
Xcm::builder_unsafe().withdraw_asset((Here, SEND_AMOUNT).into()).build();
|
||||
// Even though assets are trapped, the extrinsic returns success.
|
||||
assert_ok!(XcmPallet::execute(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
Box::new(VersionedXcm::V4(trapping_program)),
|
||||
BaseXcmWeight::get() * 2,
|
||||
));
|
||||
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT);
|
||||
|
||||
// Expected `AssetsTrapped` event info.
|
||||
let source: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into();
|
||||
let versioned_assets = VersionedAssets::V4(Assets::from((Here, SEND_AMOUNT)));
|
||||
let hash = BlakeTwo256::hash_of(&(source.clone(), versioned_assets.clone()));
|
||||
|
||||
// Assets were indeed trapped.
|
||||
assert_eq!(
|
||||
last_events(2),
|
||||
vec![
|
||||
RuntimeEvent::XcmPallet(crate::Event::AssetsTrapped {
|
||||
hash,
|
||||
origin: source,
|
||||
assets: versioned_assets
|
||||
}),
|
||||
RuntimeEvent::XcmPallet(crate::Event::Attempted {
|
||||
outcome: Outcome::Complete { used: BaseXcmWeight::get() * 1 }
|
||||
})
|
||||
],
|
||||
);
|
||||
let trapped = AssetTraps::<Test>::iter().collect::<Vec<_>>();
|
||||
assert_eq!(trapped, vec![(hash, 1)]);
|
||||
|
||||
// Now claim them with the extrinsic.
|
||||
assert_ok!(XcmPallet::claim_assets(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
Box::new(VersionedAssets::V4((Here, SEND_AMOUNT).into())),
|
||||
Box::new(VersionedLocation::V4(
|
||||
AccountId32 { network: None, id: ALICE.clone().into() }.into()
|
||||
)),
|
||||
));
|
||||
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE);
|
||||
assert_eq!(AssetTraps::<Test>::iter().collect::<Vec<_>>(), vec![]);
|
||||
});
|
||||
}
|
||||
|
||||
/// Test failure to complete execution reverts intermediate side-effects.
|
||||
///
|
||||
/// XCM program will withdraw and deposit some assets, then fail execution of a further withdraw.
|
||||
|
||||
Reference in New Issue
Block a user