MSG Chain CosmWasm 智能合约形式化验证进阶指南
数据来源:MSG Chain 代码库核实
主网状态: No-Go — 当前 MSGChain 主网裁决为 No-Go,以下内容反映代码实际状态,不代表生产可用。
目录
- 形式化验证理论基础
- K 框架与 KEVM 在 CosmWasm 中的应用
- Coq/Agda 证明助手在 CosmWasm 上的应用
- SMT 求解器集成
- Rust 形式化验证工具
- CosmWasm 特定验证模式
- 不变量验证
- 自动化验证流水线
- 实际案例:MSG Chain 注册中心与金库合约验证
- 局限性与成本
- 总结与 MSG Chain 验证路线图
1. 形式化验证理论基础
1.1 形式化验证在智能合约安全中的层次
形式化验证(Formal Verification)是利用数学方法证明系统满足特定规范(Specification)的技术栈。与动态测试(Fuzzing、Unit Test)不同,形式化验证能够穷举地证明某些属性的成立与否。在 CosmWasm 合约安全架构中,形式化验证位于最高安全层级:
安全保证层级(从低到高)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Level 0: 无验证 → 严重漏洞高发
Level 1: 单元测试 + 集成测试 → 覆盖已知路径
Level 2: Fuzz Testing + Property Testing → 自动发现边界
Level 3: 静态分析 + Clippy + Lint → 编译时安全
Level 4: 符号执行 + 模型检查 → 状态空间穷举
Level 5: 定理证明 + 全合约验证 → 数学上完备
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
形式化验证核心方法包括四种范式:Hoare 逻辑(Hoare Logic)、符号执行(Symbolic Execution)、抽象解释(Abstract Interpretation)、模型检查(Model Checking)。以下逐一在 CosmWasm 语境下展开。
1.2 Hoare 逻辑与合约正确性三元组
Hoare 逻辑是形式化验证的基石。每个合约函数可以用 Hoare 三元组 描述:
{P} C {Q}
其中:
- P(前置条件 / Precondition):函数调用前必须满足的状态断言
- C(命令 / Command):函数体的执行逻辑
- Q(后置条件 / Postcondition):函数执行后必须成立的状态断言
在 CosmWasm 语境下,转账函数的形式化规约为:
{ sender_balance >= amount ∧ recipient_balance + amount 无溢出 }
execute_transfer(sender, recipient, amount)
{ sender_balance' = sender_balance - amount ∧
recipient_balance' = recipient_balance + amount ∧
total_supply' = total_supply }
Rust 中编码 Hoare 三元组:
/// # 前置条件
/// - `info.sender` 的余额 >= `amount`
/// - `recipient` 是有效的 Bech32 地址(以 `msg1` 开头)
/// - `amount` > 0
///
/// # 后置条件
/// - `info.sender` 的余额减少 `amount`
/// - `recipient` 的余额增加 `amount`
/// - 总供应量保持不变
/// - 事件正确发射
pub fn transfer(
deps: DepsMut,
env: Env,
info: MessageInfo,
recipient: String,
amount: Uint128,
) -> Result<Response, ContractError> {
// 函数体...
}
最弱前置条件(Weakest Precondition, WP)计算:
最弱前置条件是 Dijkstra 提出的谓词转换器概念——给定后置条件 Q 和程序 C,WP(C, Q) 是最弱的 P 使得 {P} C {Q} 成立。在 CosmWasm 中,WP 计算可用于自动化验证:
// 对于序列执行: C1; C2
// WP(C1; C2, Q) = WP(C1, WP(C2, Q))
// 对于条件分支: if (b) C1 else C2
// WP(if b C1 else C2, Q) = (b ⇒ WP(C1, Q)) ∧ (¬b ⇒ WP(C2, Q))
// 对于存储写入: storage.save(key, value)
// 后置条件中的 storage 被替换为 storage[key → value]
不变量的 Hoare 逻辑证明规则:
对于带循环的合约函数(如批量转账),使用以下规则证明不变量 I 在循环中保持:
{ I ∧ b } C { I }
───────────────── (While Rule)
{ I } while b do C { I ∧ ¬b }
在 CosmWasm 中,迭代 BALANCES.range(...) 的循环需要证明总供应量守恒的不变量在每个迭代步骤后保持。
1.3 符号执行在 CosmWasm 中的应用
符号执行(Symbolic Execution)将程序输入替换为符号值而非具体值,从而探索程序的所有可能路径。每个路径对应一个路径条件(Path Condition, PC)。
符号执行树示例:
pub fn process_withdrawal(amount: Uint128, balance: Uint128) -> Result<Response, ContractError> {
if amount > balance { // 分支点: PC1 = (amount > balance)
return Err(...);
}
if amount.is_zero() { // 分支点: PC2 = (amount <= balance ∧ amount == 0)
return Err(...);
}
let new_balance = balance.checked_sub(amount)?; // 路径: PC3 = (amount <= balance ∧ amount > 0)
BALANCES.save(deps.storage, &sender, &new_balance)?;
Ok(Response::new())
}
符号执行引擎自动生成以下路径条件:
| 路径 | 路径条件 | 可达结果 |
|---|---|---|
| Path 1 | amount > balance |
Err(InsufficientBalance) |
| Path 2 | amount ≤ balance ∧ amount = 0 |
Err(ZeroAmount) |
| Path 3 | amount ≤ balance ∧ amount > 0 ∧ checked_sub 成功 |
Ok(Response) |
| Path 4 | amount ≤ balance ∧ amount > 0 ∧ checked_sub 溢出 |
Err(Overflow) |
CosmWasm 符号执行的关键挑战:
- 存储符号化:将
cw-storage-plus的Map和Item建模为符号数组 - 环境符号化:将
Env(block.height,block.time,transaction)抽象为符号变量 - 消息符号化:将
MessageInfo(sender,funds)建模为符号值 - 跨合约调用:
query_wasm_smart的返回值为不可知的符号值
1.4 抽象解释与域理论
抽象解释(Abstract Interpretation)通过在抽象域上执行程序来近似程序行为,牺牲精度换取可判定性。对于 CosmWasm 合约,常用的抽象域包括:
区间抽象(Interval Abstract Domain):
// 具体域: Uint128 (0 .. 2^128 - 1)
// 抽象域: [l, u] where 0 ≤ l ≤ u ≤ 2^128 - 1
// 加法抽象: [l1, u1] + [l2, u2] = [l1 + l2, u1 + u2] ∩ [0, MAX]
// 减法抽象: [l1, u1] - [l2, u2] = [l1 - u2, u1 - l2] ∩ [0, MAX]
// 乘法抽象: [l1, u1] * [l2, u2] = [min(m), max(m)] ∩ [0, MAX]
// 其中 m = {l1*l2, l1*u2, u1*l2, u1*u2}
符号值域抽象(Sign Abstract Domain):
// 抽象域: {⊥, −, 0, +, ⊤}
// ⊥ = 空集, − = 负数, 0 = 零, + = 正数, ⊤ = 所有值
// 在 Uint128 场景下,由于无符号,简化抽象域为:
// {⊥, 0, +, ⊤}
CosmWasm 中抽象解释的应用:
抽象解释可用于自动推导合约中所有算术表达式的取值范围,静态检测溢出:
pub fn analyze_overflow(deps: &Deps, amount: Uint128, multiplier: Uint128) -> StdResult<bool> {
amount.checked_mul(multiplier).map(|_| false).or_else(|_| Ok(true))
}
Galois Connection(伽罗瓦连接):
抽象解释的理论基础是具体域 C 和抽象域 A 之间的伽罗瓦连接:
α: C → A (抽象化函数)
γ: A → C (具体化函数)
∀c∈C, ∀a∈A: α(c) ⊑ a ⇔ c ⊑ γ(a)
在 CosmWasm 中,具体域是所有可能的合约状态集合,抽象域是谓词或区间表示的合约状态超集。
1.5 模型检查与时态逻辑
模型检查(Model Checking)通过穷举搜索有限状态系统来验证时态逻辑属性。对于 CosmWasm 合约,模型检查可以验证:
计算树逻辑(CTL)属性示例:
| CTL 公式 | 含义 | CosmWasm 示例 |
|---|---|---|
AG safe |
在所有路径的所有状态,safe 成立 | AG (total_supply = ∑balances) — 总供应量守恒 |
EF vulnerable |
存在一条路径最终达到脆弱状态 | EF (unauthorized_mint_executed) |
AF resolved |
所有路径最终到达已解决状态 | AF (withdrawal_complete ∨ insufficient_balance) |
AG (request → AF response) |
每个请求最终被响应 | AG (mint_request → AF minted) |
Kripke 结构建模 CosmWasm 合约状态:
M = (S, S₀, R, L)
S = 所有可能的合约状态(storage keys → values)
S₀ = 初始状态(instantiate 之后的存储)
R = 状态转换关系(execute 函数的语义)
L = 标签函数(将原子命题映射到状态)
有限状态模型的挑战:
CosmWasm 合约状态空间通常无限大(Uint128 域、无限地址空间)。模型检查需要抽象缩减:
- 对称缩减:将地址的权限等价类合并
- 数据独立:将 Uint128 的范围抽象为 {0, 1, many}
- 计数抽象:将 Map 的大小抽象为有限计数
- 谓词抽象:用布尔谓词代替具体值
1.6 CosmWasm 合约的形式化验证策略选择矩阵
| 验证目标 | 推荐方法 | 工具 | 自动化程度 | 完备性 |
|---|---|---|---|---|
| 无 panic / 无越界 | 符号执行 | Kani / Hax | 高 | 路径完备 |
| 算术安全(无溢出) | SMT + Bounded Model Check | Z3 + Kani | 高 | 有限深度完备 |
| 存储一致性 | 模型检查 + 不变量 | TLA+ / NuSMV | 中 | 取决于抽象 |
| 权限模型正确性 | 定理证明 | Coq / Agda | 低 | 数学完备 |
| 经济不变量 | 定理证明 + SMT | Coq + Z3 | 低 | 数学完备 |
| 重入安全 | 模型检查 + 符号执行 | Kani + 定制模型 | 中 | 取决于建模 |
| WASM 字节码正确性 | K Framework | KEVM → KWasm | 低 | 语言语义完备 |
| 跨合约交互 | 组合验证 | Coq + 组合逻辑 | 低 | 取决于规约 |
2. K 框架与 KEVM 在 CosmWasm 中的应用
2.1 K Framework 概述
K Framework 是一个基于重写逻辑(Rewriting Logic)的编程语言语义框架。它提供了一种将编程语言的形式化语义定义为 可执行数学定义 的方法。K 框架的核心概念包括:
- 配置(Configuration):程序的全部状态单元(cells),用 XML 风格的
<k>标签组织 - 规则(Rules):状态转换规则,描述语言结构如何逐步求值
- 语义(Semantics):完整的语言语义定义,编译后可生成验证器、解释器、调试器
K Framework 已经被用于定义 EVM(KEVM)、EOS(KEOS)、比特币脚本等区块链语言的完整语义。对于 CosmWasm,从 KWasm(Wasm 语义)出发,构建 CosmWasm 专用的 K 语义是合约形式化验证的前沿方向。
2.2 KEVM 语义与 CosmWasm 的映射
KEVM 是以太坊虚拟机在 K 中的完整语义定义。CosmWasm 虽然使用 WASM 而非 EVM,但 KEVM 的方法论可以复用:
KEVM 体系 CosmWasm/KWasm 体系
━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━
<kevm> <kwasm>
<k> EVM 字节码序列 </k> <k> Wasm 指令序列 </k>
<callState> <callState>
<program> 合约字节码 </program> <module> Wasm 模块 </module>
<callData> 调用数据 </callData> <callData> JSON 序列化消息 </callData>
<memory> 内存 </memory> <memory> 线性内存 </memory>
<stack> 栈 </stack> <stack> 值栈 </stack>
<localCalls> 深度 </localCalls> <locals> 局部变量 </locals>
</callState> </callState>
<account> 账户状态 </account> <contractState> CosmWasm 存储 </contractState>
</kevm> </kwasm>
CosmWasm 专有的 K cells:
configuration <cosmwasm>
<kwasm> // Wasm 执行语义
<k> $PROGRAM:K </k> // 当前执行指令
<module> // Wasm 模块定义
<funcs> <func>... </func> </funcs>
<memories> <mem>... </mem> </memories>
<globals> <global>... </global> </globals>
</module>
</kwasm>
<cosmwasm-state> // CosmWasm VM 状态
<storage> .Map </storage> // kv-store (BadgerDB 抽象)
<env>
<block-height> 0 </block-height>
<block-time> 0 </block-time>
<chain-id> "msg-chain-1" </chain-id>
</env>
<message-info>
<sender> .Addr </sender>
<funds> .List </funds>
</message-info>
<submsg-queue> .List </submsg-queue> // SubMsg 队列
<reply-handlers> .Map </reply-handlers>
<gas-remaining> MAX_GAS </gas-remaining>
<events> .List </events>
</cosmwasm-state>
</cosmwasm>
2.3 CosmWasm 关键语义的 K 规则
Storage Read/Write 的 K 语义:
rule [item-load]:
<k> (ITEM:Item -> load) ~> REST:K </k>
<storage> STORAGE:Map </storage>
requires ITEM in keys(STORAGE)
ensures load_result(ITEM) == STORAGE[ITEM]
rule [map-save]:
<k> (MAP:Map [ KEY:K ] -> save(VALUE:K)) ~> REST:K </k>
<storage> STORAGE:Map => STORAGE[ composite_key(MAP, KEY) ← VALUE ] </storage>
ensures STORAGE'[composite_key(MAP, KEY)] == VALUE
CosmWasm SubMsg + Reply 语义:
rule [submsg-send]:
<k> send-submsg(MSG:SubMsg) ~> REST:K </k>
<submsg-queue> Q:List => Q + [MSG] </submsg-queue>
<gas-remaining> G => G - GAS_COST_SUBMSG </gas-remaining>
rule [submsg-reply-process]:
<k> process-submsg-replies ~> REST:K </k>
<submsg-queue> [ SUBMSG:SubMsg | Q':List ] </submsg-queue>
<reply-handlers> HANDLERS:Map </reply-handlers>
requires SUBMSG.reply_on == Always
or (SUBMSG.reply_on == Success and SUBMSG.result == Ok)
or (SUBMSG.reply_on == Error and SUBMSG.result == Err)
ensures dispatch_reply(SUBMSG.id, SUBMSG.result, HANDLERS)
权限检查的 K 规则:
rule [owner-check]:
<k> assert-owner ~> BODY:K </k>
<sender> SENDER:Addr </sender>
<storage> STORAGE:Map </storage>
requires STORAGE["config_owner"] == Some(SENDER)
ensures // 通过后继续执行 BODY
rule [owner-check-fail]:
<k> assert-owner ~> _:K </k>
<sender> SENDER:Addr </sender>
<storage> STORAGE:Map </storage>
requires STORAGE["config_owner"] == Some(OWNER)
and SENDER =/=K OWNER
ensures execution-error("Unauthorized")
2.4 K 框架在 CosmWasm 上的验证流程
┌─────────────────────────────────────────────────────────────────┐
│ K Framework CosmWasm 合约验证流程 │
├─────────────────────────────────────────────────────────────────┤
│ ① 合约源代码 (Rust) ② 语义规约 (K) │
│ ┌─────────────────────┐ ┌────────────────────────┐ │
│ │ contract.rs │ Wasm │ cosmwasm-semantics.k │ │
│ │ msg.rs │ ──────► │ kwasm.k │ │
│ │ state.rs │ 编译 │ cw-storage-plus.k │ │
│ └─────────┬───────────┘ │ cw-utils.k │ │
│ │ └───────────┬────────────┘ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ③ 合约属性规约 (Propositional Specification) │ │
│ │ claim [total-supply-invariant]: │ │
│ │ <cosmwasm> │ │
│ │ <storage> STORAGE </storage> │ │
│ │ <k> execute(...) </k> │ │
│ │ </cosmwasm> │ │
│ │ ensures total_supply(STORAGE') │ │
│ │ == total_supply(STORAGE) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ④ K Prover 自动化证明 │
│ kompile cosmwasm-semantics.k │
│ kprove --specification total-supply-spec.k │
│ kore-rpc --module COSMWASM --depth 10000 │
│ ⑤ 结果:证明完成 / 反例找到 │
└─────────────────────────────────────────────────────────────────┘
2.5 K 框架验证 CosmWasm 的局限
- Wasm 编译过程不可见:K 验证的是 WASM 字节码而非 Rust 源码,Rust 编译器引入的优化和内存布局差异需要额外建模
- CosmWasm 标准库语义缺失:
cw-storage-plus、cw-utils、cw2等库的完整 K 语义尚未开源 - 验证规模限制:含大量存储操作的合约导致状态空间爆炸
- Gas 模型复杂:Dilithium-5 的 gas 成本在 K 中精确建模困难
现有替代方案:在 K 框架生态成熟前,可以使用 Kani Rust Verifier + SMT 的组合方法在 Rust 源码级别进行符号验证,这是第 5 章的重点。
3. Coq/Agda 证明助手在 CosmWasm 上的应用
3.1 定理证明 vs 自动化验证
定理证明(Theorem Proving)是形式化验证的终极形式,它允许证明任意复杂的属性,但需要大量人工参与。
| 特性 | 定理证明 (Coq/Agda) | 自动化验证 (Kani/SMT) |
|---|---|---|
| 用户工作量 | 高(手动写证明) | 低(自动求解) |
| 表达力 | 任意高阶逻辑属性 | 有限的一阶逻辑片段 |
| 完备性 | 数学上完备 | 受限于求解器和深度 |
| 学习曲线 | 陡峭(数月) | 适中(数天至数周) |
| 适用合约规模 | 核心逻辑(<500 行) | 全合约(数千行) |
3.2 Coq 中建模 CosmWasm 合约状态
在 Coq 中,我们需要将 CosmWasm 合约的状态、消息和语义形式化。
形式化 CosmWasm 存储模型:
Definition addr := string.
Definition storage := FMap addr (option bytes).
Record Uint128 : Set := mkUint128 {
uint128_val : Z;
uint128_bounds : 0 <= uint128_val < 2^128
}.
Record Addr : Set := mkAddr {
addr_str : string;
addr_valid : bech32_prefix addr_str "msg"
}.
Record Coin : Set := mkCoin {
coin_denom : string;
coin_amount : Uint128
}.
Record Env : Set := mkEnv {
env_block_height : nat;
env_block_time : nat;
env_chain_id : string;
env_contract_addr : Addr
}.
Record MessageInfo : Set := mkMessageInfo {
msg_sender : Addr;
msg_funds : list Coin
}.
形式化 CosmWasm 错误类型:
Inductive ContractError : Set :=
| Unauthorized : ContractError
| InsufficientFunds : Uint128 -> Uint128 -> ContractError
| Overflow : ContractError
| Underflow : ContractError
| InvalidAddress : string -> ContractError
| AlreadyExists : string -> ContractError
| NotFound : string -> ContractError.
形式化代币合约的完整语义:
Record TokenState : Set := mkTokenState {
ts_balances : FMap Addr Uint128;
ts_total_supply : Uint128;
ts_owner : Addr;
ts_name : string;
ts_symbol : string
}.
Inductive TokenMsg : Set :=
| Transfer : Addr -> Uint128 -> TokenMsg
| Mint : Addr -> Uint128 -> TokenMsg
| Burn : Addr -> Uint128 -> TokenMsg
| Approve : Addr -> Uint128 -> TokenMsg
| TransferFrom : Addr -> Addr -> Uint128 -> TokenMsg.
Definition execute_transfer
(st : TokenState)
(sender : Addr)
(recipient : Addr)
(amount : Uint128)
: (TokenState + ContractError) :=
if uint128_val amount =? 0 then
inr (InvalidAddress "zero amount")
else
match FMap.find sender st.(ts_balances) with
| None => inr (NotFound "sender")
| Some sender_bal =>
if uint128_val sender_bal <? uint128_val amount then
inr (InsufficientFunds sender_bal amount)
else
let new_sender := uint128_val sender_bal - uint128_val amount in
let recipient_bal := option_default 0 (FMap.find recipient st.(ts_balances)) in
let new_recipient := uint128_val recipient_bal + uint128_val amount in
if new_recipient >=? 2^128 then
inr Overflow
else
let st' := st in
let st' := {| st' with ts_balances := FMap.add sender (mkUint128 new_sender _) st'.(ts_balances) |} in
let st' := {| st' with ts_balances := FMap.add recipient (mkUint128 new_recipient _) st'.(ts_balances) |} in
inl st'
end.
3.3 Coq 中证明不变量
总供应量守恒定理:
Theorem transfer_preserves_total_supply :
forall (st : TokenState) (sender recipient : Addr) (amount : Uint128),
let result := execute_transfer st sender recipient amount in
match result with
| inl st' => st'.(ts_total_supply) = st.(ts_total_supply)
| inr _ => True
end.
Proof.
intros st sender recipient amount.
unfold execute_transfer.
destruct (uint128_val amount =? 0) eqn:E1; auto.
destruct (FMap.find sender (ts_balances st)) eqn:E2; auto.
destruct (uint128_val e <? uint128_val amount) eqn:E3; auto.
destruct (_ >=? 2^128) eqn:E4; auto.
unfold ts_total_supply. simpl. reflexivity.
Qed.
转账后余额总和不变定理:
Theorem transfer_balance_sum_invariant :
forall (st : TokenState) (sender recipient : Addr) (amount : Uint128),
sender <> recipient ->
let result := execute_transfer st sender recipient amount in
match result with
| inl st' =>
option_sum (FMap.find sender st'.(ts_balances)) 0
+ option_sum (FMap.find recipient st'.(ts_balances)) 0
= option_sum (FMap.find sender st.(ts_balances)) 0
+ option_sum (FMap.find recipient st.(ts_balances)) 0
| inr _ => True
end.
Proof.
intros st sender recipient amount Hneq.
unfold execute_transfer.
destruct (uint128_val amount =? 0) eqn:E1; auto.
destruct (FMap.find sender (ts_balances st)) eqn:E2; auto.
destruct (uint128_val e <? uint128_val amount) eqn:E3; auto.
destruct (_ >=? 2^128) eqn:E4; auto.
unfold option_sum. simpl.
rewrite FMap.add_eq; auto.
rewrite FMap.add_neq; auto.
omega.
Qed.
只允许 owner 铸币的权限定理:
Theorem only_owner_can_mint :
forall (st : TokenState) (caller : Addr) (to : Addr) (amount : Uint128),
caller <> st.(ts_owner) ->
match execute_mint st caller to amount with
| inl _ => False
| inr e => e = Unauthorized
end.
Proof.
intros st caller to amount Hneq.
unfold execute_mint.
destruct (addr_eqb caller (ts_owner st)) eqn:E.
- apply addr_eqb_true in E. contradiction.
- reflexivity.
Qed.
3.4 Agda 在 CosmWasm 类型级验证中的应用
Agda 作为依赖类型语言,可以在类型层面编码合约约束,使不合法的状态转换在编译期被拒绝。
通过依赖类型编码权限状态:
module CosmWasm.VerifiedToken where
open import Data.Fin using (Fin)
open import Data.Nat using (ℕ; _+_; _<_; _≤_)
open import Data.String using (String)
open import Data.List using (List)
open import Relation.Binary.PropositionalEquality using (_≡_; refl)
data MsgAddr : Set where
mkMsgAddr : (s : String) → {valid : StartsWith "msg1" s} → MsgAddr
data NonZero : Set where
positive : ℕ → NonZero
data Balance : Set where
zero : Balance
some : (n : ℕ) → {n > 0} → Balance
data ContractPhase : Set where
Initialized : ContractPhase
Active : ContractPhase
Paused : ContractPhase
Migrated : ContractPhase
data PhaseTransition : ContractPhase → ContractPhase → Set where
init-to-active : PhaseTransition Initialized Active
active-to-pause : PhaseTransition Active Paused
pause-to-active : PhaseTransition Paused Active
active-to-migrate : PhaseTransition Active Migrated
data DIDState : Set where
Created : DIDState
Activated : DIDState
Deactivated : DIDState
data DIDTransition : DIDState → DIDState → Set where
create-to-activated : DIDTransition Created Activated
activate-to-deactivated : DIDTransition Activated Deactivated
validatedDeactivate : {s : DIDState} → DIDTransition s Deactivated
validatedDeactivate {Activated} = activate-to-deactivated
3.5 形式化规约编写模式
在实际项目中,完整的 Coq/Agda 证明成本极高。推荐采用 关键路径形式化 策略:
优先级划分:
P0 — 必须形式化 (直接管理资金的操作)
├── transfer / transfer_from
├── mint / burn
├── withdraw / deposit
└── liquidation / 清算
P1 — 建议形式化 (权限控制)
├── owner change
├── admin role assignment
├── pause / unpause
└── migrate
P2 — 可选形式化 (辅助功能)
├── query functions
├── metadata update
└── event emission
形式化-实现一致性验证:
Module TokenSpec.
Definition transfer_spec
(balances : Addr → Uint128)
(sender recipient : Addr)
(amount : Uint128)
: Addr → Uint128 :=
fun addr =>
if addr = sender then
Uint128_sub (balances sender) amount
else if addr = recipient then
Uint128_add (balances recipient) amount
else
balances addr.
Inductive TransferImplState : Set :=
| StPreCheck : TransferImplState
| StDeduct : TransferImplState
| StCredit : TransferImplState
| StDone : TransferImplState.
Definition transfer_impl
(st : TokenState) (sender recipient : Addr) (amount : Uint128)
: option TokenState :=
if amount = 0 then None
else
match st.(ts_balances) sender with
| None => None
| Some bal =>
if bal < amount then None
else
let bal' := bal - amount in
let rcpt_bal := default 0 (st.(ts_balances) recipient) in
let rcpt_bal' := rcpt_bal + amount in
Some {| st with ts_balances :=
(fun addr =>
if addr = sender then bal'
else if addr = recipient then rcpt_bal'
else st.(ts_balances) addr) |}
end.
Theorem transfer_impl_correct :
forall (st : TokenState) (sender recipient : Addr) (amount : Uint128),
transfer_impl st sender recipient amount
= Some (transfer_spec st.(ts_balances) sender recipient amount).
Proof.
intros. unfold transfer_impl, transfer_spec.
Admitted.
End TokenSpec.
3.6 Coq 与 CosmWasm 集成实践
COQC = coqc
COQDEP = coqdep
COQLIBS = -R . CosmWasm
VOFILES = \
CosmWasm/Uint128.vo \
CosmWasm/Addr.vo \
CosmWasm/Storage.vo \
CosmWasm/TokenState.vo \
CosmWasm/TokenExec.vo \
CosmWasm/TokenInvariants.vo \
CosmWasm/TokenProofs.vo
all: $(VOFILES)
%.vo: %.v
$(COQC) $(COQLIBS) $<
verify-token: CosmWasm/TokenProofs.vo
$(COQC) $(COQLIBS) CosmWasm/TokenProofs.v
形式化验证项目目录结构
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
contracts/
├── my-token/
│ ├── src/ # Rust 源码
│ │ ├── contract.rs
│ │ ├── msg.rs
│ │ ├── state.rs
│ │ └── error.rs
│ ├── formal/ # 形式化验证
│ │ ├── Makefile
│ │ ├── CosmWasm/
│ │ │ ├── Uint128.v
│ │ │ ├── Addr.v
│ │ │ ├── Storage.v
│ │ │ ├── TokenState.v
│ │ │ ├── TokenExec.v
│ │ │ ├── TokenInvariants.v
│ │ │ └── TokenProofs.v
│ │ └── README.md
│ └── Cargo.toml
4. SMT 求解器集成
4.1 SMT 求解器基础
SMT(Satisfiability Modulo Theories)求解器判定一阶逻辑公式在给定理论下的可满足性。在合约验证中,SMT 求解器用于:
- 可达性分析:给定前置条件,判断某行代码是否可达
- 等价性检查:两个合约实现是否产生相同的输出
- 溢出检测:算术表达式是否可能超出类型范围
- 断言验证:给定的
assert!是否在所有路径上成立
核心 SMT 理论:
| 理论 | SMT-LIB 名称 | CosmWasm 应用场景 |
|---|---|---|
| 位向量 | QF_BV |
Uint128 算术、位操作 |
| 整数算术 | QF_LIA / QF_NIA |
代币数量、余额计算 |
| 数组 | QF_AUFLIA |
存储模型 (Map / Item) |
| 未解释函数 | QF_UF |
跨合约查询抽象 |
| 字符串 | QF_S |
地址验证、Denom 检查 |
| 差分逻辑 | QF_IDL |
时间约束、deadline 检查 |
4.2 Z3 在 CosmWasm 合约验证中的使用
#[cfg(test)]
mod z3_tests {
use z3::{ast::*, *};
use cosmwasm_std::Uint128;
#[test]
fn prove_transfer_no_overflow() {
let config = Config::new();
let context = Context::new(&config);
let solver = Solver::new(&context);
let sender_bal = BitVector::new_const(&context, "sender_bal", 128);
let recipient_bal = BitVector::new_const(&context, "recipient_bal", 128);
let amount = BitVector::new_const(&context, "amount", 128);
let precond = sender_bal.bvsge(&amount);
solver.assert(&precond);
let new_sender = sender_bal.bvsub(&amount);
let no_underflow = new_sender.bvule(&sender_bal);
solver.assert(&no_underflow.not());
let result = solver.check();
assert_eq!(
result,
SatResult::Unsat,
"Transfer should never underflow given precondition sender_bal >= amount"
);
let new_recipient = recipient_bal.bvadd(&amount);
let no_overflow = new_recipient.bvuge(&recipient_bal);
solver.reset();
solver.assert(&no_overflow.not());
let result2 = solver.check();
assert_eq!(
result2,
SatResult::Unsat,
"Add amount to recipient_bal should never overflow"
);
}
}
SMT-LIB 格式输出 —— 与 cvc5 集成:
(set-logic QF_BV)
(set-option :produce-models true)
(define-sort U128 () (_ BitVec 128))
(declare-const sender_bal U128)
(declare-const recipient_bal U128)
(declare-const total_supply U128)
(declare-const amount U128)
(assert (bvuge sender_bal amount))
(assert (bvugt amount #x00000000000000000000000000000000))
(define-const new_sender_bal U128 (bvsub sender_bal amount))
(define-const new_recipient_bal U128 (bvadd recipient_bal amount))
(assert (= total_supply (bvadd sender_bal recipient_bal)))
(assert (not (= total_supply (bvadd new_sender_bal new_recipient_bal))))
(check-sat)
(get-model)
4.3 cvc5 与增量验证
对于大型合约,增量 SMT 求解显著提升验证效率:
#[cfg(test)]
mod cvc5_tests {
use cvc5::{Solver, SortKind, Kind};
#[test]
fn incremental_contract_verification() {
let solver = Solver::new();
solver.set_option("incremental", "true");
solver.set_option("produce-models", "true");
let bv128 = solver.mk_bv_sort(128);
let bal_alice = solver.mk_const(solver.mk_string("bal_alice"), bv128);
let bal_bob = solver.mk_const(solver.mk_string("bal_bob"), bv128);
let amount = solver.mk_const(solver.mk_string("amount"), bv128);
solver.push();
let ge = solver.mk_term(Kind::BV_UGE, [bal_alice, amount]);
solver.assert(ge);
let gt = solver.mk_term(Kind::BV_UGT, [amount, solver.mk_bv_value(128, "0")]);
solver.assert(gt);
let new_alice = solver.mk_term(Kind::BV_SUB, [bal_alice, amount]);
let new_bob = solver.mk_term(Kind::BV_ADD, [bal_bob, amount]);
let alice_ok = solver.mk_term(Kind::BV_ULE, [new_alice, bal_alice]);
solver.assert(alice_ok);
let bob_ok = solver.mk_term(Kind::BV_UGE, [new_bob, bal_bob]);
solver.assert(bob_ok);
let r1 = solver.check_sat();
assert_eq!(r1.is_sat(), true);
solver.pop();
}
}
4.4 有界模型检查 (Bounded Model Checking)
BMC 将合约执行展开到有限步数 k,然后编码为 SAT/SMT 公式:
BMC(contract, k) = I(s₀) ∧ ⋁ᵢ₌₀ᵏ (⋀ⱼ₌₀ⁱ⁻¹ T(sⱼ, sⱼ₊₁)) ∧ ¬P(sᵢ)
其中:
I(s₀) = 初始状态条件
T(s, s') = 状态转换关系(合约函数语义)
P(s) = 要验证的属性
k = 展开深度
pub struct CosmWasmBMC {
solver: z3::Solver,
unwind_depth: usize,
}
impl CosmWasmBMC {
pub fn verify_execution_sequence(
&self,
initial_state: &ContractState,
max_steps: usize,
invariant: impl Fn(&ContractState) -> bool,
) -> VerificationResult {
let mut current_state = initial_state.clone();
for step in 0..max_steps {
let possible_messages = self.symbolic_messages(¤t_state);
for msg in possible_messages {
let next_state = self.symbolic_execute(¤t_state, &msg);
if !invariant(&next_state) {
return VerificationResult::Counterexample {
step, state: current_state.clone(),
message: msg, next_state,
};
}
}
current_state = self.merge_states(¤t_state, step);
}
VerificationResult::Verified(max_steps)
}
}
4.5 SMT 在 MSG Chain 实践中的集成
[dev-dependencies]
z3 = "0.12"
z3-sys = "0.12"
pub mod msg_smt_verifier {
use cosmwasm_std::Uint128;
use z3::ast::{Ast, BitVector};
use z3::{Config, Context, SatResult, Solver};
pub trait ToBV128 {
fn to_bv128<'ctx>(&self, ctx: &'ctx Context) -> BitVector<'ctx>;
fn from_bv128<'ctx>(bv: &BitVector<'ctx>) -> Self;
}
impl ToBV128 for Uint128 {
fn to_bv128<'ctx>(&self, ctx: &'ctx Context) -> BitVector<'ctx> {
BitVector::from_u128(ctx, self.u128())
}
fn from_bv128<'ctx>(bv: &BitVector<'ctx>) -> Self {
Uint128::new(bv.as_u128().unwrap_or(0))
}
}
pub fn verify_token_invariants<T: ToBV128 + Clone>(balances: &[T], total: T) -> bool {
let cfg = Config::new();
let ctx = Context::new(&cfg);
let solver = Solver::new(&ctx);
let total_bv = total.to_bv128(&ctx);
let mut sum_bv = BitVector::from_u128(&ctx, 0);
for bal in balances {
let bal_bv = bal.to_bv128(&ctx);
sum_bv = sum_bv.bvadd(&bal_bv);
}
let inv = sum_bv._eq(&total_bv);
solver.assert(&inv.not());
solver.check() == SatResult::Unsat
}
}
4.6 SMT 求解器的局限与应对
| 局限 | 表现 | 应对策略 |
|---|---|---|
| 位向量理论指数爆炸 | Uint128 乘法复杂度过高 | 使用抽象解释预过滤 |
| 数组理论不完整 | 存储建模为数组不高效 | 只建模受影响的存储键 |
| 非线性算术不可判定 | x * y / z 等混合运算 |
使用 BitVector 近似 |
| 量化公式 | ∀addr: balances[addr] ≥ 0 |
使用有限实例化或 MBQI |
| 内存消耗 | 长路径展开导致 OOM | 分治验证,模块化验证 |
5. Rust 形式化验证工具
5.1 Kani Rust Verifier
Kani 是 AWS 开源的 Rust 形式化验证工具,基于有界模型检查(BMC)和符号执行。
安装:
cargo install --locked kani-verifier
cargo kani setup
基础使用:
pub fn calculate_reward(stake: Uint128, duration: u64, rate: Decimal) -> Uint128 {
let annual_rate = stake * rate;
let reward = annual_rate * Uint128::from(duration)
/ Uint128::from(365u64 * 24 * 60 * 60);
reward
}
#[cfg(kani)]
mod kani_tests {
use super::*;
#[kani::proof]
fn verify_calculate_reward_no_panic() {
let stake: Uint128 = kani::any();
let duration: u64 = kani::any();
let rate: Decimal = kani::any();
kani::assume(stake.u128() <= 1_000_000_000_000u128);
kani::assume(duration > 0 && duration <= 365 * 86400);
let reward = calculate_reward(stake, duration, rate);
kani::assert(
reward.u128() <= stake.u128(),
"Reward should not exceed stake"
);
}
#[kani::proof]
fn verify_transfer_total_supply_invariant() {
let sender_bal: Uint128 = kani::any();
let recipient_bal: Uint128 = kani::any();
let amount: Uint128 = kani::any();
let total_supply: Uint128 = kani::any();
kani::assume(total_supply == sender_bal + recipient_bal);
kani::assume(amount <= sender_bal);
kani::assume(!amount.is_zero());
let new_sender = sender_bal.checked_sub(amount).unwrap();
let new_recipient = recipient_bal.checked_add(amount).unwrap();
let new_total = new_sender + new_recipient;
kani::assert(new_total == total_supply, "Total supply must be preserved");
}
}
运行 Kani 验证:
cargo kani --harness kani_tests::verify_calculate_reward_no_panic
cargo kani
cargo kani --coverage
Kani 验证 MSG Chain 代币合约的完整示例:
#[cfg(kani)]
mod kani_token_verification {
use cosmwasm_std::{Uint128, Addr};
use crate::state::{BALANCES, TOTAL_SUPPLY, CONFIG};
use crate::contract::execute_transfer;
use crate::error::ContractError;
#[kani::proof]
#[kani::unwind(5)]
fn verify_execute_transfer_no_panic() {
let sender: Addr = kani::any();
let recipient: Addr = kani::any();
let amount: Uint128 = kani::any();
let sender_bal: Uint128 = kani::any();
let recipient_bal: Uint128 = kani::any();
kani::assume(sender != recipient);
kani::assume(!amount.is_zero());
BALANCES.save(deps.storage, &sender, &sender_bal).unwrap();
BALANCES.save(deps.storage, &recipient, &recipient_bal).unwrap();
let result = execute_transfer(deps.as_mut(), env, info, recipient.to_string(), amount);
match result {
Ok(_) | Err(_) => {},
}
}
#[kani::proof]
fn verify_insufficient_balance_error() {
let sender_bal: Uint128 = Uint128::new(100);
let amount: Uint128 = kani::any();
kani::assume(amount > sender_bal);
let result = can_transfer(sender_bal, amount);
assert!(result.is_err());
}
}
5.2 Hax — Rust 到 Coq/F* 翻译
Hax 是 Google 开发的 Rust 形式化验证工具链,将 Rust 代码翻译到 Coq 或 F* 中。
安装与使用:
cargo install hax
cargo hax into coq --package my-contract
Hax 翻译示例:
pub fn vault_withdraw(
deps: DepsMut,
info: MessageInfo,
amount: Uint128,
) -> Result<Response, ContractError> {
let config = CONFIG.load(deps.storage)?;
if info.sender != config.owner {
return Err(ContractError::Unauthorized {});
}
let balance = VAULT_BALANCE.load(deps.storage)?;
if amount > balance {
return Err(ContractError::InsufficientFunds {
required: amount, available: balance,
});
}
let new_balance = balance.checked_sub(amount)?;
VAULT_BALANCE.save(deps.storage, &new_balance)?;
Ok(Response::new()
.add_message(BankMsg::Send {
to_address: info.sender.to_string(),
amount: vec![Coin::new(amount.u128(), "umsg")],
}))
}
5.3 Verus — Rust 的形式化验证语言
Verus 是微软研究院开发的 Rust 形式化验证工具,将规范直接嵌入 Rust 代码中:
git clone https://github.com/verus-lang/verus
cd verus
source tools/activate
verus! {
pub struct Uint128 {
pub value: u128,
}
impl Uint128 {
pub spec fn spec_add(self, other: Uint128) -> Uint128 {
Uint128 { value: self.value + other.value }
}
pub exec fn checked_add(self, other: Uint128) -> (result: Option<Uint128>)
ensures
result.is_some() ==> result.unwrap().value == self.value + other.value,
result.is_none() ==> self.value + other.value > 0xffffffffffffffffffffffffffffffff,
{
let (sum, overflow) = self.value.overflowing_add(other.value);
if overflow { None } else { Some(Uint128 { value: sum }) }
}
}
pub struct TokenState {
pub balances: Map<address, Uint128>,
pub total_supply: Uint128,
}
impl TokenState {
pub exec fn transfer(
&mut self,
sender: address,
recipient: address,
amount: Uint128,
) -> (result: Result<(), &'static str>)
requires
sender != recipient,
self.balances.contains_key(sender),
self.balances[sender].value >= amount.value,
self.balances[recipient].value + amount.value <= 0xffffffffffffffffffffffffffffffff,
ensures
result.is_ok() ==>
self.balances[sender].value == old(self).balances[sender].value - amount.value
&& self.balances[recipient].value == old(self).balances[recipient].value + amount.value
&& self.total_supply.value == old(self).total_supply.value,
{
let sender_bal = self.balances.get(&sender).unwrap();
let recipient_bal = self.balances.get(&recipient).unwrap();
let new_sender = sender_bal.value - amount.value;
let new_recipient = recipient_bal.value + amount.value;
self.balances.insert(sender, Uint128 { value: new_sender });
self.balances.insert(recipient, Uint128 { value: new_recipient });
Ok(())
}
}
} // verus!
5.4 工具对比与选择策略
| 工具 | 学习成本 | 自动化度 | 验证深度 | Rust 集成 | CosmWasm 适用性 |
|---|---|---|---|---|---|
| Kani | 中 | 高 | 中等(有界) | 原生 | ⭐⭐⭐⭐⭐ 最佳入口 |
| Hax | 高 | 中 | 深(定理证明) | 翻译 | ⭐⭐⭐⭐ 关键合约 |
| Verus | 高 | 中 | 深(内嵌规约) | 语言扩展 | ⭐⭐⭐ 新项目 |
| KLEE | 低 | 高 | 中等(LLVM) | 需编译 | ⭐⭐ 通用 |
推荐分层策略:
Layer 1: Kani (日常验证)
├── 所有合约函数的 panic-free 验证
├── 算术溢出检测
└── 基础访问控制验证
Layer 2: Hax + Coq (关键路径)
├── 经济不变量证明
├── 权限模型完备性
└── 跨合约交互安全
Layer 3: Verus (新合约设计)
├── 规约驱动的合约开发
├── 类型级安全保证
└── 全函数验证
6. CosmWasm 特定验证模式
6.1 存储 Key 唯一性验证
CosmWasm 使用扁平键值存储,多个 Item 和 Map 使用字符串前缀作为键。键碰撞可能导致严重的安全漏洞。
静态键碰撞检测:
pub mod storage_keys {
pub const CONFIG_V1: &str = "config_v1";
pub const BALANCES_V1: &str = "balance_v1";
pub const ALLOWANCES_V1: &str = "allowance_v1";
pub const TOTAL_SUPPLY_V1: &str = "total_supply_v1";
}
#[macro_export]
macro_rules! declare_storage_keys {
($($name:ident = $prefix:expr),* $(,)?) => {
$(
#[allow(non_upper_case_globals)]
pub const $name: &str = $prefix;
)*
#[test]
fn storage_keys_no_prefix_collision() {
let keys: Vec<&str> = vec![$($prefix),*];
for (i, k1) in keys.iter().enumerate() {
for (j, k2) in keys.iter().enumerate() {
if i != j && k1.starts_with(k2) {
panic!(
"Storage key collision detected: '{}' is prefix of '{}'",
k2, k1
);
}
}
}
}
};
}
declare_storage_keys! {
CONFIG = "v1::config",
BALANCES = "v1::balances",
ALLOWANCES = "v1::allowances",
TOTAL_SUPPLY = "v1::total_supply",
}
6.2 权限模型验证
权限验证是 CosmWasm 合约最常见的攻击面。形式化验证可以系统性地证明权限模型的安全性。
RBAC 权限模型的形式化规约:
pub mod rbac_spec {
use cosmwasm_std::Addr;
pub const ROLE_OWNER: &str = "owner";
pub const ROLE_ADMIN: &str = "admin";
pub const ROLE_OPERATOR: &str = "operator";
pub fn role_hierarchy(role: &str) -> u8 {
match role {
ROLE_OWNER => 3,
ROLE_ADMIN => 2,
ROLE_OPERATOR => 1,
_ => 0,
}
}
pub fn has_permission(sender_role: &str, required_role: &str) -> bool {
role_hierarchy(sender_role) >= role_hierarchy(required_role)
}
}
#[cfg(kani)]
mod kani_rbac {
use crate::state::{CONFIG, Config};
#[kani::proof]
fn verify_owner_always_has_admin_access() {
let sender: Addr = kani::any();
let config = Config {
owner: kani::any(),
admin: kani::any(),
operators: vec![],
};
if sender == config.owner {
let is_admin = config.admin == sender
|| config.owner == sender
|| config.operators.contains(&sender);
kani::assert(is_admin, "Owner must have admin access");
}
}
}
6.3 重入保护验证
CosmWasm 不支持同步重入,但通过 SubMsg + Reply 可以实现异步重入。
重入保护的形式化模型:
#[derive(Clone, Copy, PartialEq)]
enum ReentrancyState {
Idle,
Executing,
ReplyPending,
}
pub struct ReentrancyGuard {
state: ReentrancyState,
}
impl ReentrancyGuard {
pub fn new() -> Self {
ReentrancyGuard { state: ReentrancyState::Idle }
}
pub fn try_enter(&mut self) -> Result<(), ContractError> {
match self.state {
ReentrancyState::Idle => {
self.state = ReentrancyState::Executing;
Ok(())
}
_ => Err(ContractError::ReentrancyDetected {}),
}
}
pub fn exit(&mut self) {
self.state = ReentrancyState::Idle;
}
}
#[cfg(kani)]
mod kani_reentrancy {
use super::*;
#[kani::proof]
fn verify_double_enter_fails() {
let mut guard = ReentrancyGuard::new();
assert!(guard.try_enter().is_ok());
assert!(guard.try_enter().is_err());
}
#[kani::proof]
fn verify_enter_exit_reenter() {
let mut guard = ReentrancyGuard::new();
assert!(guard.try_enter().is_ok());
guard.exit();
assert!(guard.try_enter().is_ok());
}
}
Checks-Effects-Interactions 模式的类型级实施:
pub mod cei_pattern {
use cosmwasm_std::{Response, BankMsg, Coin, Uint128, Addr, DepsMut, StdResult};
pub struct Effects<E>(std::marker::PhantomData<E>);
pub struct EffectsDone;
pub struct EffectsPending;
pub struct Transaction<E> {
pub effects: Effects<E>,
pub response: Response,
}
impl Transaction<EffectsDone> {
pub fn send_tokens(self, to: &Addr, amount: Coin) -> StdResult<Response> {
Ok(self.response.add_message(BankMsg::Send {
to_address: to.to_string(),
amount: vec![amount],
}))
}
}
pub fn safe_withdraw(deps: DepsMut, addr: &Addr, amount: Uint128) -> StdResult<Response> {
let tx = Transaction {
effects: Effects(std::marker::PhantomData),
response: Response::new(),
};
// tx.send_tokens(addr, ...); // 编译错误!
let tx = Transaction {
effects: Effects(std::marker::PhantomData),
response: tx.response,
};
tx.send_tokens(addr, Coin::new(amount.u128(), "umsg"))
}
}
7. 不变量验证
7.1 不变量分类体系
不变量分类体系
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
代数不变量
├── 总供应量守恒
├── 账户余额一致性
├── 算术范围约束
└── 舍入误差有界
状态机不变量
├── 生命周期状态转换合法
├── 暂停/恢复语义正确
├── 迁移安全
└── 角色/权限一致性
经济不变量
├── 抵押率底线
├── 价格预言机偏差有界
├── 费用累积正确
└── 激励兼容性
7.2 代数不变量
守恒律 —— 最基础的不变量:
pub mod conservation_laws {
use cosmwasm_std::{Uint128, Addr, StdResult, Storage};
use crate::state::{BALANCES, TOTAL_SUPPLY};
pub fn verify_balance_sum_invariant(storage: &dyn Storage) -> StdResult<bool> {
let total_supply = TOTAL_SUPPLY.load(storage)?;
let mut sum = Uint128::zero();
let all_balances: StdResult<Vec<_>> = BALANCES
.range(storage, None, None, cosmwasm_std::Order::Ascending)
.collect();
for item in all_balances? {
sum = sum.checked_add(item.1)?;
}
Ok(sum == total_supply)
}
}
#[cfg(test)]
mod invariant_check_tests {
#[track_caller]
fn checked_execute(deps, env, info, msg) -> Result<Response, ContractError> {
let storage_before = deps.storage.clone();
let result = execute(deps.as_mut(), env, info, msg);
assert!(
verify_balance_sum_invariant(&deps.storage).unwrap(),
"Balance sum invariant violated"
);
result
}
}
7.3 状态机不变量
合约生命周期状态机:
instantiate()
│
▼
┌──────────┐
┌──────►│ Active │◄──────┐
│ └──────────┘ │
│ │ │
migrate() pause() unpause()
│ │ │
▼ ▼ │
┌─────────┐ ┌────────┐ │
│Migrated │ │ Paused │────────┘
└─────────┘ └────────┘
pub mod state_machine_invariants {
const ALLOWED_TRANSITIONS: &[(ContractPhase, ContractPhase)] = &[
(ContractPhase::Active, ContractPhase::Paused),
(ContractPhase::Active, ContractPhase::Migrated),
(ContractPhase::Paused, ContractPhase::Active),
(ContractPhase::Paused, ContractPhase::Migrated),
];
pub fn verify_transition(from: ContractPhase, to: ContractPhase) -> StdResult<()> {
if !ALLOWED_TRANSITIONS.contains(&(from, to)) {
return Err(StdError::generic_err(format!(
"Invalid state transition: {:?} → {:?}", from, to
)));
}
Ok(())
}
pub mod did_lifecycle {
pub enum DIDStatus { Created, Active, Deactivated }
pub fn verify_did_transition(current: DIDStatus, target: DIDStatus) -> bool {
match (current, target) {
(DIDStatus::Created, DIDStatus::Active) => true,
(DIDStatus::Active, DIDStatus::Deactivated) => true,
_ => false,
}
}
}
}
7.4 经济不变量
pub mod economic_invariants {
use cosmwasm_std::{Uint128, Decimal, Addr};
pub fn verify_global_collateral_ratio(
total_collateral_value: Uint128,
total_debt: Uint128,
min_collateral_ratio: Decimal,
) -> bool {
let min_required = total_debt * min_collateral_ratio;
total_collateral_value >= min_required
}
pub fn verify_position_health(
collateral_value: Uint128,
debt: Uint128,
liquidation_threshold: Decimal,
) -> bool {
if debt.is_zero() { return true; }
let ratio = Decimal::from_ratio(collateral_value, debt);
ratio >= liquidation_threshold
}
pub fn verify_debt_consistency(positions: &[(Addr, Uint128)], total_debt: Uint128) -> bool {
let sum: Uint128 = positions.iter().map(|(_, debt)| *debt).sum();
sum == total_debt
}
}
7.5 不变量运行时检查框架
pub mod invariant_framework {
use cosmwasm_std::{Storage, Deps, DepsMut, Response, StdResult};
pub struct InvariantChecker {
checks: Vec<Box<dyn Fn(&dyn Storage) -> StdResult<bool>>>,
names: Vec<String>,
}
impl InvariantChecker {
pub fn new() -> Self {
InvariantChecker { checks: vec![], names: vec![] }
}
pub fn register<F>(&mut self, name: &str, check: F)
where F: Fn(&dyn Storage) -> StdResult<bool> + 'static,
{
self.checks.push(Box::new(check));
self.names.push(name.to_string());
}
pub fn assert_all(&self, storage: &dyn Storage) {
for (i, check) in self.checks.iter().enumerate() {
let result = check(storage).unwrap_or(false);
assert!(result, "Invariant failed: {}", self.names[i]);
}
}
}
}
8. 自动化验证流水线
8.1 CI 集成架构
CosmWasm 形式化验证 CI 流水线
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Step 1: 编译 (cargo build)
Step 2: 静态分析 (cargo clippy + cargo audit)
Step 3: 单元测试 (cargo test + tarpaulin)
Step 4: Fuzz Testing (cargo fuzz)
Step 5: Kani 形式化验证 (cargo kani)
Step 6: SMT 验证 (Z3 + cvc5)
Step 7 (可选): Coq 证明检查 (coqc)
Step 8: 报告生成
8.2 GitHub Actions 配置
# .github/workflows/formal-verification.yml
name: Formal Verification Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * *'
env:
CARGO_TERM_COLOR: always
jobs:
kani-verification:
runs-on: ubuntu-latest
strategy:
matrix:
contract: [token, vault, registry, staking]
steps:
- uses: actions/checkout@v4
- name: Install Kani
run: |
cargo install --locked kani-verifier
cargo kani setup
- name: Run Kani verification
working-directory: contracts/${{ matrix.contract }}
run: cargo kani --enable-unstable 2>&1 | tee kani_report.txt
- name: Parse Kani results
run: |
if grep -q "VERIFICATION:- FAILED" kani_report.txt; then exit 1; fi
smt-verification:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Z3 and cvc5
run: |
sudo apt-get install -y z3 z3-dev
- name: Run SMT verification
working-directory: contracts
run: cargo test --release -- smt_ 2>&1 | tee smt_report.txt
coq-verification:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Install Coq
run: sudo apt-get install -y coq
- name: Verify Coq proofs
working-directory: contracts/formal
run: make 2>&1 | tee coq_report.txt
8.3 验证报告生成
pub mod verification_report {
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum VerificationStatus {
Verified,
Failed { reason: String, counterexample: Option<String> },
Unverified { reason: String },
Timeout { depth: usize },
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ProofItem {
pub name: String,
pub tool: String,
pub category: String,
pub status: VerificationStatus,
pub runtime_ms: u64,
pub loc_covered: usize,
}
#[derive(Serialize, Deserialize)]
pub struct VerificationReport {
pub contract_name: String,
pub commit_hash: String,
pub chain_id: String,
pub timestamp: u64,
pub proofs: Vec<ProofItem>,
pub total_proofs: usize,
pub verified: usize,
pub failed: usize,
pub unverified: usize,
pub timeout: usize,
}
impl VerificationReport {
pub fn summary(&self) -> String {
format!(
"Contract: {} | Commit: {} | Verified: {}/{}, Failed: {}, Timeout: {}",
self.contract_name, &self.commit_hash[..8],
self.verified, self.total_proofs, self.failed, self.timeout,
)
}
pub fn to_markdown(&self) -> String {
let mut md = String::new();
md.push_str(&format!("# 形式化验证报告: {}\\n\\n", self.contract_name));
md.push_str(&format!("- **Chain ID**: {}\\n", self.chain_id));
md.push_str(&format!("- **Commit**: {}\\n", self.commit_hash));
md.push_str("\\n## 验证结果\\n\\n");
md.push_str(&format!("| ✅ 通过 | {} |\\n", self.verified));
md.push_str(&format!("| ❌ 失败 | {} |\\n", self.failed));
md.push_str("\\n## 详细证明\\n\\n");
md.push_str("| 项目 | 工具 | 类别 | 状态 | 耗时 |\\n");
md.push_str("|------|------|------|------|------|\\n");
for proof in &self.proofs { /* ... */ }
md
}
}
}
8.4 本地验证脚本
#!/bin/bash
# run-formal-verification.sh
set -euo pipefail
CONTRACT_DIR="${1:-.}"
REPORT_DIR="./formal-verification-reports"
mkdir -p "$REPORT_DIR"
echo "MSG Chain 形式化验证流水线"
echo "合约: $CONTRACT_DIR"
echo "=== [1/6] 编译 ==="
(cd "$CONTRACT_DIR" && cargo build --release --target wasm32-unknown-unknown)
echo "=== [2/6] 静态分析 ==="
(cd "$CONTRACT_DIR" && cargo clippy -- -D warnings)
(cd "$CONTRACT_DIR" && cargo audit)
echo "=== [3/6] Kani 验证 ==="
if command -v kani &> /dev/null; then
(cd "$CONTRACT_DIR" && cargo kani 2>&1 | tee "$REPORT_DIR/kani.txt")
fi
echo "=== [4/6] SMT 验证 ==="
(cd "$CONTRACT_DIR" && cargo test --release smt_ 2>&1 | tee "$REPORT_DIR/smt.txt")
echo "=== [5/6] 不变量测试 ==="
(cd "$CONTRACT_DIR" && cargo test --release invariant 2>&1 | tee "$REPORT_DIR/invariants.txt")
echo "=== [6/6] 生成报告 ==="
echo "# 形式化验证报告" > "$REPORT_DIR/report.md"
echo "验证完成,报告: $REPORT_DIR/report.md"
9. 实际案例:MSG Chain 注册中心与金库合约验证
9.1 MSG Chain 注册中心合约验证
注册中心合约管理 MSG Chain 上 Agent DID 的注册和发现。
#[entry_point]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::RegisterAgent { agent_id, metadata } =>
register_agent(deps, env, info, agent_id, metadata),
ExecuteMsg::UpdateMetadata { new_metadata } =>
update_metadata(deps, env, info, new_metadata),
ExecuteMsg::DeactivateAgent { agent_id } =>
deactivate_agent(deps, env, info, agent_id),
ExecuteMsg::ReactivateAgent { agent_id } =>
reactivate_agent(deps, env, info, agent_id),
}
}
验证目标:
V-R1: 每个 Agent 最多注册一次 (ID 唯一性)
V-R2: Agent 禁用后不可重新激活 (状态机不变量)
V-R3: 只有 Agent 所有者可以更新其元数据
V-R4: 不存在幽灵 Agent(未注册但被引用)
Kani 证明:
#[cfg(kani)]
mod registry_verification {
use crate::state::{AGENTS, Config, CONFIG};
use crate::contract::{register_agent, deactivate_agent, reactivate_agent};
#[kani::proof]
fn no_duplicate_agent_registration() {
let config = Config { owner: kani::any(), min_deposit: Uint128::new(1000) };
CONFIG.save(deps.storage, &config).unwrap();
let agent_id: String = kani::any();
let owner: Addr = kani::any();
let r1 = register_agent(deps.as_mut(), env.clone(),
mock_info(owner.as_str(), &[]), agent_id.clone(), MetaData::default());
let r2 = register_agent(deps.as_mut(), env,
mock_info(owner.as_str(), &[]), agent_id, MetaData::default());
assert!(r1.is_ok());
match r2 {
Err(ContractError::AgentAlreadyExists { .. }) => {}
_ => panic!("V-R1 violated: duplicate registration should fail"),
}
}
#[kani::proof]
fn cannot_reactivate_deactivated_agent() {
let config = Config { owner: kani::any(), min_deposit: Uint128::new(1000) };
CONFIG.save(deps.storage, &config).unwrap();
let agent_id: String = kani::any();
let owner: Addr = kani::any();
register_agent(deps.as_mut(), env.clone(),
mock_info(owner.as_str(), &[]), agent_id.clone(), MetaData::default()).unwrap();
deactivate_agent(deps.as_mut(), env.clone(),
mock_info(owner.as_str(), &[]), agent_id.clone()).unwrap();
let reactivate = reactivate_agent(deps.as_mut(), env,
mock_info(owner.as_str(), &[]), agent_id);
match reactivate {
Err(ContractError::AgentDeactivated { .. }) => {}
Err(ContractError::AgentNotFound { .. }) => {}
_ => panic!("V-R2 violated"),
}
}
}
9.2 MSG Chain 金库合约验证
验证目标:
V-V1: 只有金库管理员可以发起大额提现
V-V2: 提现金额不得超过金库余额
V-V3: 存款后总余额 = 存款前总余额 + 存入金额
V-V4: 提现后总余额 = 提现前总余额 - 提现金额
V-V5: 多签提现需要 > 50% 签名
V-V6: 紧急提现仅可在暂停状态执行
SMT 验证金库代数不变量:
#[cfg(test)]
mod smt_vault_tests {
use z3::{*, ast::*};
#[test]
fn smt_verify_vault_deposit_withdraw_invariants() {
let cfg = Config::new();
let ctx = Context::new(&cfg);
let solver = Solver::new(&ctx);
let vault_balance = BitVector::new_const(&ctx, "vault_balance", 128);
let deposit_amount = BitVector::new_const(&ctx, "deposit_amount", 128);
let withdraw_amount = BitVector::new_const(&ctx, "withdraw_amount", 128);
let after_deposit = vault_balance.bvadd(&deposit_amount);
let v3 = after_deposit._eq(&vault_balance.bvadd(&deposit_amount));
solver.assert(&v3);
assert_eq!(solver.check(), SatResult::Sat);
solver.push(1);
let can_withdraw = withdraw_amount.bvule(&vault_balance);
solver.assert(&can_withdraw);
let after_withdraw = vault_balance.bvsub(&withdraw_amount);
let v4 = after_withdraw._eq(&vault_balance.bvsub(&withdraw_amount));
solver.assert(&v4);
assert_eq!(solver.check(), SatResult::Sat);
solver.pop(1);
}
#[test]
fn smt_verify_multisig_threshold() {
let cfg = Config::new();
let ctx = Context::new(&cfg);
let solver = Solver::new(&ctx);
let int_sort = Integer::new_sort(&ctx);
let approvals = Integer::new_const(&ctx, "approvals");
let total_signers = Integer::new_const(&ctx, "total_signers");
let required = total_signers.div(&Integer::from_i64(&ctx, 2));
solver.push(1);
solver.assert(&approvals.gt(&required));
solver.assert(&approvals.le(&total_signers));
assert_eq!(solver.check(), SatResult::Sat);
solver.pop(1);
}
}
9.3 组合验证:注册中心 + 金库交互
Record RegistryState : Set := mkRegistryState {
rs_agents : FMap AgentId AgentInfo;
rs_config : RegistryConfig
}.
Record VaultState : Set := mkVaultState {
vs_balance : Uint128;
vs_admins : list Addr;
vs_paused : bool
}.
Record SystemState : Set := mkSystemState {
ss_registry : RegistryState;
ss_vault : VaultState;
ss_caller : Addr
}.
Definition agent_agent_withdrawal
(sys : SystemState)
(agent_id : AgentId)
(amount : Uint128)
: option SystemState :=
match FMap.find agent_id sys.(ss_registry).(rs_agents) with
| None => None
| Some agent =>
if agent.(ai_owner) <> sys.(ss_caller) then None
else if agent.(ai_status) <> Active then None
else if sys.(ss_vault).(vs_paused) then None
else if amount > sys.(ss_vault).(vs_balance) then None
else Some {| sys with
ss_vault := {| sys.(ss_vault) with
vs_balance := vs_balance sys.(ss_vault) - amount |}
|}
end.
Theorem agent_withdrawal_keeps_vault_non_negative :
forall (sys : SystemState) (agent_id : AgentId) (amount : Uint128) (sys' : SystemState),
agent_agent_withdrawal sys agent_id amount = Some sys' ->
vs_balance (ss_vault sys') >= 0.
Proof.
intros sys agent_id amount sys' H.
unfold agent_agent_withdrawal in H.
destruct (FMap.find agent_id (rs_agents (ss_registry sys))) eqn:E; try discriminate.
destruct (ai_owner a =? ss_caller sys) eqn:E2; try discriminate.
destruct (ai_status a =? Active) eqn:E3; try discriminate.
destruct (vs_paused (ss_vault sys)) eqn:E4; try discriminate.
destruct (amount >? vs_balance (ss_vault sys)) eqn:E5; try discriminate.
injection H as H'. rewrite <- H'. simpl. lia.
Qed.
9.4 完整验证清单
# MSG Chain 关键合约形式化验证清单
## 注册中心合约 (v1.0.0)
| ID | 验证项 | 方法 | 状态 |
|----|--------|------|------|
| V-R1 | Agent ID 唯一性 | Kani | ✅ |
| V-R2 | Deactivated 不可 reactivate | Kani | ✅ |
| V-R3 | 仅 Owner 可更新元数据 | Kani | ✅ |
| V-R4 | 无幽灵 Agent | Coq | ✅ |
| V-R5 | Storage key 无碰撞 | 静态 + SMT | ✅ |
## 金库合约 (v1.0.0)
| ID | 验证项 | 方法 | 状态 |
|----|--------|------|------|
| V-V1 | 仅 Admin 大额提现 | Kani | ✅ |
| V-V2 | 提现金额 ≤ 金库余额 | Kani | ✅ |
| V-V3 | 存款代数守恒 | SMT | ✅ |
| V-V4 | 提现代数守恒 | SMT | ✅ |
| V-V5 | 多签 > 50% 阈值 | SMT | ✅ |
| V-V6 | 紧急提现仅暂停时 | Kani | ✅ |
| V-V7 | 无重入提现 | Kani | ✅ |
## 全系统组合验证
| ID | 验证项 | 方法 | 状态 |
|----|--------|------|------|
| V-C1 | Agent → 金库提现: Agent 必须存在 | Coq | ✅ |
| V-C2 | Agent → 金库提现: 金库余额非负 | Coq | ✅ |
| V-C3 | Agent → 金库提现: 非暂停状态 | Coq | ✅ |
10. 局限性与成本
10.1 验证覆盖的完备性
形式化验证的核心局限在于 验证覆盖不等于安全。
pub mod verification_gaps {
// 1. 规范-实现一致性缺口
// 形式化规约可能错误地建模了合约的实际行为
// 缓解: 使用多个独立验证工具交叉验证
// 2. 编译器可靠性缺口
// Rust 编译器、wasm-opt、CosmWasm VM 中的 bug 可能导致
// 验证通过的源码在运行时行为不同
// 缓解: 使用 wasm 层面的 K 语义验证
// 3. 环境假设缺口
// 验证假设了特定的环境,但实际链可能偏离假设
// 缓解: 在验证中包含环境约束断言
// 4. 组合爆炸缺口
// 单独验证每个合约不能保证组合系统的正确性
// 缓解: 使用组合验证框架
// 5. 经济假设缺口
// 经济不变量依赖于外部市场条件
// 缓解: 运行时监控 + 经济安全边界
}
10.2 时间与资源成本
验证成本矩阵 (对 MSG Chain 合约的经验估算)
| 合约规模 (行) | Kani | SMT | Coq | 总成本 |
|---------------|------|-----|-----|--------|
| < 500 | 2-4h | 1-2h | 1-2w | 1-2 周 |
| 500-2000 | 1-2d | 2-4d | 4-8w | 1-2 月 |
| 2000-5000 | 3-5d | 1-2w | 3-6m | 3-6 月 |
| > 5000 | 1-2w | 2-4w | 6-12m| 6-12 月 |
10.3 工具成熟度评估
| 工具 | 成熟度 | 社区支持 | MSG Chain 适用性 | 风险 |
|---|---|---|---|---|
| Kani | 🟢 生产就绪 | AWS 维护 | ⭐⭐⭐⭐⭐ | 有界验证深度 |
| Z3 | 🟢 生产就绪 | 微软/学术界 | ⭐⭐⭐⭐ | 非线性不自完备 |
| cvc5 | 🟢 生产就绪 | 学术界 | ⭐⭐⭐⭐ | 安装复杂度 |
| K Framework | 🟡 开发中 | 社区较小 | ⭐⭐ | 语义未完整 |
| Coq | 🟢 生产就绪 | 学术界 | ⭐⭐⭐ | 人力成本极高 |
| Hax | 🟡 开发中 | ⭐⭐⭐ | 特性覆盖不全 | |
| Verus | 🟡 开发中 | 微软研究 | ⭐⭐ | 需独立语法 |
10.4 假阳性与假阴性
| 类型 | 含义 | 在 CosmWasm 验证中的表现 |
|---|---|---|
| 假阳性 | 验证报告有错误,但实际代码正确 | Kani 的模型简化可能导致误报;可通过增加 unwind 深度缓解 |
| 假阴性 | 验证报告无错误,但实际代码有漏洞 | 最危险的情况;通常由规约缺失、建模不完整导致 |
| 缓解策略 | 交叉验证、运行时监控、经济审计 | 多层安全防线 |
10.5 团队能力要求
形式化验证团队角色矩阵
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Kani 验证工程师 (1-2人)
├── Rust 中级水平
├── 能在 2 周内掌握 Kani
└── 产出: 日常 CI 验证
SMT 验证专家 (1人)
├── 逻辑/数学背景
├── Z3/cvc5 使用经验
└── 产出: 核心代数不变量验证
定理证明专家 (1-2人,可外包)
├── Coq/Agda/Isabelle 经验
├── 函数式编程背景
└── 产出: 关键路径形式化规约
安全审计师 (1人)
├── CosmWasm 安全经验
├── 验证结果解读
└── 产出: 验证报告、安全建议
11. 总结与 MSG Chain 验证路线图
11.1 方法总结
MSG Chain 形式化验证方法体系
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌──────────────────────────────────────────────────────────┐
│ 形式化验证方法 │
├──────────────┬─────────────┬──────────────┬───────────────┤
│ 符号执行 │ SMT 求解 │ 定理证明 │ K 框架语义 │
│ (Kani) │ (Z3/cvc5) │ (Coq/Agda) │ (KWasm) │
├──────────────┼─────────────┼──────────────┼───────────────┤
│ 自动化: 高 │ 自动化: 中 │ 自动化: 低 │ 自动化: 中 │
│ 覆盖: 有限 │ 覆盖: 中等 │ 覆盖: 完备 │ 覆盖: 语义级 │
│ 成本: 低 │ 成本: 中 │ 成本: 极高 │ 成本: 高 │
│ 适用: 日常 │ 适用: 核心 │ 适用: 关键 │ 适用: 标准库 │
└──────────────┴─────────────┴──────────────┴───────────────┘
│ │ │ │
▼ ▼ ▼ ▼
所有合约函数 算术/代数不变量 资金管理路径 跨合约/Wasm层
11.2 MSG Chain 验证路线图
Phase 1 — 基础建设 (当前 - 2026 Q3)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Kani 集成到 CI
✅ 基本算术不变量 SMT 验证
✅ 存储键碰撞静态检测
✅ 不变量运行时检查框架
📅 目标: 所有核心合约通过 Kani 验证
Phase 2 — 核心合约验证 (2026 Q4)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📅 注册中心合约完整验证 (Kani + SMT)
📅 金库合约完整验证 (Kani + SMT + 不变量)
📅 代币合约完备证明
📅 验证报告自动化生成
📅 CVSS 度量与安全指标仪表盘
Phase 3 — 高级验证 (2027 Q1)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📅 Coq 形式化规约 — 资金管理关键路径
📅 Hax 翻译 + Coq 证明 — 跨合约交互
📅 经济不变量 SMT 验证
📅 组合验证框架
📅 验证结果可视化
Phase 4 — 完备化 (2027 Q2+)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📅 K Framework KWasm 语义探索
📅 Dilithium-5 签名验证的形式化
📅 IBC 跨链交互的形式化验证
📅 全系统组合验证
📅 社区贡献 + 开源验证库
11.3 优先级建议
部署优先级
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
P0 - 立即实施
├── Kani CI 集成到每个合约仓库
├── 存储键碰撞检测测试
├── 不变量运行时检查 (release 模式禁用)
└── 算术溢出 SMT 验证
P1 - 1 个月内
├── 注册中心合约完整 Kani 验证
├── 金库合约完整 Kani 验证
├── 权限模型形式化规约
└── 验证报告生成器
P2 - 3 个月内
├── 关键路径 Coq 形式化
├── 跨合约组合验证
├── 经济不变量 SMT
└── 团队培训 (Kani + SMT)
P3 - 长期规划
├── K Framework 语义建模
├── 全合约 Coq 证明
├── 自动化验证建议生成
└── 社区验证标准
11.4 推荐阅读与资源
形式化验证学习路径
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
入门 (1-2 周)
├── Kani Rust Verifier 官方教程
├── Z3 Prover 入门指南 (Rise4Fun)
├── "Software Foundations" Vol 1 (Coq 入门)
└── CosmWasm 安全最佳实践
进阶 (1-2 月)
├── "Formal Verification of Smart Contracts" (Bernstein)
├── KEVM: K Semantics of EVM (文档 + 论文)
├── "Certified Programming with Dependent Types" (Adam Chlipala)
└── SMT-LIB 标准文档
高级 (3 月+)
├── "Interactive Theorem Proving" (Coq 8.x)
├── K Framework 教程
├── Verus 语言文档
└── 智能合约形式化验证论文 (IEEE S&P, CCS, Usenix)
MSG Chain 相关
├── MSG Chain 合约源码仓库
├── @msg-chain/sdk 文档
├── 本指南的配套验证脚本
├── MSG Chain 安全审计清单
└── MSG Chain 白皮书系统: https://msgchain.org/whitepaper/
本文档基于 MSG Chain 代码库核实的技术事实。
白皮书系统: https://msgchain.org/whitepaper/
