motoko-base-to-core-migration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSkill: Motoko mo:base → mo:core Migration
技能:Motoko mo:base → mo:core 迁移
AI Quick Checklist (Do Not Skip)
AI快速检查清单(请勿跳过)
- Versions
- Ensure dfx 0.31+
- Confirm repo compiles before starting
- In mops.toml add core = "2.2.0"
- update moc in mops.toml to "1.3.0" in [toolchain] and [requirements] sections.
- if wasmtime is present in mops.toml, upgrade it to at least "42.0.1"
- Do NOT remove base dependency until Phase 5 is completed
- dfx.json Flags (per canister)
- wasm_memory_persistence: keep
- Mechanical Renames (Phases 1–2)
- Imports: mo:base/* → mo:core/* (with noted exceptions)
- Types-only: Prefer mo:core/Types for type-only imports (e.g., Iter.Iter<T> → Types.Iter<T>, Result.Result<T,E> → Types.Result<T,E>)
- Style: If importing ≤2 types from mo:core/Types, prefer named type imports (e.g., ) and then use
import { type Result; type Iter } "mo:core/Types";/Result<T>in code; if importing ≥3 types, import the whole module (Iter<T>).import Types "mo:core/Types"; - Methods: .vals() → .values(), Array., Option., Debug.trap → Runtime.trap, etc.
- Data Structure Migrations (Phase 3)
- Buffer → List (MUTABLE, add() returns void)
- HashMap/TrieMap → Map (MUTABLE, add() returns void, Map.size(map))
- TrieSet → Set (MUTABLE, add() returns void)
- Eliminate all “:= Map.add/List.add/Set.add” — they now mutate in place
- Persistent Actor (Phase 4)
- actor → persistent actor (including actor class)
- Remove mo:base (Phase 5)
- Only after zero references remain and all canisters build
- Stable Layout & Upgrades (Phase 6)
- Keep stable serialization arrays compatible or use with migration = syntax
- Test local upgrade with wasm_memory_persistence keep
- Verification & Sign-off
- Build each canister individually
- Run regex audits to ensure critical mistakes are gone
- Compare DID interfaces where applicable
- Execute local upgrade test
Acceptance Criteria:
- grep for mo:base, Buffer., HashMap., TrieSet., Debug.trap, .vals(), and “:= Map.add/List.add/Set.add” all return zero (excluding .mops where appropriate)
- All canisters build
- Upgrade test keeps state (heap) with wasm_memory_persistence keep
- API compatibility checked for public methods
- 版本要求
- 确保dfx版本为0.31+
- 开始迁移前确认代码仓库可正常编译
- 在mops.toml中添加
core = "2.2.0" - 在mops.toml的[toolchain]和[requirements]章节中,将moc版本更新为"1.3.0"
- 若mops.toml中存在wasmtime,将其升级至至少"42.0.1"
- 完成第5阶段前请勿移除base依赖
- dfx.json配置项(按canister设置)
- wasm_memory_persistence:保持为keep
- 机械命名替换(第1-2阶段)
- 导入路径:将替换为
mo:base/*(注意特殊例外情况)mo:core/* - 仅类型导入:优先使用进行仅类型导入(例如:
mo:core/Types→Iter.Iter<T>,Types.Iter<T>→Result.Result<T,E>)Types.Result<T,E> - 导入风格:若从导入≤2个类型,优先使用命名类型导入(例如:
mo:core/Types,之后在代码中直接使用import { type Result; type Iter } "mo:core/Types";/Result<T>);若导入≥3个类型,则导入整个模块(Iter<T>)import Types "mo:core/Types"; - 方法替换:→
.vals(),.values()、Array.*、Option.*→Debug.trap等Runtime.trap
- 数据结构迁移(第3阶段)
- Buffer → List(可变类型,返回void)
add() - HashMap/TrieMap → Map(可变类型,返回void,使用
add()获取大小)Map.size(map) - TrieSet → Set(可变类型,返回void)
add() - 移除所有“:= Map.add/List.add/Set.add”写法——这些方法现在会直接原地修改数据
- 持久化Actor(第4阶段)
- 将替换为
actor(包括actor class)persistent actor
- 移除mo:base依赖(第5阶段)
- 仅当代码中已无任何mo:base引用,且所有canister均可正常构建时执行
- 稳定布局与升级(第6阶段)
- 保持稳定序列化数组的兼容性,或使用语法
with migration = - 结合进行本地升级测试
wasm_memory_persistence keep
- 验证与确认
- 逐个构建每个canister
- 运行正则检查确保关键错误已修复
- 对比适用的DID接口
- 执行本地升级测试
验收标准:
- 搜索、
mo:base、Buffer.、HashMap.、TrieSet.、Debug.trap以及“:= Map.add/List.add/Set.add”的结果均为0(适当排除.mops文件).vals() - 所有canister均可正常构建
- 升级测试中,结合可保持状态(堆内存)
wasm_memory_persistence keep - 已检查公开方法的API兼容性
Prerequisites
前置条件
mops.toml (dependency staging)
- Add: core = "2.2.0"
- Keep base dependency present until Phase 5 is complete
dfx.json (per canister)
- type: "motoko"
- wasm_memory_persistence: "keep"
Requires:
- dfx 0.31+
- moc 1.3.0
mops.toml(依赖准备)
- 添加:
core = "2.2.0" - 完成第5阶段前保留base依赖
dfx.json(按canister设置)
- type: "motoko"
- wasm_memory_persistence: "keep"
所需环境:
- dfx 0.31+
- moc 1.3.0
Architecture Shift: Enhanced Orthogonal Persistence (EOP)
架构转变:增强正交持久化(EOP)
Old (mo:base)
- stable var + preupgrade/postupgrade + manual serialization to stable memory
New (mo:core)
- persistent actor + transient var
- Heap is persisted; most structures live across upgrades without serialization
Rules inside persistent actor:
- let x = ... → stable by default
- var x = ... → stable by default
- transient var x = ... → NOT stable (resets on upgrade)
- stable keyword is implicit
- preupgrade/postupgrade still available when needed
旧版(mo:base)
- 使用+
stable var/preupgrade+ 手动序列化至稳定内存postupgrade
新版(mo:core)
- 使用+
persistent actortransient var - 堆内存会被持久化;大多数数据结构无需序列化即可跨版本升级
persistent actor内的规则:
- → 默认稳定
let x = ... - → 默认稳定
var x = ... - → 不稳定(升级时重置)
transient var x = ... - 关键字为隐式
stable - 必要时仍可使用/
preupgradepostupgrade
The 6 Phases
6个迁移阶段
Phase 1: Import Renames (mechanical)
第1阶段:导入路径替换(机械操作)
Bulk rename:
- mo:base/X → mo:core/X (for most modules)
- Special cases:
- mo:base/ExperimentalCycles → mo:core/Cycles
- mo:base/ExperimentalInternetComputer → mo:core/InternetComputer
- mo:base/List → mo:core/pure/List (immutable list; not mo:core/List)
- mo:base/OrderedMap → mo:core/pure/Map
- mo:base/OrderedSet → mo:core/pure/Set
- mo:base/Deque → mo:core/pure/Queue
批量替换:
- →
mo:base/X(适用于大多数模块)mo:core/X - 特殊情况:
- →
mo:base/ExperimentalCyclesmo:core/Cycles - →
mo:base/ExperimentalInternetComputermo:core/InternetComputer - →
mo:base/List(不可变列表;非mo:core/pure/List)mo:core/List - →
mo:base/OrderedMapmo:core/pure/Map - →
mo:base/OrderedSetmo:core/pure/Set - →
mo:base/Dequemo:core/pure/Queue
Types-only imports (mo:core/Types)
仅类型导入(mo:core/Types)
-
When you only need a type and not the functions from its module, importinstead of that module.
Types -
Most common cases to fix explicitly:
- Replace used only for the type
import Iter "mo:core/Iter"withIter.Iter<T>and updateimport Types "mo:core/Types"toIter.Iter<T>.Types.Iter<T> - Replace used only for the type
import Result "mo:core/Result"withResult.Result<T,E>and updateimport Types "mo:core/Types"toResult.Result<T,E>.Types.Result<T,E>
- Replace
-
Keep it simple: only fix these two patterns unless you clearly see another types-only import.
-
Audit (to find likely spots):
grep -rn "Iter\.Iter<" . --include="*.mo" | grep -v \.mopsgrep -rn "Result\.Result<" . --include="*.mo" | grep -v \.mops
-
Minimal fix pattern:
- If no functions are called, change
Iter.*toimport Iter "mo:core/Iter"and rewrite the type toimport Types "mo:core/Types".Types.Iter<...> - Similarly for , when only the
Resulttype is used.Result.Result<...>
- If no
-
Preferred import style for small sets of types:
- If you need no more than 2 types from , import them by name as types and then use them directly in code without a prefix.
mo:core/Types - Example:
motoko
// Before (module import or prefixed usage) import Types "mo:core/Types"; type R = Types.Result<Nat, Text>; type I = Types.Iter<Nat>; // After (≤2 types: direct named type import) import { type Result; type Iter } "mo:core/Types"; type R = Result<Nat, Text>; type I = Iter<Nat>; - If you import 3 or more types from , import the whole module:
Typesand useimport Types "mo:core/Types";,Types.Result<...>, etc.Types.Iter<...>
- If you need no more than 2 types from
Use sed commands to replace all occurrences at once
Removed without direct replacement (manual fix required):
- AssocList, Buffer, Hash, HashMap, Heap, IterType, None, Prelude, RBTree, Trie, TrieMap, TrieSet
Audit:
- grep -rl 'mo:base' . --include="*.mo" | grep -v .mops
-
当仅需要类型而不需要模块中的函数时,导入而非对应模块
Types -
最常见的需显式修复场景:
- 将仅用于类型的
Iter.Iter<T>替换为import Iter "mo:core/Iter",并将import Types "mo:core/Types"更新为Iter.Iter<T>Types.Iter<T> - 将仅用于类型的
Result.Result<T,E>替换为import Result "mo:core/Result",并将import Types "mo:core/Types"更新为Result.Result<T,E>Types.Result<T,E>
- 将仅用于
-
保持简洁:除非明确发现其他仅类型导入场景,否则仅修复上述两种模式
-
审计(查找可能的场景):
grep -rn "Iter\.Iter<" . --include="*.mo" | grep -v \.mopsgrep -rn "Result\.Result<" . --include="*.mo" | grep -v \.mops
-
最小修复模式:
- 若未调用任何函数,将
Iter.*改为import Iter "mo:core/Iter",并将类型改写为import Types "mo:core/Types"Types.Iter<...> - 对于Result类型,仅当使用类型时执行类似操作
Result.Result<...>
- 若未调用任何
-
少量类型的推荐导入风格:
- 若从导入不超过2个类型,按名称导入类型,之后在代码中直接使用无需前缀
mo:core/Types - 示例:
motoko
// 之前(模块导入或带前缀使用) import Types "mo:core/Types"; type R = Types.Result<Nat, Text>; type I = Types.Iter<Nat>; // 之后(≤2个类型:直接命名类型导入) import { type Result; type Iter } "mo:core/Types"; type R = Result<Nat, Text>; type I = Iter<Nat>; - 若从Types导入3个或更多类型,则导入整个模块:,并使用
import Types "mo:core/Types";、Types.Result<...>等写法Types.Iter<...>
- 若从
使用sed命令批量替换所有匹配项
无直接替代需手动修复的内容:
- AssocList, Buffer, Hash, HashMap, Heap, IterType, None, Prelude, RBTree, Trie, TrieMap, TrieSet
审计:
- grep -rl 'mo:base' . --include="*.mo" | grep -v .mops
Phase 2: Function Renames (mechanical)
第2阶段:函数命名替换(机械操作)
Common mappings:
- Debug.trap(msg) → Runtime.trap(msg) (import Runtime "mo:core/Runtime")
- Prelude.unreachable() → Runtime.unreachable() (import Runtime "mo:core/Runtime")
- .vals() → .values()
- Array.append(a, b) → Array.concat(a, b)
- Array.init<T>(size, val) → VarArray.repeat<T>(val, size) (args reversed)
- Array.freeze(arr) → Array.fromVarArray(arr)
- Array.thaw(arr) → Array.toVarArray(arr)
- Array.slice(a, s, e) → Array.range(a, s, e)
- Array.subArray(a, s, l) → Array.sliceToArray(a, s, s + l)
- Array.make(x) → Array.singleton(x)
- Array.mapFilter → Array.filterMap
- Array.chain → Array.flatMap
- Iter.range(a, b) → Nat.range(a, b + 1) (exclusive upper bound now)
- Iter.revRange(a, b) → Nat.rangeByInclusive(a, b, -1)
- Text.toLowercase → Text.toLower
- Text.toUppercase → Text.toUpper
- Text.translate → Text.flatMap
- Option.make → Option.some
- Option.iterate → Option.forEach
- Float.equal(a,b) → Float.equal(a,b,epsilon)
- Cycles.add(n); await call(args) → use “await (with cycles = n) call(args)”
Audit:
- grep -rn ".vals()" . --include="*.mo" | grep -v .mops
常见映射:
- →
Debug.trap(msg)(需导入Runtime.trap(msg))Runtime "mo:core/Runtime" - →
Prelude.unreachable()(需导入Runtime.unreachable())Runtime "mo:core/Runtime" - →
.vals().values() - →
Array.append(a, b)Array.concat(a, b) - →
Array.init<T>(size, val)(参数顺序反转)VarArray.repeat<T>(val, size) - →
Array.freeze(arr)Array.fromVarArray(arr) - →
Array.thaw(arr)Array.toVarArray(arr) - →
Array.slice(a, s, e)Array.range(a, s, e) - →
Array.subArray(a, s, l)Array.sliceToArray(a, s, s + l) - →
Array.make(x)Array.singleton(x) - →
Array.mapFilterArray.filterMap - →
Array.chainArray.flatMap - →
Iter.range(a, b)(现在上限为排他性)Nat.range(a, b + 1) - →
Iter.revRange(a, b)Nat.rangeByInclusive(a, b, -1) - →
Text.toLowercaseText.toLower - →
Text.toUppercaseText.toUpper - →
Text.translateText.flatMap - →
Option.makeOption.some - →
Option.iterateOption.forEach - →
Float.equal(a,b)Float.equal(a,b,epsilon) - → 使用“
Cycles.add(n); await call(args)”await (with cycles = n) call(args)
审计:
- grep -rn ".vals()" . --include="*.mo" | grep -v .mops
Phase 3a: Buffer → List (MUTABLE, biggest gotcha)
第3a阶段:Buffer → List(可变类型,最易出错点)
- List in mo:core is MUTABLE; List.add(list, x) returns void and mutates in place
- Never write: list := List.add(list, x)
- Accessors:
- List.at(list, i) → T (traps on OOB)
- List.get(list, i) → ?T (safe)
- Conversions:
- List.toArray(list), List.fromArray(arr)
- Prefer stable let list = List.empty<T>() for persistent state
- mo:core中的List是可变类型;返回void并原地修改列表
List.add(list, x) - 绝不要写:
list := List.add(list, x) - 访问方法:
- → T(越界时触发trap)
List.at(list, i) - → ?T(安全访问)
List.get(list, i)
- 转换方法:
- 、
List.toArray(list)List.fromArray(arr)
- 持久化状态优先使用
stable let list = List.empty<T>()
Phase 3b: HashMap/TrieMap → Map (MUTABLE)
第3b阶段:HashMap/TrieMap → Map(可变类型)
- Map is MUTABLE; Map.add(map, compare, k, v) returns void
- Never write: map := Map.add(map, ...)
- API patterns with dot notation:
- Map.empty<K,V>()
- map.add(cmp, k, v)
- map.get(cmp, k) → ?V
- map.remove(cmp, k) → void
- map.delete(cmp, k) → Bool
- map.take(cmp, k) → ?V
- map.entries(), map.values(), map.size()
- "cmp" is a key compare function, e.g., Text.compare, Nat.compare
- Map是可变类型;返回void
Map.add(map, compare, k, v) - 绝不要写:
map := Map.add(map, ...) - 点符号API模式:
Map.empty<K,V>()map.add(cmp, k, v)- → ?V
map.get(cmp, k) - → void
map.remove(cmp, k) - → Bool
map.delete(cmp, k) - → ?V
map.take(cmp, k) - 、
map.entries()、map.values()map.size()
- "cmp"是键比较函数,例如、
Text.compareNat.compare
Phase 3c: TrieSet → Set (MUTABLE)
第3c阶段:TrieSet → Set(可变类型)
- Set.add(set, compare, x) returns void
- Set.contains(set, compare, x) → Bool
- Set.delete returns Bool; Set.remove is void
- Prefer stable let set = Set.empty<T>()
- 返回void
Set.add(set, compare, x) - → Bool
Set.contains(set, compare, x) - 返回Bool;
Set.delete返回voidSet.remove - 优先使用
stable let set = Set.empty<T>()
Phase 3d: Dot notation
第3d阶段:点符号用法
For dot‑notation details and safe patterns, see the Skill “Motoko mo:core Code Improvements” — Section B (Prefer dot‑notation) and Section C (Ensure necessary imports for dot‑notation).
Short rule: Prefer dot‑notation where supported by APIs; keep module‑level factory/constructor calls when no method exists (e.g., / ). Run this step after the basic renames so behavior stays unchanged.
mo:coreBlob.fromArrayBlob.fromVarArray关于点符号的细节和安全模式,请参考技能文档“Motoko mo:core代码优化”——B章节(优先使用点符号)和C章节(确保点符号所需的导入)。
简短规则:在mo:core API支持的情况下优先使用点符号;当无对应方法时保留模块级工厂/构造函数调用(例如 / )。在基础替换完成后执行此步骤,以确保行为不变。
Blob.fromArrayBlob.fromVarArrayPhase 4: persistent actor Keyword
第4阶段:persistent actor关键字替换
- actor → persistent actor
- actor class → persistent actor class
Audit:
- grep -rn "^actor\b|^ actor\b" . --include="*.mo" | grep -v persistent
- 将替换为
actorpersistent actor - 将替换为
actor classpersistent actor class
审计:
- grep -rn "^actor\b|^ actor\b" . --include="*.mo" | grep -v persistent
Phase 5: Remove mo:base From Dependencies
第5阶段:从依赖中移除mo:base
Only after:
- All files compile with mo:core
- No references to mo:base in your own code
Audit:
- grep -r 'mo:base' . --include="*.mo" | grep -v .mops
- If zero, remove base = "..." from mops.toml
Note:
- Third-party .mops deps may still use mo:base; skip them
仅当满足以下条件时执行:
- 所有文件均可使用mo:core编译
- 自有代码中无任何mo:base引用
审计:
- grep -r 'mo:base' . --include="*.mo" | grep -v .mops
- 若结果为0,从mops.toml中移除
base = "..."
注意:
- 第三方.mops依赖可能仍在使用mo:base;忽略这些依赖
Phase 6: Stable Variable Migration (Production-Critical)
第6阶段:稳定变量迁移(生产环境关键步骤)
If changing serialization strategy or types:
- Keep stable arrays of simple types compatible across versions; reuse same var names/types
- Use transient var for in-memory Map/List/Set that you reconstruct on upgrade
- Use preupgrade/postupgrade to serialize/deserialize when necessary
- For incompatible changes, consider the with migration = syntax to translate old state to new
若更改序列化策略或类型:
- 保持简单类型的稳定数组在版本间兼容;复用相同的变量名和类型
- 对升级时需重建的内存中的Map/List/Set使用
transient var - 必要时使用/
preupgrade进行序列化/反序列化postupgrade - 对于不兼容的变更,考虑使用语法将旧状态转换为新状态
with migration =
The 7 Critical Errors (and Their Fixes)
7个关键错误及修复方案
- := Map.add / := List.add / := Set.add
- Symptom: type error [M0098], cannot implicitly instantiate function of type
- Fix: Remove :=; these mutate in place and return void
- Array.init → VarArray.repeat (args reversed)
- Fix: VarArray.repeat<T>(val, size)
- Cycles.add removed
- Fix: await (with cycles = n) call(args)
- Garbled automated edits
- Symptom: stray constructor or type fragments near Map.empty/List.empty
- Fix: Clean declarations to simple Map.empty<K,V>() / List.empty<T>()
- .values() as module functions
- Fix: List.values(list), Map.values(map), not l.values() or m.values()
- Nat8 inference on hex literals
- Fix: Explicit type annotations, e.g., 0xB5 : Nat8
- Error.message vs Runtime.message
- Fix: Keep Error.message(e) from mo:core/Error; only Debug.trap → Runtime.trap
- /
:= Map.add/:= List.add:= Set.add
- 症状:类型错误[M0098],无法隐式实例化函数类型
- 修复:移除;这些方法会原地修改数据并返回void
:=
- →
Array.init(参数顺序反转)VarArray.repeat
- 修复:使用
VarArray.repeat<T>(val, size)
- 已移除
Cycles.add
- 修复:使用
await (with cycles = n) call(args)
- 自动编辑导致的代码混乱
- 症状:/
Map.empty附近出现多余的构造函数或类型片段List.empty - 修复:将声明清理为简洁的/
Map.empty<K,V>()List.empty<T>()
- 将作为模块函数使用
.values()
- 修复:使用、
List.values(list),而非Map.values(map)或l.values()m.values()
- 十六进制字面量的Nat8推断问题
- 修复:添加显式类型注解,例如
0xB5 : Nat8
- vs
Error.messageRuntime.message
- 修复:保留mo:core/Error中的;仅将
Error.message(e)替换为Debug.trapRuntime.trap
Agent Strategy (Scale to 100+ Files)
Agent策略(适配100+文件规模)
Safe for automated pass:
- Phase 1 (import renames)
- Phase 2 (function renames)
- Simple Buffer→List in utilities
Needs careful/manual review:
- Phase 3 Map migrations in actor mains
- preupgrade/postupgrade files (stable layout critical)
- Any Cycles.add usage
- Third-party .mops packages
Agent Prompt Template:
- Migrate [FILE] from mo:base to mo:core. CRITICAL RULES:
- Map.add/List.add/Set.add return VOID — remove all := assignments
- VarArray.repeat(val, size) — args REVERSED from Array.init(size, val)
- List/Map/Set mutate in-place — prefer stable let for persistent data
- Error.message stays as Error.message — NOT Runtime.message
- Add explicit type params when compiler gives M0098
- Show me the diff before writing.
可安全自动执行的步骤:
- 第1阶段(导入路径替换)
- 第2阶段(函数命名替换)
- 工具类中的简单Buffer→List替换
需谨慎手动检查的步骤:
- 第3阶段中actor主文件的Map迁移
- /
preupgrade文件(稳定布局至关重要)postupgrade - 任何的使用场景
Cycles.add - 第三方.mops包
Agent提示模板:
- 将[FILE]从mo:base迁移至mo:core。关键规则:
- Map.add/List.add/Set.add返回VOID——移除所有赋值
:= - VarArray.repeat(val, size)——参数顺序与Array.init(size, val)相反
- List/Map/Set为原地修改——持久化数据优先使用
stable let - Error.message保持为Error.message——不要改为Runtime.message
- 当编译器提示M0098时添加显式类型参数
- Map.add/List.add/Set.add返回VOID——移除所有
- 先展示差异再修改。
Build and Verification Workflow
构建与验证工作流
Per-canister build (fast iteration):
- dfx build <canister> 2>&1 | head -30
Migration completeness (must be zero before Phase 5):
- grep -rn "mo:base" . --include="*.mo" | grep -v .mops | wc -l
- grep -rn "HashMap." . --include="*.mo" | grep -v .mops | wc -l
- grep -rn "Buffer." . --include="*.mo" | grep -v .mops | wc -l
- grep -rn "TrieSet." . --include="*.mo" | grep -v .mops | wc -l
- grep -rn "Debug.trap" . --include="*.mo" | grep -v .mops | wc -l
- grep -rn ".vals()" . --include="*.mo" | grep -v .mops | wc -l
- grep -rn ":= Map.add|:= List.add|:= Set.add" . --include="*.mo" | wc -l
API compatibility:
- Compare generated .did files with baseline and/or run targeted calls
逐canister构建(快速迭代):
dfx build <canister> 2>&1 | head -30
迁移完整性检查(进入第5阶段前必须全部为0):
grep -rn "mo:base" . --include="*.mo" | grep -v \.mops | wc -lgrep -rn "HashMap\." . --include="*.mo" | grep -v \.mops | wc -lgrep -rn "Buffer\." . --include="*.mo" | grep -v \.mops | wc -lgrep -rn "TrieSet\." . --include="*.mo" | grep -v \.mops | wc -lgrep -rn "Debug\.trap" . --include="*.mo" | grep -v \.mops | wc -lgrep -rn "\.vals()" . --include="*.mo" | grep -v \.mops | wc -lgrep -rn ":= Map\.add\|:= List\.add\|:= Set\.add" . --include="*.mo" | wc -l
API兼容性检查:
- 将生成的.did文件与基线版本对比,或运行针对性调用测试
Local Upgrade Test (dfx)
本地升级测试(dfx)
Build both WASMs:
- Old (baseline) and new (migrated)
Install and upgrade locally:
- Ensure the .most file is in place for local EOP testing
- Use --wasm-memory-persistence keep on upgrade
- Verify module hash changes
- Populate state in old, upgrade to new, verify state persists and APIs work
构建两个WASM文件:
- 旧版(基线)和新版(已迁移)
本地安装与升级:
- 确保.most文件已存在以进行本地EOP测试
- 升级时使用
--wasm-memory-persistence keep - 验证模块哈希已变更
- 在旧版中填充状态,升级至新版,验证状态已保留且API可正常工作
Mainnet Deployment Order (Canary Strategy)
主网部署顺序(金丝雀策略)
- Deploy low-risk canisters first; verify each before proceeding
- Representative canisters early to surface systemic issues
- High-value/complex canisters last
After each deploy:
- API compatibility checks
- Domain-specific verification (crypto/math where applicable)
- 先部署低风险canister;每个部署完成后验证通过再继续
- 尽早部署代表性canister以发现系统性问题
- 最后部署高价值/复杂canister
每次部署后:
- 执行API兼容性检查
- 进行领域特定验证(如适用的加密/数学逻辑验证)
Quick Reference: mo:core APIs
mo:core API快速参考
Map
- Map.empty<K,V>() → Map<K,V>
- Map.add(m, cmp, k, v) → void
- Map.get(m, cmp, k) → ?V
- Map.remove(m, cmp, k) → void
- Map.delete(m, cmp, k) → Bool
- Map.take(m, cmp, k) → ?V
- Map.swap(m, cmp, k, v) → ?V
- Map.insert(m, cmp, k, v) → Bool
- Map.size(m) → Nat
- Map.entries(m) → Iter<(K,V)>
- Map.containsKey(m, cmp, k) → Bool
- Map.clear(m) → void
- Map.fromIter(iter, cmp) → Map<K,V>
List
- List.empty<T>() → List<T>
- List.add(l, x) → void
- List.addAll(l, iter) → void
- List.size(l) → Nat
- List.get(l, i) → ?T
- List.at(l, i) → T
- List.put(l, i, v) → void
- List.toArray(l) → [T]
- List.fromArray(arr) → List<T>
- List.values(l) → Iter<T>
- List.removeLast(l) → ?T
- List.clear(l) → void
- List.sort(l, compare) → void
Set
- Set.empty<T>() → Set<T>
- Set.add(s, cmp, x) → void
- Set.contains(s, cmp, x) → Bool
- Set.remove(s, cmp, x) → void
- Set.delete(s, cmp, x) → Bool
- Set.insert(s, cmp, x) → Bool
- Set.size(s) → Nat
- Set.values(s) → Iter<T>
- Set.fromIter(iter, cmp) → Set<T>
VarArray (mutable arrays)
- VarArray.repeat<T>(val, size) → [var T]
- VarArray.tabulate<T>(n, f) → [var T]
- Use Array.fromVarArray/Array.toVarArray for conversions
Map
- →
Map.empty<K,V>()Map<K,V> - → void
Map.add(m, cmp, k, v) - → ?V
Map.get(m, cmp, k) - → void
Map.remove(m, cmp, k) - → Bool
Map.delete(m, cmp, k) - → ?V
Map.take(m, cmp, k) - → ?V
Map.swap(m, cmp, k, v) - → Bool
Map.insert(m, cmp, k, v) - → Nat
Map.size(m) - →
Map.entries(m)Iter<(K,V)> - → Bool
Map.containsKey(m, cmp, k) - → void
Map.clear(m) - →
Map.fromIter(iter, cmp)Map<K,V>
List
- →
List.empty<T>()List<T> - → void
List.add(l, x) - → void
List.addAll(l, iter) - → Nat
List.size(l) - → ?T
List.get(l, i) - → T
List.at(l, i) - → void
List.put(l, i, v) - →
List.toArray(l)[T] - →
List.fromArray(arr)List<T> - →
List.values(l)Iter<T> - → ?T
List.removeLast(l) - → void
List.clear(l) - → void
List.sort(l, compare)
Set
- →
Set.empty<T>()Set<T> - → void
Set.add(s, cmp, x) - → Bool
Set.contains(s, cmp, x) - → void
Set.remove(s, cmp, x) - → Bool
Set.delete(s, cmp, x) - → Bool
Set.insert(s, cmp, x) - → Nat
Set.size(s) - →
Set.values(s)Iter<T> - →
Set.fromIter(iter, cmp)Set<T>
VarArray(可变数组)
- →
VarArray.repeat<T>(val, size)[var T] - →
VarArray.tabulate<T>(n, f)[var T] - 使用/
Array.fromVarArray进行转换Array.toVarArray
Lessons Learned
经验总结
- Read mo:core sources directly when in doubt; public docs may lag.
- Build one canister at a time for faster feedback.
- “:= void” is the #1 migration error; search for it explicitly.
- Third-party packages often require hands-on patches in .mops.
- EOP + persistent actor typically removes the need for serialization; still use stable arrays when upgrading across versions with changed structures.
- Local upgrades require the .most file and --wasm-memory-persistence keep.
- Use -y/--yes for non-interactive upgrades and verify module hash after.
- 有疑问时直接查看mo:core源码;公开文档可能滞后。
- 逐个构建canister以获得更快的反馈。
- “:= void”是排名第一的迁移错误;需专门搜索此类写法。
- 第三方包通常需要手动修改.mops文件。
- EOP + persistent actor通常可消除序列化需求;但当跨版本修改数据结构时,仍需使用稳定数组。
- 本地升级测试需要.most文件和参数。
--wasm-memory-persistence keep - 非交互式升级使用参数,并在升级后验证模块哈希。
-y/--yes
AI Runbook (Step-by-Step)
AI执行手册(分步指南)
- Prep
- Branch, confirm versions, add mo:core, set dfx flags
- Phase 1
- Systematic import renames
- Exceptions for pure modules (immutable)
- Phase 2
- Method/function renames per mapping list
- Fix Iter ranges and Text casing changes
- Phase 3
- Buffer → List; remove all := with List.add
- HashMap/TrieMap → Map; add compare at every call; remove :=; switch to Map.size(map)
- TrieSet → Set; remove :=; update membership to Set.contains
- Phase 4
- Convert actor/actor class to persistent actor
- Build + Audit
- Per-canister build
- Grep audits for mo:base, Debug.trap, .vals(), and := add calls
- Fix Third-Party
- Patch .mops packages as needed; re-run builds
- Phase 5
- Remove mo:base from mops.toml once audits are clean
- Phase 6
- Ensure state layout continuity or implement with migration
- Local upgrade test with wasm_memory_persistence keep
- Sign-off
- All builds passing, audits zero, upgrade verified, API checked
- 准备工作
- 创建分支,确认版本,添加mo:core依赖,设置dfx配置项
- 第1阶段
- 系统性替换导入路径
- 处理纯模块(不可变)的例外情况
- 第2阶段
- 按映射列表替换方法/函数名称
- 修复Iter范围和Text大小写变更
- 第3阶段
- Buffer → List;移除所有与List.add搭配的
:= - HashMap/TrieMap → Map;在每次调用时添加compare参数;移除;改为使用
:=Map.size(map) - TrieSet → Set;移除;将成员检查更新为
:=Set.contains
- 第4阶段
- 将actor/actor class转换为persistent actor
- 构建+审计
- 逐canister构建
- 搜索审计mo:base、Debug.trap、.vals()以及调用
:= add
- 修复第三方依赖
- 按需修改.mops包;重新运行构建
- 第5阶段
- 审计结果全部为0后,从mops.toml中移除mo:base
- 第6阶段
- 确保状态布局连续性或实现迁移逻辑
- 结合进行本地升级测试
wasm_memory_persistence keep
- 确认通过
- 所有构建通过,审计结果为0,升级测试验证通过,API检查完成