skills/multiversx/mx-ai-skills/multiversx-entry-points

multiversx-entry-points

SKILL.md

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

Macro Visibility Risk Level Description
#[endpoint] Public write High State-changing public function
#[view] Public read Low Read-only public function
#[payable("*")] Accepts any token Critical Handles value transfers
#[payable("EGLD")] Accepts EGLD only Critical Handles native currency
#[init] Deploy only Medium Constructor (runs once)
#[upgrade] Upgrade only Critical Migration logic
#[callback] Internal High Async call response handler
#[only_owner] Owner restricted Medium Admin functions

Scanning Commands

# 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.

#[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.

#[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.

#[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.

#[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.

#[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:

| 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:

// 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:

Tag Meaning Example
Refusable Rejects payments Default (no #[payable])
EGLD Only Accepts EGLD #[payable("EGLD")]
Token Only Specific ESDT #[payable("TOKEN-abc123")]
Any Token Any payment #[payable("*")]
Multi-Token Multiple payments Uses all_esdt_transfers()

Step 4: Graph Data Flow

Map which storage mappers each endpoint reads/writes:

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?

// 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?

// 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?

// 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?

// 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

# 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]
Weekly Installs
5
GitHub Stars
10
First Seen
Jan 30, 2026
Installed on
antigravity4
openclaw2
claude-code2
codex2
gemini-cli2
cursor2