Skip to main content

token

Auto-loaded by token-auditor agent during Phase 2. Provides detection patterns for: fee-on-transfer, rebasing tokens, ERC777 hooks, ERC721/1155 callbacks, missing return values, blacklist/pausable, low decimals. Core artifact: Token Compatibility Matrix.

Jump to install

Source facts

Repository
BitterSecurity/Vigilo
Last source activity
February 2, 2026 at 07:22
Detected SKILL.md language
English
Stars
66
Forks
17

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
token
description
Auto-loaded by token-auditor agent during Phase 2. Provides detection patterns for: fee-on-transfer, rebasing tokens, ERC777 hooks, ERC721/1155 callbacks, missing return values, blacklist/pausable, low decimals. Core artifact: Token Compatibility Matrix.
user-invocable
false
# Token Vulnerability Patterns This skill provides comprehensive knowledge for identifying token-related vulnerabilities in smart contracts. ## Overview Not all tokens follow the "happy path" of standard implementations. Protocols must handle edge cases from fee tokens, rebasing tokens, callback tokens, and malicious tokens. ## Token Weirdness Categories ### 1. Fee-On-Transfer Tokens **Vulnerable Pattern:** ```solidity // DANGEROUS: Assumes transfer amount equals received amount function deposit(uint256 amount) external { token.transferFrom(msg.sender, address(this), amount); balances[msg.sender] += amount; // May be more than received! } ``` **Secure Pattern:** ```solidity function deposit(uint256 amount) external { uint256 balanceBefore = token.balanceOf(address(this)); token.transferFrom(msg.sender, address(this), amount); uint256 received = token.balanceOf(address(this)) - balanceBefore; balances[msg.sender] += received; } ``` **Common Fee Tokens:** - PAXG (0.02% fee) - STA (1% fee) - USDT (potential fee, currently 0) **Detection:** ``` Grep("transferFrom.*\\+=|transfer.*\\+=", glob="**/*.sol") ``` --- ### 2. Rebasing Tokens **Vulnerable Pattern:** ```solidity // DANGEROUS: Caches balance that will change function stake(uint256 amount) external { token.transferFrom(msg.sender, address(this), amount); stakedAmount[msg.sender] = amount; // Becomes stale! } ``` **Secure Pattern:** ```solidity // Use shares instead of amounts function stake(uint256 amount) external { uint256 totalBefore = token.balanceOf(address(this)); token.transferFrom(msg.sender, address(this), amount); uint256 received = token.balanceOf(address(this)) - totalBefore; uint256 shares = totalShares == 0 ? received : received * totalShares / totalBefore; userShares[msg.sender] += shares; totalShares += shares; } ``` **Common Rebasing Tokens:** - stETH (positive rebase daily) - AMPL (elastic supply) - OHM (rebasing) - aTokens (AAVE interest) **Detection:** - Look for balance caching - Check if protocol claims to support stETH, AMPL --- ### 3. ERC777 Callback Reentrancy **Vulnerable Pattern:** ```solidity // DANGEROUS: State updated after transfer function withdraw(uint256 amount) external { require(balances[msg.sender] >= amount); token.transfer(msg.sender, amount); // Triggers tokensReceived! balances[msg.sender] -= amount; // Too late! } ``` **ERC777 Hooks:** - `tokensToSend()` - Called BEFORE transfer on sender - `tokensReceived()` - Called AFTER transfer on recipient **Detection:** ``` Grep("IERC777|tokensReceived|tokensToSend", glob="**/*.sol") ``` --- ### 4. ERC721/1155 Callback Reentrancy **Vulnerable Pattern:** ```solidity // DANGEROUS: safeTransfer triggers callback function claimNFT(uint256 tokenId) external { nft.safeTransferFrom(address(this), msg.sender, tokenId); // onERC721Received callback runs HERE claimed[tokenId] = true; // State update after callback! } ``` **Callback Functions:** - `onERC721Received()` - ERC721 safeTransfer - `onERC1155Received()` - ERC1155 safeTransfer - `onERC1155BatchReceived()` - ERC1155 safeBatchTransfer **Detection:** ``` Grep("safeTransferFrom|safeMint|safeTransfer", glob="**/*.sol") ``` --- ### 5. Missing Return Value (USDT) **Vulnerable Pattern:** ```solidity // DANGEROUS: USDT doesn't return bool function deposit(uint256 amount) external { bool success = token.transfer(address(this), amount); require(success, "Transfer failed"); // USDT reverts here! } ``` **Secure Pattern:** ```solidity import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; function deposit(uint256 amount) external { token.safeTransferFrom(msg.sender, address(this), amount); } ``` **Tokens Without Return:** - USDT - BNB - Some older tokens **Detection:** ``` Grep("transfer\\(|transferFrom\\(", glob="**/*.sol") ``` Check if using SafeERC20 wrapper. --- ### 6. Blacklist/Pausable Tokens **Risk:** Transfers can be blocked, causing DoS. **Affected Tokens:** - USDC (blacklist) - USDT (blacklist + pausable) - DAI (pausable) **Vulnerable Pattern:** ```solidity // DANGEROUS: Can be blocked if recipient blacklisted function withdraw() external { token.transfer(msg.sender, balances[msg.sender]); // May revert! } ``` **Mitigation:** - Use pull-over-push pattern - Allow alternate withdrawal addresses - Handle transfer failures gracefully --- ### 7. Low Decimal Tokens **Vulnerable Pattern:** ```solidity // DANGEROUS: Precision loss with low decimals // WBTC has 8 decimals, not 18 uint256 priceInWei = wbtcAmount * ethPrice / 1e18; // Wrong scale! ``` **Common Low Decimal Tokens:** - WBTC (8 decimals) - USDC (6 decimals) - USDT (6 decimals) **Detection:** ``` Grep("decimals|1e18|1e6|1e8", glob="**/*.sol") ``` --- ### 8. Approval Race Condition **Vulnerable Pattern:** ```solidity // DANGEROUS: Front-runnable approve token.approve(spender, newAmount); ``` **Attack:** 1. User has 100 approved 2. User calls approve(50) to reduce 3. Attacker front-runs: transferFrom(100) 4. Approve(50) executes 5. Attacker calls: transferFrom(50) 6. Total stolen: 150 **Secure Pattern:** ```solidity // Reset to 0 first, or use increaseAllowance token.approve(spender, 0); token.approve(spender, newAmount); // Or use OZ increaseAllowance/decreaseAllowance token.increaseAllowance(spender, amount); ``` --- ## Token Compatibility Matrix | Token | Fee | Rebase | Blacklist | Callback | Return | |-------|-----|--------|-----------|----------|--------| | USDT | No* | No | Yes | No | None | | USDC | No | No | Yes | No | Yes | | DAI | No | No | No | No | Yes | | stETH | No | Yes | No | No | Yes | | WBTC | No | No | No | No | Yes | | PAXG | Yes | No | No | No | Yes | | ERC777 | No | No | No | Yes | Yes | *USDT has fee mechanism but set to 0 --- ## Token Audit Checklist - [ ] Balance changes measured, not assumed (fee tokens) - [ ] Shares used instead of amounts (rebasing tokens) - [ ] CEI pattern followed for callbacks (ERC721/777/1155) - [ ] SafeERC20 used for transfers - [ ] Blacklist handling considered - [ ] Decimal precision handled correctly - [ ] Approval race condition mitigated - [ ] Zero-amount transfers handled ## Severity Classification ### Critical - ERC777 reentrancy - ERC721/1155 callback reentrancy - Fee token accounting mismatch ### High - Rebasing token balance caching - Missing return value handling - Decimal precision errors ### Medium - Blacklist DoS potential - Approval race condition - No zero-amount validation --- ## Additional Token Patterns (2025-2026) ### 9. ERC-2612 Permit Vulnerabilities **Vulnerable Pattern:** ```solidity // DANGEROUS: No deadline validation function depositWithPermit( uint256 amount, uint8 v, bytes32 r, bytes32 s ) external { token.permit(msg.sender, address(this), amount, type(uint256).max, v, r, s); // @audit deadline = max allows signature to be used forever token.transferFrom(msg.sender, address(this), amount); } ``` **Secure Pattern:** ```solidity function depositWithPermit( uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external { require(deadline >= block.timestamp, "Expired"); token.permit(msg.sender, address(this), amount, deadline, v, r, s); token.transferFrom(msg.sender, address(this), amount); } ``` **Signature Replay Risks:** - Same signature valid on multiple chains (missing chainId) - Permit can be front-run if deadline is far future - DoS by calling permit with invalid signature (some implementations) **Search Queries:** ``` Grep("permit|Permit|PERMIT_TYPEHASH|nonces", glob="**/*.sol") Grep("ecrecover|ECDSA|v.*r.*s", glob="**/*.sol") ``` --- ### 10. Permit2 Vulnerabilities (Uniswap) **Overview**: Permit2 is a universal approval system that introduces new attack vectors. **Vulnerable Pattern:** ```solidity // DANGEROUS: No expiration check on permit2 function swapWithPermit2( ISignatureTransfer.PermitTransferFrom calldata permit, ISignatureTransfer.SignatureTransferDetails calldata transferDetails, bytes calldata signature ) external { permit2.permitTransferFrom(permit, transferDetails, msg.sender, signature); // @audit What if permit.deadline is far in future? } ``` **Permit2 Specific Risks:** 1. **Batch Permits**: Single signature approves multiple tokens 2. **Witness Data**: Additional data can be signed but misinterpreted 3. **Signature Malleability**: Different signatures for same approval 4. **Nonce Invalidation**: Can invalidate others' permits **Search Queries:** ``` Grep("Permit2|permit2|ISignatureTransfer|IAllowanceTransfer", glob="**/*.sol") Grep("permitTransferFrom|permitWitnessTransferFrom", glob="**/*.sol") ``` **Security Checklist:** - [ ] Deadline always validated - [ ] Nonce management understood - [ ] Witness data properly validated - [ ] Signature cannot be reused maliciously --- ### 11. ERC-4626 Token Integration Risks When integrating ERC-4626 vaults as collateral or tokens: **Vulnerable Pattern:** ```solidity // DANGEROUS: Trusting vault's reported value function getCollateralValue(address user) external view returns (uint256) { uint256 shares = vault.balanceOf(user); return vault.convertToAssets(shares); // @audit Can be manipulated via donation! } ``` **Attack Vector:** 1. Attacker donates to vault, inflating `totalAssets` 2. `convertToAssets()` returns inflated value 3. Protocol accepts as collateral at wrong price 4. Attacker borrows against inflated collateral **Secure Pattern:** ```solidity // Use time-weighted or oracle-based pricing for ERC-4626 tokens function getCollateralValue(address user) external view returns (uint256) { uint256 shares = vault.balanceOf(user); // Use external oracle for share price, NOT vault.convertToAssets() uint256 sharePrice = oracle.getPrice(address(vault)); return shares * sharePrice / 1e18; } ``` **Search Queries:** ``` Grep("ERC4626|convertToAssets|convertToShares|previewDeposit", glob="**/*.sol") Grep("vault.*balanceOf|collateral.*vault", glob="**/*.sol") ``` --- ### 12. ERC-1363 (transferAndCall) Reentrancy **Vulnerable Pattern:** ```solidity // DANGEROUS: ERC-1363 triggers callback on receive function receiveApproval( address from, uint256 amount, address token, bytes calldata data ) external { // @audit This is called during transferAndCall/approveAndCall // State may not be updated yet! } ``` **Search Queries:** ``` Grep("ERC1363|transferAndCall|approveAndCall|onApprovalReceived", glob="**/*.sol") ``` --- ### 13. ERC-6909 Minimal Multi-Token Streamlined ERC-1155 alternative: no callbacks, no batching, per-token granular approvals. **Vulnerable Pattern:** ```solidity // DANGEROUS: Missing per-token allowance check require(isApprovedForAll[from][msg.sender], "Not approved");
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub