skills/vulnerability-patterns/weird-tokens/SKILL.md
Non-standard ERC20 behaviors, integration pitfalls, and token-handling safeguards.
npx skillsauth add apegurus/solidity-argus weird-tokensInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Tokens that don't behave like standard ERC20. Integrating with these requires special handling.
| Pattern | Example Tokens | Risk | Mitigation | |---------|---------------|------|------------| | Missing return values | USDT, BNB, OMG | Silent failures | SafeERC20 | | Fee on transfer | STA, PAXG, SAFEMOON | Balance mismatch | Measure before/after | | Rebasing | AMPL, stETH, OHM | Cached balance wrong | Don't cache balances | | Pausable | BNB, ZIL | DOS | Handle gracefully | | Blocklist | USDC, USDT | User funds frozen | Document risk | | Upgradeable | USDC, USDT | Behavior can change | Monitor upgrades | | Flash mintable | DAI | Infinite supply attacks | Check total supply | | Multiple addresses | TUSD | Accounting errors | Verify canonical address | | Low decimals | USDC (6), GUSD (2) | Precision loss | Handle decimals explicitly | | High decimals | YAM-V2 (24) | Overflow risk | Check bounds | | Approval race | USDT, KNC | Front-running | Set to 0 first | | Revert on zero | LEND | Unexpected reverts | Check amount > 0 | | Non-string metadata | MKR | Type errors | Handle bytes32 | | ERC777 hooks | imBTC | Reentrancy | Treat as untrusted |
Tokens: USDT, BNB, OMG, BADGER
Problem:
// Returns nothing, but ERC20 interface expects bool
function transfer(address to, uint value) public {
// ... no return statement
}
Exploit:
// This compiles but fails at runtime for USDT
IERC20(usdt).transfer(to, amount); // Reverts: expected return data
Fix:
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
using SafeERC20 for IERC20;
IERC20(usdt).safeTransfer(to, amount); // Works
Tokens: STA, PAXG, SAFEMOON, most "reflection" tokens
Problem:
function transfer(address to, uint value) public returns (bool) {
uint fee = value * feePercent / 100;
_transfer(msg.sender, feeCollector, fee);
_transfer(msg.sender, to, value - fee);
return true;
}
Exploit:
// User deposits 100, protocol credits 100
// But only 98 actually arrived (2% fee)
// User can withdraw 100, stealing 2 from protocol
function deposit(uint amount) external {
token.transferFrom(msg.sender, address(this), amount);
balances[msg.sender] += amount; // WRONG!
}
Fix:
function deposit(uint amount) external {
uint balanceBefore = token.balanceOf(address(this));
token.transferFrom(msg.sender, address(this), amount);
uint received = token.balanceOf(address(this)) - balanceBefore;
balances[msg.sender] += received; // Credit actual amount
}
Tokens: AMPL, stETH (rebases), OHM, AAVE aTokens
Problem:
// Balance changes without any transfers
function rebase(int supplyDelta) external {
totalSupply = totalSupply + supplyDelta;
// All balances scale proportionally
}
Exploit:
// Protocol caches balance
uint cachedBalance = steth.balanceOf(address(this));
// ... time passes, rebase happens ...
// Cached balance is now wrong
// Can lead to accounting errors, insolvency
Fix:
Tokens: BNB, ZIL, USDC (admin can pause)
Problem:
modifier whenNotPaused() {
require(!paused, "Paused");
_;
}
function transfer(address to, uint value) public whenNotPaused {
// ...
}
Impact:
Fix:
Tokens: USDC, USDT (compliance blacklists)
Problem:
function transfer(address to, uint value) public {
require(!blacklisted[msg.sender], "Blacklisted");
require(!blacklisted[to], "Blacklisted");
// ...
}
Impact:
Fix:
Tokens: USDC, USDT, many newer tokens
Problem:
Impact:
Fix:
Tokens: DAI (flash mint), some wrapped tokens
Problem:
function flashLoan(uint amount) external {
_mint(msg.sender, amount);
IFlashBorrower(msg.sender).onFlashLoan(amount);
_burn(msg.sender, amount);
}
Impact:
Fix:
Tokens: USDT requires setting to 0 first
Problem:
// USDT's approve reverts if allowance > 0 and newValue > 0
function approve(address spender, uint value) public {
require(value == 0 || allowance[msg.sender][spender] == 0);
// ...
}
Fix:
// Always set to 0 first
token.approve(spender, 0);
token.approve(spender, newAmount);
// Or use SafeERC20
token.safeIncreaseAllowance(spender, amount);
Tokens: imBTC, any ERC777
Problem:
// ERC777 calls hooks on sender and receiver
function _send(address from, address to, uint256 amount) internal {
_callTokensToSend(from, to, amount); // HOOK - reentrancy!
_transfer(from, to, amount);
_callTokensReceived(from, to, amount); // HOOK - reentrancy!
}
Impact:
Fix:
When integrating any token:
testing
Specialist profile for mechanically applying the attack-vector deck and classifying vectors as skip, drop, or investigate.
tools
Specialist profile for libraries, helpers, base contracts, adapters, encoders, wrappers, and integration glue.
testing
Specialist profile for rounding, scale, decimal, downcast, and arithmetic accounting edge cases.
testing
Specialist profile for extracting conservation laws and state couplings, then searching for violating paths.