Skip to content

Commit 51b4617

Browse files
committed
feat: added asset registry
1 parent 39057da commit 51b4617

File tree

12 files changed

+1761
-0
lines changed

12 files changed

+1761
-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

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

0 commit comments

Comments
 (0)