原文作者:PaperMoon团队


在基于 Polkadot SDK 的区块链中,运行时(Runtime)功能是通过称为 pallet 的模块化组件来构建的。Pallet 是使用 FRAME(Framework for Runtime Aggregation of Modular Entities) 创建的 Rust 运行时模块。FRAME 是一个功能强大的库,它通过提供专用宏和标准化模式,大大简化了区块链逻辑的开发。

一个 pallet 通常封装了一组特定的区块链功能,例如:
    •    管理代币余额
    •    实现治理机制
    •    定义自定义状态转换逻辑

在本教程中,你将从零开始创建一个自定义 pallet。你将实现一个简单的计数器(Counter)pallet,具备以下功能:
    •    用户可以对计数器进行递增和递减
    •    只有 Root 权限才能将计数器设置为任意值

前置条件

你将使用在 Set Up a Template 教程中创建的 Polkadot SDK Parachain Template。

创建一个新项目

在本教程中,我们将从零开始构建一个自定义 pallet,以完整展示整个开发流程,而不是使用预先提供的 pallet-template。

第一步:创建 pallet 工程

进入工作区中的 pallets 目录:

cd pallets

创建一个新的 Rust library 项目:

cargo new --lib custom-pallet

进入新创建的项目目录:

cd custom-pallet

确认项目结构如下:

custom-pallet
├── Cargo.toml
└── src
    └── lib.rs

如果文件结构正确,说明项目初始化完成,可以开始构建你的自定义 pallet 了。

添加依赖

要将你的自定义 pallet 集成进基于 Polkadot SDK 的 runtime,你需要在 Cargo.toml 中添加必要的依赖。这些依赖提供了 pallet 开发所需的核心功能。

由于你的 pallet 属于一个 workspace,需要使用 workspace 统一管理依赖版本。

编辑 Cargo.toml

打开 Cargo.toml,在 [dependencies] 中添加:

[dependencies]
codec = { features = ["derive"], workspace = true }
scale-info = { features = ["derive"], workspace = true }
frame = { features = ["experimental", "runtime"], workspace = true }

启用 std 特性:

[features]
default = ["std"]
std = ["codec/std", "frame/std", "scale-info/std"]

完成后,你的 Cargo.toml 应与示例一致。

实现 Pallet 核心逻辑

接下来,你将构建 pallet 的核心结构,作为后续添加存储、事件、错误和可调用函数的基础。

添加 Pallet 脚手架结构

初始化 lib.rs

打开 src/lib.rs,删除所有内容,然后添加以下代码:

#![cfg_attr(not(feature = "std"), no_std)]

pub use pallet::*;

#[frame::pallet]
pub mod pallet {
    use super::*;
    use frame::prelude::*;

    #[pallet::pallet]
    pub struct Pallet<T>(_);

    // Pallet 的配置 trait
    #[pallet::config]
    pub trait Config: frame_system::Config {
        // 在这里定义事件类型等配置
    }
}

验证编译

运行以下命令,确认代码可以正常编译:

cargo build --package custom-pallet

Pallet 配置(Config)

#[pallet::config] 是 必须实现的,它用于声明 pallet 对 runtime 的依赖关系,以及由 runtime 提供的类型和常量。

本步骤中,你将配置两个关键组件:

1. RuntimeEvent

由于 pallet 会发出事件,因此需要声明 RuntimeEvent 类型,以确保事件能被 runtime 正确处理。

2. CounterMaxValue

一个常量,用于限制计数器的最大值,防止计数器无限增长。

添加 Config 定义

#[pallet::config]
pub trait Config: frame_system::Config {
    /// 定义 pallet 使用的事件类型
    type RuntimeEvent: From<Event<Self>>
        + IsType<<Self as frame_system::Config>::RuntimeEvent>;

    /// 定义计数器允许的最大值
    #[pallet::constant]
    type CounterMaxValue: Get<u32>;
}

添加事件(Events)

事件用于向外部系统(如 UI、索引器、监控工具)传递 pallet 中发生的状态变化信息。

本 pallet 定义了以下事件:

事件说明
    •    CounterValueSet
Root 成功设置计数器新值时触发
    •    CounterIncremented
用户成功递增计数器时触发,包含:
    •    新的计数器值
    •    操作者账户
    •    增加的数量
    •    CounterDecremented
用户成功递减计数器时触发,包含:
    •    新的计数器值
    •    操作者账户
    •    减少的数量

定义事件

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
    /// Root 设置了新的计数器值
    CounterValueSet {
        counter_value: u32,
    },
    /// 用户递增了计数器
    CounterIncremented {
        counter_value: u32,
        who: T::AccountId,
        incremented_amount: u32,
    },
    /// 用户递减了计数器
    CounterDecremented {
        counter_value: u32,
        who: T::AccountId,
        decremented_amount: u32,
    },
}

添加存储项(Storage)

存储项用于管理 pallet 的链上状态。

存储设计
    •    CounterValue
存储当前计数器的值
    •    UserInteractions
记录每个账户与计数器交互的次数

定义存储

#[pallet::storage]
pub type CounterValue<T> = StorageValue<_, u32>;

/// 记录每个账户的交互次数
#[pallet::storage]
pub type UserInteractions<T: Config> =
    StorageMap<_, Twox64Concat, T::AccountId, u32>;

实现自定义错误(Errors)

错误用于在 extrinsic 执行失败时提供明确的失败原因。

错误定义说明
    •    CounterValueExceedsMax:计数器超过最大值
    •    CounterValueBelowZero:递减导致计数器小于 0
    •    CounterOverflow:计数器计算溢出
    •    UserInteractionOverflow:用户交互计数溢出

定义错误

#[pallet::error]
pub enum Error<T> {
    CounterValueExceedsMax,
    CounterValueBelowZero,
    CounterOverflow,
    UserInteractionOverflow,
}

实现可调用函数(Calls)

#[pallet::call] 定义了 pallet 对外暴露的 dispatchable functions,即 extrinsics。

函数概览

#[pallet::call]
impl<T: Config> Pallet<T> {

    /// Root 设置计数器的值
    #[pallet::call_index(0)]
    #[pallet::weight(0)]
    pub fn set_counter_value(
        origin: OriginFor<T>,
        new_value: u32
    ) -> DispatchResult {
    }

    /// 用户递增计数器
    #[pallet::call_index(1)]
    #[pallet::weight(0)]
    pub fn increment(
        origin: OriginFor<T>,
        amount_to_increment: u32
    ) -> DispatchResult {
    }

    /// 用户递减计数器
    #[pallet::call_index(2)]
    #[pallet::weight(0)]
    pub fn decrement(
        origin: OriginFor<T>,
        amount_to_decrement: u32
    ) -> DispatchResult {
    }
}

验证编译

完成所有实现后,运行以下命令验证 pallet 是否可以成功编译:

cargo build --package custom-pallet

如果出现错误,请逐一检查代码;当构建成功后,说明你的 pallet 已经可以正常工作。

通过本教程,你学习了:
    •    如何从零创建一个自定义 pallet
    •    如何定义存储、事件、错误和 extrinsics
    •    如何使用 FRAME 的宏体系构建 runtime 逻辑

这些都是构建 Polkadot SDK 区块链运行时 的核心基础。

原文链接:https://docs.polkadot.com/tutorials/polkadot-sdk/parachains/zero-to-hero/build-custom-pallet/

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