From substrate-skills
Provides FRAME pallet development best practices, patterns, and antipatterns for Substrate runtime development. Use when building new pallets, reviewing pallet code, or debugging pallet issues in the Individuality SDK or polkadot-sdk. Do not use for smart contract development, frontend code, or non-Substrate Rust projects. Guarantees correct pallet structure, safe arithmetic, bounded storage, and proper testing via curated reference from polkadot-sdk, PBA curriculum, and Individuality SDK patterns.
How this skill is triggered — by the user, by Claude, or both
Slash command
/substrate-skills:frame-pallet-guideThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
**MODE:** AUSTERE REFERENCE (Diataxis)
MODE: AUSTERE REFERENCE (Diataxis) ROLE: Substrate FRAME Pallet Development Expert CONSTRAINT: Thin FCIS shell. Load references per-intent only.
*** PROCESS GATE — MANDATORY BEFORE ANY PALLET CHANGE *** BEFORE writing pallet code: (1) Load relevant references. (2) Verify all storage uses bounded types + MaxEncodedLen. (3) Verify all arithmetic uses checked/saturating operations. (4) Verify all extrinsics have call_index + weight + origin check. *** END PROCESS GATE ***
MUST enforce on every pallet creation or modification. NEVER skip.
| Invariant | Rule |
|---|---|
| no_std | #![cfg_attr(not(feature = "std"), no_std)] header REQUIRED. All deps default-features = false. |
| Bounded Storage | ALL storage MUST implement MaxEncodedLen. No unbounded Vec in production. |
| Safe Arithmetic | NEVER raw +, -, *, / on balances. Use checked_* or saturating_*. |
| No Panics | NEVER unwrap(), direct indexing, or division by zero in dispatchables. |
| Determinism | NEVER HashMap, HashSet, f32, f64, OS randomness, or SystemTime. |
| Call Index | EVERY extrinsic MUST have #[pallet::call_index(n)]. |
| Origin Check | First line of every extrinsic MUST check origin. |
| Events Last | Emit events AFTER all state mutations succeed. |
Locate matching row. Load ALL files in Artifacts column.
| User Intent | Action | Artifacts |
|---|---|---|
| "Create new pallet" / scaffold pallet | Load structure + all references | references/pallet-structure.md, references/storage-and-types.md, references/calls-events-hooks.md, references/testing-patterns.md |
| "Add storage" / storage design | Load storage reference | references/storage-and-types.md |
| "Add extrinsic" / "add call" | Load calls reference | references/calls-events-hooks.md |
| "Write tests" / "add tests" / mock setup | Load testing reference | references/testing-patterns.md |
| "Add benchmarks" / weights | Load testing reference | references/testing-patterns.md |
| "Review pallet" / check for issues | Load all references, check against invariants | references/pallet-structure.md, references/storage-and-types.md, references/calls-events-hooks.md |
| Cargo.toml / dependencies / features | Load structure reference | references/pallet-structure.md |
| Smart contract / non-Substrate code | STOP — Not applicable | — |
| Phase | Content |
|---|---|
| Trigger | "Add a vote tracking storage item" |
| Action | StorageDoubleMap<_, Blake2_128Concat, RoundId, Blake2_128Concat, Alias, Ballot, OptionQuery> with BoundedVec for proposals, checked_mul for vote cost |
| Result | Bounded, deterministic, safe from overflow |
| Phase | Content |
|---|---|
| Trigger | "Add a vote tracking storage item" |
| Action | StorageMap<_, Identity, AccountId, Vec<Vote>> with votes * votes for cost |
| Result | Unbounded Vec (no MaxEncodedLen), Identity hasher on user key (DoS), raw multiplication (overflow) |
| Phase | Content |
|---|---|
| Trigger | "Add a vote extrinsic" |
| Action | #[pallet::call_index(1)], #[pallet::weight(T::WeightInfo::vote())], let alias = T::EnsurePerson::ensure_origin(origin, &CTX)?; as first line, ensure! for validation, event last |
| Result | Versioned, weighted, origin-checked, non-panicking |
| Phase | Content |
|---|---|
| Trigger | "Add a vote extrinsic" |
| Action | No call_index, hardcoded weight, missing origin check, unwrap() on storage read |
| Result | Breaking encoding changes, incorrect fees, anyone can call, runtime panic |
| Pattern | Status | Reason |
|---|---|---|
HashMap/HashSet in runtime | FORBIDDEN | Non-deterministic iteration breaks consensus |
f32/f64 arithmetic | FORBIDDEN | Non-deterministic across platforms |
unwrap()/expect() in dispatchables | FORBIDDEN | User-triggerable panic bypasses fees |
vec[i] direct indexing | FORBIDDEN | Panic on out-of-bounds |
Raw +/-/*// on balances | FORBIDDEN | Silent overflow/underflow |
Unbounded Vec in storage | FORBIDDEN | No MaxEncodedLen, unprovable PoV |
Identity hasher on user-controlled keys | FORBIDDEN | Storage key prediction enables DoS |
Reading System::events() in production | FORBIDDEN | Inflates PoV size unboundedly |
Unbounded iteration in on_initialize | FORBIDDEN | Block production DoS |
Panic in on_initialize | FORBIDDEN | Halts block production (fatal for parachains) |
Missing #[pallet::call_index] | FORBIDDEN | Extrinsic encoding breaks on reorder |
| Tests at block 0 expecting events | FORBIDDEN | Events not recorded at genesis block |
| Tight coupling when loose suffices | FORBIDDEN | Reduces reusability |
*** RUNTIME CONTRACT — RECENCY ENFORCEMENT *** BEFORE finalizing any pallet code, verify:
npx claudepluginhub paritytech/substrate-skills --plugin substrate-skillsGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.