MultiversX代码分析工具包Skill multiversx-code-analysis

这个技能是一个全面的MultiversX智能合约代码分析工具包,涵盖差异审查(版本比较、升级安全性)、修复验证(验证补丁、回归测试)和变体分析(在代码库中查找类似漏洞)。适用于审查拉取请求、验证安全补丁或查找漏洞变体。关键词:MultiversX,智能合约,代码分析,安全审计,差异审查,修复验证,变体分析。

智能合约 0 次安装 0 次浏览 更新于 3/21/2026

name: multiversx-code-analysis description: MultiversX智能合约的全面代码分析工具包。涵盖差异审查(版本比较、升级安全性)、修复验证(验证补丁、回归测试)和变体分析(在代码库中查找类似漏洞)。适用于审查PR、验证安全补丁或查找漏洞变体。

MultiversX代码分析

用于分析MultiversX智能合约代码变更、验证修复和在代码库中查找漏洞变体的工具包。

何时使用

  • 审查包含合约变更的拉取请求
  • 部署前审计升级提案
  • 验证安全补丁和漏洞修复
  • 在代码库中查找类似漏洞
  • 创建全面的漏洞报告

第1节:差异审查

分析两个版本MultiversX代码库之间的差异,重点关注变更的安全影响、存储布局兼容性和升级安全性。

1.1 可升级性检查(MultiversX特定)

存储布局兼容性

关键:存储布局变更可能损坏现有数据。

结构体字段顺序
// v1 - 原始结构体
#[derive(TopEncode, TopDecode, TypeAbi)]
pub struct UserData {
    pub balance: BigUint,      // 偏移量 0
    pub last_claim: u64,       // 偏移量 1
}

// v2 - 危险:字段重排序
pub struct UserData {
    pub last_claim: u64,       // 现在在偏移量 0 - 破坏现有数据
    pub balance: BigUint,      // 现在在偏移量 1 - 已损坏
}

// v2 - 安全:仅追加新字段
pub struct UserData {
    pub balance: BigUint,      // 偏移量 0 - 未变
    pub last_claim: u64,       // 偏移量 1 - 未变
    pub new_field: bool,       // 偏移量 2 - 新字段(安全)
}
存储映射器键变更
// v1
#[storage_mapper("user_balance")]
fn user_balance(&self, user: &ManagedAddress) -> SingleValueMapper<BigUint>;

// v2 - 危险:更改存储键
#[storage_mapper("userBalance")]  // 不同键 = 新的空存储!
fn user_balance(&self, user: &ManagedAddress) -> SingleValueMapper<BigUint>;

升级时的初始化

关键#[init] 在升级时不会调用。只有 #[upgrade] 会运行。

// v2 - 添加了新存储映射器
#[storage_mapper("newFeatureEnabled")]
fn new_feature_enabled(&self) -> SingleValueMapper<bool>;

// 错误:假设init运行
#[init]
fn init(&self) {
    self.config().set(DefaultConfig::new());
    self.new_feature_enabled().set(true);  // 升级时永不运行!
}

// 正确:在升级中初始化
#[upgrade]
fn upgrade(&self) {
    self.new_feature_enabled().set(true);  // 正确初始化
}

破坏性变更清单

变更类型 风险 缓解措施
结构体字段重排序 关键 永远不要重排序,仅追加
存储键重命名 关键 保留旧键,迁移数据
新必需存储 #[upgrade] 中初始化
移除端点 确保没有外部依赖
更改端点签名 版本化API或保持兼容性
新验证规则 考虑现有状态有效性

1.2 回归分析

新功能影响

  • 新功能是否破坏现有不变量?
  • 是否引入了新的攻击向量?
  • 气体成本是否显著变化?

删除代码分析

当代码被移除时,验证:

  • 这是否是故意的安全修复?
  • 是否移除了验证检查(潜在漏洞)?
  • 是否有其他代码路径依赖于此?
// v1 - 有余额检查
fn withdraw(&self, amount: BigUint) {
    require!(amount <= self.balance().get(), "余额不足");
    // ... 提款逻辑
}

// v2 - 检查被移除 - 为什么?
fn withdraw(&self, amount: BigUint) {
    // 缺少余额检查!这是故意的吗?
    // ... 提款逻辑
}

修改逻辑分析

对于更改的代码,验证:

  • 边界情况是否仍正确处理
  • 错误消息是否适当更新
  • 相关代码路径是否一致更新

1.3 审查工作流

步骤1:生成干净差异

# 在git标签/提交之间
git diff v1.0.0..v2.0.0 -- src/

# 忽略格式化更改
git diff -w v1.0.0..v2.0.0 -- src/

# 关注特定文件
git diff v1.0.0..v2.0.0 -- src/lib.rs

步骤2:分类变更

## 变更摘要

### 存储变更
- [ ] user_data结构体:添加了 `reward_multiplier` 字段(安全 - 追加)
- [ ] 新映射器:`feature_flags`(验证:在升级中初始化)

### 端点变更
- [ ] deposit():添加了代币验证(安全修复)
- [ ] withdraw():更改了气体计算(验证:无DoS向量)

### 移除代码
- [ ] legacy_claim():移除整个端点(验证:无外部调用者)

### 新代码
- [ ] batch_transfer():新端点(需要完整审查)

步骤3:跟踪数据流

对于每个更改的数据结构:

  1. 查找所有读取位置
  2. 查找所有写入位置
  3. 验证跨变更的一致性

步骤4:验证测试覆盖率

# 检查新代码路径是否被测试
sc-meta test

# 生成测试覆盖率报告
cargo tarpaulin --out Html

1.4 安全特定的差异检查

访问控制变更

// v1 - 仅所有者
#[only_owner]
#[endpoint]
fn sensitive_action(&self) { }

// v2 - 危险:移除访问控制
#[endpoint]  // 现在公开!这是故意的吗?
fn sensitive_action(&self) { }

支付处理变更

// v1 - 已验证代币
#[payable]
fn deposit(&self) {
    let payment = self.call_value().single();
    require!(payment.token_identifier == self.accepted_token().get(), "错误的代币");
}

// v2 - 危险:移除验证
#[payable]
fn deposit(&self) {
    let payment = self.call_value().single();
    // 缺少代币验证!现在接受任何代币
}

算术变更

// v1 - 安全算术
let result = a.checked_add(&b).unwrap_or_else(|| sc_panic!("溢出"));

// v2 - 危险:移除溢出保护
let result = a + b;  // 可能溢出!

1.5 差异审查输出模板

# 差异审查报告

**比较版本**:v1.0.0 → v2.0.0
**审查者**:[姓名]
**日期**:[日期]

## 摘要
[变更的概述段落]

## 关键发现
1. [发现及其严重性和建议]

## 存储兼容性
- [ ] 无结构体字段重排序
- [ ] 新映射器在 #[upgrade] 中初始化
- [ ] 存储键未变

## 破坏性变更
| 变更 | 影响 | 是否需要迁移 |
|--------|--------|-------------------|
| ... | ... | ... |

## 建议
1. [具体的可操作建议]

常见陷阱

  • 假设init在升级时运行:始终检查 #[upgrade] 函数
  • 缺少存储迁移:重命名键丢失现有数据
  • 移除验证:可能是故意的安全修复或意外的漏洞
  • 更改数学精度:可能影响现有计算
  • 修改访问控制:可能暴露敏感函数

第2节:修复验证

严格验证报告的漏洞是否已被消除,且未引入回归或新问题。

2.1 验证循环

步骤1:复现漏洞

创建演示漏洞的测试场景:

// scenarios/exploit_before_fix.scen.json
{
    "name": "演示漏洞 - 修复前应失败",
    "steps": [
        {
            "step": "scCall",
            "comment": "攻击者利用漏洞",
            "tx": {
                "from": "address:attacker",
                "to": "sc:vulnerable_contract",
                "function": "vulnerable_endpoint",
                "arguments": ["...exploit_payload..."],
                "gasLimit": "5,000,000"
            },
            "expect": {
                "status": "0",
                "message": "*"
            }
        }
    ]
}

步骤2:应用修复

审查解决漏洞的代码修改。

步骤3:验证修复有效性

运行漏洞场景 — 现在必须失败(或行为正确):

# 漏洞场景现在应通过(漏洞被阻止)
sc-meta test --scenario scenarios/exploit_before_fix.scen.json

步骤4:运行回归套件

所有现有测试必须仍通过:

# 完整测试套件
sc-meta test

# 或使用cargo
cargo test

2.2 常见修复失败

部分修复

修复处理了一条路径但错过了变体:

// 漏洞:缺少金额验证
#[endpoint]
fn deposit(&self) {
    let amount = self.call_value().egld();
    // 没有检查金额 > 0
}

// 部分修复:只修复了deposit,未修复transfer
#[endpoint]
fn deposit(&self) {
    let amount = self.call_value().egld();
    require!(amount > 0, "金额必须为正");  // 已修复!
}

#[endpoint]
fn transfer(&self, amount: BigUint) {
    // 仍缺少金额 > 0检查!  <- 变体未修复
}

验证:使用变体分析(第3节)查找所有类似代码路径。

移动漏洞(修复创建新问题)

// 漏洞:重入
#[endpoint]
fn withdraw(&self) {
    let balance = self.balance().get();
    self.tx().to(&caller).egld(&balance).transfer();  // 状态更新前外部调用
    self.balance().clear();
}

// 错误修复:防止重入但创建DoS
#[endpoint]
fn withdraw(&self) {
    self.locked().set(true);  // 添加锁定
    let balance = self.balance().get();
    self.tx().to(&caller).egld(&balance).transfer();
    self.balance().clear();
    // 缺少:self.locked().set(false);  <- 锁定从未释放!
}

// 正确修复:检查-效果-交互模式
#[endpoint]
fn withdraw(&self) {
    let balance = self.balance().get();
    self.balance().clear();  // 外部调用前状态更新
    self.tx().to(&caller).egld(&balance).transfer();
}

不完整验证

// 漏洞:整数溢出
let total = amount1 + amount2;  // 可能溢出

// 不完整修复:检查一个但不检查两个
require!(amount1 < MAX_AMOUNT, "Amount1过大");
let total = amount1 + amount2;  // 如果amount2大,仍会溢出!

// 正确修复:BigUint是任意精度(无溢出),但验证边界
let total = &amount1 + &amount2;
require!(total <= BigUint::from(MAX_ALLOWED), "金额超过最大值");

2.3 验证清单

代码审查

  • [ ] 修复解决根本原因,而不仅仅是症状
  • [ ] 所有类似模式的代码路径都被修复(变体分析)
  • [ ] 修复未引入新漏洞
  • [ ] 修复遵循MultiversX最佳实践

测试

  • [ ] 创建漏洞场景,在漏洞代码上失败
  • [ ] 漏洞场景在修复代码上通过(被阻止)
  • [ ] 所有现有测试通过(无回归)
  • [ ] 测试边界情况(边界值、空输入、最大值)

文档

  • [ ] 修复提交清晰描述漏洞
  • [ ] 测试场景记录攻击向量
  • [ ] 任何行为变更被记录

2.4 测试场景模板

{
    "name": "验证修复 [VULNERABILITY_ID]",
    "comment": "此场景验证 [DESCRIPTION] 是否已正确修复",
    "steps": [
        {
            "step": "setState",
            "comment": "设置漏洞状态",
            "accounts": {
                "address:attacker": { "nonce": "0", "balance": "1000" },
                "sc:contract": { "code": "file:output/contract.wasm" }
            }
        },
        {
            "step": "scCall",
            "comment": "尝试利用 - 修复后应失败",
            "tx": {
                "from": "address:attacker",
                "to": "sc:contract",
                "function": "vulnerable_function",
                "arguments": ["exploit_input"]
            },
            "expect": {
                "status": "4",
                "message": "str:预期错误消息"
            }
        },
        {
            "step": "checkState",
            "comment": "验证状态未变(利用被阻止)",
            "accounts": {
                "sc:contract": {
                    "storage": {
                        "str:sensitive_value": "original_value"
                    }
                }
            }
        }
    ]
}

2.5 验证报告模板

# 修复验证报告

## 漏洞参考
- **ID**:[CVE/内部ID]
- **严重性**:[关键/高/中/低]
- **描述**:[简要描述]

## 修复详情
- **提交**:[git提交哈希]
- **更改的文件**:[文件列表]
- **方法**:[修复方法描述]

## 验证结果

### 漏洞复现
- [ ] 创建漏洞场景:`scenarios/[名称].scen.json`
- [ ] 场景在漏洞代码上失败(提交:[哈希])
- [ ] 场景在修复代码上通过(提交:[哈希])

### 回归测试
- [ ] 所有现有测试通过
- [ ] 无新警告来自 `cargo clippy`
- [ ] 气体成本在可接受范围内

### 变体分析
- [ ] 使用变体分析搜索相似模式
- [ ] 所有变体已解决:[列表或“未找到”]

## 结论
**状态**:[已验证 / 需要改进 / 拒绝]

**备注**:[任何额外观察]

**签名**:[审查者姓名, 日期]

2.6 验证期间的危险信号

  • 修复对于问题过于复杂
  • 修复更改了无关代码
  • 未添加针对具体漏洞的测试
  • 修复依赖外部假设
  • 气体成本显著增加
  • 访问控制修改无明确理由

第3节:变体分析

通过系统地在代码库其他地方定位类似问题,倍增单个漏洞发现的价值。

3.1 变体分析过程

1. 找到初始漏洞 → 具体漏洞实例
2. 抽象模式 → 什么使其成为漏洞?
3. 创建搜索 → Grep/Semgrep查询
4. 找到变体 → 所有类似出现
5. 验证每个 → 确认真正正例
6. 报告所有 → 记录漏洞类别

3.2 常见MultiversX变体模式

模式:缺少支付验证

变体搜索

# 查找所有可支付端点
grep -rn "#\[payable" src/

# 检查缺少代币验证
grep -A 30 "#\[payable" src/*.rs > payable_endpoints.txt
# 手动审查每个代币标识符验证

Semgrep规则

rules:
  - id: mvx-payable-no-token-check
    patterns:
      - pattern: |
          #[payable]
          $ANNOTATIONS
          fn $FUNC(&self, $...PARAMS) {
              $...BODY
          }
      - pattern-not: |
          #[payable]
          $ANNOTATIONS
          fn $FUNC(&self, $...PARAMS) {
              <... token_identifier ...>
          }

模式:无界迭代

# 查找所有存储映射器的.iter()调用
grep -rn "\.iter()" src/

# 查找所有存储上的for循环
grep -rn "for.*in.*self\." src/

每个的清单

  • [ ] 迭代是否受限?
  • [ ] 用户能否增长集合?
  • [ ] 是否有分页?

模式:回调状态假设

# 查找所有回调
grep -rn "#\[callback\]" src/

# 检查正确处理结果
grep -A 20 "#\[callback\]" src/*.rs | grep -c "ManagedAsyncCallResult"

所有回调需要

#[callback]
fn any_callback(&self, #[call_result] result: ManagedAsyncCallResult<T>) {
    match result {
        ManagedAsyncCallResult::Ok(_) => { /* 成功 */ },
        ManagedAsyncCallResult::Err(_) => { /* 处理失败! */ }
    }
}

模式:缺少访问控制

# 查找修改类似管理员存储的函数
grep -rn "admin\|owner\|config\|fee" src/ | grep "\.set("

# 交叉参考访问控制
grep -B 10 "admin.*\.set\|config.*\.set" src/*.rs | grep -v "only_owner"

模式:算术无检查

# 查找所有算术操作
grep -rn " + \| - \| \* " src/*.rs

# 排除测试文件和注释
grep -rn " + \| - \| \* " src/*.rs | grep -v "test\|//"

3.3 系统变体搜索

步骤1:特征化漏洞

回答这些问题:

  1. 什么是漏洞代码模式?
  2. 什么使其可被利用?
  3. 修复应是什么样?

步骤2:创建检测查询

基于Grep

# 模式:[具体代码模式]
grep -rn "[pattern]" src/

# 负面模式(应该存在但不存在)
grep -L "[expected_pattern]" src/*.rs

基于Semgrep

rules:
  - id: variant-pattern
    patterns:
      - pattern: <vulnerable pattern>
      - pattern-not: <fixed pattern>

步骤3:分类结果

结果 分类 行动
明确易受攻击 真正正例 报告
需要上下文 调查 手动审查
有缓解措施 假正例 记录原因
不同模式 不是变体 跳过

步骤4:记录发现

## 变体分析:[漏洞类别名称]

### 初始发现
- 位置:[文件:行]
- 描述:[错误之处]

### 模式描述
[抽象描述什么使其成为漏洞]

### 搜索方法
```bash
[grep/semgrep命令使用]

找到的变体

位置 状态 备注
file1.rs:23 确认 相同模式
file2.rs:45 确认 轻微变体
file3.rs:67 假正例 其他地方有验证

修复

[如何修复所有实例]


### 3.4 未来预防的自动化

#### 转换为CI/CD检查

```yaml
# .github/workflows/security.yml
- name: 检查漏洞模式
  run: |
    # 运行semgrep与自定义规则
    semgrep --config rules/mvx-security.yaml src/

    # 基于Grep的检查
    if grep -rn "unsafe_pattern" src/; then
      echo "找到潜在漏洞"
      exit 1
    fi

3.5 变体分析清单

找到任何漏洞后:

  • [ ] 抽象模式(什么使其成为漏洞?)
  • [ ] 创建搜索查询(grep, semgrep)
  • [ ] 搜索整个代码库
  • [ ] 分类每个结果(TP/FP/需调查)
  • [ ] 验证真正正例可被利用
  • [ ] 记录所有变体
  • [ ] 为CI/CD创建预防规则
  • [ ] 建议修复所有实例

3.6 常见变体类别

输入验证变体

  • 一个端点缺少 → 检查所有端点
  • 一个参数缺少 → 检查所有参数

访问控制变体

  • 一个管理员函数缺少 → 检查所有管理员函数
  • 不一致的角色检查 → 审计整个角色系统

状态管理变体

  • 一个函数中的重入 → 检查所有外部调用
  • 缺少回调处理 → 检查所有回调

算术变体

  • 一个计算中的溢出 → 检查所有数学操作
  • 一个公式中的精度损失 → 检查所有除法操作

3.7 报告多个变体

整合报告

# 漏洞类别:[名称]

## 摘要
在代码库中找到 [N] 个 [漏洞描述] 实例。

## 根本原因
[为什么此模式易受攻击]

## 实例

### 实例1 (file1.rs:23)
[详情]

### 实例2 (file2.rs:45)
[详情]

## 推荐修复
[通用修复模式]

## 预防
[如何预防此类漏洞未来出现]

严重性聚合

个体严重性 计数 聚合严重性
关键 3+ 关键
5+ 关键
10+
任意

3.8 示例:完整变体分析

初始漏洞:stake()中缺少金额验证

// 在stake.rs:45找到
#[payable("EGLD")]
fn stake(&self) {
    let payment = self.call_value().egld();
    // 漏洞:没有检查金额 > 0
    self.staked().update(|s| *s += payment.amount.as_big_uint());
}

模式:可支付端点缺少金额 > 0检查

注意:使用SDK v0.64+ Payment类型时,amountNonZeroBigUint> 0 检查对于类型化支付是冗余的。以下模式适用于旧SDK版本或原始 call_value() 使用。

搜索

grep -rn "#\[payable" src/ | cut -d: -f1 | sort -u | while read file; do
    echo "=== $file ==="
    grep -A 30 "#\[payable" "$file" | head -40
done > payable_review.txt

找到的变体

  1. stake.rs:45 - stake() - 确认
  2. stake.rs:78 - add_stake() - 确认
  3. rewards.rs:23 - deposit_rewards() - 确认
  4. fees.rs:12 - pay_fee() - 假正例(第15行有检查)

应用到所有的修复

#[payable("EGLD")]
fn stake(&self) {
    let payment = self.call_value().egld();
    require!(payment.amount.as_big_uint() > 0, "金额必须为正");
    self.staked().update(|s| *s += payment.amount.as_big_uint());
}

创建的CI规则rules/mvx-amount-validation.yaml