Rust支付交易插件库pallet-asset-tx-payment的使用,支持资产交易手续费支付与链上经济模型构建

pallet-asset-tx-payment

资产交易支付模块

这个模块允许运行时系统使用除原生代币以外的资产来支付交易费用。

概述

该模块通过在交易中包含一个可选的AssetId参数来实现功能,该参数指定用于支付的资产类型(当为None时默认使用原生代币)。它需要一个与pallet-transaction-payment类似的OnChargeAssetTransaction实现。内置的FungiblesAdapter(实现了OnChargeAssetTransaction)通过将pallet-transaction-payment计算的手续费转换为指定资产来确定最终费用金额。

集成方式

此模块是对FRAME交易支付模块的封装和替代。使用时需要在construct_runtime宏中同时包含两个模块,但只需包含此模块的TransactionExtension(即ChargeAssetTxPayment)。

许可证:Apache-2.0

完整示例代码

以下是更完整的集成示例,包含更多上下文和注释:

// runtime/src/lib.rs

// 1. 引入必要的依赖和类型
use frame_support::{
    construct_runtime, parameter_types,
    traits::{AsEnsureOriginWithArg, Currency, ExistenceRequirement, Fungibles},
    weights::{Weight, WeightToFee as WeightToFeeT},
};
use frame_system::limits::BlockWeights;
use pallet_asset_tx_payment::{
    ChargeAssetTxPayment, FungiblesAdapter, OnChargeAssetTransaction,
};
use sp_runtime::{
    traits::{Convert, IdentifyAccount, Verify},
    FixedPointNumber, FixedU128, Perbill,
};

// 2. 定义手续费相关参数
parameter_types! {
    pub const BlockHashCount: u64 = 250;
    pub const MaximumBlockWeight: Weight = Weight::from_parts(1024, 1);
    pub const MaximumBlockLength: u32 = 2 * 1024;
    pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75);
    pub const TransactionByteFee: u128 = 1;  // 每字节交易费用
    pub const OperationalFeeMultiplier: u8 = 5;  // 操作类交易费用乘数
}

// 3. 实现WeightToFee转换器
pub struct WeightToFee;
impl WeightToFeeT for WeightToFee {
    type Balance = Balance;

    fn weight_to_fee(weight: &Weight) -> Self::Balance {
        // 简单线性转换:1单位Weight = 1单位费用
        weight.ref_time()
    }
}

// 4. 实现OnChargeAssetTransaction适配器
pub struct OnChargeAssetTransactionAdapter;
impl OnChargeAssetTransaction<Runtime> for OnChargeAssetTransactionAdapter {
    type LiquidityInfo = Option<Balance>;
    type AssetId = AssetId;
    type Balance = Balance;

    fn withdraw_fee(
        who: &AccountId,
        call: &RuntimeCall,
        info: &DispatchInfoOf<RuntimeCall>,
        fee: Balance,
        tip: Balance,
        asset_id: Option<Self::AssetId>,
    ) -> Result<Self::LiquidityInfo, TransactionValidityError> {
        match asset_id {
            // 使用指定资产支付手续费
            Some(asset_id) => FungiblesAdapter::<Runtime>::withdraw_fee(
                who, call, info, fee, tip, Some(asset_id),
            ),
            // 使用原生代币支付手续费
            None => Currency::<Runtime>::withdraw_fee(who, call, info, fee, tip),
        }
    }
}

// 5. 定义Runtime实现
construct_runtime!(
    pub enum Runtime where
        Block = Block,
        NodeBlock = opaque::Block,
        UncheckedExtrinsic = UncheckedExtrinsic,
    {
        // 系统基础模块
        System: frame_system,
        // 原生代币模块
        Balances: pallet_balances,
        // 多资产模块
        Assets: pallet_assets,
        // 标准交易支付模块
        TransactionPayment: pallet_transaction_payment,
        // 资产交易支付模块
        AssetTxPayment: pallet_asset_tx_payment,
        // 其他模块...
    }
);

// 6. 配置资产交易支付模块
impl pallet_asset_tx_payment::Config for Runtime {
    type Event = Event;
    type OnChargeAssetTransaction = OnChargeAssetTransactionAdapter;
    type AssetId = AssetId;  // 资产ID类型
    type Balance = Balance;  // 余额类型
    type WeightToFee = WeightToFee;  // 权重到费用的转换器
    type LengthToFee = ConstantMultiplier<Balance, TransactionByteFee>;  // 长度到费用的转换
    type FeeMultiplierUpdate = ();  // 费用乘数更新机制
}

// 7. 配置交易签名额外信息
type SignedExtra = (
    // 标准检查项
    frame_system::CheckSpecVersion<Runtime>,
    frame_system::CheckTxVersion<Runtime>,
    frame_system::CheckGenesis<Runtime>,
    frame_system::CheckEra<Runtime>,
    frame_system::CheckNonce<Runtime>,
    frame_system::CheckWeight<Runtime>,
    // 原生代币支付处理器
    pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
    // 资产支付处理器
    pallet_asset_tx_payment::ChargeAssetTxPayment<Runtime>,
);

这个完整示例展示了如何在Substrate runtime中集成资产交易支付功能,主要包含以下关键部分:

  1. 定义手续费相关参数和转换规则
  2. 实现权重到费用的转换逻辑
  3. 创建资产交易支付适配器
  4. 在runtime构建中包含必要模块
  5. 配置资产交易支付模块的具体实现
  6. 设置交易签名额外信息处理流程

通过这种配置,用户可以在发送交易时选择使用原生代币或其他注册资产支付手续费,为链上经济系统提供了更大的灵活性。


1 回复

Rust支付交易插件库pallet-asset-tx-payment使用指南

概述

pallet-asset-tx-payment是Substrate框架中的一个重要模块,它允许用户使用特定资产(而非原生代币)支付交易手续费,为区块链经济模型提供了更大的灵活性。

主要功能

  1. 支持使用非原生资产支付交易手续费
  2. 提供资产兑换率配置功能
  3. 实现手续费计算和扣除逻辑
  4. 支持链上经济模型的自定义构建

使用方法

1. 在runtime中集成

首先需要在runtime中引入该pallet:

// runtime/src/lib.rs

impl pallet_asset_tx_payment::Config for Runtime {
    type Event = Event;
    type OnChargeAssetTransaction = pallet_asset_tx_payment::FungiblesAdapter<
        pallet_assets::Pallet<Runtime>,
        pallet_balances::Pallet<Runtime>,
        ConvertInto,
    >;
}

construct_runtime!(
    pub enum Runtime where
        Block = Block,
        NodeBlock = opaque::Block,
        UncheckedExtrinsic = UncheckedExtrinsic,
    {
        // ... 其他pallet
        AssetTxPayment: pallet_asset_tx_payment::{Pallet, Storage, Event<T>},
    }
);

2. 配置资产兑换率

// 设置资产ID为1的兑换率为1:10 (1单位资产 = 10单位原生代币)
pallet_asset_tx_payment::Pallet::<Runtime>::set_asset_ratio(
    Origin::root(),
    1,  // 资产ID
    10, // 兑换率分子
    1   // 兑换率分母
);

3. 使用资产支付手续费

用户发送交易时,可以指定使用特定资产支付手续费:

use frame_support::weights::{DispatchClass, Pays};
use pallet_asset_tx_payment::ChargeAssetTxPayment;

let call = Call::SomeModule(SomeModuleCall::some_function { param: 123 });
let info = call.get_dispatch_info();

let signed_extension = ChargeAssetTxPayment::<Runtime>::from(Some((1, who))); // 使用资产ID 1支付
let extrinsic = UncheckedExtrinsic::new_signed(
    call,
    who,
    signature,
    extra,
);

示例场景

场景1: DApp使用自有代币支付手续费

// 假设DApp的代币资产ID为5
let call = Call::DAppModule(DAppCall::user_action { data: vec![1, 2, 3] });
let signed_extension = ChargeAssetTxPayment::<Runtime>::from(Some((5, user)));

// 构建并提交交易
submit_extrinsic(UncheckedExtrinsic::new_signed(
    call,
    user,
    signature,
    (signed_extension, /* 其他signed extensions */),
));

场景2: 多资产手续费支付系统

// 设置多种资产的兑换率
pallet_asset_tx_payment::Pallet::<Runtime>::set_asset_ratio(Origin::root(), 1, 1, 1);  // 资产1 1:1
pallet_asset_tx_payment::Pallet::<Runtime>::set_asset_ratio(Origin::root(), 2, 2, 1);  // 资产2 2:1
pallet_asset_tx_payment::Pallet::<Runtime>::set_asset_ratio(Origin::root(), 3, 1, 10); // 资产3 1:10

// 用户可以选择最优惠的资产支付手续费

高级配置

自定义手续费计算

impl pallet_asset_tx_payment::Config for Runtime {
    // ... 其他配置
    
    type OnChargeAssetTransaction = MyCustomFeeHandler;
}

pub struct MyCustomFeeHandler;

impl OnChargeAssetTransaction<Runtime> for MyCustomFeeHandler {
    fn withdraw_fee(
        who: &AccountId,
        call: &CallOf<Runtime>,
        info: &DispatchInfoOf<Runtime>,
        fee: BalanceOf<Runtime>,
        tip: BalanceOf<Runtime>,
        asset_id: Option<u32>,
    ) -> Result<(BalanceOf<Runtime>, InitialPayment<Runtime>), TransactionValidityError> {
        // 自定义手续费扣除逻辑
        // ...
    }
}

完整示例代码

// runtime/src/lib.rs

// 1. 引入必要的依赖
use frame_support::{
    construct_runtime, parameter_types,
    traits::{AsEnsureOriginWithArg, ConstU32, ConstU64, ConvertInto},
    weights::IdentityFee,
};
use sp_runtime::{
    traits::{AccountIdConversion, BlakeTwo256, Convert, IdentifyAccount, Verify},
    MultiSignature, Perbill,
};

// 2. 配置AssetTxPayment pallet
impl pallet_asset_tx_payment::Config for Runtime {
    type Event = Event;
    type OnChargeAssetTransaction = pallet_asset_tx_payment::FungiblesAdapter<
        pallet_assets::Pallet<Runtime>,
        pallet_balances::Pallet<Runtime>,
        ConvertInto, // 使用默认的转换实现
    >;
}

// 3. 在construct_runtime中注册
construct_runtime!(
    pub enum Runtime where
        Block = Block,
        NodeBlock = opaque::Block,
        UncheckedExtrinsic = UncheckedExtrinsic,
    {
        // ... 其他pallet
        Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
        Assets: pallet_assets::{Pallet, Call, Storage, Event<T>},
        AssetTxPayment: pallet_asset_tx_payment::{Pallet, Storage, Event<T>},
    }
);

// 4. 使用示例
fn example_usage() {
    // 设置资产兑换率
    pallet_asset_tx_payment::Pallet::<Runtime>::set_asset_ratio(
        Origin::root(),
        1,  // 资产ID
        10, // 兑换率分子
        1   // 兑换率分母
    );

    // 创建交易
    let call = Call::MyModule(MyModuleCall::do_something { param: 42 });
    let info = call.get_dispatch_info();
    
    // 使用资产1支付手续费
    let signed_extension = ChargeAssetTxPayment::<Runtime>::from(Some((1, sender)));
    
    // 构建并提交交易
    let extrinsic = UncheckedExtrinsic::new_signed(
        call,
        sender,
        signature,
        (signed_extension, /* 其他signed extensions */),
    );
    
    // 提交交易到链上
    submit_extrinsic(extrinsic).unwrap();
}

// 5. 自定义手续费处理示例
pub struct CustomFeeHandler;

impl OnChargeAssetTransaction<Runtime> for CustomFeeHandler {
    fn withdraw_fee(
        who: &AccountId,
        call: &CallOf<Runtime>,
        info: &DispatchInfoOf<Runtime>,
        fee: BalanceOf<Runtime>,
        tip: BalanceOf<Runtime>,
        asset_id: Option<u32>,
    ) -> Result<(BalanceOf<Runtime>, InitialPayment<Runtime>), TransactionValidityError> {
        // 实现自定义手续费逻辑
        // 例如:对特定资产给予手续费折扣
        let adjusted_fee = if asset_id == Some(1) {
            fee / 2  // 对资产1给予50%折扣
        } else {
            fee
        };
        
        // 默认扣除逻辑
        pallet_asset_tx_payment::FungiblesAdapter::<Assets, Balances, ConvertInto>::withdraw_fee(
            who, call, info, adjusted_fee, tip, asset_id
        )
    }
}

注意事项

  1. 需要确保资产账户有足够余额支付手续费
  2. 兑换率设置需要合理,避免手续费计算溢出
  3. 原生代币仍需保留,用于系统基础操作
  4. 需要适当配置资产的最小余额要求

这个pallet为Substrate链提供了灵活的手续费支付方案,特别适合需要多资产经济模型的区块链项目。

回到顶部