🎯

multiversx-diff-review

🎯Skill

from multiversx/mx-ai-skills

VibeIndex|
What it does

Identifies and analyzes critical security and upgradeability risks in MultiversX smart contract version changes, focusing on storage layout, key modifications, and safe upgrade patterns.

πŸ“¦

Part of

multiversx/mx-ai-skills(8 items)

multiversx-diff-review

Installation

npxRun with npx
npx openskills install multiversx/mx-ai-skills
Quick InstallInstall with npx
npx skills install multiversx/mx-ai-skills
npxRun with npx
npx openskills install multiversx/mx-ai-skills -g
git cloneClone repository
git clone https://github.com/multiversx/mx-ai-skills.git .ai-skills
πŸ“– Extracted from docs: multiversx/mx-ai-skills
3Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

Review changes between smart contract versions with focus on upgradeability and security implications. Use when reviewing PRs, upgrade proposals, or analyzing differences between deployed and new code.

Overview

# Differential Review

Analyze differences between two versions of a MultiversX codebase, focusing on security implications of changes, storage layout compatibility, and upgrade safety.

When to Use

  • Reviewing pull requests with contract changes
  • Auditing upgrade proposals before deployment
  • Analyzing differences between deployed code and proposed updates
  • Verifying fix implementations don't introduce regressions

1. Upgradeability Checks (MultiversX-Specific)

Storage Layout Compatibility

CRITICAL: Storage layout changes can corrupt existing data.

#### Struct Field Ordering

```rust

// v1 - Original struct

#[derive(TopEncode, TopDecode, TypeAbi)]

pub struct UserData {

pub balance: BigUint, // Offset 0

pub last_claim: u64, // Offset 1

}

// v2 - DANGEROUS: Reordered fields

pub struct UserData {

pub last_claim: u64, // Now at Offset 0 - BREAKS EXISTING DATA

pub balance: BigUint, // Now at Offset 1 - CORRUPTED

}

// v2 - SAFE: Append new fields only

pub struct UserData {

pub balance: BigUint, // Offset 0 - unchanged

pub last_claim: u64, // Offset 1 - unchanged

pub new_field: bool, // Offset 2 - NEW (safe)

}

```

#### Storage Mapper Key Changes

```rust

// v1

#[storage_mapper("user_balance")]

fn user_balance(&self, user: &ManagedAddress) -> SingleValueMapper;

// v2 - DANGEROUS: Changed storage key

#[storage_mapper("userBalance")] // Different key = new empty storage!

fn user_balance(&self, user: &ManagedAddress) -> SingleValueMapper;

```

Initialization on Upgrade

CRITICAL: #[init] is NOT called on upgrade. Only #[upgrade] runs.

```rust

// v1 - Original contract

#[init]

fn init(&self) {

self.config().set(DefaultConfig::new());

}

// v2 - Added new storage mapper

#[storage_mapper("newFeatureEnabled")]

fn new_feature_enabled(&self) -> SingleValueMapper;

// WRONG: Assuming init runs

#[init]

fn init(&self) {

self.config().set(DefaultConfig::new());

self.new_feature_enabled().set(true); // Never runs on upgrade!

}

// CORRECT: Initialize in upgrade

#[upgrade]

fn upgrade(&self) {

self.new_feature_enabled().set(true); // Properly initializes

}

```

Breaking Changes Checklist

| Change Type | Risk | Mitigation |

|------------|------|------------|

| Struct field reorder | Critical | Never reorder, only append |

| Storage key rename | Critical | Keep old key, migrate data |

| New required storage | High | Initialize in #[upgrade] |

| Removed endpoint | Medium | Ensure no external dependencies |

| Changed endpoint signature | Medium | Version API or maintain compatibility |

| New validation rules | Medium | Consider existing state validity |

2. Regression Analysis

New Features Impact

  • Do new features break existing invariants?
  • Are there new attack vectors introduced?
  • Do gas costs change significantly?

Deleted Code Analysis

When code is removed, verify:

  • Was this an intentional security fix?
  • Was a validation check removed (potential vulnerability)?
  • Are there other code paths that depended on this?

```rust

// v1 - Had balance check

fn withdraw(&self, amount: BigUint) {

require!(amount <= self.balance().get(), "Insufficient balance");

// ... withdrawal logic

}

// v2 - Check removed - WHY?

fn withdraw(&self, amount: BigUint) {

// Missing balance check! Was this intentional?

// ... withdrawal logic

}

```

Modified Logic Analysis

For changed code, verify:

  • Edge cases still handled correctly
  • Error messages updated appropriately
  • Related code paths updated consistently

3. Review Workflow

Step 1: Generate Clean Diff

```bash

# Between git tags/commits

git diff v1.0.0..v2.0.0 -- src/

# Ignore formatting changes

git diff -w v1.0.0..v2.0.0 -- src/

# Focus on specific file

git diff v1.0.0..v2.0.0 -- src/lib.rs

```

Step 2: Categorize Changes

Create a change inventory:

```markdown

Change Summary

Storage Changes

  • [ ] user_data struct: Added reward_multiplier field (SAFE - appended)
  • [ ] New mapper: feature_flags (VERIFY: initialized in upgrade)

Endpoint Changes

  • [ ] deposit(): Added token validation (SECURITY FIX)
  • [ ] withdraw(): Changed gas calculation (VERIFY: no DoS vector)

Removed Code

  • [ ] legacy_claim(): Removed entire endpoint (VERIFY: no external callers)

New Code

  • [ ] batch_transfer(): New endpoint (FULL REVIEW NEEDED)

```

Step 3: Trace Data Flow

For each changed data structure:

  1. Find all read locations
  2. Find all write locations
  3. Verify consistency across changes

Step 4: Verify Test Coverage

```bash

# Check if new code paths are tested

sc-meta test

# Generate test coverage report

cargo tarpaulin --out Html

```

4. Security-Specific Diff Checks

Access Control Changes

```rust

// v1 - Owner only

#[only_owner]

#[endpoint]

fn sensitive_action(&self) { }

// v2 - DANGEROUS: Removed access control

#[endpoint] // Now public! Was this intentional?

fn sensitive_action(&self) { }

```

Payment Handling Changes

```rust

// v1 - Validated token

#[payable("*")]

fn deposit(&self) {

let payment = self.call_value().single_esdt();

require!(payment.token_identifier == self.accepted_token().get(), "Wrong token");

}

// v2 - DANGEROUS: Removed validation

#[payable("*")]

fn deposit(&self) {

let payment = self.call_value().single_esdt();

// Missing token validation! Accepts any token now

}

```

Arithmetic Changes

```rust

// v1 - Safe arithmetic

let result = a.checked_add(&b).unwrap_or_else(|| sc_panic!("Overflow"));

// v2 - DANGEROUS: Removed overflow protection

let result = a + b; // Can overflow!

```

5. Deliverable Template

```markdown

# Differential Review Report

Versions Compared: v1.0.0 β†’ v2.0.0

Reviewer: [Name]

Date: [Date]

Summary

[One paragraph overview of changes]

Critical Findings

  1. [Finding with severity and recommendation]

Storage Compatibility

  • [ ] No struct field reordering
  • [ ] New mappers initialized in #[upgrade]
  • [ ] Storage keys unchanged

Breaking Changes

| Change | Impact | Migration Required |

|--------|--------|-------------------|

| ... | ... | ... |

Recommendations

  1. [Specific actionable recommendation]

```

Common Pitfalls

  • Assuming init runs on upgrade: Always check #[upgrade] function
  • Missing storage migration: Renamed keys lose existing data
  • Removed validations: Could be intentional security fix or accidental vulnerability
  • Changed math precision: Can affect existing calculations
  • Modified access control: Could expose sensitive functions