motoko-base-to-core-migration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Skill: Motoko mo:base → mo:core Migration

技能:Motoko mo:base → mo:core 迁移

AI Quick Checklist (Do Not Skip)

AI快速检查清单(请勿跳过)

  1. 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
  1. dfx.json Flags (per canister)
  • wasm_memory_persistence: keep
  1. 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.,
    import { type Result; type Iter } "mo:core/Types";
    ) and then use
    Result<T>
    /
    Iter<T>
    in code; if importing ≥3 types, import the whole module (
    import Types "mo:core/Types";
    ).
  • Methods: .vals() → .values(), Array., Option., Debug.trap → Runtime.trap, etc.
  1. 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
  1. Persistent Actor (Phase 4)
  • actor → persistent actor (including actor class)
  1. Remove mo:base (Phase 5)
  • Only after zero references remain and all canisters build
  1. Stable Layout & Upgrades (Phase 6)
  • Keep stable serialization arrays compatible or use with migration = syntax
  • Test local upgrade with wasm_memory_persistence keep
  1. 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

  1. 版本要求
  • 确保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依赖
  1. dfx.json配置项(按canister设置)
  • wasm_memory_persistence:保持为keep
  1. 机械命名替换(第1-2阶段)
  • 导入路径:将
    mo:base/*
    替换为
    mo:core/*
    (注意特殊例外情况)
  • 仅类型导入:优先使用
    mo:core/Types
    进行仅类型导入(例如:
    Iter.Iter<T>
    Types.Iter<T>
    Result.Result<T,E>
    Types.Result<T,E>
  • 导入风格:若从
    mo:core/Types
    导入≤2个类型,优先使用命名类型导入(例如:
    import { type Result; type Iter } "mo:core/Types";
    ,之后在代码中直接使用
    Result<T>
    /
    Iter<T>
    );若导入≥3个类型,则导入整个模块(
    import Types "mo:core/Types";
  • 方法替换:
    .vals()
    .values()
    Array.*
    Option.*
    Debug.trap
    Runtime.trap
  1. 数据结构迁移(第3阶段)
  • Buffer → List(可变类型,
    add()
    返回void)
  • HashMap/TrieMap → Map(可变类型,
    add()
    返回void,使用
    Map.size(map)
    获取大小)
  • TrieSet → Set(可变类型,
    add()
    返回void)
  • 移除所有“:= Map.add/List.add/Set.add”写法——这些方法现在会直接原地修改数据
  1. 持久化Actor(第4阶段)
  • actor
    替换为
    persistent actor
    (包括actor class)
  1. 移除mo:base依赖(第5阶段)
  • 仅当代码中已无任何mo:base引用,且所有canister均可正常构建时执行
  1. 稳定布局与升级(第6阶段)
  • 保持稳定序列化数组的兼容性,或使用
    with migration =
    语法
  • 结合
    wasm_memory_persistence keep
    进行本地升级测试
  1. 验证与确认
  • 逐个构建每个canister
  • 运行正则检查确保关键错误已修复
  • 对比适用的DID接口
  • 执行本地升级测试
验收标准:
  • 搜索
    mo:base
    Buffer.
    HashMap.
    TrieSet.
    Debug.trap
    .vals()
    以及“:= Map.add/List.add/Set.add”的结果均为0(适当排除.mops文件)
  • 所有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 actor
    +
    transient var
  • 堆内存会被持久化;大多数数据结构无需序列化即可跨版本升级
persistent actor内的规则:
  • let x = ...
    → 默认稳定
  • var x = ...
    → 默认稳定
  • transient var x = ...
    → 不稳定(升级时重置)
  • stable
    关键字为隐式
  • 必要时仍可使用
    preupgrade
    /
    postupgrade

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/ExperimentalCycles
      mo:core/Cycles
    • mo:base/ExperimentalInternetComputer
      mo:core/InternetComputer
    • mo:base/List
      mo:core/pure/List
      (不可变列表;非
      mo:core/List
    • mo:base/OrderedMap
      mo:core/pure/Map
    • mo:base/OrderedSet
      mo:core/pure/Set
    • mo:base/Deque
      mo: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, import
    Types
    instead of that module.
  • Most common cases to fix explicitly:
    • Replace
      import Iter "mo:core/Iter"
      used only for the type
      Iter.Iter<T>
      with
      import Types "mo:core/Types"
      and update
      Iter.Iter<T>
      to
      Types.Iter<T>
      .
    • Replace
      import Result "mo:core/Result"
      used only for the type
      Result.Result<T,E>
      with
      import Types "mo:core/Types"
      and update
      Result.Result<T,E>
      to
      Types.Result<T,E>
      .
  • 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 \.mops
    • grep -rn "Result\.Result<" . --include="*.mo" | grep -v \.mops
  • Minimal fix pattern:
    • If no
      Iter.*
      functions are called, change
      import Iter "mo:core/Iter"
      to
      import Types "mo:core/Types"
      and rewrite the type to
      Types.Iter<...>
      .
    • Similarly for
      Result
      , when only the
      Result.Result<...>
      type is used.
  • Preferred import style for small sets of types:
    • If you need no more than 2 types from
      mo:core/Types
      , import them by name as types and then use them directly in code without a prefix.
    • 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
      Types
      , import the whole module:
      import Types "mo:core/Types";
      and use
      Types.Result<...>
      ,
      Types.Iter<...>
      , etc.
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 \.mops
    • grep -rn "Result\.Result<" . --include="*.mo" | grep -v \.mops
  • 最小修复模式:
    • 若未调用任何
      Iter.*
      函数,将
      import Iter "mo:core/Iter"
      改为
      import Types "mo:core/Types"
      ,并将类型改写为
      Types.Iter<...>
    • 对于Result类型,仅当使用
      Result.Result<...>
      类型时执行类似操作
  • 少量类型的推荐导入风格:
    • 若从
      mo:core/Types
      导入不超过2个类型,按名称导入类型,之后在代码中直接使用无需前缀
    • 示例:
      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.mapFilter
    Array.filterMap
  • Array.chain
    Array.flatMap
  • Iter.range(a, b)
    Nat.range(a, b + 1)
    (现在上限为排他性)
  • 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)
    → 使用“
    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是可变类型;
    List.add(list, x)
    返回void并原地修改列表
  • 绝不要写:
    list := List.add(list, x)
  • 访问方法:
    • List.at(list, i)
      → T(越界时触发trap)
    • List.get(list, i)
      → ?T(安全访问)
  • 转换方法:
    • 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是可变类型;
    Map.add(map, compare, k, v)
    返回void
  • 绝不要写:
    map := Map.add(map, ...)
  • 点符号API模式:
    • 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"是键比较函数,例如
    Text.compare
    Nat.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>()
  • Set.add(set, compare, x)
    返回void
  • Set.contains(set, compare, x)
    → Bool
  • Set.delete
    返回Bool;
    Set.remove
    返回void
  • 优先使用
    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
mo:core
APIs; keep module‑level factory/constructor calls when no method exists (e.g.,
Blob.fromArray
/
Blob.fromVarArray
). Run this step after the basic renames so behavior stays unchanged.
关于点符号的细节和安全模式,请参考技能文档“Motoko mo:core代码优化”——B章节(优先使用点符号)和C章节(确保点符号所需的导入)。
简短规则:在mo:core API支持的情况下优先使用点符号;当无对应方法时保留模块级工厂/构造函数调用(例如
Blob.fromArray
/
Blob.fromVarArray
)。在基础替换完成后执行此步骤,以确保行为不变。

Phase 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
  • actor
    替换为
    persistent actor
  • actor class
    替换为
    persistent 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个关键错误及修复方案

  1. := 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
  1. Array.init → VarArray.repeat (args reversed)
  • Fix: VarArray.repeat<T>(val, size)
  1. Cycles.add removed
  • Fix: await (with cycles = n) call(args)
  1. 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>()
  1. .values() as module functions
  • Fix: List.values(list), Map.values(map), not l.values() or m.values()
  1. Nat8 inference on hex literals
  • Fix: Explicit type annotations, e.g., 0xB5 : Nat8
  1. Error.message vs Runtime.message
  • Fix: Keep Error.message(e) from mo:core/Error; only Debug.trap → Runtime.trap

  1. := Map.add
    /
    := List.add
    /
    := Set.add
  • 症状:类型错误[M0098],无法隐式实例化函数类型
  • 修复:移除
    :=
    ;这些方法会原地修改数据并返回void
  1. Array.init
    VarArray.repeat
    (参数顺序反转)
  • 修复:使用
    VarArray.repeat<T>(val, size)
  1. Cycles.add
    已移除
  • 修复:使用
    await (with cycles = n) call(args)
  1. 自动编辑导致的代码混乱
  • 症状:
    Map.empty
    /
    List.empty
    附近出现多余的构造函数或类型片段
  • 修复:将声明清理为简洁的
    Map.empty<K,V>()
    /
    List.empty<T>()
  1. .values()
    作为模块函数使用
  • 修复:使用
    List.values(list)
    Map.values(map)
    ,而非
    l.values()
    m.values()
  1. 十六进制字面量的Nat8推断问题
  • 修复:添加显式类型注解,例如
    0xB5 : Nat8
  1. Error.message
    vs
    Runtime.message
  • 修复:保留mo:core/Error中的
    Error.message(e)
    ;仅将
    Debug.trap
    替换为
    Runtime.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:
    1. Map.add/List.add/Set.add return VOID — remove all := assignments
    2. VarArray.repeat(val, size) — args REVERSED from Array.init(size, val)
    3. List/Map/Set mutate in-place — prefer stable let for persistent data
    4. Error.message stays as Error.message — NOT Runtime.message
    5. 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。关键规则:
    1. Map.add/List.add/Set.add返回VOID——移除所有
      :=
      赋值
    2. VarArray.repeat(val, size)——参数顺序与Array.init(size, val)相反
    3. List/Map/Set为原地修改——持久化数据优先使用
      stable let
    4. Error.message保持为Error.message——不要改为Runtime.message
    5. 当编译器提示M0098时添加显式类型参数
  • 先展示差异再修改。

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 -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兼容性检查:
  • 将生成的.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>
  • 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(可变数组)
  • VarArray.repeat<T>(val, size)
    [var T]
  • VarArray.tabulate<T>(n, f)
    [var T]
  • 使用
    Array.fromVarArray
    /
    Array.toVarArray
    进行转换

Lessons Learned

经验总结

  1. Read mo:core sources directly when in doubt; public docs may lag.
  2. Build one canister at a time for faster feedback.
  3. “:= void” is the #1 migration error; search for it explicitly.
  4. Third-party packages often require hands-on patches in .mops.
  5. EOP + persistent actor typically removes the need for serialization; still use stable arrays when upgrading across versions with changed structures.
  6. Local upgrades require the .most file and --wasm-memory-persistence keep.
  7. Use -y/--yes for non-interactive upgrades and verify module hash after.

  1. 有疑问时直接查看mo:core源码;公开文档可能滞后。
  2. 逐个构建canister以获得更快的反馈。
  3. “:= void”是排名第一的迁移错误;需专门搜索此类写法。
  4. 第三方包通常需要手动修改.mops文件。
  5. EOP + persistent actor通常可消除序列化需求;但当跨版本修改数据结构时,仍需使用稳定数组。
  6. 本地升级测试需要.most文件和
    --wasm-memory-persistence keep
    参数。
  7. 非交互式升级使用
    -y/--yes
    参数,并在升级后验证模块哈希。

AI Runbook (Step-by-Step)

AI执行手册(分步指南)

  1. Prep
  • Branch, confirm versions, add mo:core, set dfx flags
  1. Phase 1
  • Systematic import renames
  • Exceptions for pure modules (immutable)
  1. Phase 2
  • Method/function renames per mapping list
  • Fix Iter ranges and Text casing changes
  1. 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
  1. Phase 4
  • Convert actor/actor class to persistent actor
  1. Build + Audit
  • Per-canister build
  • Grep audits for mo:base, Debug.trap, .vals(), and := add calls
  1. Fix Third-Party
  • Patch .mops packages as needed; re-run builds
  1. Phase 5
  • Remove mo:base from mops.toml once audits are clean
  1. Phase 6
  • Ensure state layout continuity or implement with migration
  • Local upgrade test with wasm_memory_persistence keep
  1. Sign-off
  • All builds passing, audits zero, upgrade verified, API checked
  1. 准备工作
  • 创建分支,确认版本,添加mo:core依赖,设置dfx配置项
  1. 第1阶段
  • 系统性替换导入路径
  • 处理纯模块(不可变)的例外情况
  1. 第2阶段
  • 按映射列表替换方法/函数名称
  • 修复Iter范围和Text大小写变更
  1. 第3阶段
  • Buffer → List;移除所有与List.add搭配的
    :=
  • HashMap/TrieMap → Map;在每次调用时添加compare参数;移除
    :=
    ;改为使用
    Map.size(map)
  • TrieSet → Set;移除
    :=
    ;将成员检查更新为
    Set.contains
  1. 第4阶段
  • 将actor/actor class转换为persistent actor
  1. 构建+审计
  • 逐canister构建
  • 搜索审计mo:base、Debug.trap、.vals()以及
    := add
    调用
  1. 修复第三方依赖
  • 按需修改.mops包;重新运行构建
  1. 第5阶段
  • 审计结果全部为0后,从mops.toml中移除mo:base
  1. 第6阶段
  • 确保状态布局连续性或实现迁移逻辑
  • 结合
    wasm_memory_persistence keep
    进行本地升级测试
  1. 确认通过
  • 所有构建通过,审计结果为0,升级测试验证通过,API检查完成