Detect ERC-20 token compatibility issues — fee-on-transfer, rebasing, non-standard return values, missing decimals(), low-decimal tokens, blacklistable tokens (USDC), pausable tokens. Activate on any ERC-20 integration, `transfer`/`transferFrom` use, balance-based accounting, decimal scaling.
Scanned 10/4/2026
npx -y skills add iktok90-design/ai-smart-contract-auditor --skill token-compatibility --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Token Compatibility?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/iktok90-design-token-compatibility)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: token-compatibility
description: Detect ERC-20 token compatibility issues — fee-on-transfer, rebasing, non-standard return values, missing decimals(), low-decimal tokens, blacklistable tokens (USDC), pausable tokens. Activate on any ERC-20 integration, `transfer`/`transferFrom` use, balance-based accounting, decimal scaling.
---
# Token compatibility detection
## When this applies
- Any ERC-20 integration: lending, AMM, vault, staking, bridge
- Balance-based accounting (`balanceOf(this) - prevBalance`)
- Decimal scaling between tokens
- Cross-chain bridges with various token implementations
- Aggregators / routers handling user-supplied tokens
## Detection patterns
### Fee-on-transfer assumed away (HIGH)
```solidity
uint256 before = token.balanceOf(address(this));
token.transferFrom(user, address(this), amount);
shares = amount * X / Y; // ← uses `amount`, not actual received
```
Tokens like USDT (when fee enabled), SafeMoon, etc. take a fee. Use `balanceOf(this) - before` as the effective amount.
### Rebasing token accounting (HIGH)
stETH, aUSDC, AMPL — balances change without `transfer`. Vaults assuming `balanceOf` ≡ deposit will mis-account. Either don't accept rebasing tokens, or use share-based accounting (wstETH-style wrappers).
### Non-standard return values — USDT (HIGH)
USDT's `transfer` does NOT return bool (pre-`SafeERC20` use breaks):
```solidity
bool ok = IERC20(usdt).transfer(to, amt); // ← reverts: ABI mismatch
```
Use OZ `SafeERC20` which uses low-level call + return-data inspection.
### Blacklistable tokens (HIGH)
USDC, USDT can blacklist addresses. If a user gets blacklisted after deposit, the protocol may be unable to return funds → other users' funds stuck if pooled.
### Pausable tokens (HIGH)
USDC, USDT can pause transfers. Protocol functions that must succeed (liquidations) may revert.
### Low-decimal tokens (MEDIUM-HIGH)
GUSD has 2 decimals. Math assuming 18 decimals causes huge rounding errors.
```solidity
shares = assets * 1e18 / something; // ← shares now astronomical or zero
```
### Tokens with hooks (ERC-777) (HIGH)
Reentrancy surface (see [[reentrancy]]). Many protocols implicitly assume vanilla ERC-20.
### Tokens with double-entry (HIGH)
TUSD historically had two valid addresses. `transferFrom` could be invoked from either. Approval to one didn't bind the other.
### Missing return on approve / non-zero approve revert (HIGH)
Some tokens (early USDT) require `approve(spender, 0)` before `approve(spender, X)`. Use `safeApprove` or `forceApprove`. See [[approval-issues]].
### Missing `decimals()` (LOW)
Non-standard tokens may lack `decimals()`. Guard with try/catch and default to 18.
### Tokens that revert on zero-amount transfer (LOW-MEDIUM)
LEND, others. Cap-by-zero guards needed.
## Severity rubric
| Pattern | Severity |
|---|---|
| Vault accepts fee-on-transfer with no balance-delta accounting | **High** |
| Protocol accepts rebasing token without share-wrapper | **High** |
| Naive ERC-20 transfer (no SafeERC20) | **High** |
| Pooled funds with blacklistable token + no escape hatch | **High** |
| Low-decimal token with 18-decimal-assumed math | **High** |
| Pausable token in liquidation path | **High** |
| Approve-non-zero revert ignored | **High** |
| Zero-amount-revert in batch flow | **Medium** |
| Missing `decimals()` graceful handling | **Low** |
## Remediation patterns
- **Allowlist tokens** — easier than supporting every token.
- **Use SafeERC20** — covers most return-value quirks.
- **Balance-delta accounting** — measure `balanceOf(this) - prev` after transferFrom.
- **Disallow rebasing tokens** — or only accept their share-wrapped form (wstETH not stETH).
- **Reset approval before set** — `forceApprove` from OZ ≥4.9, or `safeApprove(0)` then `safeApprove(amount)`.
- **Escape hatches** — admin can swap a single user's blacklisted balance out of pooled state into a side mapping.
## False-positive notes
- Protocol explicitly lists supported tokens and tests against them → not a finding for unsupported.
- Test/mock tokens in test/ → ignore.
## Related
- [[unchecked-calls]]
- [[approval-issues]]
- [[reentrancy]] — ERC-777 hooks
- [[integer-issues]] — decimals math
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!