Skip to content

Conversation

@omarinuwa
Copy link

Implements comprehensive helper system for testing protocols against non-standard ERC20 token behaviors. Addresses issue #20.

Overview

Creates ERC20EdgeCases helper that deploys 20 different ERC20 token implementations, each exhibiting specific non-standard behaviors found in real tokens. This enables developers to test their protocols against all known ERC20 edge cases with a single import.

Implements comprehensive helper system for testing protocols against non-standard
ERC20 token behaviors. Addresses GitHub issue crytic#20.

## Overview

Creates `ERC20EdgeCases` helper that deploys 20 different ERC20 token implementations,
each exhibiting specific non-standard behaviors found in real tokens. This enables
developers to test their protocols (DEXs, vaults, lending platforms) against all
known ERC20 edge cases with a single import.

## Created Files (23 total)

### Core Helper (1 file)
- **ERC20EdgeCases.sol**: Main helper contract that deploys all tokens and provides
  accessor functions:
  - `all_erc20()` - returns all 20 token addresses
  - `all_erc20_standard()` - returns only standard-compliant tokens
  - `all_erc20_non_standard()` - returns only non-standard tokens
  - `tokenByName(string)` - get specific token by name (e.g., "USDT-like")
  - Category helpers: `tokens_with_fee()`, `tokens_reentrant()`, etc.

### Token Implementations (20 files)
Each token is a complete, working ERC20 with one specific edge case behavior:

**Missing Return Values:**
- MissingReturns.sol - No return value (USDT, BNB, OMG)
- ReturnsFalse.sol - Returns false on success (Tether Gold)

**Fee on Transfer:**
- TransferFee.sol - Charges fee on transfer (STA, PAXG) - caused $500k Balancer drain

**Reentrant Callbacks:**
- Reentrant.sol - ERC777-style hooks (AMP, imBTC) - caused Uniswap/lendf.me drains

**Admin Controls:**
- BlockList.sol - Admin blocklist (USDC, USDT)
- Pausable.sol - Admin pause (BNB, ZIL)

**Transfer Quirks:**
- RevertZero.sol - Reverts on zero transfers (LEND)
- RevertToZero.sol - Reverts on transfer to address(0) (OpenZeppelin)
- NoRevert.sol - Returns false instead of reverting (ZRX, EURS)
- TransferFromSelf.sol - Doesn't decrease allowance if from == msg.sender (DSToken, WETH)
- TransferMax.sol - Transfers full balance if amount == max uint256 (cUSDCv3)

**Approval Quirks:**
- ApprovalRaceProtection.sol - Can't change non-zero allowance (USDT, KNC)
- ApprovalToZeroAddress.sol - Reverts on approve(address(0), amt) (OpenZeppelin)
- RevertZeroApproval.sol - Reverts on approve(spender, 0) (BNB)
- Uint96.sol - Reverts if amount >= 2^96 (UNI, COMP)

**Metadata Quirks:**
- Bytes32Metadata.sol - name/symbol as bytes32 (MKR)
- LowDecimals.sol - 6 decimals (USDC) or 2 decimals (Gemini USD)
- HighDecimals.sol - 24 decimals (YAM-V2)

**Permit Issues:**
- PermitNoOp.sol - permit() doesn't revert but does nothing (WETH) - caused Multichain hack

**Baseline:**
- StandardERC20.sol - Compliant implementation for comparison

### Documentation (1 file)
- **README.md**: Comprehensive guide covering:
  - All 20 token types with real-world examples
  - Usage patterns and best practices
  - Real exploits that would have been prevented
  - Integration with Echidna/Medusa
  - Code examples for common testing scenarios

### Example Test Harness (1 file)
- **tests/ERC20EdgeCases/ExampleVaultTest.sol**: Complete example showing:
  - Testing vault with all token types
  - Testing specific edge cases (fee tokens, reentrant tokens)
  - Proper accounting that handles fee-on-transfer
  - SafeERC20-style handling of missing return values
  - Example vault implementation with correct edge case handling

## Usage

```solidity
import "@crytic/properties/contracts/util/erc20/ERC20EdgeCases.sol";

contract MyProtocolTest {
    ERC20EdgeCases edgeCases;

    constructor() {
        edgeCases = new ERC20EdgeCases();
    }

    function test_protocolWithAllTokens() public {
        address[] memory tokens = edgeCases.all_erc20();
        for (uint i = 0; i < tokens.length; i++) {
            // Test your protocol with each token
        }
    }
}
```

## Real-World Exploits Prevented

This helper would have caught:
- **Balancer STA Exploit (2020)**: $500k drained via fee-on-transfer tokens
- **imBTC Uniswap Pool Drain**: Reentrancy via ERC777 hooks
- **lendf.me Hack**: $25M stolen via reentrancy
- **Multichain Hack**: Assumed permit succeeded without checking allowance
- **Countless integration bugs**: Missing return values, approval race conditions, etc.

## Integration Points

- Updated CLAUDE.md with new section on ERC20 edge case testing
- Follows existing project patterns (NatSpec, testing structure)
- Compatible with Echidna/Medusa fuzzing workflows
- Complementary to existing ERC20 property tests (tests implementations vs. integrations)

## References

- GitHub issue crytic#20: Create helpers for non-standard ERC20
- weird-erc20 repository: https://github.com/d-xo/weird-erc20
- Trail of Bits token integration checklist
- All tokens based on real-world examples with documented exploits

Closes crytic#20
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Omar Inuwa seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants