Skip to content

Implement assets forwarder pallet - #67

Open
georgepisaltu wants to merge 5 commits into
mainfrom
george-asset-forwarding
Open

Implement assets forwarder pallet#67
georgepisaltu wants to merge 5 commits into
mainfrom
george-asset-forwarding

Conversation

@georgepisaltu

Copy link
Copy Markdown
Contributor

This PR adds permissionless forwarding of assets from one chain to another (built for AH to PC trust backed asset registration). As in the paseo example in our repo, the assets being forwarded are force created and can only be minted via XCM transfers and are usable in pools. Force creation means there will be no issuer, so nobody can alter the issuance outside of XCM transfers, which are checked on AH. The owner of these assets will be the forwarder pallet on AH. An existing asset (like HOLLAR on PC) is unaffected as it was force created before this change.

Still TODO:

  • review benchmarks
  • compute weights
  • zombienet test (though we have integration tests which look at the entire flow by examining the XCMP queue)

In another PR, we would need to port this to Polkadot runtimes.

Signed-off-by: georgepisaltu <george.pisaltu@parity.io>
@georgepisaltu georgepisaltu self-assigned this Aug 26, 2026
Comment thread pallets/assets-forwarder/src/lib.rs
Signed-off-by: georgepisaltu <george.pisaltu@parity.io>
@ggwpez ggwpez moved this from Todo to Optional in Runtime releases Aug 27, 2026
Signed-off-by: georgepisaltu <george.pisaltu@parity.io>
@georgepisaltu georgepisaltu added the D9-needs audit audit is needed label Aug 27, 2026

/// Location of the local `pallet-assets` instance the forwarded assets live in. Asset ids
/// are appended to it as `GeneralIndex` junctions and reanchored to the destination.
type AssetsPalletLocation: Get<Location>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this can be deducted from the pallet assets. The index is available through the trait PalletInfo or PalletInfoAccess, then I believe it is standard to make the location of the asset 'Here, PalletIndex, GeneralIndex'.

On the other hand if we wanted to support the forward of foreign asset in this pallet then we would need to specify None as the location here I guess, so that forwarded asset like Eth would keep their same location on the destination (converted to the destination point of view).

It is just a nit, anyway the code will be fine.

type DestinationAccountOf: ConvertLocation<Self::AccountId>;

/// Converts an asset id into the `GeneralIndex` value of its location.
type AssetIdToIndex: Convert<Self::AssetId, u128>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or you can change this to asset id to location and remove the associated type pallet assets location.

So it would support both local assets and foreign assets, foreign assets would convert to themselves.

(Also given Hollar is already created on PC, we would need to take it into account if we ever support foreign asset forwarding. We would need to do a migration to put the forwarded value in the state here on AH, or maybe let the force create fail on PC, anyway not an concern for now)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will just fail on PC, no harm done and state on AH will be updated.

@gui1117 gui1117 Sep 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what would fail on PC? The Hollar forwarding? yes true, we don't even need migration. but this is not my main comment:

My proposition is to generalize this pallet by improving the Config trait:

  • remove AssetsPalletLocation and AssetIdToIndex
  • introduce AssetIdToLocation.

So we could support AH foreign assets to be forwarded to PC as well in the future using the same pallet.

@georgepisaltu
georgepisaltu marked this pull request as ready for review August 31, 2026 07:23
@georgepisaltu
georgepisaltu requested a review from a team August 31, 2026 07:23
let message = Self::build_remote_xcm(&create);
let message_id = Self::send_remote_xcm(origin, message)?;

ForwardedAssets::<T, I>::insert(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you assume all went well so I wonder if we don't need a mechanism to handle failures.
For example clearing the ForwardedAssets entry via an origin-gated call.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using TechnicalMaintenance instead of root would help indeed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a call to remove the entry.

let details =
pallet_assets::Asset::<T, I>::get(&asset_id).ok_or(Error::<T, I>::UnknownAsset)?;
ensure!(
details.status == pallet_assets::AssetStatus::Live,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also check if details.min_balance is not zero?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be fine. I don't see why we should limit what is on AH, if the asset is min balance 0, it is likely not sufficient, or maybe only distributed to some specific individuals.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should aim to mirror the state 1:1 with AH and this also lets us keep this pallet simple. I don't want to reinvent another pallet-assets.

Error::<T, I>::AssetNotLive
);
ensure!(
record.min_balance != details.min_balance ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these assets can be created by anyone, then the owner could be changing these values and requesting the sync without limit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but they pay for the call. They can manually send it even without this particular call wrapper in this pallet by going straight to pallet-xcm. Obviously it will just fail on arrival and it will burn funds, so it's not a valid attack.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the execution fee on the destination chain is waived, but it isn't much, the delivery fee are still charged at the moment.
We can probably ignore or charge a little more with a configurable constant.

freezer: MultiAddress::Id(owner),
min_balance: details.min_balance,
is_sufficient: details.is_sufficient,
is_frozen: false,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can there be a scenario where the remote chain froze an asset on purpose?
In such a scenario this call would unfreeze it.

@georgepisaltu georgepisaltu Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the remote chain isn't a "sovereign" 3rd party parachain, it's a system chain answering to the same governance as PC, therefore it does not need to assert control over the assets mirrored from AH.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we introduce some test using the XCM emulator similarly to what exists on fellowship/runtimes we could have more accurate testing maybe. Anyway it is ok for now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd defer this for later.

Comment thread pallets/assets-forwarder/src/benchmarking.rs

@gui1117 gui1117 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some nits were opened: refactor of the config types and add an assertion to the benchmark. Otherwise all looks good.

/// destination is untouched, so a later re-forward fails there while the replica exists.
#[pallet::call_index(2)]
#[pallet::weight(<T as Config<I>>::WeightInfo::remove_forwarded_asset())]
pub fn remove_forwarded_asset(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this doesn't cover the failure of sync_asset_status.

If we remove the forwarded asset, then the next call to forward_asset will re-recreate the entry and the XCM will fail, and sync_asset_status can't be called because the entry exists and is up to date.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

D9-needs audit audit is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants