Skip to content
Back to skills

Solady Safetransferlib No Contract Check

ASecurity

Detect Solady SafeTransferLib calls that assume the token has code. SafeTransferLib.safeTransfer/safeTransferFrom/safeApprove deliberately skip the EXTCODESIZE check that OpenZeppelin's SafeERC20 performs, so a call to an EOA or a self-destructed/not-yet-deployed token address returns success with no transfer. Activate whenever code imports solady SafeTransferLib, calls safeTransfer/safeTransferFrom on a user-supplied or upgradeable token address, or routes arbitrary tokens.

  • 36 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 4, 2026
blockchainrust

Security analysis

A100/100

Scanned October 4, 2026

npx -y skills add iktok90-design/ai-smart-contract-auditor --skill solady-safetransferlib-no-contract-check --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Solady Safetransferlib No Contract Check?

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

Security grade badge for Solady Safetransferlib No Contract Check
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/iktok90-design-solady-safetransferlib-no-contract-check/badge)](https://www.skillsdirectory.com/skills/iktok90-design-solady-safetransferlib-no-contract-check)

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

Download with Pro
SKILL.md
---
name: solady-safetransferlib-no-contract-check
description: Detect Solady SafeTransferLib calls that assume the token has code. SafeTransferLib.safeTransfer/safeTransferFrom/safeApprove deliberately skip the EXTCODESIZE check that OpenZeppelin's SafeERC20 performs, so a call to an EOA or a self-destructed/not-yet-deployed token address returns success with no transfer. Activate whenever code imports solady SafeTransferLib, calls safeTransfer/safeTransferFrom on a user-supplied or upgradeable token address, or routes arbitrary tokens.
---

# Solady SafeTransferLib missing-contract-check detection

## When this applies

Trigger on any of:

- `import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";`
- `SafeTransferLib.safeTransfer(token, to, amt)` / `safeTransferFrom` / `safeApprove` / `safeApproveWithRetry`
- `using SafeTransferLib for address;` followed by `token.safeTransfer(...)`
- Token address sourced from user input, a registry, a factory, or a CREATE2 prediction
- Routers, aggregators, vaults, or bridges that accept arbitrary token addresses
- Any path where the token contract could be a not-yet-deployed or self-destructed address

## Detection patterns

### Transfer to an address with no code (HIGH)
```solidity
using SafeTransferLib for address;

function rescue(address token, address to, uint256 amt) external onlyOwner {
    token.safeTransfer(to, amt);   // ← if `token` has no code, this SUCCEEDS silently
}
```
Solady's `safeTransfer` reverts only if the call itself reverts OR returns a non-truthy bool. A call to an address with no code returns success and empty returndata, which Solady treats as a passing transfer.
**Signal:** SafeTransferLib used on a token address that is never verified to contain code (`token.code.length > 0`).

### User-supplied token in accounting (HIGH)
```solidity
function deposit(address token, uint256 amt) external {
    token.safeTransferFrom(msg.sender, address(this), amt);  // no-op if token is an EOA
    shares[msg.sender][token] += amt;                         // credited anyway
}
```
Attacker passes an EOA they control as `token`; the "transfer" no-ops but `shares` is credited, minting unbacked accounting balance.
**Signal:** `safeTransferFrom` result drives state without a balance-delta check and the token address is attacker-controlled.

### CREATE2-predicted / pre-deploy token (MEDIUM-HIGH)
```solidity
address predicted = _computeAddress(salt);
predicted.safeTransfer(to, amt);   // token not deployed yet → silent success
```
**Signal:** transfer to an address computed/predicted before the contract is known to be deployed.

## Severity rubric

| Pattern | Severity | Notes |
|---|---|---|
| User-supplied token credited without balance-delta check | **High** | Mints unbacked balance / drains pool |
| safeTransfer to address with no code in fund-moving path | **High** | Funds "sent" but never move |
| Rescue/admin path only (trusted token) | **Medium** | Centralization-bounded, op error |
| Hardcoded, audited token address | **Info** | Code presence is implied constant |

## Remediation patterns

1. **Explicit code check** — `require(token.code.length != 0, "no token");` before the first transfer.
2. **Balance-delta accounting** — measure `IERC20(token).balanceOf(address(this))` before/after and credit the delta, never the requested `amount`.
3. **Token allowlist** — only accept registry-approved tokens whose code presence is established at registration.
4. **Use OZ SafeERC20** if you specifically want the built-in `isContract`/`functionCall` revert-on-no-code behavior and can afford the extra gas.

## False-positive notes

- A hardcoded immutable token address set at construction to a known deployed contract does not need a runtime code check — Info at most.
- If the same path already does `balanceOf`-delta accounting, the no-code case is caught by a zero delta — downgrade.
- `safeTransferETH` is unaffected (no token contract involved).

## Related

- [[token-compatibility]] — non-standard return values vs. missing code are distinct issues
- [[unchecked-calls]] — empty returndata treated as success
- [[centralization-risk]] — rescue paths

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…