从零开始写一个链上模块只要15分钟,以Polkadot为例
本文介绍了如何使用FRAME框架在Polkadot SDK中创建自定义pallet。通过实现一个计数器pallet,详细讲解了pallet开发的关键组件:包括配置trait定义、存储项设计、事件声明、错误处理以及可调用函数的实现。教程涵盖了从项目初始化到完整pallet构建的全流程,展示了如何利用FRAME提供的宏和工具简化区块链逻辑开发。最终实现的计数器pallet具备基本计数功能,并支持权限控
原文作者: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/
更多推荐



所有评论(0)