From 2c698b96346bf0736a069c5053c439e50327a4e5 Mon Sep 17 00:00:00 2001 From: Sergej Sakac <73715684+Szegoo@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:21:40 +0200 Subject: [PATCH] Fee payment in non native assets (#77) * Fee payment in non native assets * fix * modify signed extra * e2e native fee payment check * fix * non-native transfer * asset-rate * fix * .. * use nightly-2024-02-03 * fixes * add missing asset rate setup * fix * fix tests * fix dependency check * test fix * last touches --- .github/workflows/dependencies.yml | 2 +- .gitignore | 5 ++ Cargo.lock | 2 + Cargo.toml | 2 + README.md | 2 +- e2e_tests/common.js | 24 +++++++++ e2e_tests/custom-fee-payment.js | 48 +++++++++++++++++ e2e_tests/native-fee-payment.js | 18 +++++++ e2e_tests/package.json | 15 ++++++ node/src/chain_spec.rs | 4 +- runtime/primitives/src/assets.rs | 5 +- runtime/regionx/Cargo.toml | 8 +++ runtime/regionx/src/impls.rs | 51 +++++++++++++++++-- runtime/regionx/src/lib.rs | 30 +++++++++-- ...e-test.toml => 0001-block-production.toml} | 7 --- ...test.zndsl => 0001-block-production.zndsl} | 0 zombienet_tests/0002-native-fee-payment.toml | 23 +++++++++ zombienet_tests/0002-native-fee-payment.zndsl | 10 ++++ zombienet_tests/0003-custom-fee-payment.toml | 25 +++++++++ zombienet_tests/0003-custom-fee-payment.zndsl | 10 ++++ 20 files changed, 268 insertions(+), 23 deletions(-) create mode 100644 e2e_tests/common.js create mode 100644 e2e_tests/custom-fee-payment.js create mode 100644 e2e_tests/native-fee-payment.js create mode 100644 e2e_tests/package.json rename zombienet_tests/{0001-smoke-test.toml => 0001-block-production.toml} (76%) rename zombienet_tests/{0001-smoke-test.zndsl => 0001-block-production.zndsl} (100%) create mode 100644 zombienet_tests/0002-native-fee-payment.toml create mode 100644 zombienet_tests/0002-native-fee-payment.zndsl create mode 100644 zombienet_tests/0003-custom-fee-payment.toml create mode 100644 zombienet_tests/0003-custom-fee-payment.zndsl diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index a0804e63..cb1af387 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -22,7 +22,7 @@ jobs: - uses: ./.github/actions/setup - name: Install Rust - run: cargo install --git https://github.com/paritytech/psvm psvm + run: cargo install psvm@0.1.0 - name: Check Dependency Versions run: | chmod +x ./scripts/check-dependency-versions.sh diff --git a/.gitignore b/.gitignore index a34d7ec0..ca01b4c0 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,8 @@ bin/ .DS_Store .idea .vscode + +bin/ + +node_modules +**/package-lock.json diff --git a/Cargo.lock b/Cargo.lock index bf131e61..a9f88f45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9412,6 +9412,8 @@ dependencies = [ "orml-currencies", "orml-tokens", "orml-traits", + "pallet-asset-rate", + "pallet-asset-tx-payment", "pallet-aura", "pallet-authorship", "pallet-balances", diff --git a/Cargo.toml b/Cargo.toml index 5f30ff23..fcfaafa4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,8 @@ pallet-session = { version = "28.0.0", default-features = false } pallet-sudo = { version = "28.0.0", default-features = false } pallet-timestamp = { version = "27.0.0", default-features = false } pallet-transaction-payment = { version = "28.0.0", default-features = false } +pallet-asset-rate = { version = "7.0.0", default-features = false } +pallet-asset-tx-payment = { version = "28.0.0", default-features = false } pallet-transaction-payment-rpc-runtime-api = { version = "28.0.0", default-features = false } pallet-message-queue = { version = "31.0.0", default-features = false } pallet-multisig = { version = "28.0.0", default-features = false } diff --git a/README.md b/README.md index 25982308..19a4f421 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ zombienet-linux setup polkadot Please add the dir to your $PATH by running the command: - export PATH=/home//zombienet/dist:$PATH + export PATH=/home//RegionX-Node/:$PATH ``` 4. Run the test: diff --git a/e2e_tests/common.js b/e2e_tests/common.js new file mode 100644 index 00000000..590a78f7 --- /dev/null +++ b/e2e_tests/common.js @@ -0,0 +1,24 @@ +async function submitExtrinsic(signer, call, options) { + return new Promise(async (resolve, reject) => { + const unsub = await call.signAndSend(signer, options, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log( + `Transaction included at blockHash ${result.status.asInBlock}` + ); + } else if (result.status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${result.status.asFinalized}` + ); + unsub(); + return resolve(); + } else if (result.isError) { + console.log(`Transaction error`); + unsub(); + return resolve(); + } + }); + }); +} + +module.exports = { submitExtrinsic } diff --git a/e2e_tests/custom-fee-payment.js b/e2e_tests/custom-fee-payment.js new file mode 100644 index 00000000..95c52c01 --- /dev/null +++ b/e2e_tests/custom-fee-payment.js @@ -0,0 +1,48 @@ +const { ApiPromise, WsProvider } = require("@polkadot/api"); +const { submitExtrinsic } = require("./common"); + +const ASSET_ID = 42; + +async function run(nodeName, networkInfo, _jsArgs) { + const { wsUri } = networkInfo.nodesByName[nodeName]; + const api = await ApiPromise.create({ + provider: new WsProvider(wsUri), + signedExtensions: { + ChargeAssetTxPayment: { + extrinsic: { + tip: "Compact", + assetId: "Option", + }, + payload: {}, + }, + }, + }); + + // account to submit tx + const keyring = new zombie.Keyring({ type: "sr25519" }); + const alice = keyring.addFromUri("//Alice"); + + const assetMetadata = { + decimals: 10, + name: "DOT", + symbol: "DOT", + existentialDeposit: 10n**3n, + location: 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), + ]; + const batchCall = api.tx.utility.batch(assetSetupCalls); + const sudo = api.tx.sudo.sudo(batchCall); + + await submitExtrinsic(alice, sudo, {}); + + const remarkCall = api.tx.system.remark("0x44"); + await submitExtrinsic(alice, remarkCall, {assetId: ASSET_ID}); +} + +module.exports = { run }; diff --git a/e2e_tests/native-fee-payment.js b/e2e_tests/native-fee-payment.js new file mode 100644 index 00000000..affa8e9c --- /dev/null +++ b/e2e_tests/native-fee-payment.js @@ -0,0 +1,18 @@ +const { ApiPromise, WsProvider } = require("@polkadot/api"); +const { submitExtrinsic } = require("./common"); + +async function run(nodeName, networkInfo, _jsArgs) { + const { wsUri } = networkInfo.nodesByName[nodeName]; + const api = await ApiPromise.create({provider: new WsProvider(wsUri)}); + + // account to submit tx + const keyring = new zombie.Keyring({ type: "sr25519" }); + const alice = keyring.addFromUri("//Alice"); + const bob = keyring.addFromUri("//Bob"); + + const call = api.tx.balances.transferKeepAlive(bob.address, 10n**6n); + const sudo = api.tx.sudo.sudo(call); + await submitExtrinsic(alice, sudo, {}); +} + +module.exports = { run }; diff --git a/e2e_tests/package.json b/e2e_tests/package.json new file mode 100644 index 00000000..488bb14c --- /dev/null +++ b/e2e_tests/package.json @@ -0,0 +1,15 @@ +{ + "name": "test", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@polkadot/api": "^11.0.1" + } +} diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index 9429cff1..d2ea3b91 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -79,7 +79,7 @@ pub fn session_keys(keys: AuraId) -> regionx_runtime::SessionKeys { pub fn development_config(id: u32) -> ChainSpec { // Give your base currency a unit name and decimal places let mut properties = sc_chain_spec::Properties::new(); - properties.insert("tokenSymbol".into(), "REGX".into()); + properties.insert("tokenSymbol".into(), "M4X".into()); properties.insert("tokenDecimals".into(), 12.into()); // TODO: chose an ss58Format properties.insert("ss58Format".into(), 42.into()); @@ -130,7 +130,7 @@ pub fn development_config(id: u32) -> ChainSpec ChainSpec { // Give your base currency a unit name and decimal places let mut properties = sc_chain_spec::Properties::new(); - properties.insert("tokenSymbol".into(), "REGX".into()); + properties.insert("tokenSymbol".into(), "M4X".into()); properties.insert("tokenDecimals".into(), 12.into()); // TODO: chose an ss58Format properties.insert("ss58Format".into(), 42.into()); diff --git a/runtime/primitives/src/assets.rs b/runtime/primitives/src/assets.rs index 1a57b186..929a2e84 100644 --- a/runtime/primitives/src/assets.rs +++ b/runtime/primitives/src/assets.rs @@ -1,4 +1,3 @@ -use crate::Balance; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -19,9 +18,7 @@ pub type AssetId = u32; TypeInfo, MaxEncodedLen, )] -pub struct CustomMetadata { - pub existential_deposit: Balance, -} +pub struct CustomMetadata; pub const REGX_ASSET_ID: AssetId = 0; pub const RELAY_CHAIN_ASSET_ID: AssetId = 1; diff --git a/runtime/regionx/Cargo.toml b/runtime/regionx/Cargo.toml index 711b830c..2c9bc985 100644 --- a/runtime/regionx/Cargo.toml +++ b/runtime/regionx/Cargo.toml @@ -46,6 +46,8 @@ frame-system-rpc-runtime-api = { workspace = true } frame-try-runtime = { workspace = true, optional = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } +pallet-asset-rate = { workspace = true } +pallet-asset-tx-payment = { workspace = true } pallet-balances = { workspace = true } pallet-message-queue = { workspace = true } pallet-multisig = { workspace = true } @@ -123,6 +125,8 @@ std = [ "orml-traits/std", "pallet-aura/std", "pallet-authorship/std", + "pallet-asset-rate/std", + "pallet-asset-tx-payment/std", "pallet-balances/std", "pallet-collator-selection/std", "pallet-message-queue/std", @@ -174,6 +178,8 @@ runtime-benchmarks = [ "hex-literal", "orml-asset-registry/runtime-benchmarks", "orml-tokens/runtime-benchmarks", + "pallet-asset-rate/runtime-benchmarks", + "pallet-asset-tx-payment/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-collator-selection/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", @@ -209,6 +215,8 @@ try-runtime = [ "ismp-parachain/try-runtime", "pallet-aura/try-runtime", "pallet-authorship/try-runtime", + "pallet-asset-rate/try-runtime", + "pallet-asset-tx-payment/try-runtime", "pallet-balances/try-runtime", "pallet-collator-selection/try-runtime", "pallet-message-queue/try-runtime", diff --git a/runtime/regionx/src/impls.rs b/runtime/regionx/src/impls.rs index 9a0d73d4..06ceb026 100644 --- a/runtime/regionx/src/impls.rs +++ b/runtime/regionx/src/impls.rs @@ -1,10 +1,16 @@ -use crate::{AssetId, Balance, OrmlAssetRegistry, Runtime, RuntimeCall}; +use crate::{AccountId, AssetId, AssetRegistry, Authorship, Balance, Runtime, RuntimeCall, Tokens}; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::traits::InstanceFilter; +use frame_support::traits::{ + fungibles, tokens::ConversionToAssetBalance, Defensive, InstanceFilter, +}; use orml_asset_registry::DefaultAssetMetadata; use orml_traits::{asset_registry::AssetProcessor, GetByKey}; +use pallet_asset_tx_payment::HandleCredit; use scale_info::TypeInfo; -use sp_runtime::{DispatchError, RuntimeDebug}; +use sp_runtime::{ + traits::CheckedDiv, ArithmeticError, DispatchError, FixedPointNumber, FixedU128, RuntimeDebug, + TokenError, +}; #[derive( Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen, @@ -30,6 +36,43 @@ impl AssetProcessor> for CustomAssetProce } } +/// A `HandleCredit` implementation that naively transfers the fees to the block author. +/// Will drop and burn the assets in case the transfer fails. +pub struct TokensToBlockAuthor; +impl HandleCredit for TokensToBlockAuthor { + fn handle_credit(credit: fungibles::Credit) { + use frame_support::traits::fungibles::Balanced; + if let Some(author) = Authorship::author() { + // In case of error: Will drop the result triggering the `OnDrop` of the imbalance. + let _ = Tokens::resolve(&author, credit).defensive(); + } + } +} + +pub struct TokenToNativeConverter; +impl ConversionToAssetBalance for TokenToNativeConverter { + type Error = DispatchError; + + fn to_asset_balance(balance: Balance, asset_id: AssetId) -> Result { + // NOTE: in the newer version of the asset-rate pallet the `ConversionToAssetBalance` + // is implemented. + // + // However, that version is not matching with the rest of the versions we use, so we + // will implement it manually for now. + // + // TODO: This should be updated once we start using the versions from `1.7.0` release. + + let rate = pallet_asset_rate::ConversionRateToNative::::get(asset_id) + .ok_or(DispatchError::Token(TokenError::UnknownAsset))?; + + // We cannot use `saturating_div` here so we use `checked_div`. + Ok(FixedU128::from_u32(1) + .checked_div(&rate) + .ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow))? + .saturating_mul_int(balance)) + } +} + /// The type used to represent the kinds of proxying allowed. #[derive( Copy, @@ -79,7 +122,7 @@ impl InstanceFilter for ProxyType { pub struct ExistentialDeposits; impl GetByKey for ExistentialDeposits { fn get(asset: &AssetId) -> Balance { - if let Some(metadata) = OrmlAssetRegistry::metadata(asset) { + if let Some(metadata) = AssetRegistry::metadata(asset) { metadata.existential_deposit } else { // As restrictive as we can be. The asset must have associated metadata. diff --git a/runtime/regionx/src/lib.rs b/runtime/regionx/src/lib.rs index 59cf43d5..334a7340 100644 --- a/runtime/regionx/src/lib.rs +++ b/runtime/regionx/src/lib.rs @@ -74,6 +74,7 @@ use frame_system::{ EnsureRoot, Phase, }; use orml_currencies::BasicCurrencyAdapter; +use pallet_asset_tx_payment::FungiblesAdapter; use pallet_ismp::{ dispatcher::Dispatcher, mmr::primitives::{Leaf, LeafIndex}, @@ -117,7 +118,7 @@ pub type SignedExtra = ( frame_system::CheckEra, frame_system::CheckNonce, frame_system::CheckWeight, - pallet_transaction_payment::ChargeTransactionPayment, + pallet_asset_tx_payment::ChargeAssetTxPayment, ); /// Unchecked extrinsic type as expected by this runtime. @@ -418,6 +419,7 @@ parameter_types! { impl pallet_transaction_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; + // TODO: send fees to treasury. type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter; type WeightToFee = WeightToFee; type LengthToFee = ConstantMultiplier; @@ -425,6 +427,24 @@ impl pallet_transaction_payment::Config for Runtime { type OperationalFeeMultiplier = ConstU8<5>; } +impl pallet_asset_rate::Config for Runtime { + type CreateOrigin = EnsureRoot; + type RemoveOrigin = EnsureRoot; + type UpdateOrigin = EnsureRoot; + type Currency = Balances; + type AssetKind = AssetId; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +impl pallet_asset_tx_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Fungibles = Tokens; + type OnChargeAssetTransaction = FungiblesAdapter; +} + impl pallet_sudo::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; @@ -643,9 +663,11 @@ construct_runtime!( // Monetary stuff. Balances: pallet_balances = 10, TransactionPayment: pallet_transaction_payment = 11, - OrmlAssetRegistry: orml_asset_registry = 12, - Tokens: orml_tokens = 13, - Currencies: orml_currencies = 14, + AssetTxPayment: pallet_asset_tx_payment = 12, + AssetRegistry: orml_asset_registry = 13, + Tokens: orml_tokens = 14, + Currencies: orml_currencies = 15, + AssetRate: pallet_asset_rate = 16, // Governance Sudo: pallet_sudo = 20, diff --git a/zombienet_tests/0001-smoke-test.toml b/zombienet_tests/0001-block-production.toml similarity index 76% rename from zombienet_tests/0001-smoke-test.toml rename to zombienet_tests/0001-block-production.toml index 5bbdfad1..48759714 100644 --- a/zombienet_tests/0001-smoke-test.toml +++ b/zombienet_tests/0001-block-production.toml @@ -3,7 +3,6 @@ timeout = 1000 [relaychain] -default_image = "{{POLKADOT_IMAGE}}" chain = "rococo-local" command = "polkadot" @@ -21,11 +20,5 @@ addToGenesis = false [parachains.collator] name = "regionx-collator01" - image = "{{REGIONX_IMAGE}}" command = "regionx-node" args = [ "-lruntime=debug,parachain=trace" ] - -[types.Header] -number = "u64" -parent_hash = "Hash" -post_state = "Hash" diff --git a/zombienet_tests/0001-smoke-test.zndsl b/zombienet_tests/0001-block-production.zndsl similarity index 100% rename from zombienet_tests/0001-smoke-test.zndsl rename to zombienet_tests/0001-block-production.zndsl diff --git a/zombienet_tests/0002-native-fee-payment.toml b/zombienet_tests/0002-native-fee-payment.toml new file mode 100644 index 00000000..6a2bf943 --- /dev/null +++ b/zombienet_tests/0002-native-fee-payment.toml @@ -0,0 +1,23 @@ +[settings] +timeout = 1000 + +[relaychain] +chain = "rococo-local" +command = "polkadot" + + [[relaychain.nodes]] + name = "alice" + validator = true + + [[relaychain.nodes]] + name = "bob" + validator = true + +[[parachains]] +id = 2000 +addToGenesis = false + + [parachains.collator] + name = "regionx-collator01" + command = "regionx-node" + args = [ "-lruntime=debug,parachain=trace" ] diff --git a/zombienet_tests/0002-native-fee-payment.zndsl b/zombienet_tests/0002-native-fee-payment.zndsl new file mode 100644 index 00000000..22446fd9 --- /dev/null +++ b/zombienet_tests/0002-native-fee-payment.zndsl @@ -0,0 +1,10 @@ +Description: Native currency fee payment +Network: ./0002-native-fee-payment.toml +Creds: config + +alice: is up +bob: is up + +alice: parachain 2000 is registered within 225 seconds + +regionx-collator01: js-script ../e2e_tests/native-fee-payment.js return is 0 within 200 seconds diff --git a/zombienet_tests/0003-custom-fee-payment.toml b/zombienet_tests/0003-custom-fee-payment.toml new file mode 100644 index 00000000..c74fbef6 --- /dev/null +++ b/zombienet_tests/0003-custom-fee-payment.toml @@ -0,0 +1,25 @@ +[settings] +timeout = 1000 + +[relaychain] +chain = "rococo-local" +command = "polkadot" + + [[relaychain.nodes]] + name = "alice" + validator = true + + [[relaychain.nodes]] + name = "bob" + validator = true + +[[parachains]] +id = 2000 +addToGenesis = false + + [parachains.collator] + name = "regionx-collator01" + command = "regionx-node" + args = [ "-lruntime=debug,parachain=trace" ] + + diff --git a/zombienet_tests/0003-custom-fee-payment.zndsl b/zombienet_tests/0003-custom-fee-payment.zndsl new file mode 100644 index 00000000..36561db3 --- /dev/null +++ b/zombienet_tests/0003-custom-fee-payment.zndsl @@ -0,0 +1,10 @@ +Description: Native currency fee payment +Network: ./0003-custom-fee-payment.toml +Creds: config + +alice: is up +bob: is up + +alice: parachain 2000 is registered within 225 seconds + +regionx-collator01: js-script ../e2e_tests/custom-fee-payment.js return is 0 within 400 seconds