Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Treasury Spend Detects Relative Locations of Native Asset #233

Conversation

muharem
Copy link
Contributor

@muharem muharem commented Mar 11, 2024

Treasury Spend detects the relative locations of native asset to compare origin's spend limits.

When a spend is approved for a native asset located on Asset Hub, the AssetRate pallet instance does not recognize the location (parent: 1, here) as a native asset. Therefore, it requires the asset rate to be set in advance to the rate of 1. To address this issue in this PR, we introduce new types to wrap the AssetRate implementation of balance conversion. These types match the native asset's relative locations and result in a 1:1 balance conversion in such cases.

In addition we provide integration tests to cover these cases.

Examples:
Treasury pallet on Relay Chain accepts the spend of the native asset on Asset Hub with it's ID presented as VersionedLocatableAsset type

{
  `location`: (0, Parachain(1000)),
  `asset_id`: (1, Here),
}

and it's balance should be converted 1:1 by treasury pallet's BalanceConverter type.

Note: I plan to introduce a similar but configurable implementation in the polkadot-sdk and utilize it here. However, to get this change in the next release, I open this PR first.

Frame version: paritytech/polkadot-sdk#3659

V3 { location, asset_id } => (location.try_into()?, asset_id.try_into()?),
V4 { location, asset_id } => (location, asset_id),
};
if asset_id.0.contains_parents_only(1) &&
Copy link
Contributor

Choose a reason for hiding this comment

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

From the Relay Chain, wouldn't this be contains_parents_only(0)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

asset_id will be relative to the parachain, this is why it's only parent 1
when I tried (0, here) on Asset Hub, it was not recognised as native asset, only (1, here)

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, this is checking a message from AH, and it hasn't been reanchored.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Treasury pallet receives the locatable asset as

VersionedLocatableAsset: {
  `location`: (0, Parachain(1000)),
  `asset_id`: (1, Here),
}

and it should know it's a native asset

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated the PR description to include the example

Copy link
Contributor

Choose a reason for hiding this comment

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

You should add it to this type's (or impl's) doc comment as well

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added

@muharem muharem changed the title Treasury Spend determines relative locations of native asset Treasury Spend Detects Relative Locations of Native Asset Mar 13, 2024
@muharem muharem requested a review from joepetrowski March 13, 2024 09:36
Copy link
Contributor

@joepetrowski joepetrowski left a comment

Choose a reason for hiding this comment

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

A few questions, mostly about origins/locations.

