Skip to content

Commit 78f2d45

Browse files
authored
feat: asset registry (#728)
* feat: added asset registry * fix: deposit event when updating asset * chore: comments & small refactoring * refactor: extract do_update_location and cleanup do_update_asset
1 parent 52ec52b commit 78f2d45

File tree

12 files changed

+1774
-0
lines changed

12 files changed

+1774
-0
lines changed

Cargo.dev.toml

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[workspace]
22
members = [
3+
"asset-registry",
34
"auction",
45
"authority",
56
"bencher",

asset-registry/Cargo.toml

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
[package]
2+
name = "orml-asset-registry"
3+
description = "Registry for (foreign) assets"
4+
repository = "https://github.com/open-web3-stack/open-runtime-module-library/tree/master/asset-registry"
5+
license = "Apache-2.0"
6+
version = "0.4.1-dev"
7+
authors = ["Interlay Ltd, etc"]
8+
edition = "2021"
9+
10+
[dependencies]
11+
scale-info = { version = "2.0.1", default-features = false, features = ["derive"] }
12+
serde = { version = "1.0.136", optional = true }
13+
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["max-encoded-len"] }
14+
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.20", default-features = false }
15+
sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.20", default-features = false }
16+
frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.20", default-features = false }
17+
frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.20", default-features = false }
18+
orml-traits = { path = "../traits", version = "0.4.1-dev", default-features = false }
19+
xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.20", default-features = false }
20+
xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.20", default-features = false }
21+
xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.20", default-features = false }
22+
23+
[dev-dependencies]
24+
sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.20", default-features = false }
25+
sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.20" }
26+
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.20" }
27+
28+
# cumulus
29+
cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.20" }
30+
cumulus-pallet-dmp-queue = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.20" }
31+
cumulus-pallet-xcmp-queue = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.20" }
32+
cumulus-pallet-xcm = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.20" }
33+
parachain-info = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.20" }
34+
35+
# polkadot
36+
polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.20" }
37+
xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.20" }
38+
xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.20" }
39+
xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.20" }
40+
pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.20" }
41+
polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.20" }
42+
xcm-simulator = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.20"}
43+
44+
orml-tokens = { path = "../tokens" }
45+
orml-xtokens = { path = "../xtokens" }
46+
orml-xcm = { path = "../xcm" }
47+
orml-xcm-support = { path = "../xcm-support", default-features = false }
48+
49+
[features]
50+
default = ["std"]
51+
std = [
52+
"serde",
53+
"codec/std",
54+
"scale-info/std",
55+
"sp-runtime/std",
56+
"sp-std/std",
57+
"frame-support/std",
58+
"frame-system/std",
59+
"orml-traits/std",
60+
"xcm/std",
61+
"xcm-builder/std",
62+
"xcm-executor/std",
63+
]
64+
runtime-benchmarks = [
65+
"frame-support/runtime-benchmarks",
66+
"frame-system/runtime-benchmarks",
67+
]
68+
try-runtime = ["frame-support/try-runtime"]

asset-registry/README.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Asset Registry Module
2+
3+
## Overview
4+
5+
This module provides functionality for storing asset metadata. For each asset, it stores the number of decimals, asset name, asset symbol, existential deposit and (optional) location. Additionally, it stores a value of a generic type that chains can use to store any other metadata that the parachain may need (such as the fee rate, for example). It is designed to be easy to integrate into xcm setups. Various default implementations are provided for this purpose.
6+
7+
The pallet only contains two extrinsics, `register_asset` and `update_asset`:
8+
9+
- `register_asset` creates a new asset
10+
- `update_asset` modifies some (or all) of the fields of an existing asset

asset-registry/src/impls.rs

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
use crate::{module::*, AssetMetadata};
2+
use frame_support::{log, pallet_prelude::*, weights::constants::WEIGHT_PER_SECOND};
3+
use orml_traits::{
4+
asset_registry::{AssetProcessor, FixedConversionRateProvider, WeightToFeeConverter},
5+
GetByKey,
6+
};
7+
use sp_runtime::FixedPointNumber;
8+
use sp_runtime::{
9+
traits::{AtLeast32BitUnsigned, Bounded, CheckedAdd, One},
10+
ArithmeticError, FixedU128,
11+
};
12+
use sp_std::prelude::*;
13+
use xcm::v2::prelude::*;
14+
use xcm_builder::TakeRevenue;
15+
use xcm_executor::{traits::WeightTrader, Assets};
16+
17+
/// Alias for AssetMetadata to improve readability (and to placate clippy)
18+
pub type DefaultAssetMetadata<T> = AssetMetadata<<T as Config>::Balance, <T as Config>::CustomMetadata>;
19+
20+
/// An AssetProcessor that assigns a sequential ID
21+
pub struct SequentialId<T>(PhantomData<T>);
22+
23+
impl<T> AssetProcessor<T::AssetId, DefaultAssetMetadata<T>> for SequentialId<T>
24+
where
25+
T: Config,
26+
T::AssetId: AtLeast32BitUnsigned,
27+
{
28+
fn pre_register(
29+
id: Option<T::AssetId>,
30+
asset_metadata: DefaultAssetMetadata<T>,
31+
) -> Result<(T::AssetId, DefaultAssetMetadata<T>), DispatchError> {
32+
let next_id = LastAssetId::<T>::get()
33+
.checked_add(&T::AssetId::one())
34+
.ok_or(ArithmeticError::Overflow)?;
35+
36+
match id {
37+
Some(explicit_id) if explicit_id != next_id => {
38+
// we don't allow non-sequential ids
39+
Err(Error::<T>::InvalidAssetId.into())
40+
}
41+
_ => {
42+
LastAssetId::<T>::put(&next_id);
43+
Ok((next_id, asset_metadata))
44+
}
45+
}
46+
}
47+
}
48+
49+
/// A default implementation for WeightToFeeConverter that takes a fixed
50+
/// conversion rate.
51+
pub struct FixedRateAssetRegistryTrader<P: FixedConversionRateProvider>(PhantomData<P>);
52+
impl<P: FixedConversionRateProvider> WeightToFeeConverter for FixedRateAssetRegistryTrader<P> {
53+
fn convert_weight_to_fee(location: &MultiLocation, weight: Weight) -> Option<u128> {
54+
let fee_per_second = P::get_fee_per_second(location)?;
55+
let weight_ratio = FixedU128::saturating_from_rational(weight as u128, WEIGHT_PER_SECOND as u128);
56+
let amount = weight_ratio.saturating_mul_int(fee_per_second);
57+
Some(amount)
58+
}
59+
}
60+
61+
/// Helper struct for the AssetRegistryTrader that stores the data about
62+
/// bought weight.
63+
pub struct BoughtWeight {
64+
weight: Weight,
65+
asset_location: MultiLocation,
66+
amount: u128,
67+
}
68+
69+
/// A WeightTrader implementation that tries to buy weight using a single
70+
/// currency. It tries all assets in `payment` and uses the first asset that can
71+
/// cover the weight. This asset is then "locked in" - later calls to
72+
/// `buy_weight` in the same xcm message only try the same asset.
73+
/// This is because only a single asset can be refunded due to the return type
74+
/// of `refund_weight`. This implementation assumes that `WeightToFeeConverter`
75+
/// implements a linear function, i.e. fee(x) + fee(y) = fee(x+y).
76+
pub struct AssetRegistryTrader<W: WeightToFeeConverter, R: TakeRevenue> {
77+
bought_weight: Option<BoughtWeight>,
78+
_phantom: PhantomData<(W, R)>,
79+
}
80+
81+
impl<W: WeightToFeeConverter, R: TakeRevenue> WeightTrader for AssetRegistryTrader<W, R> {
82+
fn new() -> Self {
83+
Self {
84+
bought_weight: None,
85+
_phantom: Default::default(),
86+
}
87+
}
88+
89+
fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError> {
90+
log::trace!(
91+
target: "xcm::weight",
92+
"AssetRegistryTrader::buy_weight weight: {:?}, payment: {:?}",
93+
weight, payment,
94+
);
95+
96+
for (asset, _) in payment.fungible.iter() {
97+
if let AssetId::Concrete(ref location) = asset {
98+
if matches!(self.bought_weight, Some(ref bought) if &bought.asset_location != location) {
99+
// we already bought another asset - don't attempt to buy this one since
100+
// we won't be able to refund it
101+
continue;
102+
}
103+
104+
if let Some(fee_increase) = W::convert_weight_to_fee(location, weight) {
105+
if fee_increase == 0 {
106+
// if the fee is set very low it could lead to zero fees, in which case
107+
// constructing the fee asset item to subtract from payment would fail.
108+
// Therefore, provide early exit
109+
return Ok(payment);
110+
}
111+
112+
if let Ok(unused) = payment.clone().checked_sub((asset.clone(), fee_increase).into()) {
113+
let (existing_weight, existing_fee) = match self.bought_weight {
114+
Some(ref x) => (x.weight, x.amount),
115+
None => (0, 0),
116+
};
117+
118+
self.bought_weight = Some(BoughtWeight {
119+
amount: existing_fee.checked_add(fee_increase).ok_or(XcmError::Overflow)?,
120+
weight: existing_weight.checked_add(weight).ok_or(XcmError::Overflow)?,
121+
asset_location: location.clone(),
122+
});
123+
return Ok(unused);
124+
}
125+
}
126+
}
127+
}
128+
Err(XcmError::TooExpensive)
129+
}
130+
131+
fn refund_weight(&mut self, weight: Weight) -> Option<MultiAsset> {
132+
log::trace!(target: "xcm::weight", "AssetRegistryTrader::refund_weight weight: {:?}", weight);
133+
134+
match self.bought_weight {
135+
Some(ref mut bought) => {
136+
let new_weight = bought.weight.saturating_sub(weight);
137+
let new_amount = W::convert_weight_to_fee(&bought.asset_location, new_weight)?;
138+
let refunded_amount = bought.amount.saturating_sub(new_amount);
139+
140+
bought.weight = new_weight;
141+
bought.amount = new_amount;
142+
143+
Some((AssetId::Concrete(bought.asset_location.clone()), refunded_amount).into())
144+
}
145+
None => None, // nothing to refund
146+
}
147+
}
148+
}
149+
150+
impl<W: WeightToFeeConverter, R: TakeRevenue> Drop for AssetRegistryTrader<W, R> {
151+
fn drop(&mut self) {
152+
if let Some(ref bought) = self.bought_weight {
153+
R::take_revenue((AssetId::Concrete(bought.asset_location.clone()), bought.amount).into());
154+
}
155+
}
156+
}
157+
158+
pub struct ExistentialDeposits<T: Config>(PhantomData<T>);
159+
160+
// Return Existential deposit of an asset. Implementing this trait allows the
161+
// pallet to be used in the tokens::ExistentialDeposits config item
162+
impl<T: Config> GetByKey<T::AssetId, T::Balance> for ExistentialDeposits<T> {
163+
fn get(k: &T::AssetId) -> T::Balance {
164+
if let Some(metadata) = Pallet::<T>::metadata(k) {
165+
metadata.existential_deposit
166+
} else {
167+
// Asset does not exist - not supported
168+
T::Balance::max_value()
169+
}
170+
}
171+
}

0 commit comments

Comments
 (0)