← ClaudeAtlas

solady-safetransferlib-no-contract-checklisted

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.
iktok90-design/ai-smart-contract-auditor · ★ 36 · AI & Automation · score 80
Install: claude install-skill iktok90-design/ai-smart-contract-auditor
# 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;