as_origin: bx!(OriginCaller::system(RawOrigin::Signed(treasury_account))),
call: bx!(RuntimeCall::XcmPallet(pallet_xcm::Call::<Runtime>::teleport_assets {
dest: bx!(VersionedLocation::V4(asset_hub_location.clone())),
beneficiary: bx!(VersionedLocation::V4(treasury_location_on_asset_hub)),
Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting that the beneficiary is actually the Location of the Relay's Treasury pallet and not the converted local Account ID.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since the implementation Pay trait which used to execute the spend uses xcm TransferAsset instruction, we can use the pallet's location from which the xcm-executor will derive the account id.

Comment on lines +78 to +79
// Dispatched from Root to `despatch_as` `Signed(treasury_account)`.
assert_ok!(teleport_call.dispatch(root));
Copy link
Contributor

Choose a reason for hiding this comment

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

We allow Treasurer to send messages. Could we just do a referendum to teleport_assets on the Treasurer track?
https://github.com/polkadot-fellows/runtimes/blob/ba359a4/relay/polkadot/src/xcm_config.rs#L286

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the type SovereignAccountOf we will need to associate a Treasurer Origin with Treasury::account_id(). Not sure if this is right, I think pallet's location account id can be Treasury::account_id(). And also it would mean that Treasurer Origin can spend any amount from Treasury.

Comment on lines +98 to +103
let treasury_origin: RuntimeOrigin =
polkadot_runtime::governance::pallet_custom_origins::Origin::Treasurer.into();
let fellowship_treasury_location: Location =
Location::new(1, [Parachain(1001), PalletInstance(65)]);
let asset_hub_location: Location = [Parachain(1000)].into();
let native_asset_on_asset_hub = Location::parent();
Copy link
Contributor

Choose a reason for hiding this comment

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

treasury_origin is on the Relay Chain, but all the others are defined from the perspective of Asset Hub. Got a bit confused with this. Perhaps a comment to clarify?

Copy link
Contributor Author

@muharem muharem Mar 13, 2024

Choose a reason for hiding this comment

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

asset_kind = {
location - or destination, from the perspective of Relay Chain,
asset_id - from the perspective of asset_kind.location
}
beneficiary - from the perspective of asset_kind.location

Copy link
Contributor

Choose a reason for hiding this comment

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

Add that as a comment in the code maybe?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have all this documented in the functions' docs.

@joepetrowski joepetrowski mentioned this pull request Mar 15, 2024
19 tasks
{
Ok(balance)
} else {
I::from_asset_balance(balance, asset_kind).map_err(|_| ())
Copy link
Member

Choose a reason for hiding this comment

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

This hinders debuggability. Can you please log the error?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this particular case, upstream we do not really care about type of error and there is only one reason for error.
But for a general implementation I will add the log.

{
Ok(balance)
} else {
I::from_asset_balance(balance, asset_kind).map_err(|_| ())
Copy link
Member

Choose a reason for hiding this comment

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

ditto

{
Ok(balance)
} else {
I::from_asset_balance(balance, asset_kind).map_err(|_| ())
Copy link
Member

Choose a reason for hiding this comment

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

ditto

Copy link
Member

@ggwpez ggwpez left a comment

Choose a reason for hiding this comment

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

I dont understand enough of the code, so please consider further review before merging.

@muharem
Copy link
Contributor Author

muharem commented Mar 18, 2024

@acatangiu can you please check this PR

@muharem
Copy link
Contributor Author

muharem commented Mar 18, 2024

@bkchr can you please check this PR

Copy link
Contributor

@franciscoaguirre franciscoaguirre left a comment

Choose a reason for hiding this comment

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

The whole LocatableAsset thing seems very convoluted, it'd be good to simplify it in the future.
It looks like it works though


let native_asset = Location::here();
let asset_hub_location: Location = [Parachain(1000)].into();
let treasury_location_on_asset_hub: Location = (Parent, PalletInstance(18)).into();
Copy link
Contributor

Choose a reason for hiding this comment

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

The name is confusing. Previous variables are relative to the relay, this one starts with Parent but says on_asset_hub, leading to believe that it should be relative to the relay as well.

Looks like you want the relay's treasury sovereign account. I'd probably name it relay_treasury_location and add a comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I try to avoid a prefix relay for the treasury. We have only one the Treasury, or the Polkadot/Kusama Treasury, which eventually will move from Relay chain. I rename it to treasury_location.


let native_asset = Location::here();
let asset_hub_location: Location = [Parachain(1000)].into();
let treasury_location_on_asset_hub: Location = (Parent, PalletInstance(19)).into();
Copy link
Contributor

Choose a reason for hiding this comment

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

The pallet index is different, should this be called collectives_treasury_location?
Also, maybe add a constant for the magic number 19 or even get it from the pallet itself with PalletAccessInfo, that'd be great

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's different, because this is polkadot's treasury

let treasury_origin: RuntimeOrigin =
polkadot_runtime::governance::pallet_custom_origins::Origin::Treasurer.into();
let fellowship_treasury_location: Location =
Location::new(1, [Parachain(1001), PalletInstance(65)]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I definitely think we should try to access the actual pallet's index for these locations

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I do not have that strong opinion about this. When I use literals, I want this test fail if for some reason pallet's index changes.

Comment on lines +98 to +103
let treasury_origin: RuntimeOrigin =
polkadot_runtime::governance::pallet_custom_origins::Origin::Treasurer.into();
let fellowship_treasury_location: Location =
Location::new(1, [Parachain(1001), PalletInstance(65)]);
let asset_hub_location: Location = [Parachain(1000)].into();
let native_asset_on_asset_hub = Location::parent();
Copy link
Contributor

Choose a reason for hiding this comment

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

Add that as a comment in the code maybe?

relay/polkadot/src/impls.rs Show resolved Hide resolved
V3 { location, asset_id } => (location.try_into()?, asset_id.try_into()?),
V4 { location, asset_id } => (location, asset_id),
};
if asset_id.0.contains_parents_only(1) &&
Copy link
Contributor

Choose a reason for hiding this comment

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

You should add it to this type's (or impl's) doc comment as well

@muharem
Copy link
Contributor Author

muharem commented Mar 19, 2024

The whole LocatableAsset thing seems very convoluted, it'd be good to simplify it in the future. It looks like it works though

Do you have an idea how? The origin for the current structure comes from this type - https://github.com/paritytech/polkadot-sdk/blob/430ad2f5610dc3d53b4708c651617e34affa97f7/polkadot/xcm/xcm-builder/src/pay.rs#L193

muharem and others added 2 commits March 19, 2024 14:36
Comment on lines +32 to +34
pub struct NativeOnSystemParachain<I>(PhantomData<I>);
impl<I> ConversionFromAssetBalance<Balance, VersionedLocatableAsset, Balance>
for NativeOnSystemParachain<I>
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
pub struct NativeOnSystemParachain<I>(PhantomData<I>);
impl<I> ConversionFromAssetBalance<Balance, VersionedLocatableAsset, Balance>
for NativeOnSystemParachain<I>
pub struct NativeOnSystemParachainElseConvertWith<I>(PhantomData<I>);
impl<I> ConversionFromAssetBalance<Balance, VersionedLocatableAsset, Balance>
for NativeOnSystemParachainElseConvertWith<I>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like my version more, I personally prefer short and laconic names with docs, instead long names.
But if you get one more vote for your rename suggestion, I rename

Copy link
Contributor

Choose a reason for hiding this comment

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

well, I don't mind longer names if they are self-describing :)
e.g. NativeOnSiblingParachain does not mention in the docs that it filters just system parachains, so I think at least NativeOnSystemParachain or NativeOnSiblingSystemParachain is better

Copy link
Contributor

Choose a reason for hiding this comment

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

well, I don't mind longer names if they are self-describing :) e.g. NativeOnSiblingParachain does not mention in the docs that it filters just system parachains, so I think at least NativeOnSystemParachain or NativeOnSiblingSystemParachain is better

@muharem to avoid any confusions (e.g. NativeOnSibling, can be ACA or whatever), why not to just rename all of NativeOnSiblingParachain and NativeOnSystemParachain to the final UnityOrOuterConversion temporary with several impls here? When we do polkadot-sdk bump with paritytech/polkadot-sdk#3659, it wil just "change imports"

Comment on lines +194 to +196
pub struct NativeOnSystemParachain<I>(PhantomData<I>);
impl<I> ConversionFromAssetBalance<Balance, VersionedLocatableAsset, Balance>
for NativeOnSystemParachain<I>
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
pub struct NativeOnSystemParachain<I>(PhantomData<I>);
impl<I> ConversionFromAssetBalance<Balance, VersionedLocatableAsset, Balance>
for NativeOnSystemParachain<I>
pub struct NativeOnSystemParachainElseConvertWith<I>(PhantomData<I>);
impl<I> ConversionFromAssetBalance<Balance, VersionedLocatableAsset, Balance>
for NativeOnSystemParachainElseConvertWith<I>

Comment on lines +182 to +184
pub struct NativeOnSiblingParachain<I, SelfParaId>(PhantomData<(I, SelfParaId)>);
impl<I, SelfParaId> ConversionFromAssetBalance<Balance, VersionedLocatableAsset, Balance>
for NativeOnSiblingParachain<I, SelfParaId>
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
pub struct NativeOnSiblingParachain<I, SelfParaId>(PhantomData<(I, SelfParaId)>);
impl<I, SelfParaId> ConversionFromAssetBalance<Balance, VersionedLocatableAsset, Balance>
for NativeOnSiblingParachain<I, SelfParaId>
pub struct NativeOnSystemParachainElseConvertWith<I, SelfParaId>(PhantomData<(I, SelfParaId)>);
impl<I, SelfParaId> ConversionFromAssetBalance<Balance, VersionedLocatableAsset, Balance>
for NativeOnSystemParachainElseConvertWith<I, SelfParaId>

Comment on lines +200 to +201
if asset_id.0.contains_parents_only(1) &&
IsSiblingSystemParachain::<ParaId, SelfParaId>::contains(&location)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that the NativeOnSiblingParachain for collectives and NativeOnSystemParachain for relays are "almost" the same implementation and differ only with the different ::contains(&location) implementation for matching system parachains.

I'm also not sure about the same asset_id.0.contains_parents_only(1) in both implementations, but regardless of that, they are exactly the same. I would suggest creating just one generic implementation and placing it in the polkadot-sdk repository somewhere. As a temporary solution, with a TODO, it could be placed in the system-parachains-constants repository. Yes, it may be ugly, but it serves the purpose. Alternatively, you could copy the exact same generic implementation where needed and remove it later.

Suggested generic implementation:

pub struct NativeOnSystemParachainElseConvertWith<I, N, IsSystemParachain>(PhantomData<(I, N, IsSystemParachain)>);
impl<I, N, IsSystemParachain> ConversionFromAssetBalance<Balance, VersionedLocatableAsset, Balance>
	for NativeOnSystemParachainElseConvertWith<I, N, IsSystemParachain>
where
	I: ConversionFromAssetBalance<Balance, VersionedLocatableAsset, Balance>,
        IsNativeAsset: Contains<Location> // <-- or maybe Contains<AssetId>?, we have already matchers for Contains<Location>
	IsSystemParachain: Contains<Location>,
{
	type Error = ();
	fn from_asset_balance(
		balance: Balance,
		asset_kind: VersionedLocatableAsset,
	) -> Result<Balance, Self::Error> {
		use VersionedLocatableAsset::*;
		let (location, asset_id) = match asset_kind.clone() {
			V3 { location, asset_id } => (location.try_into()?, asset_id.try_into()?),
			V4 { location, asset_id } => (location, asset_id),
		};

		if IsNativeAsset::contains(asset_id.0) && IsSystemParachain::contains(&location)
                {
			Ok(balance)
		} else {
			I::from_asset_balance(balance, asset_kind).map_err(|_| ())
		}
	}
	#[cfg(feature = "runtime-benchmarks")]
	fn ensure_successful(asset_kind: VersionedLocatableAsset) {
		I::ensure_successful(asset_kind)
	}
}                

so with this one generic implementation, it can be used for both:

// for collectives
type BalanceConverter = NativeOnSystemParachainElseConvertWith<
     AssetRate,
     Equals<xcm_config::DotLocation>
     IsSiblingSystemParachain<ParaId, ParachainInfo>,
>;


// for relays
type BalanceConverter = NativeOnSystemParachainElseConvertWith<
     AssetRate,
     ( 
         Equals<DotLocation>
         Equals<DotLocationWithParent1>   // <- kind of OR matching
     ),
     IsChildSystemParachain::<ParaId>,
>;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

PR description referes to polkadot-sdk version - paritytech/polkadot-sdk#3659

Copy link
Contributor

Choose a reason for hiding this comment

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

PR description referes to polkadot-sdk version - paritytech/polkadot-sdk#3659

ahaaa, ok, I see, looks like it does exactly the same even more generic way, cool :)

Copy link
Contributor

@bkontur bkontur left a comment

Choose a reason for hiding this comment

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

lgtm, as it will be changed later by paritytech/polkadot-sdk#3659

@muharem
Copy link
Contributor Author

muharem commented Mar 21, 2024

@ordian can you please check this PR

@bkchr
Copy link
Contributor

bkchr commented Mar 24, 2024

/merge

@bkchr
Copy link
Contributor

bkchr commented Mar 24, 2024

/merge

@fellowship-merge-bot fellowship-merge-bot bot enabled auto-merge (squash) March 24, 2024 14:41
@fellowship-merge-bot
Copy link
Contributor

Enabled auto-merge in Pull Request

Available commands
  • /merge: Enables auto-merge for Pull Request
  • /merge cancel: Cancels auto-merge for Pull Request
  • /merge help: Shows this menu

For more information see the documentation

@fellowship-merge-bot fellowship-merge-bot bot merged commit 3ae3d29 into polkadot-fellows:main Mar 24, 2024
33 of 34 checks passed
fellowship-merge-bot bot pushed a commit that referenced this pull request May 17, 2024
Introduce teleport and whitelist call tests for Polkadot Collectives.

Based on #233

- [x] Does not require a CHANGELOG entry

---------

Co-authored-by: joe petrowski <[email protected]>
Co-authored-by: Branislav Kontur <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants