AgentSkillsCN

multiversx-entry-points

系统性地识别并分析所有智能合约的入口点,以绘制攻击面地图。当您启动安全审查、记录合约接口,或评估访问控制覆盖范围时,可使用此方法。

SKILL.md
--- frontmatter
name: multiversx-entry-points
description: Systematically identify and analyze all smart contract entry points for attack surface mapping. Use when starting security reviews, documenting contract interfaces, or assessing access control coverage.

MultiversX Entry Point Analyzer

Identify the complete attack surface of a MultiversX smart contract by enumerating all public interaction points and classifying their risk levels. This is typically the first step in any security review.

When to Use

  • Starting a new security audit
  • Documenting contract public interface
  • Assessing access control coverage
  • Mapping data flow through the contract
  • Identifying high-risk endpoints for focused review

1. Entry Point Identification

MultiversX Macros That Expose Functions

MacroVisibilityRisk LevelDescription
#[endpoint]Public writeHighState-changing public function
#[view]Public readLowRead-only public function
#[payable("*")]Accepts any tokenCriticalHandles value transfers
#[payable("EGLD")]Accepts EGLD onlyCriticalHandles native currency
#[init]Deploy onlyMediumConstructor (runs once)
#[upgrade]Upgrade onlyCriticalMigration logic
#[callback]InternalHighAsync call response handler
#[only_owner]Owner restrictedMediumAdmin functions

Scanning Commands

bash
# Find all endpoints
grep -n "#\[endpoint" src/*.rs

# Find all payable endpoints
grep -n "#\[payable" src/*.rs

# Find all views
grep -n "#\[view" src/*.rs

# Find callbacks
grep -n "#\[callback" src/*.rs

# Find init and upgrade
grep -n "#\[init\]\|#\[upgrade\]" src/*.rs

2. Risk Classification

Category A: Payable Endpoints (Critical Risk)

Functions receiving value require the most scrutiny.

rust
#[payable("*")]
#[endpoint]
fn deposit(&self) {
    // MUST CHECK:
    // 1. Token identifier validation
    // 2. Amount > 0 validation
    // 3. Correct handling of multi-token transfers
    // 4. State updates before external calls

    let payment = self.call_value().single_esdt();
    require!(
        payment.token_identifier == self.accepted_token().get(),
        "Wrong token"
    );
    require!(payment.amount > 0, "Zero amount");

    // Process deposit...
}

Checklist for Payable Endpoints:

  • Token ID validated against expected token(s)
  • Amount checked for minimum/maximum bounds
  • Multi-transfer handling if all_esdt_transfers() used
  • Nonce validation for NFT/SFT
  • Reentrancy protection (Checks-Effects-Interactions)

Category B: Non-Payable State-Changing Endpoints (High Risk)

Functions that modify state without payment.

rust
#[endpoint]
fn update_config(&self, new_value: BigUint) {
    // MUST CHECK:
    // 1. Who can call this? (access control)
    // 2. Input validation
    // 3. State transition validity

    self.require_caller_is_admin();
    require!(new_value > 0, "Invalid value");
    self.config().set(new_value);
}

Checklist for State-Changing Endpoints:

  • Access control implemented and correct
  • Input validation for all parameters
  • State transitions are valid
  • Events emitted for important changes
  • No DoS vectors (unbounded loops, etc.)

Category C: View Functions (Low Risk)

Read-only functions, but still need review.

rust
#[view(getBalance)]
fn get_balance(&self, user: ManagedAddress) -> BigUint {
    // SHOULD CHECK:
    // 1. Does it actually modify state? (interior mutability)
    // 2. Does it leak sensitive information?
    // 3. Is the calculation expensive (DoS via gas)?

    self.balances(&user).get()
}

Checklist for View Functions:

  • No state modification (verify no storage writes)
  • No sensitive data exposure
  • Bounded computation (no unbounded loops)
  • Block info usage appropriate (get_block_timestamp() may differ off-chain)

Category D: Init and Upgrade (Critical Risk)

Lifecycle functions with special considerations.

rust
#[init]
fn init(&self, admin: ManagedAddress) {
    // MUST CHECK:
    // 1. All required state initialized
    // 2. No way to re-initialize
    // 3. Admin/owner properly set

    self.admin().set(admin);
}

#[upgrade]
fn upgrade(&self) {
    // MUST CHECK:
    // 1. New storage mappers initialized
    // 2. Storage layout compatibility
    // 3. Migration logic correct
}

Category E: Callbacks (High Risk)

Async call handlers with specific vulnerabilities.

rust
#[callback]
fn transfer_callback(
    &self,
    #[call_result] result: ManagedAsyncCallResult<()>
) {
    // MUST CHECK:
    // 1. Error handling (don't assume success)
    // 2. State reversion on failure
    // 3. Correct identification of original call

    match result {
        ManagedAsyncCallResult::Ok(_) => {
            // Success path
        },
        ManagedAsyncCallResult::Err(_) => {
            // CRITICAL: Must handle failure!
            // Revert any state changes from original call
        }
    }
}

3. Analysis Workflow

Step 1: List All Entry Points

Create an inventory table:

markdown
| Endpoint | Type | Payable | Access | Storage Touched | Risk |
|----------|------|---------|--------|-----------------|------|
| deposit | endpoint | * | Public | balances | Critical |
| withdraw | endpoint | No | Public | balances | Critical |
| setAdmin | endpoint | No | Owner | admin | High |
| getBalance | view | No | Public | balances (read) | Low |
| init | init | No | Deploy | admin, config | Medium |

Step 2: Tag Access Control

For each endpoint, document who can call it:

rust
// Public - anyone can call
#[endpoint]
fn public_function(&self) { }

// Owner only - blockchain owner
#[only_owner]
#[endpoint]
fn owner_function(&self) { }

// Admin only - custom access control
#[endpoint]
fn admin_function(&self) {
    self.require_caller_is_admin();
}

// Whitelisted - address in set
#[endpoint]
fn whitelist_function(&self) {
    let caller = self.blockchain().get_caller();
    require!(self.whitelist().contains(&caller), "Not whitelisted");
}

Step 3: Tag Value Handling

Classify how each endpoint handles value:

TagMeaningExample
RefusableRejects paymentsDefault (no #[payable])
EGLD OnlyAccepts EGLD#[payable("EGLD")]
Token OnlySpecific ESDT#[payable("TOKEN-abc123")]
Any TokenAny payment#[payable("*")]
Multi-TokenMultiple paymentsUses all_esdt_transfers()

Step 4: Graph Data Flow

Map which storage mappers each endpoint reads/writes:

code
deposit() ──writes──▶ balances
          ──writes──▶ total_deposited
          ──reads───▶ accepted_token

withdraw() ──reads/writes──▶ balances
           ──reads────────▶ withdrawal_fee

getBalance() ──reads──▶ balances

4. Specific Attack Vectors

Privilege Escalation

Is a sensitive endpoint accidentally public?

rust
// VULNERABLE: Missing access control
#[endpoint]
fn set_admin(&self, new_admin: ManagedAddress) {
    self.admin().set(new_admin);  // Anyone can become admin!
}

// CORRECT: Protected
#[only_owner]
#[endpoint]
fn set_admin(&self, new_admin: ManagedAddress) {
    self.admin().set(new_admin);
}

DoS via Unbounded Growth

Can public endpoints cause unbounded storage growth?

rust
// VULNERABLE: Public endpoint adds to unbounded set
#[endpoint]
fn register(&self) {
    let caller = self.blockchain().get_caller();
    self.participants().insert(caller);  // Grows forever!
}

// Attack: Call register() with many addresses until
// any function iterating participants() runs out of gas

Missing Payment Validation

Does a payable endpoint verify what it receives?

rust
// VULNERABLE: Accepts any token
#[payable("*")]
#[endpoint]
fn stake(&self) {
    let payment = self.call_value().single_esdt();
    self.staked().update(|s| *s += payment.amount);  // Fake tokens accepted!
}

Callback State Assumptions

Does a callback assume the async call succeeded?

rust
// VULNERABLE: Assumes success
#[callback]
fn on_transfer_complete(&self) {
    // This runs even if transfer FAILED!
    self.transfer_count().update(|c| *c += 1);
}

5. Output Template

markdown
# Entry Point Analysis: [Contract Name]

## Summary
- Total Endpoints: X
- Payable Endpoints: Y (Critical)
- State-Changing: Z (High)
- Views: W (Low)

## Detailed Inventory

### Critical Risk (Payable)
| Endpoint | Accepts | Access | Concerns |
|----------|---------|--------|----------|
| deposit | * | Public | Token validation needed |

### High Risk (State-Changing)
| Endpoint | Access | Storage Modified | Concerns |
|----------|--------|------------------|----------|
| withdraw | Public | balances | Amount validation |

### Medium Risk (Admin)
| Endpoint | Access | Storage Modified | Concerns |
|----------|--------|------------------|----------|
| setConfig | Owner | config | Privilege escalation if misconfigured |

### Low Risk (Views)
| Endpoint | Storage Read | Concerns |
|----------|--------------|----------|
| getBalance | balances | None |

## Access Control Matrix

| Endpoint | Public | Owner | Admin | Whitelist |
|----------|--------|-------|-------|-----------|
| deposit | Yes | - | - | - |
| setAdmin | - | Yes | - | - |

## Recommended Focus Areas
1. [Highest priority endpoint and why]
2. [Second priority]
3. [Third priority]