Skip to content

Commit

Permalink
Handle incoming relay chain asset transfers (#99)
Browse files Browse the repository at this point in the history
* MultiCurrency Asset Transactor

* fee payment with rc token

* prettier

* handle unknown tokens

* fmt

* some improvements

* fix block-production.zndsl

* upgrade version of pallet-collator-selection

---------

Co-authored-by: cuteolaf <[email protected]>
  • Loading branch information
Szegoo and cuteolaf authored Apr 27, 2024
1 parent 9335638 commit 696850d
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 48 deletions.
33 changes: 33 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ cumulus-pallet-xcmp-queue = { version = "0.7.0", default-features = false }
cumulus-primitives-core = { version = "0.7.0", default-features = false }
cumulus-primitives-timestamp = { version = "0.7.0", default-features = false }
cumulus-primitives-utility = { version = "0.7.3", default-features = false }
pallet-collator-selection = { version = "9.0.0", default-features = false }
pallet-collator-selection = { version = "9.0.2", default-features = false }
parachain-info = { version = "0.7.0", package = "staging-parachain-info", default-features = false }
parachains-common = { version = "7.0.0", default-features = false }
sp-timestamp = { version = "26.0.0", default-features = false }
Expand Down Expand Up @@ -141,6 +141,8 @@ orml-asset-registry = { version = "0.7.0", default-features = false }
orml-currencies = { version = "0.7.0", default-features = false }
orml-tokens = { version = "0.7.0", default-features = false }
orml-traits = { version = "0.7.0", default-features = false }
orml-unknown-tokens = { version = "0.7.0", default-features = false }
orml-xcm-support = { version = "0.7.0", default-features = false }

# Polytope Labs
ismp = { git="https://github.com/Szegoo/hyperbridge.git", branch="fix-to-string", default-features = false }
Expand Down
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,23 @@
export PATH=/home/<username>/RegionX-Node/:$PATH
```

4. Run the test:
4. Run the tests:

- block production

```
zombienet-linux -p native test ./zombienet_tests/0001-smoke-test.zndsl
```

```
zombienet-linux -p native test ./zombienet_tests/0001-block-production.zndsl
```

- native fee payment

```
zombienet-linux -p native test ./zombienet_tests/0002-native-fee-payment.zndsl
```

- custom fee payment

```
zombienet-linux -p native test ./zombienet_tests/0003-custom-fee-payment.zndsl
```
87 changes: 69 additions & 18 deletions e2e_tests/custom-fee-payment.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
const { ApiPromise, WsProvider } = require("@polkadot/api");
const { ApiPromise, WsProvider, Keyring } = require("@polkadot/api");
const { submitExtrinsic } = require("./common");

const ASSET_ID = 42;
const RELAY_ASSET_ID = 1;

async function run(nodeName, networkInfo, _jsArgs) {
const { wsUri } = networkInfo.nodesByName[nodeName];
const api = await ApiPromise.create({
provider: new WsProvider(wsUri),
const { wsUri: regionXUri } = networkInfo.nodesByName[nodeName];
const { wsUri: rococoUri } = networkInfo.nodesByName["rococo-validator01"];

const rococoApi = await ApiPromise.create({
provider: new WsProvider(rococoUri),
});
const regionXApi = await ApiPromise.create({
provider: new WsProvider(regionXUri),
signedExtensions: {
ChargeAssetTxPayment: {
extrinsic: {
Expand All @@ -22,27 +27,73 @@ async function run(nodeName, networkInfo, _jsArgs) {
const keyring = new zombie.Keyring({ type: "sr25519" });
const alice = keyring.addFromUri("//Alice");

const setXcmVersion = rococoApi.tx.xcmPallet.forceDefaultXcmVersion([3]);
await submitExtrinsic(alice, rococoApi.tx.sudo.sudo(setXcmVersion), {});

const assetMetadata = {
decimals: 10,
name: "DOT",
symbol: "DOT",
existentialDeposit: 10n**3n,
decimals: 12,
name: "ROC",
symbol: "ROC",
existentialDeposit: 10n ** 3n,
location: null,
additional: null
additional: null,
};

const assetSetupCalls = [
api.tx.assetRegistry.registerAsset(assetMetadata, ASSET_ID),
api.tx.assetRate.create(ASSET_ID, 1000000000000000000n), // 1 on 1
api.tx.tokens.setBalance(alice.address, ASSET_ID, 10n**12n, 0),
regionXApi.tx.assetRegistry.registerAsset(assetMetadata, RELAY_ASSET_ID),
regionXApi.tx.assetRate.create(RELAY_ASSET_ID, 1_000_000_000_000_000_000n), // 1 on 1
regionXApi.tx.tokens.setBalance(
alice.address,
RELAY_ASSET_ID,
10n ** 12n,
0,
),
];
const batchCall = api.tx.utility.batch(assetSetupCalls);
const sudo = api.tx.sudo.sudo(batchCall);
const batchCall = regionXApi.tx.utility.batch(assetSetupCalls);
const sudoCall = regionXApi.tx.sudo.sudo(batchCall);

await submitExtrinsic(alice, sudoCall, {});

await submitExtrinsic(alice, sudo, {});
const receiverKeypair = new Keyring();
receiverKeypair.addFromAddress(alice.address);

const feeAssetItem = 0;
const weightLimit = "Unlimited";
const reserveTransfer = rococoApi.tx.xcmPallet.limitedReserveTransferAssets(
{ V3: { parents: 0, interior: { X1: { Parachain: 2000 } } } }, //dest
{
V3: {
parents: 0,
interior: {
X1: {
AccountId32: {
chain: "Any",
id: receiverKeypair.pairs[0].publicKey,
},
},
},
},
}, //beneficiary
{
V3: [
{
id: {
Concrete: { parents: 0, interior: "Here" },
},
fun: {
Fungible: 10n ** 9n,
},
},
],
}, //asset
feeAssetItem,
weightLimit,
);
await submitExtrinsic(alice, reserveTransfer, {});

const remarkCall = api.tx.system.remark("0x44");
await submitExtrinsic(alice, remarkCall, {assetId: ASSET_ID});
// Try to pay for fees with relay chain asset.
const remarkCall = regionXApi.tx.system.remark("0x44");
await submitExtrinsic(alice, remarkCall, { assetId: RELAY_ASSET_ID });
}

module.exports = { run };
4 changes: 2 additions & 2 deletions node/src/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub fn session_keys(keys: AuraId) -> regionx_runtime::SessionKeys {
pub fn development_config(id: u32) -> ChainSpec<regionx_runtime::RuntimeGenesisConfig> {
// Give your base currency a unit name and decimal places
let mut properties = sc_chain_spec::Properties::new();
properties.insert("tokenSymbol".into(), "M4X".into());
properties.insert("tokenSymbol".into(), "REGX".into());
properties.insert("tokenDecimals".into(), 12.into());
// TODO: chose an ss58Format
properties.insert("ss58Format".into(), 42.into());
Expand Down Expand Up @@ -130,7 +130,7 @@ pub fn development_config(id: u32) -> ChainSpec<regionx_runtime::RuntimeGenesisC
pub fn local_testnet_config(id: u32) -> ChainSpec<regionx_runtime::RuntimeGenesisConfig> {
// Give your base currency a unit name and decimal places
let mut properties = sc_chain_spec::Properties::new();
properties.insert("tokenSymbol".into(), "M4X".into());
properties.insert("tokenSymbol".into(), "REGX".into());
properties.insert("tokenDecimals".into(), 12.into());
// TODO: chose an ss58Format
properties.insert("ss58Format".into(), 42.into());
Expand Down
4 changes: 4 additions & 0 deletions runtime/regionx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ orml-asset-registry = { workspace = true }
orml-currencies = { workspace = true }
orml-tokens = { workspace = true }
orml-traits = { workspace = true }
orml-unknown-tokens = { workspace = true }
orml-xcm-support = { workspace = true }

# Substrate
frame-benchmarking = { workspace = true, optional = true }
Expand Down Expand Up @@ -125,6 +127,8 @@ std = [
"orml-currencies/std",
"orml-tokens/std",
"orml-traits/std",
"orml-unknown-tokens/std",
"orml-xcm-support/std",
"pallet-aura/std",
"pallet-authorship/std",
"pallet-asset-rate/std",
Expand Down
5 changes: 5 additions & 0 deletions runtime/regionx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,10 @@ impl pallet_asset_rate::Config for Runtime {
type BenchmarkHelper = ();
}

impl orml_unknown_tokens::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
}

impl pallet_asset_tx_payment::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Fungibles = Tokens;
Expand Down Expand Up @@ -709,6 +713,7 @@ construct_runtime!(
Tokens: orml_tokens = 14,
Currencies: orml_currencies = 15,
AssetRate: pallet_asset_rate = 16,
UnknownTokens: orml_unknown_tokens = 17,

// Governance
Sudo: pallet_sudo = 20,
Expand Down
73 changes: 56 additions & 17 deletions runtime/regionx/src/xcm_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,30 @@
// along with RegionX. If not, see <https://www.gnu.org/licenses/>.

use super::{
AccountId, AllPalletsWithSystem, Balances, ParachainInfo, ParachainSystem, PolkadotXcm,
Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, XcmpQueue,
AccountId, AllPalletsWithSystem, AssetId, Balance, Balances, Currencies, ParachainInfo,
ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, UnknownTokens,
WeightToFee, XcmpQueue,
};
use frame_support::{
match_types, parameter_types,
traits::{ConstU32, Everything, Nothing},
PalletId,
};
use frame_system::EnsureRoot;
use orml_xcm_support::{DepositToAlternative, IsNativeConcrete, MultiCurrencyAdapter};
use pallet_xcm::XcmPassthrough;
use polkadot_parachain_primitives::primitives::Sibling;
use polkadot_runtime_common::impls::ToAuthor;
use regionx_primitives::assets::{REGX_ASSET_ID, RELAY_CHAIN_ASSET_ID};
use sp_runtime::traits::{AccountIdConversion, Convert};
use xcm::latest::prelude::*;
use xcm_builder::{
AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom,
DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FixedWeightBounds,
FrameTransactionalProcessor, FungibleAdapter, IsConcrete, NativeAsset, ParentIsPreset,
RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia,
SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit,
TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic,
FrameTransactionalProcessor, NativeAsset, ParentIsPreset, RelayChainAsNative,
SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative,
SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId,
UsingComponents, WithComputedOrigin, WithUniqueTopic,
};
use xcm_executor::XcmExecutor;

Expand All @@ -55,20 +60,54 @@ pub type LocationToAccountId = (
AccountId32Aliases<RelayNetwork, AccountId>,
);

parameter_types! {
// The account which receives multi-currency tokens from failed attempts to deposit them
pub Alternative: AccountId = PalletId(*b"xcm/alte").into_account_truncating();
}

/// Means for transacting assets on this chain.
pub type LocalAssetTransactor = FungibleAdapter<
// Use this currency:
Balances,
// Use this currency when it is a fungible asset matching the given location or name:
IsConcrete<RelayLocation>,
// Do a simple punn to convert an AccountId32 MultiLocation into a native chain account ID:
LocationToAccountId,
// Our chain's account ID type (we can't get away without mentioning it explicitly):
pub type FungiblesAssetTransactor = MultiCurrencyAdapter<
Currencies,
UnknownTokens,
IsNativeConcrete<AssetId, AssetIdConverter>,
AccountId,
// We don't track any teleports.
(),
LocationToAccountId,
AssetId,
AssetIdConverter,
DepositToAlternative<Alternative, Currencies, AssetId, AccountId, Balance>,
>;

pub struct AssetIdConverter;
impl Convert<AssetId, Option<MultiLocation>> for AssetIdConverter {
fn convert(id: AssetId) -> Option<MultiLocation> {
match id {
RELAY_CHAIN_ASSET_ID => Some(MultiLocation::parent()),
REGX_ASSET_ID => Some(MultiLocation::here()),
_ => None,
}
}
}

impl Convert<MultiLocation, Option<AssetId>> for AssetIdConverter {
fn convert(location: MultiLocation) -> Option<AssetId> {
match location {
MultiLocation { parents: 1, interior: Here } => Some(RELAY_CHAIN_ASSET_ID),
MultiLocation { parents: 0, interior: Here } => Some(REGX_ASSET_ID),
_ => None,
}
}
}

impl Convert<MultiAsset, Option<AssetId>> for AssetIdConverter {
fn convert(asset: MultiAsset) -> Option<AssetId> {
if let MultiAsset { id: Concrete(location), .. } = asset {
Self::convert(location)
} else {
None
}
}
}

/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance,
/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can
/// biases the kind of local `Origin` it will become.
Expand Down Expand Up @@ -127,7 +166,7 @@ impl xcm_executor::Config for XcmConfig {
type RuntimeCall = RuntimeCall;
type XcmSender = XcmRouter;
// How to withdraw and deposit an asset.
type AssetTransactor = LocalAssetTransactor;
type AssetTransactor = FungiblesAssetTransactor;
type OriginConverter = XcmOriginToTransactDispatchOrigin;
type IsReserve = NativeAsset;
type IsTeleporter = (); // Teleporting is disabled.
Expand Down
2 changes: 1 addition & 1 deletion zombienet_tests/0001-block-production.zndsl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Description: Block production smoke test
Network: ./0001-smoke-test.toml
Network: ./0001-block-production.toml
Creds: config

alice: is up
Expand Down
4 changes: 2 additions & 2 deletions zombienet_tests/0003-custom-fee-payment.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ chain = "rococo-local"
command = "polkadot"

[[relaychain.nodes]]
name = "alice"
name = "rococo-validator01"
validator = true

[[relaychain.nodes]]
name = "bob"
name = "rococo-validator02"
validator = true

[[parachains]]
Expand Down
Loading

0 comments on commit 696850d

Please sign in to comment.