Skip to content

feat: asset registry #728

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

Merged
merged 4 commits into from
May 24, 2022
Merged

Conversation

sander2
Copy link
Contributor

@sander2 sander2 commented Apr 21, 2022

This is meant to address #721.

This PR will need cleanup, tests, etc, but I'm opening this as a draft now to get some feedback regarding the direction. Some things to note about the implementation:

  • In the issue I raised two possible implementations: either we can provide some of the metadata fields by default, or we leave it fully customizable. I went with the latter approach in this PR.
  • The only dispatchable function is one to register (or update) an asset.
  • The metadata is fully customizable - the only requirement being that there is a way to extract a MultiAsset from it. This is needed to be able to map xcm assets to assets in this pallet
  • The assigned asset id is customizable. The last assigned asset id is stored, so you can either derive a sequential id or base it purely on the metadata
  • Some traits and trait implementations are provided to allow for easy WeightTrader implementations. Unfortunately because WeightTrader::refund_weight allows only a single asset to be refunded, the default implementation does not support paying with different assets. That is, it can pay with any asset in the registry, but only one asset per xcm message. I opened WeightTrader refund limitations paritytech/polkadot-sdk#836 to see if refund_weight can refund multiple assets in the future.

An example of usage:

// the metadata definition...
#[derive(scale_info::TypeInfo, Encode, Decode, Clone, Eq, PartialEq, Debug)]
pub struct AssetMetadata {
    pub location: MultiLocation,
    pub decimals: u32,
    pub fee_per_second: u128,
    pub name: Vec<u8>,
    pub symbol: Vec<u8>,
}
impl Into<MultiLocation> for AssetMetadata {
    fn into(self) -> MultiLocation {
        self.location
    }
}

// the config...
impl orml_asset_registry::Config for Runtime {
    type Event = Event;
    type AssetMetadata = AssetMetadata;
    type AssetId = u32;
    type AuthorityOrigin = EnsureRoot<AccountId>;
    type AssignAsset = SequentialId<u32>;
}

// for paying xcm fees..
pub struct MyFixedConversionRateProvider;
impl FixedConversionRateProvider for MyFixedConversionRateProvider {
    fn get_fee_per_second(location: &MultiLocation) -> Option<u128> {
        let metadata = AssetRegistry::fetch_metadata_by_location(location)?;
        Some(metadata.fee_per_second)
    }
}
pub type Trader = (
    (...)
    AssetRegistryTrader<FixedRateAssetRegistryTrader<MyFixedConversionRateProvider>, ToTreasury>,
);

I would love to get some feedback on this. I'm out of office until tuesday though so might not be able to address any comments until then.

@sander2 sander2 marked this pull request as draft April 21, 2022 18:48
@sander2 sander2 mentioned this pull request Apr 21, 2022
@xlc xlc requested review from xlc and zqhxuyuan April 24, 2022 11:07
@xlc
Copy link
Member

xlc commented Apr 24, 2022

I think we should still require some basic fields like name, symbol, decimals. otherwise the js sdk still need specialized code to handle it for different parachains.

@codecov-commenter
Copy link

codecov-commenter commented Apr 24, 2022

Codecov Report

Merging #728 (ac2ca60) into master (39057da) will decrease coverage by 0.00%.
The diff coverage is 87.42%.

@@            Coverage Diff             @@
##           master     paritytech/polkadot#728      +/-   ##
==========================================
- Coverage   77.68%   77.67%   -0.01%     
==========================================
  Files          92      100       +8     
  Lines        9069     9598     +529     
==========================================
+ Hits         7045     7455     +410     
- Misses       2024     2143     +119     
Impacted Files Coverage Δ
asset-registry/src/weights.rs 0.00% <0.00%> (ø)
traits/src/lib.rs 0.00% <ø> (ø)
asset-registry/src/mock/relay.rs 25.00% <25.00%> (ø)
asset-registry/src/impls.rs 66.07% <66.07%> (ø)
asset-registry/src/mock/para.rs 66.66% <66.66%> (ø)
asset-registry/src/mock/mod.rs 82.53% <82.53%> (ø)
asset-registry/src/lib.rs 92.59% <92.59%> (ø)
asset-registry/src/tests.rs 96.95% <96.95%> (ø)
traits/src/asset_registry.rs 100.00% <100.00%> (ø)
traits/src/currency.rs 34.88% <0.00%> (-25.12%) ⬇️
... and 1 more

📣 Codecov can now indicate which changes are the most critical in Pull Requests. Learn more

@enthusiastmartin
Copy link

I agree with xlc - that some basic information can be already part of the pallet ( name,symbol, decimals).

personally, i would not require Into<MultiLocation> for asset metadata, and i would not map the location to asset id when asset is registered.
I'd add ability to set/update/remove location explicitly after asset is already registered.

@sander2
Copy link
Contributor Author

sander2 commented Apr 26, 2022

I think we should still require some basic fields like name, symbol, decimals. otherwise the js sdk still need specialized code to handle it for different parachains.

Sure, I added name, symbol, decimals and multilocation.

I agree with xlc - that some basic information can be already part of the pallet ( name,symbol, decimals).

personally, i would not require Into<MultiLocation> for asset metadata, and i would not map the location to asset id when asset is registered. I'd add ability to set/update/remove location explicitly after asset is already registered.

When I added default fields, I also added the (multi)location. Are you opposed to this? Do you think it should be an optional item? Or should it be removed as a default field altogether?

@enthusiastmartin
Copy link

I think we should still require some basic fields like name, symbol, decimals. otherwise the js sdk still need specialized code to handle it for different parachains.

Sure, I added name, symbol, decimals and multilocation.

I agree with xlc - that some basic information can be already part of the pallet ( name,symbol, decimals).
personally, i would not require Into<MultiLocation> for asset metadata, and i would not map the location to asset id when asset is registered. I'd add ability to set/update/remove location explicitly after asset is already registered.

When I added default fields, I also added the (multi)location. Are you opposed to this? Do you think it should be an optional item? Or should it be removed as a default field altogether?

I might be wrong here - but i think, if you set a location for asset and then xcm config is set to map asset and locations - that will make the asset automatically transferable.

which is probably fine, that's why you add foreign assets in first place.

but not all assets need location, and not all asset are transferable. For example - in HydraDX's xyk pool - we use asset registry to register 'share asset' for each subpool as another asset in the system. However, this asset does not need location nor it can be transferred somewhere else.

@sander2
Copy link
Contributor Author

sander2 commented Apr 27, 2022

I might be wrong here - but i think, if you set a location for asset and then xcm config is set to map asset and locations - that will make the asset automatically transferable.

which is probably fine, that's why you add foreign assets in first place.

but not all assets need location, and not all asset are transferable. For example - in HydraDX's xyk pool - we use asset registry to register 'share asset' for each subpool as another asset in the system. However, this asset does not need location nor it can be transferred somewhere else.

Thank you for the clarification, that makes sense indeed. I changed the location to be a Option<MultiLocation>, and also added a set_asset_location function. In your usecase you'd set the location in the metadata to None, while xcm transferable assets can still be registered in a single step using only register_asset. The reason why I think it's good to be able to register xcm-transferrable assets in a single call, is that making multiple calls is problematic if you have to go through governance. You could also batch register_asset and set_asset_location, but the problem there is that the asset id might not be known in advance in all cases

@enthusiastmartin
Copy link

What about existential deposit / min balance ?

I believe this could be one of the required fields too.

i think that it would be useful if you could also plug asset registry to eg. orml_tokens's config ExistentialDeposits.
that would mean, it needs GetByKey implementation.

Eg. in our asset-registry here.

@sander2
Copy link
Contributor Author

sander2 commented Apr 27, 2022

What about existential deposit / min balance ?

I believe this could be one of the required fields too.

i think that it would be useful if you could also plug asset registry to eg. orml_tokens's config ExistentialDeposits. that would mean, it needs GetByKey implementation.

Eg. in our asset-registry here.

Agreed, added.

@sander2 sander2 marked this pull request as ready for review May 4, 2022 08:15
@sander2 sander2 requested review from xlc and zqhxuyuan May 4, 2022 08:15
@sander2
Copy link
Contributor Author

sander2 commented May 4, 2022

I cleaned the code up a bit and added some tests, including some that show that it can be used in xcm transfers. From my PoV, this PR is ready to be reviewed.

There is one thing that I still need address: the default weights. For the other pallets, the default weights are generated with acala/target/release/acala benchmark. Since this pallet is not included in that repo, should I add benchmark code in the crate itself?

One more note on the ProcessAsset trait. By request of @xlc it's returning (AssetId, Metadata), meaning that it can modify the supplied metadata. I'm not certain what the use case of this is, but running the same function again when modifying metadata is problematic, so I only call it in the register_asset function and not in update_asset.

Copy link
Member

@xlc xlc left a comment

Choose a reason for hiding this comment

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

not 100% sure if we want all the XCM weight related code in this pallet. I guess we don't really want another crate just for that so it is probably fine.


#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::without_storage_info]
Copy link
Member

Choose a reason for hiding this comment

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

should use bonded types, but that can happen in later PR

@xlc
Copy link
Member

