dApp Docs/合约模糊测试与形式化验证
Development reference. Not independently verified for production.

MSG Chain CosmWasm 合约模糊测试与形式化验证指南

数据来源:MSG Chain 代码库核实

主网状态: No-Go — 当前 MSGChain 主网裁决为 No-Go,以下内容反映代码实际状态,不代表生产可用。


目录

  1. 概述
  2. Rust 单元测试基础
  3. Fuzz Testing 与 cargo-fuzz
  4. 特性测试 (Property-based Testing)
  5. CosmWasm VM 仿真测试
  6. 形式化验证
  7. CI 集成
  8. 常见漏洞测试模式
  9. 附录

1. 概述

1.1 为什么智能合约需要模糊测试

智能合约一旦部署即不可篡改,漏洞可能导致不可逆的资金损失。CosmWasm 合约虽然受益于 Rust 的内存安全性,但逻辑漏洞仍然存在:

模糊测试(Fuzz Testing)通过生成大量随机输入来探索合约的状态空间,能够发现传统单元测试覆盖不到的边界情况。

1.2 模糊测试 vs 单元测试 vs 集成测试

维度 单元测试 集成测试 模糊测试
输入来源 开发者手动构造 有限的场景组合 自动生成随机输入
覆盖范围 已知路径 多合约交互 边界/异常/未预期
断言方式 期望结果对比 场景验证 断言不变量/Crash 检测
运行频率 每次提交 每次提交 定期/CI 夜间
发现漏洞类型 逻辑错误 交互错误 内存/溢出/边界/拒绝服务

1.3 形式化验证概述

形式化验证使用数学方法证明合约的正确性。不同于测试(只能证明存在 bug),形式化验证可以证明不存在特定类型的 bug。

在 CosmWasm 生态中,形式化验证的层次:

合约源代码 (Rust)
    │
    ├── K Framework 语义模型 → 全合约验证
    ├── Rust 类型系统         → 编译时安全
    ├── 不变量断言            → 运行时检查
    └── Model Checking        → 状态空间穷举

1.4 MSG Chain 环境速查

Chain ID:       msg-chain-1
Bech32 前缀:    msg
代币精度:       18 位小数 (umsg)
签名算法:       Dilithium-5 (后量子)
存储引擎:       BadgerDB (LSM-Tree)
共识:           Round-Robin + DAR
出块时间:       ~5 秒

1.5 可用工具栈

工具 用途 适用阶段
cargo test 单元测试 开发/CI
cargo clippy 静态分析 开发/CI
cargo fuzz 覆盖率引导模糊测试 开发/CI 夜间
proptest 属性基测试 开发/CI
cw-multi-test 多合约集成测试 开发/CI
cosmwasm-vm 全节点仿真测试 部署前
K Framework 形式化验证 审计/关键合约
make test Go 端测试 CI
make test-quantum 量子密码测试 CI
make ci-contracts 合约编译+测试 CI

2. Rust 单元测试基础

2.1 标准测试模块结构

每个 CosmWasm 合约必须在 src/contract.rs 或专用的 tests/ 中包含完整的测试覆盖。

#[cfg(test)]
mod tests {
    use cosmwasm_std::testing::{
        mock_dependencies, mock_env, mock_info,
    };
    use cosmwasm_std::{from_binary, Addr, Binary, Uint128, coins};

    use crate::contract::{instantiate, execute, query};
    use crate::msg::{
        InstantiateMsg, ExecuteMsg, QueryMsg,
        GetBalanceResponse, TransferResponse,
    };
    use crate::error::ContractError;
    use crate::state::{Config, State, CONFIG, BALANCES};
}

2.2 辅助函数模式

fn setup_contract() -> (
    cosmwasm_std::OwnedDeps<
        cosmwasm_std::MemoryStorage,
        cosmwasm_std::testing::MockApi,
        cosmwasm_std::testing::MockQuerier,
    >,
    cosmwasm_std::Env,
) {
    let mut deps = mock_dependencies();
    let env = mock_env();

    instantiate(
        deps.as_mut(),
        env.clone(),
        mock_info("admin", &coins(1000, "umsg")),
        InstantiateMsg {
            owner: "admin".to_string(),
            token_name: "MSG Test Token".to_string(),
            token_symbol: "MTT".to_string(),
            initial_supply: Uint128::new(1_000_000),
        },
    )
    .unwrap();

    (deps, env)
}

fn mint_tokens(
    deps: &mut cosmwasm_std::OwnedDeps<
        cosmwasm_std::MemoryStorage,
        cosmwasm_std::testing::MockApi,
        cosmwasm_std::testing::MockQuerier,
    >,
    to: &str,
    amount: Uint128,
) {
    execute(
        deps.as_mut(),
        mock_env(),
        mock_info("admin", &[]),
        ExecuteMsg::Mint {
            to: to.to_string(),
            amount,
        },
    )
    .unwrap();
}

2.3 标准测试场景

#[test]
fn proper_initialization() {
    let mut deps = mock_dependencies();
    let env = mock_env();
    let info = mock_info("admin", &coins(1000, "umsg"));

    let res = instantiate(
        deps.as_mut(),
        env,
        info,
        InstantiateMsg {
            owner: "admin".to_string(),
            token_name: "MSG Test".to_string(),
            token_symbol: "MST".to_string(),
            initial_supply: Uint128::new(1_000_000),
        },
    )
    .unwrap();

    assert_eq!(res.attributes.len(), 4);
    assert_eq!(res.attributes[0].value, "instantiate");
    assert_eq!(res.attributes[1].value, "admin");
    assert_eq!(res.attributes[2].value, "MSG Test");

    let config = CONFIG.load(&deps.storage).unwrap();
    assert_eq!(config.owner, "admin");
    assert_eq!(config.token_name, "MSG Test");
    assert_eq!(config.token_symbol, "MST");
}

#[test]
fn transfer_updates_balances() {
    let mut deps = mock_dependencies();
    let env = mock_env();

    instantiate(
        deps.as_mut(),
        env.clone(),
        mock_info("admin", &coins(1000, "umsg")),
        InstantiateMsg {
            owner: "admin".to_string(),
            token_name: "Test".to_string(),
            token_symbol: "TST".to_string(),
            initial_supply: Uint128::new(1_000_000),
        },
    )
    .unwrap();

    BALANCES
        .save(
            deps.as_mut().storage,
            &Addr::unchecked("alice"),
            &Uint128::new(5000),
        )
        .unwrap();

    execute(
        deps.as_mut(),
        env.clone(),
        mock_info("alice", &[]),
        ExecuteMsg::Transfer {
            recipient: "bob".to_string(),
            amount: Uint128::new(1000),
        },
    )
    .unwrap();

    let alice_bal = BALANCES
        .load(&deps.storage, &Addr::unchecked("alice"))
        .unwrap();
    let bob_bal = BALANCES
        .load(&deps.storage, &Addr::unchecked("bob"))
        .unwrap();

    assert_eq!(alice_bal, Uint128::new(4000));
    assert_eq!(bob_bal, Uint128::new(1000));
}

2.4 错误路径测试

#[test]
fn transfer_insufficient_balance_fails() {
    let mut deps = mock_dependencies();
    let env = mock_env();

    instantiate(
        deps.as_mut(),
        env.clone(),
        mock_info("admin", &[]),
        InstantiateMsg {
            owner: "admin".to_string(),
            token_name: "Test".to_string(),
            token_symbol: "TST".to_string(),
            initial_supply: Uint128::new(1_000_000),
        },
    )
    .unwrap();

    BALANCES
        .save(
            deps.as_mut().storage,
            &Addr::unchecked("alice"),
            &Uint128::new(100),
        )
        .unwrap();

    let err = execute(
        deps.as_mut(),
        env,
        mock_info("alice", &[]),
        ExecuteMsg::Transfer {
            recipient: "bob".to_string(),
            amount: Uint128::new(200),
        },
    )
    .unwrap_err();

    assert_eq!(
        err,
        ContractError::InsufficientBalance {
            balance: Uint128::new(100),
            required: Uint128::new(200),
        }
    );
}

#[test]
fn unauthorized_mint_fails() {
    let mut deps = mock_dependencies();
    let env = mock_env();

    instantiate(
        deps.as_mut(),
        env.clone(),
        mock_info("admin", &[]),
        InstantiateMsg {
            owner: "admin".to_string(),
            token_name: "Test".to_string(),
            token_symbol: "TST".to_string(),
            initial_supply: Uint128::new(1_000_000),
        },
    )
    .unwrap();

    let err = execute(
        deps.as_mut(),
        env,
        mock_info("attacker", &[]),
        ExecuteMsg::Mint {
            to: "attacker".to_string(),
            amount: Uint128::new(1_000_000),
        },
    )
    .unwrap_err();

    assert_eq!(err, ContractError::Unauthorized {});
}

2.5 边界条件测试

#[test]
fn transfer_max_uint128_handling() {
    let mut deps = mock_dependencies();
    let env = mock_env();

    instantiate(
        deps.as_mut(),
        env.clone(),
        mock_info("admin", &[]),
        InstantiateMsg {
            owner: "admin".to_string(),
            token_name: "Test".to_string(),
            token_symbol: "TST".to_string(),
            initial_supply: Uint128::new(1_000_000),
        },
    )
    .unwrap();

    BALANCES
        .save(
            deps.as_mut().storage,
            &Addr::unchecked("alice"),
            &Uint128::MAX,
        )
        .unwrap();

    let err = execute(
        deps.as_mut(),
        env,
        mock_info("alice", &[]),
        ExecuteMsg::Transfer {
            recipient: "bob".to_string(),
            amount: Uint128::new(1),
        },
    )
    .unwrap();

    assert!(err.is_ok());
}

#[test]
fn transfer_zero_amount() {
    let mut deps = mock_dependencies();
    let env = mock_env();

    instantiate(
        deps.as_mut(),
        env.clone(),
        mock_info("admin", &[]),
        InstantiateMsg {
            owner: "admin".to_string(),
            token_name: "Test".to_string(),
            token_symbol: "TST".to_string(),
            initial_supply: Uint128::new(1_000_000),
        },
    )
    .unwrap();

    BALANCES
        .save(
            deps.as_mut().storage,
            &Addr::unchecked("alice"),
            &Uint128::new(100),
        )
        .unwrap();

    let res = execute(
        deps.as_mut(),
        env,
        mock_info("alice", &[]),
        ExecuteMsg::Transfer {
            recipient: "bob".to_string(),
            amount: Uint128::zero(),
        },
    )
    .unwrap();

    assert_eq!(res.attributes.len(), 3);
}

#[test]
fn self_transfer() {
    let mut deps = mock_dependencies();
    let env = mock_env();

    instantiate(
        deps.as_mut(),
        env.clone(),
        mock_info("admin", &[]),
        InstantiateMsg {
            owner: "admin".to_string(),
            token_name: "Test".to_string(),
            token_symbol: "TST".to_string(),
            initial_supply: Uint128::new(1_000_000),
        },
    )
    .unwrap();

    BALANCES
        .save(
            deps.as_mut().storage,
            &Addr::unchecked("alice"),
            &Uint128::new(500),
        )
        .unwrap();

    execute(
        deps.as_mut(),
        env,
        mock_info("alice", &[]),
        ExecuteMsg::Transfer {
            recipient: "alice".to_string(),
            amount: Uint128::new(100),
        },
    )
    .unwrap();

    let bal = BALANCES
        .load(&deps.storage, &Addr::unchecked("alice"))
        .unwrap();
    assert_eq!(bal, Uint128::new(500));
}

2.6 Gas 边界测试

#[test]
fn test_gas_heavy_operation() {
    let mut deps = mock_dependencies();
    let env = mock_env();

    instantiate(
        deps.as_mut(),
        env.clone(),
        mock_info("admin", &[]),
        InstantiateMsg {
            owner: "admin".to_string(),
            token_name: "Test".to_string(),
            token_symbol: "TST".to_string(),
            initial_supply: Uint128::new(1_000_000),
        },
    )
    .unwrap();

    let many_recipients: Vec<String> = (0..100)
        .map(|i| format!("user{}", i))
        .collect();

    let err = execute(
        deps.as_mut(),
        env,
        mock_info("admin", &[]),
        ExecuteMsg::BatchTransfer {
            recipients: many_recipients,
            amount: Uint128::new(10),
        },
    );

    match err {
        Ok(_) => {}
        Err(ContractError::GasLimitExceeded { .. }) => {}
        Err(e) => panic!("Unexpected error: {:?}", e),
    }
}

2.7 测试覆盖率要求

在 Cargo.toml 中配置覆盖率工具:

[dev-dependencies]
tarpaulin = "0.27"
# 生成覆盖率报告
cargo tarpaulin --ignore-tests --out Html --output-dir coverage

# 在 CI 中验证覆盖率
cargo tarpaulin --ignore-tests --fail-under 80

覆盖率目标:

指标 最低要求 目标
函数覆盖率 90% 95%+
行覆盖率 85% 92%+
分支覆盖率 80% 90%+
错误路径覆盖 70% 85%+

3. Fuzz Testing 与 cargo-fuzz

3.1 概述

cargo-fuzz 使用 libFuzzer 引擎,通过覆盖率引导(coverage-guided)的方式自动生成输入,探索程序中所有可达路径。对于 CosmWasm 合约,fuzz 测试可以发现:

3.2 环境安装

# 安装 cargo-fuzz
cargo install cargo-fuzz

# 验证安装
cargo fuzz --help

# 在合约项目中初始化 fuzz 目录
cargo fuzz init

初始化后,项目结构变为:

my_contract/
├── Cargo.toml
├── src/
│   ├── contract.rs
│   ├── msg.rs
│   ├── state.rs
│   └── error.rs
├── fuzz/
│   ├── Cargo.toml       # fuzz 依赖配置
│   └── targets/
│       └── fuzz_target_1.rs

3.3 Fuzz 依赖配置

# fuzz/Cargo.toml
[package]
name = "my_contract-fuzz"
version = "0.0.1"
publish = false
edition = "2021"

[package.metadata]
cargo-fuzz = true

[dependencies]
libfuzzer_sys = "0.4"
cosmwasm_std = { version = "1.5", features = ["stargate"] }
cosmwasm-storage = "1.5"
cw-storage-plus = "1.2"
cw2 = "1.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

# 被测试的合约
my_contract = { path = ".." }

3.4 基础 Fuzz Target:Instantiate + Execute 模糊测试

// fuzz/targets/fuzz_execute.rs
#![no_main]

use libfuzzer_sys::fuzz_target;
use cosmwasm_std::testing::{
    mock_dependencies, mock_env, mock_info,
};
use cosmwasm_std::{coins, from_binary};

use my_contract::contract::{instantiate, execute, query};
use my_contract::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};

fuzz_target!(|data: &[u8]| {
    if let Ok(msg) = from_binary::<ExecuteMsg>(&data.into()) {
        let mut deps = mock_dependencies();
        let env = mock_env();

        let init_msg = InstantiateMsg {
            owner: "admin".to_string(),
            token_name: "Fuzz".to_string(),
            token_symbol: "FUZZ".to_string(),
            initial_supply: Uint128::new(1_000_000),
        };

        if instantiate(
            deps.as_mut(),
            env.clone(),
            mock_info("admin", &[]),
            init_msg,
        )
        .is_err()
        {
            return;
        }

        let random_funds = coins(
            (data.len() % 1000) as u128,
            "umsg",
        );

        let _ = execute(
            deps.as_mut(),
            env,
            mock_info("user", &random_funds),
            msg,
        );
    }
});

3.5 结构体 Fuzz Target

// fuzz/targets/fuzz_instantiate_params.rs
#![no_main]

use libfuzzer_sys::fuzz_target;
use cosmwasm_std::testing::{
    mock_dependencies, mock_env, mock_info,
};

use my_contract::contract::instantiate;

fuzz_target!(|data: &[u8]| {
    let mut deps = mock_dependencies();
    let env = mock_env();

    if let Ok(owner_str) = String::from_utf8(data.to_vec()) {
        let truncated = if owner_str.len() > 128 {
            &owner_str[..128]
        } else {
            &owner_str
        };

        let msg = my_contract::msg::InstantiateMsg {
            owner: truncated.to_string(),
            token_name: "FuzzTest".to_string(),
            token_symbol: "FT".to_string(),
            initial_supply: cosmwasm_std::Uint128::new(
                data.len() as u128,
            ),
        };

        let _ = instantiate(
            deps.as_mut(),
            env,
            mock_info("fuzzer", &[]),
            msg,
        );
    }
});

3.6 多步操作 Fuzz Target

// fuzz/targets/fuzz_multi_step.rs
#![no_main]

use libfuzzer_sys::fuzz_target;
use cosmwasm_std::testing::{
    mock_dependencies, mock_env, mock_info,
};
use cosmwasm_std::{coins, from_binary, Uint128};

use my_contract::contract::{instantiate, execute};
use my_contract::msg::{ExecuteMsg, InstantiateMsg};

const MAX_STEPS: usize = 100;

fuzz_target!(|data: &[u8]| {
    let mut deps = mock_dependencies();
    let env = mock_env();

    if instantiate(
        deps.as_mut(),
        env.clone(),
        mock_info("admin", &[]),
        InstantiateMsg {
            owner: "admin".to_string(),
            token_name: "MultiStep".to_string(),
            token_symbol: "MSTEP".to_string(),
            initial_supply: Uint128::new(1_000_000_000),
        },
    )
    .is_err()
    {
        return;
    }

    let chunks: Vec<&[u8]> = data.chunks(64).collect();

    for (step_count, chunk) in chunks.iter().enumerate().take(MAX_STEPS) {
        if let Ok(msg) = from_binary::<ExecuteMsg>(chunk) {
            let sender = format!("user_{}", step_count % 10);
            let funds = coins(
                (chunk[0] as u128) * 1000,
                "umsg",
            );

            if execute(
                deps.as_mut(),
                env.clone(),
                mock_info(&sender, &funds),
                msg,
            )
            .is_err()
            {
                continue;
            }
        }
    }
});

3.7 Fuzz 测试 Execute Message 枚举变种

// fuzz/targets/fuzz_execute_variants.rs
#![no_main]

use libfuzzer_sys::fuzz_target;
use cosmwasm_std::testing::{
    mock_dependencies, mock_env, mock_info,
};
use cosmwasm_std::{coins, Uint128, Addr};

use my_contract::contract::{instantiate, execute};
use my_contract::msg::{ExecuteMsg, InstantiateMsg};
use my_contract::state::BALANCES;

fuzz_target!(|data: &[u8]| {
    if data.len() < 4 {
        return;
    }

    let mut deps = mock_dependencies();
    let env = mock_env();
    let variant = data[0] % 5;

    if instantiate(
        deps.as_mut(),
        env.clone(),
        mock_info("admin", &[]),
        InstantiateMsg {
            owner: "admin".to_string(),
            token_name: "FuzzVariant".to_string(),
            token_symbol: "FV".to_string(),
            initial_supply: Uint128::new(1_000_000),
        },
    )
    .is_err()
    {
        return;
    }

    BALANCES
        .save(
            deps.as_mut().storage,
            &Addr::unchecked("alice"),
            &Uint128::new(u128::from_be_bytes(
                data[..16].try_into().unwrap_or([0u8; 16]),
            )),
        )
        .unwrap();

    let amount = Uint128::new(u128::from_be_bytes(
        data[..16].try_into().unwrap_or([0u8; 16]),
    ));

    let msg = match variant {
        0 => ExecuteMsg::Transfer {
            recipient: format!("user_{}", data[1] % 10),
            amount,
        },
        1 => ExecuteMsg::Mint {
            to: format!("user_{}", data[2] % 10),
            amount,
        },
        2 => ExecuteMsg::Burn {
            from: format!("user_{}", data[3] % 10),
            amount,
        },
        3 => ExecuteMsg::Approve {
            spender: format!("spender_{}", data[1] % 5),
            amount,
        },
        4 => ExecuteMsg::TransferFrom {
            owner: "alice".to_string(),
            recipient: format!("recipient_{}", data[2] % 5),
            amount,
        },
        _ => return,
    };

    let _ = execute(
        deps.as_mut(),
        env,
        mock_info("alice", &coins(0, "umsg")),
        msg,
    );
});

3.8 Fuzz 边界条件

// fuzz/targets/fuzz_edge_cases.rs
#![no_main]

use libfuzzer_sys::fuzz_target;
use cosmwasm_std::testing::{
    mock_dependencies, mock_env, mock_info,
};
use cosmwasm_std::{coins, Uint128};

use my_contract::contract::{instantiate, execute};
use my_contract::msg::{ExecuteMsg, InstantiateMsg};

fuzz_target!(|data: &[u8]| {
    let mut deps = mock_dependencies();
    let env = mock_env();

    if instantiate(
        deps.as_mut(),
        env.clone(),
        mock_info("admin", &[]),
        InstantiateMsg {
            owner: "admin".to_string(),
            token_name: "Edge".to_string(),
            token_symbol: "EDGE".to_string(),
            initial_supply: Uint128::new(1_000_000),
        },
    )
    .is_err()
    {
        return;
    }

    let edge_amounts = [
        Uint128::zero(),
        Uint128::new(1),
        Uint128::new(u128::MAX),
        Uint128::new(1_000_000_000_000_000_000_000_000u128),
        Uint128::new(340282366920938463463374607431768211455u128),
    ];

    for &amount in &edge_amounts {
        let addr = String::from_utf8_lossy(
            &data[..data.len().min(64)],
        );
        let msg = ExecuteMsg::Transfer {
            recipient: addr.to_string(),
            amount,
        };
        let _ = execute(
            deps.as_mut(),
            env.clone(),
            mock_info("alice", &coins(0, "umsg")),
            msg,
        );
    }
});

3.9 Fuzz 测试 SubMsg 与 Reply

// fuzz/targets/fuzz_submsg.rs
#![no_main]

use libfuzzer_sys::fuzz_target;
use cosmwasm_std::testing::{
    mock_dependencies, mock_env, mock_info,
};
use cosmwasm_std::{coins, from_binary, Uint128, Binary, Reply,
    SubMsgResult, SubMsgResponse,
};

use my_contract::contract::reply;

fuzz_target!(|data: &[u8]| {
    let mut deps = mock_dependencies();
    let env = mock_env();

    let reply_id = u64::from_be_bytes(
        data[..8].try_into().unwrap_or([0u8; 8]),
    );

    let (result, _error_msg) = if data.len() > 8 && data[8] % 2 == 0 {
        let payload = if data.len() > 16 {
            Binary::from(&data[16..])
        } else {
            Binary::from(b"{}")
        };
        (
            SubMsgResult::Ok(SubMsgResponse {
                data: Some(payload),
                events: vec![],
            }),
            String::new(),
        )
    } else {
        (
            SubMsgResult::Error(
                String::from_utf8_lossy(&data[8..]).to_string(),
            ),
            String::new(),
        )
    };

    let msg = Reply {
        id: reply_id,
        result,
    };

    let _ = reply(deps.as_mut(), env, msg);
});

3.10 运行 Fuzz 测试

# 基本运行(使用默认配置)
cargo fuzz run fuzz_execute

# 指定运行时间
cargo fuzz run fuzz_execute -- -max_total_time=300

# 指定最大输入大小
cargo fuzz run fuzz_execute -- -max_len=4096

# 限制内存使用
cargo fuzz run fuzz_execute -- -rss_limit_mb=2048

# 显示更多统计
cargo fuzz run fuzz_execute -- -print_final_stats=1

# 多核运行
cargo fuzz run fuzz_execute -- -jobs=4

# 使用语料库(corpus)
cargo fuzz run fuzz_execute fuzz/corpus/fuzz_execute/

# 合并语料库(减少冗余)
cargo fuzz run fuzz_execute -- -merge=1 fuzz/corpus/fuzz_execute/

3.11 Fuzz 测试配置

# fuzz/.options
[libfuzzer]
max_len = 8192
rss_limit_mb = 4096
timeout = 30
print_final_stats = 1
detect_leaks = 1
artifact_prefix = "fuzz/artifacts/"

3.12 处理 Crash 回放

# 重现 crash
cargo fuzz run fuzz_execute fuzz/artifacts/fuzz_execute/crash-*

# 缩减 crash 输入
cargo fuzz run fuzz_execute fuzz/artifacts/fuzz_execute/crash-* -- -minimize_crash=1

# 分析 panic 栈
RUST_BACKTRACE=1 cargo fuzz run fuzz_execute fuzz/artifacts/crash-xxx

3.13 Crash 修复流程

// 当 fuzz 发现 crash 后,记录并修复
//
// 1. 复现 crash:
//    cargo fuzz run fuzz_execute fuzz/artifacts/crash-abc123
//
// 2. 编写回归测试:
#[test]
fn fuzz_regression_crash_abc123() {
    let crash_input = include_bytes!("../fuzz/artifacts/crash-abc123");
    let mut deps = mock_dependencies();
    let env = mock_env();

    instantiate(
        deps.as_mut(),
        env.clone(),
        mock_info("admin", &[]),
        InstantiateMsg {
            owner: "admin".to_string(),
            token_name: "Reg".to_string(),
            token_symbol: "R".to_string(),
            initial_supply: Uint128::new(1_000_000),
        },
    )
    .unwrap();

    if let Ok(msg) = from_binary::<ExecuteMsg>(crash_input) {
        let result = execute(
            deps.as_mut(),
            env,
            mock_info("user", &coins(0, "umsg")),
            msg,
        );
        assert!(result.is_ok() || result.is_err());
    }
}
//
// 3. 将 crash 输入添加到语料库
// cp fuzz/artifacts/crash-abc123 fuzz/corpus/fuzz_execute/
//
// 4. 验证修复
// cargo fuzz run fuzz_execute -- -max_total_time=60

3.14 Fuzz 测试覆盖率分析

# 生成覆盖率报告
cargo fuzz coverage fuzz_execute

# 使用 grcov 生成详细报告
grcov fuzz/coverage --output-type html --output-path fuzz-coverage-report

3.15 MSG Chain 专属 Fuzz Target

// fuzz/targets/fuzz_msg_chain.rs
#![no_main]

use libfuzzer_sys::fuzz_target;
use cosmwasm_std::testing::{
    mock_dependencies, mock_env, mock_info,
};
use cosmwasm_std::{coins, Uint128, from_binary};

use my_contract::contract::{instantiate, execute, query};
use my_contract::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};

fuzz_target!(|data: &[u8]| {
    let mut deps = mock_dependencies();
    let env = mock_env();

    if instantiate(
        deps.as_mut(),
        env.clone(),
        mock_info("admin", &coins(1_000_000, "umsg")),
        InstantiateMsg {
            owner: "admin".to_string(),
            token_name: "MSGChainFuzz".to_string(),
            token_symbol: "MCF".to_string(),
            initial_supply: Uint128::new(1_000_000),
        },
    )
    .is_err()
    {
        return;
    }

    let amounts = [
        Uint128::new(0),
        Uint128::new(1),
        Uint128::new(u64::MAX as u128),
        Uint128::new(u128::MAX),
    ];

    let recipients = [
        "msg1qypqxpq9qcrsszg2pvxq6rs0zqg3yyc5f6k4nq",
    ];

    for &amnt in &amounts {
        let msg = ExecuteMsg::Transfer {
            recipient: recipients[0].to_string(),
            amount: amnt,
        };
        let _ = execute(
            deps.as_mut(),
            env.clone(),
            mock_info("user", &coins(0, "umsg")),
            msg,
        );
    }

    let _ = query(
        deps.as_ref(),
        env,
        QueryMsg::GetBalance {
            address: String::from_utf8_lossy(data).to_string(),
        },
    );
});

4. 特性测试 (Property-based Testing)

4.1 概述

特性测试(Property-based Testing)不同于单元测试手动构造输入输出对,而是声明不变量并由工具自动生成输入来验证不变量是否成立。

在 CosmWasm 合约中,典型的不变量包括:

4.2 安装 proptest

# Cargo.toml
[dev-dependencies]
proptest = "1.4"

4.3 基础不变量测试

#[cfg(test)]
mod property_tests {
    use proptest::prelude::*;
    use cosmwasm_std::testing::{
        mock_dependencies, mock_env, mock_info,
    };
    use cosmwasm_std::{from_binary, Uint128, Addr, coins};

    use crate::contract::{instantiate, execute};
    use crate::msg::{ExecuteMsg, InstantiateMsg};
    use crate::state::{BALANCES, TOTAL_SUPPLY};

    proptest! {
        #[test]
        fn transfer_never_changes_total_supply(
            sender_balance in 100u128..1_000_000u128,
            transfer_amount in 1u128..=sender_balance,
        ) {
            let mut deps = mock_dependencies();
            let env = mock_env();

            instantiate(
                deps.as_mut(),
                env.clone(),
                mock_info("admin", &[]),
                InstantiateMsg {
                    owner: "admin".to_string(),
                    token_name: "PropTest".to_string(),
                    token_symbol: "PROP".to_string(),
                    initial_supply: Uint128::new(1_000_000),
                },
            ).unwrap();

            TOTAL_SUPPLY.save(
                deps.as_mut().storage,
                &Uint128::new(1_000_000),
            ).unwrap();

            BALANCES.save(
                deps.as_mut().storage,
                &Addr::unchecked("sender"),
                &Uint128::new(sender_balance),
            ).unwrap();

            let supply_before = TOTAL_SUPPLY.load(&deps.storage).unwrap();

            let result = execute(
                deps.as_mut(),
                env,
                mock_info("sender", &[]),
                ExecuteMsg::Transfer {
                    recipient: "recipient".to_string(),
                    amount: Uint128::new(transfer_amount),
                },
            );

            if sender_balance >= transfer_amount {
                prop_assert!(result.is_ok());
                let supply_after = TOTAL_SUPPLY.load(&deps.storage).unwrap();
                prop_assert_eq!(
                    supply_before, supply_after,
                    "Total supply must remain constant after transfer"
                );
            }
        }
    }
}

4.4 状态机测试

#[cfg(test)]
mod state_machine_tests {
    use proptest::prelude::*;
    use cosmwasm_std::testing::{
        mock_dependencies, mock_env, mock_info,
    };
    use cosmwasm_std::{Uint128, Addr, coins};

    use crate::contract::{instantiate, execute};
    use crate::msg::{ExecuteMsg, InstantiateMsg};
    use crate::state::{BALANCES, TOTAL_SUPPLY};

    #[derive(Clone, Debug)]
    enum Action {
        Mint { to: String, amount: u128 },
        Burn { from: String, amount: u128 },
        Transfer { from: String, to: String, amount: u128 },
    }

    fn arb_action() -> impl Strategy<Value = Action> {
        prop_oneof![
            (any::<String>(), 0..10_000u128).prop_map(|(to, amount)|
                Action::Mint { to, amount }),
            (any::<String>(), 0..10_000u128).prop_map(|(from, amount)|
                Action::Burn { from, amount }),
            (any::<String>(), any::<String>(), 0..10_000u128)
                .prop_map(|(from, to, amount)|
                    Action::Transfer { from, to, amount }),
        ]
    }

    proptest! {
        #[test]
        fn state_machine_invariants(
            actions in proptest::collection::vec(arb_action(), 1..50),
        ) {
            let mut deps = mock_dependencies();
            let env = mock_env();

            instantiate(
                deps.as_mut(),
                env.clone(),
                mock_info("admin", &[]),
                InstantiateMsg {
                    owner: "admin".to_string(),
                    token_name: "SM".to_string(),
                    token_symbol: "SM".to_string(),
                    initial_supply: Uint128::new(1_000_000),
                },
            ).unwrap();

            for (i, action) in actions.iter().enumerate() {
                let sender = format!("user_{}", i % 5);
                let msg = match action {
                    Action::Mint { to, amount } =>
                        ExecuteMsg::Mint { to: to.clone(), amount: Uint128::new(*amount) },
                    Action::Burn { from, amount } =>
                        ExecuteMsg::Burn { from: from.clone(), amount: Uint128::new(*amount) },
                    Action::Transfer { from: _, to, amount } =>
                        ExecuteMsg::Transfer { recipient: to.clone(), amount: Uint128::new(*amount) },
                };
                let _ = execute(
                    deps.as_mut(),
                    env.clone(),
                    mock_info(&sender, &coins(0, "umsg")),
                    msg,
                );
                if let Ok(supply) = TOTAL_SUPPLY.load(&deps.storage) {
                    prop_assert!(supply <= Uint128::new(1_000_000_000_000_000u128));
                }
            }
        }
    }
}

4.5 不变量:余额守恒

#[cfg(test)]
mod invariance_tests {
    use cosmwasm_std::testing::{
        mock_dependencies, mock_env, mock_info,
    };
    use cosmwasm_std::{Uint128, Addr, coins};
    use crate::contract::{instantiate, execute};
    use crate::msg::*;
    use crate::state::{BALANCES, TOTAL_SUPPLY};

    #[test]
    fn total_supply_equals_sum_of_balances() {
        let mut deps = mock_dependencies();
        let env = mock_env();

        instantiate(
            deps.as_mut(),
            env.clone(),
            mock_info("admin", &[]),
            InstantiateMsg {
                owner: "admin".to_string(),
                token_name: "Inv".to_string(),
                token_symbol: "INV".to_string(),
                initial_supply: Uint128::new(1_000_000),
            },
        ).unwrap();

        BALANCES.save(deps.as_mut().storage, &Addr::unchecked("alice"), &Uint128::new(500_000)).unwrap();
        BALANCES.save(deps.as_mut().storage, &Addr::unchecked("bob"), &Uint128::new(500_000)).unwrap();
        TOTAL_SUPPLY.save(deps.as_mut().storage, &Uint128::new(1_000_000)).unwrap();

        for _ in 0..10 {
            let _ = execute(
                deps.as_mut(), env.clone(), mock_info("alice", &[]),
                ExecuteMsg::Transfer { recipient: "bob".to_string(), amount: Uint128::new(1000) },
            );
        }

        let supply = TOTAL_SUPPLY.load(&deps.storage).unwrap();
        let alice_bal = BALANCES.load(&deps.storage, &Addr::unchecked("alice")).unwrap_or(Uint128::zero());
        let bob_bal = BALANCES.load(&deps.storage, &Addr::unchecked("bob")).unwrap_or(Uint128::zero());
        assert_eq!(supply, alice_bal.checked_add(bob_bal).unwrap());
    }
}

4.6 不变量:权限不变

#[test]
fn unauthorized_calls_always_fail() {
    let mut deps = mock_dependencies();
    let env = mock_env();

    instantiate(
        deps.as_mut(),
        env.clone(),
        mock_info("admin", &[]),
        InstantiateMsg {
            owner: "admin".to_string(),
            token_name: "AuthCheck".to_string(),
            token_symbol: "AUTH".to_string(),
            initial_supply: Uint128::new(1_000_000),
        },
    ).unwrap();

    let unauthorized = vec![
        ExecuteMsg::Mint { to: "attacker".to_string(), amount: Uint128::new(1000) },
        ExecuteMsg::Pause {},
        ExecuteMsg::Unpause {},
        ExecuteMsg::UpdateOwner { new_owner: "attacker".to_string() },
    ];

    for msg in unauthorized {
        let result = execute(
            deps.as_mut(), env.clone(), mock_info("attacker", &[]), msg
        );
        assert!(result.is_err(), "Unauthorized action should fail");
    }
}

4.7 DID 生命周期不变量

#[test]
fn did_lifecycle_invariant() {
    let mut deps = mock_dependencies();
    let env = mock_env();

    instantiate(
        deps.as_mut(),
        env.clone(),
        mock_info("admin", &[]),
        InstantiateMsg {
            owner: "admin".to_string(),
            token_name: "DIDCheck".to_string(),
            token_symbol: "DID".to_string(),
            initial_supply: Uint128::new(1_000_000),
        },
    ).unwrap();

    let mut was_created = false;
    let mut was_deactivated = false;

    let operations = vec![
        ExecuteMsg::CreateDID { did_id: "did:msg:agent:test-1".to_string(), controller: "controller".to_string(), verification_methods: vec![], services: vec![], metadata: None },
        ExecuteMsg::CreateDID { did_id: "did:msg:agent:test-1".to_string(), controller: "controller".to_string(), verification_methods: vec![], services: vec![], metadata: None },
        ExecuteMsg::DeactivateDID { did_id: "did:msg:agent:test-1".to_string() },
        ExecuteMsg::DeactivateDID { did_id: "did:msg:agent:test-1".to_string() },
    ];

    for msg in operations {
        let result = execute(
            deps.as_mut(), env.clone(), mock_info("controller", &[]), msg.clone()
        );
        match msg {
            ExecuteMsg::CreateDID { .. } => {
                if !was_created { assert!(result.is_ok()); was_created = true; }
                else { assert!(result.is_err()); }
            }
            ExecuteMsg::DeactivateDID { .. } => {
                if was_created && !was_deactivated { assert!(result.is_ok()); was_deactivated = true; }
                else { assert!(result.is_err()); }
            }
            _ => {}
        }
    }
}

4.8 使用 proptest 策略生成有效消息

#[cfg(test)]
mod proptest_strategies {
    use proptest::prelude::*;
    use cosmwasm_std::{Uint128, Addr, coins};
    use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
    use crate::contract::{instantiate, execute};
    use crate::msg::*;
    use crate::state::BALANCES;

    fn valid_addr() -> impl Strategy<Value = String> {
        prop::string::string_regex("msg1[a-zA-Z0-9]{38}").expect("Invalid regex")
    }

    fn reasonable_amount() -> impl Strategy<Value = Uint128> {
        (0u128..10_000_000_000u128).prop_map(Uint128::new)
    }

    proptest! {
        #[test]
        fn transfer_with_valid_addresses(
            sender in valid_addr(),
            recipient in valid_addr(),
            amount in reasonable_amount(),
        ) {
            let mut deps = mock_dependencies();
            let env = mock_env();
            instantiate(
                deps.as_mut(), env.clone(), mock_info("admin", &[]),
                InstantiateMsg { owner: "admin".to_string(), token_name: "S".to_string(), token_symbol: "S".to_string(), initial_supply: Uint128::new(1_000_000) },
            ).unwrap();
            BALANCES.save(deps.as_mut().storage, &deps.api.addr_validate(&sender).unwrap(), &Uint128::new(10_000_000_000u128)).unwrap();
            let result = execute(
                deps.as_mut(), env, mock_info(&sender, &[]),
                ExecuteMsg::Transfer { recipient, amount },
            );
            prop_assert!(result.is_ok());
        }
    }
}

4.9 配置 proptest 执行参数

fn custom_proptest_config() -> ProptestConfig {
    ProptestConfig {
        cases: 10000,
        max_shrink_iters: 1000,
        timeout: 300,
        failure_persistence: Some(Box::new(
            proptest::test_runner::FileFailurePersistence::WithSource("proptest-regressions"),
        )),
        ..ProptestConfig::default()
    }
}

proptest! {
    #![proptest_config = "custom_proptest_config()"]
    #[test]
    fn high_volume_transfer_test(amount in 1u128..1_000_000u128) {
        let mut deps = mock_dependencies();
        let env = mock_env();
        instantiate(
            deps.as_mut(), env.clone(), mock_info("admin", &[]),
            InstantiateMsg { owner: "admin".to_string(), token_name: "HV".to_string(), token_symbol: "HV".to_string(), initial_supply: Uint128::new(1_000_000) },
        ).unwrap();
        BALANCES.save(deps.as_mut().storage, &Addr::unchecked("alice"), &Uint128::new(1_000_000)).unwrap();
        for i in 0..100 {
            let r = format!("user_{}", i);
            let res = execute(
                deps.as_mut(), env.clone(), mock_info("alice", &[]),
                ExecuteMsg::Transfer { recipient: r, amount: Uint128::new(amount) },
            );
            if amount * (i as u128 + 1) <= 1_000_000 { prop_assert!(res.is_ok()); }
            else { prop_assert!(res.is_err()); break; }
        }
    }
}

5. CosmWasm VM 仿真测试

5.1 概述

cosmwasm-vm 是 CosmWasm 的官方虚拟机实现,它使用 Wasmer 运行时执行编译后的 Wasm 合约。相比于 mock_dependencies,VM 测试:

在 MSG Chain 上,VM 测试用于部署前的最终验证。

5.2 安装与配置

[dev-dependencies]
cosmwasm-vm = "1.5"

确保 Wasm 合约已编译:

cargo build --target wasm32-unknown-unknown --release

5.3 基础 VM 测试

#[cfg(test)]
mod vm_tests {
    use cosmwasm_vm::testing::{
        mock_backend, mock_env, mock_info,
    };
    use cosmwasm_vm::{
        Instance, Options, ContractResult,
    };
    use cosmwasm_std::{coins, from_slice, Uint128, Binary};

    const WASM_BYTES: &[u8] =
        include_bytes!("../../target/wasm32-unknown-unknown/release/my_contract.wasm");

    #[test]
    fn vm_instantiate_and_query() {
        let backend = mock_backend(&[]);
        let options = Options {
            gas_limit: 10_000_000_000,
            ..Options::default()
        };

        let mut instance = Instance::from_code(
            WASM_BYTES, backend, options, Default::default(),
        ).expect("Failed to create VM instance");

        let env = mock_env();
        let info = mock_info("admin", &coins(1000, "umsg"));

        let msg = serde_json::to_vec(&InstantiateMsg {
            owner: "admin".to_string(),
            token_name: "VM Test".to_string(),
            token_symbol: "VMT".to_string(),
            initial_supply: Uint128::new(1_000_000),
        }).unwrap();

        let res: ContractResult<Binary> = instance
            .instantiate(env.clone(), info, msg)
            .expect("Instantiate should succeed");

        let gas_used = 10_000_000_000 - instance.get_gas_left();
        println!("Gas used for instantiate: {}", gas_used);
        assert!(gas_used < 1_000_000, "Instantiate used too much gas");
        instance.recycle().unwrap();
    }
}

5.4 VM Gas 计量测试

#[test]
fn vm_gas_budget_test() {
    let backend = mock_backend(&[]);
    let tight_options = Options {
        gas_limit: 500_000,
        ..Options::default()
    };

    let mut instance = Instance::from_code(
        WASM_BYTES, backend, tight_options, Default::default(),
    ).expect("Failed to create VM instance");

    let env = mock_env();
    let msg = serde_json::to_vec(&InstantiateMsg {
        owner: "admin".to_string(),
        token_name: "GasTest".to_string(),
        token_symbol: "GAS".to_string(),
        initial_supply: Uint128::new(1_000_000),
    }).unwrap();

    match instance.instantiate(env, mock_info("admin", &coins(1000, "umsg")), msg) {
        Ok(_) => println!("Contract instantiated within gas limit"),
        Err(e) => {
            if e.to_string().contains("gas") {
                println!("Expected: Out of gas");
            } else {
                panic!("Unexpected error: {}", e);
            }
        }
    }
    instance.recycle().unwrap();
}

#[test]
fn vm_gas_cost_breakdown() {
    let backend = mock_backend(&[]);
    let mut instance = Instance::from_code(
        WASM_BYTES, backend,
        Options { gas_limit: 10_000_000_000, ..Options::default() },
        Default::default(),
    ).unwrap();

    let env = mock_env();
    let gas_before = instance.get_gas_left();
    instance.instantiate(
        env.clone(),
        mock_info("admin", &coins(1000, "umsg")),
        serde_json::to_vec(&InstantiateMsg {
            owner: "admin".to_string(), token_name: "GBD".to_string(),
            token_symbol: "GBD".to_string(), initial_supply: Uint128::new(1_000_000),
        }).unwrap(),
    ).unwrap();
    let instantiate_cost = gas_before - instance.get_gas_left();
    println!("Instantiate cost: {}", instantiate_cost);

    let gas_before2 = instance.get_gas_left();
    instance.execute(
        env.clone(), mock_info("admin", &[]),
        serde_json::to_vec(&ExecuteMsg::Mint { to: "user".to_string(), amount: Uint128::new(1000) }).unwrap(),
    ).unwrap();
    let execute_cost = gas_before2 - instance.get_gas_left();
    println!("Execute (mint) cost: {}", execute_cost);

    assert!(instantiate_cost < 5_000_000, "Instantiate gas too high");
    assert!(execute_cost < 2_000_000, "Execute gas too high");
    instance.recycle().unwrap();
}

5.5 VM Load Testing

#[test]
fn vm_load_test_high_throughput() {
    use std::time::Instant;

    let backend = mock_backend(&[]);
    let options = Options {
        gas_limit: 100_000_000_000,
        ..Options::default()
    };
    let mut instance = Instance::from_code(
        WASM_BYTES, backend, options, Default::default(),
    ).unwrap();

    let env = mock_env();
    instance.instantiate(
        env.clone(), mock_info("admin", &coins(1000, "umsg")),
        serde_json::to_vec(&InstantiateMsg {
            owner: "admin".to_string(), token_name: "LoadTest".to_string(),
            token_symbol: "LOAD".to_string(), initial_supply: Uint128::new(1_000_000_000),
        }).unwrap(),
    ).unwrap();

    let num_transfers = 100;
    let start = Instant::now();

    for i in 0..num_transfers {
        let _ = instance.execute(
            env.clone(), mock_info("admin", &[]),
            serde_json::to_vec(&ExecuteMsg::Mint {
                to: format!("bulk_user_{}", i),
                amount: Uint128::new(1000),
            }).unwrap(),
        ).unwrap();
    }

    let duration = start.elapsed();
    let tps = num_transfers as f64 / duration.as_secs_f64();
    println!("Processed {} txns in {:?} ({:.0} tps)", num_transfers, duration, tps);
    instance.recycle().unwrap();
}

5.6 VM 边界条件测试

#[test]
fn vm_edge_cases() {
    let backend = mock_backend(&[]);

    let result = Instance::from_code(
        &[], backend.clone(), Options::default(), Default::default(),
    );
    assert!(result.is_err(), "Empty wasm should fail");

    let invalid_wasm = [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
    let result = Instance::from_code(
        &invalid_wasm, backend,
        Options { gas_limit: 0, ..Options::default() },
        Default::default(),
    );
    match result {
        Ok(mut inst) => {
            let r = inst.instantiate(
                mock_env(), mock_info("admin", &coins(1000, "umsg")),
                serde_json::to_vec(&InstantiateMsg {
                    owner: "admin".to_string(), token_name: "Z".to_string(),
                    token_symbol: "Z".to_string(), initial_supply: Uint128::new(1),
                }).unwrap(),
            );
            match r {
                Ok(_) => {}
                Err(e) => println!("Zero gas result: {}", e),
            }
            inst.recycle().unwrap();
        }
        Err(_) => {}
    }
}

5.7 VM 跨合约查询模拟

#[test]
fn vm_cross_contract_query() {
    use cosmwasm_vm::testing::MockQuerier;

    let mut querier = MockQuerier::new(&[]);
    querier.update_wasm(|query| {
        match query {
            cosmwasm_vm::WasmQuery::Smart { contract_addr, msg } => {
                if contract_addr == "oracle" {
                    Ok(ContractResult::Ok(
                        serde_json::to_vec(&serde_json::json!({"price": "1000000"})).unwrap().into(),
                    ))
                } else {
                    Err("Unknown contract")
                }
            }
            _ => Err("Unsupported query"),
        }
    });

    let backend = mock_backend(&[]).set_querier(querier);
    let mut instance = Instance::from_code(
        WASM_BYTES, backend,
        Options { gas_limit: 10_000_000_000, ..Options::default() },
        Default::default(),
    ).unwrap();

    instance.instantiate(
        mock_env(), mock_info("admin", &coins(1000, "umsg")),
        serde_json::to_vec(&InstantiateMsg {
            owner: "admin".to_string(), token_name: "OracleTest".to_string(),
            token_symbol: "ORA".to_string(), initial_supply: Uint128::new(1_000_000),
        }).unwrap(),
    ).unwrap();
    instance.recycle().unwrap();
}

6. 形式化验证

6.1 概述

形式化验证使用数学方法证明合约满足特定规范。对于 CosmWasm 合约,形式化验证可以分为:

方法 工具 覆盖范围 成本
Rust 类型系统 编译器 类型安全 免费
运行时断言 debug_assert! 运行时路径 低
不变量编码 #[cfg(test)] 测试路径 低
符号执行 KLEE / SymCC 路径探索 中
模型检查 TLA+ / NuSMV 状态空间 高
定理证明 Coq / Isabelle 全合约 极高

6.2 Rust 类型层形式化

利用 Rust 的类型系统和 Uint128、Decimal 等安全类型防止整型错误:

use cosmwasm_std::{Uint128, Decimal, StdResult, StdError};

#[derive(Clone, Debug, PartialEq)]
pub struct Collateral(Uint128);
#[derive(Clone, Debug, PartialEq)]
pub struct Debt(Uint128);

impl Collateral {
    pub fn new(amount: Uint128) -> Self { Collateral(amount) }
    pub fn value_at_price(&self, price: &Decimal) -> StdResult<Uint128> {
        self.0.checked_mul(price).map_err(|_| StdError::generic_err("collateral overflow"))
    }
}

impl Debt {
    pub fn new(amount: Uint128) -> Self { Debt(amount) }
    pub fn is_healthy(&self, collateral_value: Uint128) -> bool {
        collateral_value >= self.0 * Uint128::new(150) / Uint128::new(100)
    }
}

pub fn evaluate_position(collateral: Collateral, debt: Debt, price: &Decimal) -> StdResult<bool> {
    let cv = collateral.value_at_price(price)?;
    Ok(debt.is_healthy(cv))
}

6.3 运行时断言

pub fn transfer(
    deps: DepsMut,
    info: MessageInfo,
    recipient: String,
    amount: Uint128,
) -> StdResult<Response> {
    debug_assert!(!amount.is_zero(), "Amount must not be zero");
    let sender_balance = BALANCES.load(deps.storage, &info.sender)?
        .checked_sub(amount)
        .map_err(|_| StdError::generic_err("Insufficient balance"))?;
    let recipient_addr = deps.api.addr_validate(&recipient)?;
    let recipient_balance = BALANCES.load(deps.storage, &recipient_addr)
        .unwrap_or(Uint128::zero());
    let sum_before = sender_balance.checked_add(recipient_balance).unwrap();
    BALANCES.save(deps.storage, &info.sender, &sender_balance)?;
    BALANCES.save(deps.storage, &recipient_addr, &recipient_balance.checked_add(amount).map_err(|_| StdError::generic_err("overflow"))?)?;
    debug_assert_eq!(sum_before, sender_balance.checked_add(recipient_balance).unwrap());
    Ok(Response::new().add_attribute("action", "transfer"))
}

6.4 合约级不变量规范

/// ## 全局不变量
/// I1: total_supply == sum(balances[all addresses])
/// I2: total_supply <= MAX_SUPPLY
/// I3: balance[addr] >= 0 (由 Uint128 保证)
///
/// Transfer preserves total_supply
///   post: total_supply' == total_supply
/// Mint 增加 total_supply
///   pre: caller == owner AND total_supply + amount <= MAX_SUPPLY

pub fn verify_state_invariants(storage: &dyn Storage) -> StdResult<()> {
    let supply = TOTAL_SUPPLY.load(storage)?;
    if supply > CONFIG.load(storage)?.max_supply {
        return Err(StdError::generic_err("supply exceeds max"));
    }
    Ok(())
}

6.5 状态转换模型检测

#[derive(Clone, Debug, PartialEq)]
pub enum ContractState { Init, Active, Paused, Migrating }

/// Init -> Active -> Paused <-> Active
/// Active -> Migrating, Paused -> Migrating
pub fn transition_to(
    storage: &mut dyn Storage, current: &ContractState, target: ContractState,
) -> StdResult<ContractState> {
    let allowed = matches!((current, &target),
        (ContractState::Init, ContractState::Active)
        | (ContractState::Active, ContractState::Paused)
        | (ContractState::Paused, ContractState::Active)
        | (ContractState::Active, ContractState::Migrating)
        | (ContractState::Paused, ContractState::Migrating)
    );
    if !allowed {
        return Err(StdError::generic_err(format!("Illegal transition")));
    }
    STATE.save(storage, &target)?;
    Ok(target)
}

#[cfg(test)]
mod state_transition_tests {
    use super::*;
    use cosmwasm_std::testing::mock_dependencies;
    #[test]
    fn test_all_transitions() {
        let mut deps = mock_dependencies();
        let legal = vec![
            (ContractState::Init, ContractState::Active),
            (ContractState::Active, ContractState::Paused),
            (ContractState::Paused, ContractState::Active),
            (ContractState::Active, ContractState::Migrating),
            (ContractState::Paused, ContractState::Migrating),
        ];
        for (from, to) in legal {
            STATE.save(deps.as_mut().storage, &from).unwrap();
            assert!(transition_to(deps.as_mut().storage, &from, to).is_ok());
        }
        let illegal = vec![
            (ContractState::Init, ContractState::Paused),
            (ContractState::Init, ContractState::Migrating),
            (ContractState::Active, ContractState::Init),
            (ContractState::Migrating, ContractState::Active),
        ];
        for (from, to) in illegal {
            STATE.save(deps.as_mut().storage, &from).unwrap();
            assert!(transition_to(deps.as_mut().storage, &from, to).is_err());
        }
    }
}

6.6 K Framework 集成

# K Framework 语义模型
# CosmWasm 合约在 K Framework 中的形式化验证:
#
# 1. 定义 CosmWasm 语义
#    - Wasm 执行模型
#    - 存储操作语义
#
# 2. 合约规范 (K 规则)
#    rule [transfer-preserves-supply]:
#      <k> transfer(From, To, Amount) => ... </k>
#      requires BFrom >=Int Amount
#      ensures  S == #supply
#
# 3. 验证命令
#    kprove --spec contract-spec.k contract-semantics.k
#
# 可用工具:
# - K Framework 5.0+ (https://github.com/runtimeverification/k)
# - K-CosmWasm (社区开发中)

6.7 穷举验证(小范围)

#[test]
fn proof_checked_sub_never_panics() {
    for a in 0u128..500u128 {
        for b in 0u128..500u128 {
            let result = Uint128::new(a).checked_sub(Uint128::new(b));
            if a >= b { assert!(result.is_ok()); }
            else { assert!(result.is_err()); }
        }
    }
}

6.8 不变量宏辅助

macro_rules! ensure_invariants {
    ($storage:expr) => {
        if let Err(e) = assert_all_invariants($storage) {
            return Err(StdError::generic_err(
                format!("Invariant failed: {}", e)
            ));
        }
    };
}

pub fn assert_all_invariants(storage: &dyn Storage) -> StdResult<()> {
    let supply = TOTAL_SUPPLY.load(storage)?;
    if supply > CONFIG.load(storage)?.max_supply {
        return Err(StdError::generic_err("supply exceeds max"));
    }
    Ok(())
}

7. CI 集成

7.1 GitHub Actions Fuzz 工作流

name: Contract Fuzz Testing

on:
  schedule:
    - cron: '0 2 * * *'
  workflow_dispatch:
  push:
    branches: [main, develop]
    paths:
      - 'contracts/cosmwasm/all/**'

env:
  RUST_TOOLCHAIN: nightly

jobs:
  fuzz:
    name: Fuzz ${{ matrix.fuzz-target }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        fuzz-target:
          - fuzz_execute
          - fuzz_multi_step
          - fuzz_edge_cases

    steps:
      - uses: actions/checkout@v4
      - name: Set up Rust nightly
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          toolchain: nightly
          target: wasm32-unknown-unknown
      - name: Cache
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            **/target
            **/fuzz/target
          key: ${{ runner.os }}-fuzz-${{ hashFiles('**/Cargo.lock') }}
      - name: Install cargo-fuzz
        run: cargo install cargo-fuzz
      - name: Build Wasm
        run: cargo build --target wasm32-unknown-unknown --release
      - name: Run fuzz target
        run: |
          cargo fuzz run ${{ matrix.fuzz-target }} \
            -- -max_total_time=600 -rss_limit_mb=4096 -print_final_stats=1
        continue-on-error: true
        timeout-minutes: 15
      - name: Upload crash artifacts
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: fuzz-crashes-${{ matrix.fuzz-target }}-${{ github.sha }}
          path: fuzz/artifacts/

7.2 属性测试 CI 工作流

name: Property-Based Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  proptest:
    name: Property Tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Rust
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          toolchain: stable
      - name: Run property tests
        run: cargo test property_tests -- --nocapture
        env:
          PROPTEST_CASES: '1000'
      - name: Run invariance tests
        run: cargo test invariance_tests -- --nocapture

7.3 覆盖率 CI

name: Test Coverage

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  coverage:
    name: Coverage Report
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Rust
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          toolchain: stable
          target: wasm32-unknown-unknown
      - name: Install tarpaulin
        run: cargo install cargo-tarpaulin
      - name: Generate coverage
        run: cargo tarpaulin --ignore-tests --out Xml --fail-under 80
      - name: Upload to Codecov
        uses: codecov/codecov-action@v4

7.4 回归测试套件

#[cfg(test)]
mod fuzz_regression_tests {
    use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
    use cosmwasm_std::{from_binary, Uint128, coins};
    use crate::contract::{instantiate, execute};
    use crate::msg::*;

    #[test]
    fn regression_burn_overflow() {
        let mut deps = mock_dependencies();
        let env = mock_env();
        instantiate(
            deps.as_mut(), env.clone(), mock_info("admin", &[]),
            InstantiateMsg { owner: "admin".to_string(), token_name: "R".to_string(), token_symbol: "R".to_string(), initial_supply: Uint128::new(0) },
        ).unwrap();
        let result = execute(
            deps.as_mut(), env, mock_info("user", &[]),
            ExecuteMsg::Burn { from: "user".to_string(), amount: Uint128::new(100) },
        );
        assert!(result.is_err());
    }

    #[test]
    fn regression_empty_address() {
        let mut deps = mock_dependencies();
        let env = mock_env();
        instantiate(
            deps.as_mut(), env.clone(), mock_info("admin", &[]),
            InstantiateMsg { owner: "admin".to_string(), token_name: "R".to_string(), token_symbol: "R".to_string(), initial_supply: Uint128::new(1_000_000) },
        ).unwrap();
        let result = execute(
            deps.as_mut(), env, mock_info("admin", &[]),
            ExecuteMsg::Transfer { recipient: "".to_string(), amount: Uint128::new(100) },
        );
        assert!(result.is_err());
    }
}

7.5 Gas Benchmark CI

name: Gas Benchmark
on:
  push:
    branches: [main]

jobs:
  benchmark:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Rust
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          toolchain: stable
          target: wasm32-unknown-unknown
      - name: Build Wasm
        run: cargo build --target wasm32-unknown-unknown --release
      - name: Run gas benchmarks
        run: cargo test vm_gas_cost_breakdown -- --nocapture | tee gas-report.txt
      - name: Upload gas report
        uses: actions/upload-artifact@v4
        with:
          name: gas-benchmark-${{ github.sha }}
          path: gas-report.txt

7.6 MSG Chain CI 完整流水线

name: MSG Chain Full CI Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Rust
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          toolchain: stable
          components: clippy, rustfmt
      - name: Clippy
        run: cargo clippy -- -D warnings
      - name: Rust fmt
        run: cargo fmt -- --check
      - name: Go lint
        run: make lint

  test:
    name: Tests
    needs: [lint]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.24'
      - name: Set up Rust
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          toolchain: stable
          target: wasm32-unknown-unknown
      - name: Go tests
        run: make test
      - name: Quantum tests
        run: make test-quantum
      - name: Contract CI
        run: make ci-contracts

  proptest:
    name: Property Tests
    needs: [lint]
    runs-on: ubuntu-latest
    steps:
      - name: Run property tests
        run: cargo test property_tests -- --nocapture
        env:
          PROPTEST_CASES: '500'

  fuzz:
    if: github.event_name == 'schedule'
    name: Fuzz Tests
    needs: [test]
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        target: [fuzz_execute, fuzz_multi_step]
    steps:
      - uses: actions/checkout@v4
      - name: Set up Rust nightly
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          toolchain: nightly
      - name: Install cargo-fuzz
        run: cargo install cargo-fuzz
      - name: Fuzz
        run: cargo fuzz run ${{ matrix.target }} -- -max_total_time=300
        continue-on-error: true

  coverage:
    name: Coverage
    needs: [test]
    runs-on: ubuntu-latest
    steps:
      - name: Install tarpaulin
        run: cargo install cargo-tarpaulin
      - name: Coverage
        run: cargo tarpaulin --ignore-tests --out Xml --fail-under 80

8. 常见漏洞测试模式

8.1 重入攻击测试 (VULN-01)

#[test]
fn reentrancy_protection_test() {
    let mut deps = mock_dependencies();
    let env = mock_env();

    STATE.save(deps.as_mut().storage, &State {
        balances: Uint128::new(1000),
        locked: false,
    }).unwrap();

    let res = withdraw(deps.as_mut(), env.clone(), mock_info("user", &coins(0, "umsg")), Uint128::new(500));
    if let Ok(_) = res {
        let state = STATE.load(&deps.storage).unwrap();
        assert_eq!(state.balances, Uint128::new(500));
        let reentry = withdraw(deps.as_mut(), env, mock_info("user", &coins(0, "umsg")), Uint128::new(600));
        assert!(reentry.is_err());
    }
}

8.2 整数溢出/下溢测试 (VULN-03)

#[test]
fn overflow_attack_simulation() {
    let mut deps = mock_dependencies();
    let env = mock_env();

    instantiate(
        deps.as_mut(), env.clone(), mock_info("admin", &[]),
        InstantiateMsg { owner: "admin".to_string(), token_name: "OFT".to_string(), token_symbol: "OFT".to_string(), initial_supply: Uint128::new(1_000_000) },
    ).unwrap();

    BALANCES.save(deps.as_mut().storage, &Addr::unchecked("victim"), &Uint128::new(100)).unwrap();
    let result = execute(
        deps.as_mut(), env.clone(), mock_info("victim", &[]),
        ExecuteMsg::Transfer { recipient: "attacker".to_string(), amount: Uint128::new(101) },
    );
    assert!(result.is_err(), "Over-transfer must be caught");

    BALANCES.save(deps.as_mut().storage, &Addr::unchecked("whale"), &Uint128::MAX).unwrap();
    let result = execute(
        deps.as_mut(), env, mock_info("admin", &[]),
        ExecuteMsg::Mint { to: "whale".to_string(), amount: Uint128::new(1) },
    );
    assert!(result.is_err() || result.is_ok());
}

8.3 访问控制测试 (VULN-02)

#[cfg(test)]
mod access_control_proptest {
    use proptest::prelude::*;
    use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
    use cosmwasm_std::Uint128;
    use crate::contract::{instantiate, execute};
    use crate::msg::*;

    proptest! {
        #[test]
        fn only_owner_can_mint(caller in any::<String>(), amount in 0u128..1_000_000u128) {
            let mut deps = mock_dependencies();
            let env = mock_env();
            instantiate(
                deps.as_mut(), env.clone(), mock_info("admin", &[]),
                InstantiateMsg { owner: "admin".to_string(), token_name: "ACL".to_string(), token_symbol: "ACL".to_string(), initial_supply: Uint128::new(1_000_000) },
            ).unwrap();
            let result = execute(
                deps.as_mut(), env, mock_info(&caller, &[]),
                ExecuteMsg::Mint { to: "user".to_string(), amount: Uint128::new(amount) },
            );
            if caller == "admin" { prop_assert!(result.is_ok()); }
            else { prop_assert!(result.is_err()); }
        }
    }
}

8.4 预言机操纵测试 (VULN-07)

#[test]
fn oracle_manipulation_simulation() {
    let mut deps = mock_dependencies();
    let env = mock_env();

    instantiate(
        deps.as_mut(), env.clone(), mock_info("admin", &[]),
        InstantiateMsg { owner: "admin".to_string(), token_name: "O".to_string(), token_symbol: "O".to_string(), initial_supply: Uint128::new(1_000_000) },
    ).unwrap();

    PRICE.save(deps.as_mut().storage, &"umsg".to_string(), &Uint128::new(100)).unwrap();
    let result = execute(
        deps.as_mut(), env.clone(), mock_info("user", &coins(0, "umsg")),
        ExecuteMsg::Swap { token_in: "umsg".to_string(), amount_in: Uint128::new(1000), min_amount_out: Uint128::new(9) },
    );
    assert!(result.is_ok(), "Normal swap should succeed");

    PRICE.save(deps.as_mut().storage, &"umsg".to_string(), &Uint128::new(10000)).unwrap();
    let result = execute(
        deps.as_mut(), env, mock_info("attacker", &coins(0, "umsg")),
        ExecuteMsg::Swap { token_in: "umsg".to_string(), amount_in: Uint128::new(1), min_amount_out: Uint128::new(50) },
    );
    match result {
        Ok(_) => {}
        Err(e) => assert!(e.to_string().contains("price") || e.to_string().contains("slippage")),
    }
}

8.5 精度损失测试 (VULN-19)

#[test]
fn precision_loss_detection() {
    let total_reward = Uint128::new(1000);
    let stakers = vec![
        (Uint128::new(333)),
        (Uint128::new(333)),
        (Uint128::new(334)),
    ];
    let total_staked = Uint128::new(1000);
    let mut distributed = Uint128::zero();

    for &stake in &stakers {
        let share = Decimal::from_ratio(stake, total_staked);
        distributed = distributed.checked_add(total_reward * share).unwrap();
    }

    if distributed != total_reward {
        let remainder = total_reward.checked_sub(distributed).unwrap();
        println!("Precision loss: {} remainder", remainder);
    }
}

#[test]
fn division_before_multiplication_loss() {
    for numerator in 1u128..=100u128 {
        for denominator in 1u128..=100u128 {
            let amt = Uint128::new(1000);
            let ratio = Decimal::from_ratio(Uint128::new(numerator), Uint128::new(denominator));
            let decimal_result = amt * ratio;
            let int_result = Uint128::new(amt.u128() * numerator / denominator);
            assert_eq!(decimal_result, int_result);
        }
    }
}

8.6 Gas Griefing 测试 (VULN-12)

#[test]
fn gas_griefing_detection() {
    let mut deps = mock_dependencies();
    let env = mock_env();

    instantiate(
        deps.as_mut(), env.clone(), mock_info("admin", &[]),
        InstantiateMsg { owner: "admin".to_string(), token_name: "G".to_string(), token_symbol: "G".to_string(), initial_supply: Uint128::new(1_000_000) },
    ).unwrap();

    let many_users: Vec<String> = (0..1000).map(|i| format!("user_{}", i)).collect();
    let result = execute(
        deps.as_mut(), env.clone(), mock_info("admin", &[]),
        ExecuteMsg::BatchProcess { users: many_users },
    );
    match result {
        Ok(_) => {}
        Err(e) => assert!(
            e.to_string().contains("gas") || e.to_string().contains("limit")
        ),
    }
}

8.7 存储碰撞测试 (VULN-04)

#[test]
fn storage_collision_detection() {
    let mut deps = mock_dependencies();
    ITEM_A.save(deps.as_mut().storage, &"admin_a".to_string()).unwrap();
    ITEM_B.save(deps.as_mut().storage, &"admin_b".to_string()).unwrap();
    let val_a: String = ITEM_A.load(&deps.storage).unwrap();
    let val_b: String = ITEM_B.load(&deps.storage).unwrap();
    if val_a == "admin_b" {
        panic!("STORAGE COLLISION: ITEM_A and ITEM_B share the same key!");
    }
    assert_eq!(val_a, "admin_a");
    assert_eq!(val_b, "admin_b");
}

8.8 跨合约查询测试 (VULN-15)

#[test]
fn cross_contract_query_safety() {
    let mut deps = mock_dependencies();
    let env = mock_env();

    CONFIG.save(deps.as_mut().storage, &Config {
        owner: "admin".to_string(),
        trusted_oracles: vec!["msg1oracle".to_string()],
        max_supply: Uint128::new(1_000_000),
    }).unwrap();

    let result = execute(
        deps.as_mut(), env.clone(), mock_info("user", &[]),
        ExecuteMsg::GetPrice { asset: "umsg".to_string(), oracle: "msg1trusted".to_string() },
    );
    match result {
        Ok(_) => {}
        Err(e) => assert!(e.to_string().contains("oracle") || e.to_string().contains("trusted")),
    }
}

8.9 签名重放测试 (VULN-09)

#[test]
fn signature_replay_protection() {
    let mut deps = mock_dependencies();
    let env = mock_env();

    let permit = Permit {
        signer: "alice".to_string(),
        nonce: 1,
        deadline: 9999999999,
        message: vec![].into(),
        signature: vec![].into(),
    };

    let res1 = execute_permit(deps.as_mut(), env.clone(), mock_info("relayer", &[]), permit.clone());
    if res1.is_ok() {
        let res2 = execute_permit(deps.as_mut(), env, mock_info("relayer", &[]), permit);
        assert!(res2.is_err(), "Replay must fail");
    }
}

8.10 无限制铸币测试 (VULN-17)

#[test]
fn unchecked_minting_detection() {
    let mut deps = mock_dependencies();
    let env = mock_env();

    instantiate(
        deps.as_mut(), env.clone(), mock_info("admin", &[]),
        InstantiateMsg { owner: "admin".to_string(), token_name: "M".to_string(), token_symbol: "M".to_string(), initial_supply: Uint128::new(1_000_000) },
    ).unwrap();

    let supply_before = TOTAL_SUPPLY.load(&deps.storage).unwrap_or(Uint128::zero());
    let _ = execute(
        deps.as_mut(), env.clone(), mock_info("attacker", &[]),
        ExecuteMsg::Mint { to: "attacker".to_string(), amount: Uint128::new(999_999_999) },
    );
    let supply_after = TOTAL_SUPPLY.load(&deps.storage).unwrap_or(Uint128::zero());

    if supply_after > supply_before && supply_after > Uint128::new(1_000_000) {
        panic!("UNCHECKED MINTING: non-admin was able to mint tokens!");
    }
}

9. 附录

A. 工具安装速查

# Rust 工具链
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown
rustup component add clippy rustfmt

# 模糊测试
cargo install cargo-fuzz

# 覆盖率
cargo install cargo-tarpaulin

# 安全审计
cargo install cargo-audit
cargo install cargo-geiger

# Wasm 优化
cargo install wasm-opt

# Go 工具
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go install github.com/securego/gosec/v2/cmd/gosec@latest

B. Makefile 测试目标

# 标准测试
make test            # Go pkg 测试
make test-quantum    # Dilithium-5 密码学测试
make ci-contracts    # 所有合约编译 + cargo test
make test-coverage   # 覆盖率报告

# CI 流水线
make ci-tests        # 完整测试流水线
make ci-security     # 安全审计 (gosec + cargo audit)
make lint            # 代码质量检查

C. Cargo.toml 依赖速查

[dependencies]
cosmwasm-std = { version = "1.5", features = ["stargate"] }
cosmwasm-storage = "1.5"
cw-storage-plus = "1.2"
cw2 = "1.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1"

[dev-dependencies]
cosmwasm-vm = "1.5"
cw-multi-test = "0.18"
proptest = "1.4"
tarpaulin = "0.27"
libfuzzer_sys = "0.4"

D. 常见 Fuzz Crash 信号解释

信号 Fuzz 输出 含义
SIGSEGV SEGV on unknown address 内存访问越界
SIGABRT Aborted 断言失败 assert!()
SIGILL Illegal instruction 非法指令
OOM Out of memory 内存超限
Timeout Timeout 执行超时 > 30s
Stack Overflow Stack overflow 无限递归

E. MSG Chain 20 漏洞类别速查

ID 漏洞名称 严重等级 模糊测试可检测
VULN-01 重入攻击 🔴 Critical ✅
VULN-02 未授权访问 🔴 Critical ✅
VULN-03 整数溢出/下溢 🟠 High ✅
VULN-04 存储碰撞 🟠 High ✅
VULN-05 Reply 处理漏洞 🟠 High ✅
VULN-06 IBC 超时漏洞 🟠 High ❌ (需集成测试)
VULN-07 价格预言机操纵 🔴 Critical ✅
VULN-08 闪电贷攻击 🟠 High ✅
VULN-09 签名重放 🟠 High ✅
VULN-10 随机数预测 🟠 Medium ✅
VULN-11 抢先交易/MEV 🟠 High ❌
VULN-12 Gas Griefing 🟡 Medium ✅
VULN-13 不安全地址验证 🟡 Medium ✅
VULN-14 Funds 验证遗漏 🟡 Medium ✅
VULN-15 不安全跨合约查询 🟠 High ✅
VULN-16 不安全 IBC 包验证 🟠 High ❌
VULN-17 无限制代币增发 🔴 Critical ✅
VULN-18 权限提升 🔴 Critical ✅
VULN-19 精度损失 🟡 Medium ✅
VULN-20 Agent API 滥用 🟠 High ✅

F. Fuzz 测试用例清单

Target 文件 测试重点 建议运行时间
fuzz_execute 通用 Execute 反序列化 5 min
fuzz_instantiate_params Instantiate 参数边界 3 min
fuzz_multi_step 多步操作状态积累 10 min
fuzz_execute_variants 枚举变种覆盖 5 min
fuzz_edge_cases 极值边界 3 min
fuzz_submsg SubMsg/Reply 安全 5 min
fuzz_msg_chain MSG Chain 专属场景 5 min

G. 参考资源


本文档是 MSG Chain 开发指南系列的一部分。
如有问题,请提交 Issue 至 MSG Chain 官方仓库。