Skip to content

Commit

Permalink
Fee payment in non native assets (#77)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Szegoo authored Apr 25, 2024
1 parent baeee1c commit 2c698b9
Show file tree
Hide file tree
Showing 20 changed files with 268 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/dependencies.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@ bin/
.DS_Store
.idea
.vscode

bin/

node_modules
**/package-lock.json
2 changes: 2 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
zombienet-linux setup polkadot

Please add the dir to your $PATH by running the command:
export PATH=/home/<username>/zombienet/dist:$PATH
export PATH=/home/<username>/RegionX-Node/:$PATH
```

4. Run the test:
Expand Down
24 changes: 24 additions & 0 deletions e2e_tests/common.js
Original file line number Diff line number Diff line change
@@ -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 }
48 changes: 48 additions & 0 deletions e2e_tests/custom-fee-payment.js
Original file line number Diff line number Diff line change
@@ -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<Balance>",
assetId: "Option<AssetId>",
},
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 };
18 changes: 18 additions & 0 deletions e2e_tests/native-fee-payment.js
Original file line number Diff line number Diff line change
@@ -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 };
15 changes: 15 additions & 0 deletions e2e_tests/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
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(), "REGX".into());
properties.insert("tokenSymbol".into(), "M4X".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(), "REGX".into());
properties.insert("tokenSymbol".into(), "M4X".into());
properties.insert("tokenDecimals".into(), 12.into());
// TODO: chose an ss58Format
properties.insert("ss58Format".into(), 42.into());
Expand Down
5 changes: 1 addition & 4 deletions runtime/primitives/src/assets.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::Balance;
use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;

Expand All @@ -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;
8 changes: 8 additions & 0 deletions runtime/regionx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
51 changes: 47 additions & 4 deletions runtime/regionx/src/impls.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -30,6 +36,43 @@ impl AssetProcessor<AssetId, DefaultAssetMetadata<Runtime>> 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<AccountId, Tokens> for TokensToBlockAuthor {
fn handle_credit(credit: fungibles::Credit<AccountId, Tokens>) {
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<Balance, AssetId, Balance> for TokenToNativeConverter {
type Error = DispatchError;

fn to_asset_balance(balance: Balance, asset_id: AssetId) -> Result<Balance, Self::Error> {
// 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::<Runtime>::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,
Expand Down Expand Up @@ -79,7 +122,7 @@ impl InstanceFilter<RuntimeCall> for ProxyType {
pub struct ExistentialDeposits;
impl GetByKey<AssetId, Balance> 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.
Expand Down
30 changes: 26 additions & 4 deletions runtime/regionx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -117,7 +118,7 @@ pub type SignedExtra = (
frame_system::CheckEra<Runtime>,
frame_system::CheckNonce<Runtime>,
frame_system::CheckWeight<Runtime>,
pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
pallet_asset_tx_payment::ChargeAssetTxPayment<Runtime>,
);

/// Unchecked extrinsic type as expected by this runtime.
Expand Down Expand Up @@ -418,13 +419,32 @@ parameter_types! {

impl pallet_transaction_payment::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
// TODO: send fees to treasury.
type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter<Balances, ()>;
type WeightToFee = WeightToFee;
type LengthToFee = ConstantMultiplier<Balance, TransactionByteFee>;
type FeeMultiplierUpdate = SlowAdjustingFeeUpdate<Self>;
type OperationalFeeMultiplier = ConstU8<5>;
}

impl pallet_asset_rate::Config for Runtime {
type CreateOrigin = EnsureRoot<AccountId>;
type RemoveOrigin = EnsureRoot<AccountId>;
type UpdateOrigin = EnsureRoot<AccountId>;
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<TokenToNativeConverter, TokensToBlockAuthor>;
}

impl pallet_sudo::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ timeout = 1000


[relaychain]
default_image = "{{POLKADOT_IMAGE}}"
chain = "rococo-local"
command = "polkadot"

Expand All @@ -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"
File renamed without changes.
Loading

0 comments on commit 2c698b9

Please sign in to comment.