xlc commented May 7, 2022

other than nits it is good for a V1

Comment on lines +176 to +186
pub struct MyFixedConversionRateProvider;
impl FixedConversionRateProvider for MyFixedConversionRateProvider {
fn get_fee_per_second(location: &MultiLocation) -> Option<u128> {
let metadata = AssetRegistry::fetch_metadata_by_location(location)?;
Some(metadata.additional.fee_per_second)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this move to impl.rs, I think this is a common implementation.

Copy link
Contributor Author

@sander2 sander2 May 16, 2022

Choose a reason for hiding this comment

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

We can't - the fee_per_second is part of the custom metadata rather than the default metadata.

Edit: unless we make the impl generic over some callback Fn(Metadata) -> Option<Balance> - should we?

@zqhxuyuan zqhxuyuan requested a review from shaunxw May 17, 2022 02:40
let new_weight = existing_weight.saturating_add(weight);

if let Some(amount) = W::convert_weight_to_fee(location, new_weight) {
let fee_increase = amount.saturating_sub(existing_fee);
Copy link
Member

Choose a reason for hiding this comment

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

I might miss something, but not sure why adding weight to existing to get new_weight first, then convert to fee and subtract. Simply converting weight to fee should have the same result no?

And the embedded if/if let checks could be flattened for readability, such as:

if let Some(amount) = W::convert_weight_to_fee(location, new_weight) {

-->

let amount = W::convert_weight_to_fee(location, new_weight).ok_or(XcmError::TooExpensive)?;
// ...

Also

if fee_increase == 0 {
    return Ok(payment);
} else if let Ok(unused) = payment.clone().checked_sub((asset.clone(), fee_increase).into()) {

-->

if fee_increase == 0 {
    return Ok(payment);
}

let unused = payment.clone().checked_sub((asset.clone(), fee_increase).into()).ok_or(XcmError::TooExpensive)?;
// ...

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 might miss something, but not sure why adding weight to existing to get new_weight first, then convert to fee and subtract. Simply converting weight to fee should have the same result no?

It's equivalent only when convert_weight_to_fee is a linear function, which it is not guaranteed to be. Though maybe I'm overcomplicating things - I don't expect to see non-linear functions being used in practice..

And the embedded if/if let checks could be flattened for readability, such as:
if let Some(amount) = W::convert_weight_to_fee(location, new_weight) {
-->
let amount = W::convert_weight_to_fee(location, new_weight).ok_or(XcmError::TooExpensive)?;

That would stop the iteration over the assets in payment.fungible. The same applies for the last refactoring you suggest

Copy link
Member

Choose a reason for hiding this comment

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

I don't see non-linear functions being used either. AFAIK ppl are all using fixed rate conversions, specifically the fixed rate weight traders implemented in Polkadot repo.

IMO we can simply aim for the same impl to provide a fixed rate trader. It's easier to use and maintain.

Copy link
Member

@shaunxw shaunxw left a comment

Choose a reason for hiding this comment

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

I suggest to change update_asset dispatchable call signature to

pub fn update_asset(
			origin: OriginFor<T>,
			asset_id: T::AssetId,
			decimals: Option<u32>,
			name: Option<Vec<u8>>,
			// ... rest fields wrapped in `Option`
		) -> DispatchResult

The impl checks every field and update it if Some. Note location needs to be Option<Option<VersionedMultiLocation>>.

The rationale behind it is that in most cases, only one field will be updated, but the current call requires to copy all existing unchanged ones, which is unnecessary and prone to mistakes. Additionally, set_asset_location call can be merged into this one.

@sander2
Copy link
Contributor Author

sander2 commented May 18, 2022

I think I've addressed all comments - PR should be ready for final review pass & merging

Copy link
Member

@shaunxw shaunxw left a comment

Choose a reason for hiding this comment

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

LGTM. A few more trivial issues and I think we are good to go.

let new_weight = existing_weight.saturating_add(weight);

if let Some(amount) = W::convert_weight_to_fee(location, new_weight) {
let fee_increase = amount.saturating_sub(existing_fee);
Copy link
Member

Choose a reason for hiding this comment

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

I don't see non-linear functions being used either. AFAIK ppl are all using fixed rate conversions, specifically the fixed rate weight traders implemented in Polkadot repo.

IMO we can simply aim for the same impl to provide a fixed rate trader. It's easier to use and maintain.

@sander2
Copy link
Contributor Author

sander2 commented May 19, 2022

Fixed the last comments and rebased (and squashed) the branch.

@zqhxuyuan zqhxuyuan merged commit 78f2d45 into open-web3-stack:master May 24, 2022
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.

6 participants