Skip to content
Back to skills

2593 Reentrancy 44db9f25

ASecurity

- Contract makes an external call (ETH transfer, token transfer, `.call()`, `.send()`, `.transfer()`, callback hook) - State is modified after the external call, not before - No reentrancy guard (`nonReentrant` modifier) on the function - For cross-function: two or more functions share state, and at least one makes an external call before updating that shared state - For cross-contract: Contract B reads Contract A's state, and A makes an external call before updating it - For read-only: Contract

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
blockchainrust

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 2593-reentrancy_44db9f25 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 2593 Reentrancy 44db9f25?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 2593 Reentrancy 44db9f25
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-2593-reentrancy-44db9f25/badge)](https://www.skillsdirectory.com/skills/tools-only-2593-reentrancy-44db9f25)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# Reentrancy

## Preconditions
- Contract makes an external call (ETH transfer, token transfer, `.call()`, `.send()`, `.transfer()`, callback hook)
- State is modified after the external call, not before
- No reentrancy guard (`nonReentrant` modifier) on the function
- For cross-function: two or more functions share state, and at least one makes an external call before updating that shared state
- For cross-contract: Contract B reads Contract A's state, and A makes an external call before updating it
- For read-only: Contract A has a reentrancy guard but updates state after an external call; Contract B reads A's state without sharing the same lock

## Vulnerable Pattern
```solidity
// Single-function reentrancy
function withdraw() external {
    uint256 bal = balances[msg.sender];
    // External call BEFORE state update
    (bool success,) = msg.sender.call{value: bal}("");
    require(success);
    // State update AFTER external call — attacker reenters withdraw()
    // and balances[msg.sender] is still the original value
    balances[msg.sender] = 0;
}

// Hidden external calls that trigger callbacks:
// ERC721._safeMint() -> onERC721Received()
// ERC1155.safeTransferFrom() -> onERC1155Received()
// ERC777 token transfers -> tokensReceived() hook
```

## Detection Heuristics
1. Identify all external calls: `.call()`, `.send()`, `.transfer()`, token transfers, `_safeMint()`, `_safeTransfer()`, ERC777/ERC1155 safe transfers
2. For each external call, check if any state variable is written AFTER the call in the same function
3. If state is written after an external call, check if a `nonReentrant` guard is present on the function — flag if absent
4. Check for cross-function reentrancy: does the function share state with other functions that could be called during the reentrant window?
5. Check for cross-contract reentrancy: does any other contract read this contract's state that is stale during the external call?
6. Check for hidden callbacks: `_safeMint`, `_safeTransfer`, ERC777 hooks, ERC1155 hooks — these are external calls even though they don't look like `.call()`

## False Positives
- State is updated BEFORE the external call (checks-effects-interactions pattern correctly followed)
- `nonReentrant` modifier is applied to the function
- The external call target is a trusted, immutable contract (e.g., WETH) with no callback mechanism
- The function is `view`/`pure` and cannot modify state
- The only state read after reentry is already finalized (e.g., immutable variables)

## Remediation
- Apply the checks-effects-interactions pattern: perform all state changes before any external call
- Add OpenZeppelin's `ReentrancyGuard` with `nonReentrant` modifier to all functions that make external calls
- For cross-contract reentrancy, use a shared reentrancy lock across contracts or ensure state is finalized before external calls
```solidity
function withdraw() external nonReentrant {
    uint256 bal = balances[msg.sender];
    balances[msg.sender] = 0;  // State update BEFORE external call
    (bool success,) = msg.sender.call{value: bal}("");
    require(success);
}
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…