原文作者:PaperMoon团队

FRAME(Framework for Runtime Aggregation of Modular Entities)是 Polkadot SDK 提供的一套模块化区块链开发框架。它通过“Pallet(功能模块)”的方式,让开发者可以灵活组合链上功能。

Pallet 本质上是基于 Rust 编写的 Runtime 模块,用于实现链上的业务逻辑。FRAME 内置了大量常用 Pallet(如余额、治理、共识等),但其真正的优势在于支持开发者编写完全自定义的 Pallet,以满足特定业务需求。

本教程将从零开始,带你实现一个简单的「计数器 Pallet」,用于讲解 Pallet 开发的核心流程与设计模式。

前置条件(Prerequisites)

在开始之前,请确保你已经具备以下环境:
    •    已正确安装 Polkadot SDK 相关依赖
    •    已在本地初始化 Parachain Template
    •    对 FRAME 的基本概念有一定了解

Pallet 的核心组成结构

一个标准 Pallet 通常由以下部分组成:
    1.    依赖导入(Imports)
    2.    配置 Trait(Config Trait)
    3.    事件定义(Events)
    4.    错误类型(Errors)
    5.    链上存储(Storage)
    6.    创世配置(Genesis Config)
    7.    可调用函数(Extrinsics)

这些模块共同构成 Pallet 的完整生命周期。

创建 Pallet 项目

1. 进入 Parachain 项目根目录

cd polkadot-sdk-parachain-template

2. 进入 pallets 目录

cd pallets

3. 创建新的 Rust 库项目

cargo new --lib pallet-customcargo 

4. 进入项目目录

cd pallet-custom

5. 目录结构应如下所示

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

配置依赖(Configure Dependencies)

由于 Parachain Template 使用 Workspace 机制统一管理依赖版本,因此需要使用 workspace 继承方式配置依赖。

打开 pallet-custom/Cargo.toml,替换为以下内容:

[package]
name = "pallet-custom"
description = "A custom counter pallet for demonstration purposes."
version = "0.1.0"
license = "Unlicense"
authors.workspace = true
homepage.workspace = true
repository.workspace = true
edition.workspace = true
publish = false

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

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

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

版本管理说明

Workspace 会在根目录 Cargo.toml 中统一定义依赖版本:

[workspace.members]
members = [
    "node",
    "pallets/*",
    "runtime",
]

因此无需在子项目中指定版本号。

初始化 Pallet 结构(Initialize Pallet Structure)

打开 src/lib.rs,删除原有内容,添加以下基础框架:

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

extern crate alloc;

pub use pallet::*;

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

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

    #[pallet::config]
    pub trait Config: frame_system::Config {
    }

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

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

该结构定义了:
    •    Pallet 主体
    •    Config Trait
    •    存储结构
    •    可调用接口区域

执行编译验证:

cargo build --package pallet-custom

配置 Config Trait(Configure the Pallet)

#[pallet::config]
pub trait Config: frame_system::Config {
    type RuntimeEvent: From<Event<Self>>
        + IsType<<Self as frame_system::Config>::RuntimeEvent>;

    #[pallet::constant]
    type CounterMaxValue: Get<u32>;
}

该配置定义:
    •    RuntimeEvent:事件系统绑定
    •    CounterMaxValue:计数器最大值常量

定义事件(Define Events)

添加事件模块:

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
    CounterValueSet { new_value: u32 },

    CounterIncremented {
        new_value: u32,
        who: T::AccountId,
        amount: u32,
    },

    CounterDecremented {
        new_value: u32,
        who: T::AccountId,
        amount: u32,
    },
}

事件用于通知链下系统状态变更。

定义错误类型(Define Errors)

添加错误模块:

#[pallet::error]
pub enum Error<T> {
    NoneValue,
    Overflow,
    Underflow,
    CounterMaxValueExceeded,
}

错误信息会被写入 Runtime Metadata,便于前端解析。

添加存储项(Add Storage Items)

补充用户交互记录:

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

#[pallet::storage]
pub type UserInteractions<T: Config> =
    StorageMap<_, Blake2_128Concat, T::AccountId, u32, ValueQuery>;

功能说明:
    •    CounterValue:全局计数器
    •    UserInteractions:用户操作次数统计

配置创世状态(Configure Genesis)

1. 导入 Vec

use alloc::vec::Vec;

2. 添加创世配置

#[pallet::genesis_config]
#[derive(DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
    pub initial_counter_value: u32,
    pub initial_user_interactions: Vec<(T::AccountId, u32)>,
}

#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
    fn build(&self) {
        CounterValue::<T>::put(self.initial_counter_value);

        for (account, count) in &self.initial_user_interactions {
            UserInteractions::<T>::insert(account, count);
        }
    }
}

Genesis 配置用于初始化链的起始状态。

实现可调用函数(Dispatchable Functions)

替换 #[pallet::call] 模块:

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

    #[pallet::call_index(0)]
    #[pallet::weight(0)]
    pub fn set_counter_value(
        origin: OriginFor<T>,
        new_value: u32,
    ) -> DispatchResult {

        ensure_root(origin)?;

        ensure!(
            new_value <= T::CounterMaxValue::get(),
            Error::<T>::CounterMaxValueExceeded
        );

        CounterValue::<T>::put(new_value);

        Self::deposit_event(Event::CounterValueSet { new_value });

        Ok(())
    }

    #[pallet::call_index(1)]
    #[pallet::weight(0)]
    pub fn increment(
        origin: OriginFor<T>,
        amount: u32,
    ) -> DispatchResult {

        let who = ensure_signed(origin)?;

        let current = CounterValue::<T>::get();

        let new_value =
            current.checked_add(amount)
            .ok_or(Error::<T>::Overflow)?;

        ensure!(
            new_value <= T::CounterMaxValue::get(),
            Error::<T>::CounterMaxValueExceeded
        );

        CounterValue::<T>::put(new_value);

        UserInteractions::<T>::mutate(&who, |c| {
            *c = c.saturating_add(1);
        });

        Self::deposit_event(
            Event::CounterIncremented {
                new_value,
                who,
                amount,
            }
        );

        Ok(())
    }

    #[pallet::call_index(2)]
    #[pallet::weight(0)]
    pub fn decrement(
        origin: OriginFor<T>,
        amount: u32,
    ) -> DispatchResult {

        let who = ensure_signed(origin)?;

        let current = CounterValue::<T>::get();

        let new_value =
            current.checked_sub(amount)
            .ok_or(Error::<T>::Underflow)?;

        CounterValue::<T>::put(new_value);

        UserInteractions::<T>::mutate(&who, |c| {
            *c = c.saturating_add(1);
        });

        Self::deposit_event(
            Event::CounterDecremented {
                new_value,
                who,
                amount,
            }
        );

        Ok(())
    }
}

方法

权限

功能

set_counter_value

Root

设置计数器

increment

Signed

增加

decrement

Signed

减少

编译验证(Verify Compilation)

cargo build --package pallet-custom

集成到 Runtime(Add to Runtime)

1. 添加依赖

runtime/Cargo.toml:

pallet-custom = { path = "../pallets/pallet-custom", default-features = false }

features:

"pallet-custom/std",

2. 实现 Config

runtime/src/configs/mod.rs:

impl pallet_custom::Config for Runtime {
    type RuntimeEvent = RuntimeEvent;
    type CounterMaxValue = ConstU32<1000>;
}

3. 注册 Pallet

runtime/src/lib.rs:

#[runtime::pallet_index(51)]
pub type CustomPallet = pallet_custom;

注意:索引必须唯一。

生成链配置并启动节点

生成 Chain Spec

chain-spec-builder create -t development \
--relay-chain paseo \
--para-id 1000 \
--runtime ./target/release/wbuild/parachain-template-runtime/parachain_template_runtime.compact.compressed.wasm \
named-preset development

启动节点

polkadot-omni-node --chain ./chain_spec.json --dev

交互测试(Interact with Your Pallet)

打开 Polkadot.js Apps:
    •    地址:ws://127.0.0.1:9944
    •    进入 Developer → Extrinsics
    •    选择 customPallet

可调用接口:
    •    increment
    •    decrement
    •    setCounterValue

核心总结(Key Takeaways)

通过本教程,你已经完成:
    •    自定义 Pallet 架构设计
    •    Runtime 配置绑定
    •    链上存储设计
    •    事件与错误系统
    •    创世状态初始化
    •    Extrinsic 访问控制
    •    Runtime 集成与部署

这些能力构成了 Polkadot Runtime 开发的基础框架,为后续开发复杂业务模块打下坚实基础。

原文链接:https://docs.polkadot.com/parachains/customize-runtime/pallet-development/create-a-pallet/

Logo

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

更多推荐