This document catalogs proxy patterns NOT currently supported by evm-proxy-tools, their importance, usage across EVM networks, and implementation plans.
The library currently supports 7 core standards (EIP-1167, EIP-1967, EIP-1822, EIP-2535, EIP-3448, EIP-7511, EIP-897). This document identifies 15+ additional patterns found in production, ranging from EIP standards to protocol-specific implementations.
Priority Tiers:
- P0 (Critical): Widely deployed, billions in TVL - Safe Proxy, ERC-6551, Compound Unitroller
- P1 (High): Significant usage, growing adoption - CWIA variants, 0age proxy, Vyper Beta
- P2 (Medium): Niche but standardized - ERC-1538, ERC-3561, Sequence Wallet
- P3 (Low): Emerging/Draft standards - ERC-7760, ERC-7702, ERC-7546
Status: Production (billions in TVL)
Where Used:
- Safe multisig wallets (9.7M+ deployments, $100B+ in assets)
- Ethereum, Polygon, Arbitrum, Optimism, BSC, and all major EVM chains
- Most DAO treasuries and protocol admin contracts
How It Works:
The Safe proxy uses a simple storage-based delegation pattern. The implementation address (called singleton or masterCopy) is stored at slot 0. The fallback function intercepts calls to masterCopy() (selector 0xa619486e) to return the implementation directly without delegatecall.
// Storage layout
address internal singleton; // slot 0
// Fallback intercepts masterCopy() selector
fallback() external payable {
if (msg.sig == 0xa619486e) {
// Return singleton directly
assembly { mstore(0, sload(0)) return(0, 0x20) }
}
// Otherwise delegatecall to singleton
}Detection:
- Bytecode contains selector
a619486e(masterCopy) - OR: Storage slot 0 contains a valid contract address
- Bytecode pattern: Look for the specific Safe assembly fallback
Implementation Read:
// Method 1: Call masterCopy()
let impl = provider.call(proxy, "masterCopy()").await?;
// Method 2: Read storage slot 0
let impl = provider.get_storage_at(proxy, U256::ZERO).await?;Why Important:
- Single most widely used smart contract wallet
- Critical infrastructure for DAOs, protocols, and institutions
- Many tools already special-case Safe detection
Implementation Plan:
- Add
ProxyType::GnosisSafevariant - Detect via bytecode pattern (selector
a619486e) OR storage slot 0 heuristic - Read implementation from slot 0
- Add support for both v1.3.0 and v1.4.x patterns
Status: Final (ERC), rapidly growing adoption
Where Used:
- NFT ecosystems (NFTs that own assets)
- Gaming (character inventories)
- Ethereum, Polygon, Base, Optimism
- 5,700+ deployments on mainnet (as of 2023)
How It Works: ERC-6551 creates smart contract accounts owned by NFTs. Each account is an EIP-1167-style minimal proxy with additional immutable data appended:
[EIP-1167 proxy bytecode][salt][chainId][tokenContract][tokenId]
The registry deploys accounts deterministically based on the NFT's identity.
Detection:
- Bytecode starts with EIP-1167 pattern
- Bytecode length is 173 bytes (45 + 128 bytes of packed data)
- Data section contains:
uint256 salt, uint256 chainId, address tokenContract, uint256 tokenId
// Bytecode structure
const EIP_6551_SIZE: usize = 173;
const EIP_1167_SIZE: usize = 45;
fn is_eip_6551(code: &[u8]) -> bool {
code.len() == EIP_6551_SIZE && is_eip_1167(&code[..EIP_1167_SIZE])
}Implementation Read:
// Implementation is in the EIP-1167 portion
let impl_addr = extract_eip_1167_address(&code[..45]);
// Token info is in the data section
let (salt, chain_id, token_contract, token_id) = decode_6551_data(&code[45..]);Why Important:
- Enables NFTs to own assets and interact with DeFi
- Growing ecosystem (games, social, identity)
- Already partially detected as EIP-1167 but metadata is lost
Implementation Plan:
- Add
ProxyType::Eip6551variant - Detect via bytecode length (173 bytes) + EIP-1167 prefix
- Add
Dispatch::Static6551 { address, token_contract, token_id, chain_id }variant - Parse immutable data section
Status: Production (billions in TVL)
Where Used:
- Compound V2 and all forks (Venus, Cream, Benqi, etc.)
- Most lending protocol comptrollers
- Ethereum, BSC, Avalanche, Polygon, Fantom
How It Works:
The Unitroller is a transparent proxy with a simple storage layout. The implementation is stored in a named variable (comptrollerImplementation) rather than an EIP-1967 slot.
contract UnitrollerAdminStorage {
address public admin; // slot 0
address public pendingAdmin; // slot 1
address public comptrollerImplementation; // slot 2
address public pendingComptrollerImplementation; // slot 3
}Detection:
- Contract has function selector for
comptrollerImplementation()(0xbb82aa5e) - OR: Storage slot 2 contains implementation address
- Often has
_setPendingImplementationfunction
Implementation Read:
// Method 1: Call the getter
let impl = provider.call(proxy, "comptrollerImplementation()").await?;
// Method 2: Read storage slot 2
let impl = provider.get_storage_at(proxy, U256::from(2)).await?;Why Important:
- Foundation of DeFi lending (Compound, Aave v2 uses similar)
- Billions in TVL across forks
- Pattern copied by many protocols
Implementation Plan:
- Add
ProxyType::CompoundUnitrollervariant - Detect via bytecode function selector scan OR EVM trace
- Add
Dispatch::Storagewith slot 2 - Consider generic "named storage" detection
Status: Production (multiple variants)
Where Used:
- Sudoswap (AMM pools)
- 0xSplits (payment splitting)
- Ajna (lending)
- Astaria (lending)
- Buttonwood (bonds)
How It Works:
CWIA proxies append immutable configuration data to the bytecode. Before delegating, the proxy reads this data and appends it to calldata. The logic contract reads it via _getArgXxx() helpers.
[proxy bytecode][immutable args][2-byte length]
Variants Detected in Production:
| Variant | Instances | Bytecode Signature |
|---|---|---|
| ClonesWithImmutableArgs | 264 | 3d3d3d3d363d3d3761...5af43d3d93803e6057fd5bf3 |
| ClonesWithCallData | 336 | 363d3d3761...5af43d82803e903d91603657fd5bf3 |
| Sudoswap CWIA | 318 | 3d3d3d3d363d3d37605160353639... |
| Solady CWIA | 78 | 36602c57343d527f9e4ac34f21c619... |
| 0xSplits CWIA | 3 | 36602f57343d527f9e4ac34f21c619... |
Detection: Each variant has a distinct bytecode pattern. The implementation address is embedded, and immutable args follow.
Implementation Read:
// Extract implementation from bytecode
let impl = extract_cwia_implementation(code, variant);
// Extract immutable args (optional, for advanced use)
let args = extract_cwia_args(code, variant);Why Important:
- Gas-efficient for factory patterns
- Used by major DeFi protocols
- Multiple incompatible variants in production
Implementation Plan:
- Add
ProxyType::ClonesWithImmutableArgswith variant enum - Add patterns for all 5+ variants
- Extract implementation address from each pattern
- Optionally expose immutable args length
Status: Production (9,928 instances)
Where Used:
- Various factory contracts
- Gas-optimized deployments
How It Works: A 44-byte variant of EIP-1167 that saves 1 byte and 4 gas per call by reordering instructions.
Bytecode Pattern:
3d3d3d3d363d3d37363d73<ADDR>5af43d3d93803e602a57fd5bf3
Detection:
const ZERO_AGE_FIRST: &[u8] = &hex!("3d3d3d3d363d3d37363d73");
const ZERO_AGE_SECOND: &[u8] = &hex!("5af43d3d93803e602a57fd5bf3");Implementation Plan:
- Add
ProxyType::ZeroAgeMinimalvariant - Add bytecode pattern detection
- Extract static address from bytecode
Status: Legacy but still in use (4,270 instances)
Where Used:
- Uniswap V1 (all exchange contracts)
- Early Curve pools
- Legacy Vyper deployments
How It Works: Pre-EIP-1167 proxy from early Vyper. Notable quirk: always returns 4096 bytes regardless of actual response size.
Bytecode Pattern:
366000600037611000600036600073<ADDR>5af41558576110006000f3
Detection:
The 611000 (PUSH2 0x1000) is distinctive - it's the 4096-byte return buffer.
Implementation Plan:
- Add
ProxyType::VyperBetavariant (already partially supported as noted in detect.rs) - Ensure proper bytecode pattern matching
- Document the 4096-byte return quirk
Status: Production (growing post-Shanghai)
Where Used:
- New deployments on post-Shanghai networks
- Gas-optimized minimal proxies
- Ethereum mainnet, L2s
How It Works:
Uses the PUSH0 opcode (introduced in Shanghai) to push zero more efficiently. 45 bytes but 8 gas cheaper per call than EIP-1167.
Bytecode Pattern:
5f5f365f5f37365f73<ADDR>5af43d5f5f3e6029573d5ffd5b3d5ff3
Detection:
Starts with 5f5f (PUSH0 PUSH0).
Implementation Plan:
- Already have
ProxyType::Eip7511- verify this covers Solady variant - Add any missing PUSH0-based patterns
- Note: Only works on Shanghai+ networks
Status: Withdrawn (superseded by EIP-2535)
Where Used:
- Legacy multi-facet contracts
- Some older upgradeable systems
How It Works: Predecessor to Diamond. Uses a manager contract to route function calls to different logic contracts based on selector.
Detection:
- Has
updateContract(address,string,string)function - Has
delegateAddress(bytes4)to query routing
Implementation Read:
// For each function selector, query the delegate
let impl = provider.call(proxy, "delegateAddress(bytes4)", selector).await?;Implementation Plan:
- Add
ProxyType::Eip1538variant - Detect via function selector presence
- Add
Dispatch::FunctionRouterthat requires per-selector queries
Status: Stagnant
Where Used:
- Security-focused upgradeable contracts
- Timelock-protected upgrades
How It Works: Adds a mandatory time delay before upgrades take effect. Uses a "next implementation" slot.
Storage Slots:
next_implementation: keccak256("eip3561.proxy.next.implementation") - 1
upgrade_time: keccak256("eip3561.proxy.upgrade.time") - 1
Implementation Plan:
- Add
ProxyType::Eip3561variant - Check for next_implementation slot
- Return both current (EIP-1967) and pending implementation
Status: Production (1,888 instances)
Where Used:
- Sequence smart wallet
- Modular wallet infrastructure
How It Works: Uses the proxy's own address as a storage key for the implementation. Most expensive proxy pattern due to storage read.
Detection:
363d3d373d3d3d363d30545af43d82803e903d91601857fd5bf3
The 30 (ADDRESS) 54 (SLOAD) sequence is distinctive - it loads from storage[address(this)].
Implementation Read:
// Implementation stored at slot = address(proxy)
let slot = U256::from_be_bytes(proxy.into());
let impl = provider.get_storage_at(proxy, slot).await?;Implementation Plan:
- Add
ProxyType::SequenceWalletvariant - Detect via bytecode pattern
- Add
Dispatch::SelfAddressSlotvariant
Status: Draft (Pectra upgrade target)
Where Used:
- Future: EOA smart account upgrades
- Account abstraction integration
How It Works:
Allows EOAs to temporarily set their code to delegate to a contract. The EOA signs an authorization that sets code = 0xef0100 || address.
Detection:
- Code starts with
0xef0100followed by 20-byte address - Only valid after Pectra upgrade
Implementation Plan:
- Add
ProxyType::Eip7702variant (post-Pectra) - Detect magic prefix
0xef0100 - Extract delegated address
Status: Draft
Where Used:
- Proposed standard for upgradeable clones
- Not yet widely deployed
How It Works:
Combines minimal proxy with upgradeability and immutable args support. Exposes implementation() on-chain.
Implementation Plan:
- Monitor adoption
- Add support when finalized
Status: Draft
Where Used:
- Hybrid clone/diamond pattern
- Experimental deployments
How It Works: Combines EIP-1967 upgradeability with horizontal extensibility (facets).
Implementation Plan:
- Monitor adoption
- Implement when patterns stabilize
Status: Production (via EIP-1967)
Where Used:
- Yearn V3 Vaults
- NFT collections
- Any "upgrade all clones at once" pattern
How It Works: Proxy stores beacon address (not implementation). Beacon provides implementation to all attached proxies.
Current Support:
Already supported via ProxyType::Eip1967Beacon and slot 0xa3f0ad74...
Gap: Need to ensure two-step resolution works:
- Read beacon address from proxy
- Call
implementation()on beacon
Status: Production (2,890 instances)
Where Used:
- 0xSplits payment splitting
- Revenue share contracts
How It Works: Custom minimal proxy with embedded split configuration.
Bytecode Pattern:
36603057343d52307f830d2d700a97af574b186c80d40429385d24241565b08a7c559ba283a964d9b160203da23d3df35b3d3d3d3d363d3d37363d73<ADDR>5af43d3d93803e605b57fd5bf3
Implementation Plan:
- Add
ProxyType::ZeroXSplitsClonesvariant - Add bytecode pattern
- Extract implementation address
- Safe Proxy detection and read
- ERC-6551 Token Bound Accounts
- Compound Unitroller pattern
- CWIA variants (all 5+)
- 0age More-Minimal Proxy
- Vyper Beta verification
- Solady PUSH0 variants
- Sequence Wallet
- 0xSplits Clones
- ERC-1538 (if demand exists)
- ERC-3561 (if demand exists)
- ERC-7702 (post-Pectra)
- ERC-7760 (when finalized)
- ERC-7546 (when finalized)
| Pattern | Detection Method | Implementation Location |
|---|---|---|
| Safe Proxy | Selector 0xa619486e in bytecode |
Storage slot 0 |
| ERC-6551 | EIP-1167 + 173 byte length | Bytecode offset 10-30 |
| Compound | Selector 0xbb82aa5e |
Storage slot 2 |
| CWIA | Variant-specific bytecode | Bytecode (varies) |
| 0age | 3d3d3d3d363d3d37363d73 prefix |
Bytecode offset 10-30 |
| Vyper Beta | 611000 in bytecode |
Bytecode offset 23-43 |
| Solady PUSH0 | 5f5f365f5f37 prefix |
Bytecode offset 9-29 |
| Sequence | 30545af4 in bytecode |
Storage[address(this)] |
| 0xSplits | Long prefix with 307f830d2d70 |
Bytecode (late offset) |
- Banteg's Minimal Proxy Compendium - Comprehensive proxy archaeology
- OpenZeppelin Proxy Docs
- EIP-1167 - Minimal Proxy
- EIP-1967 - Proxy Storage Slots
- EIP-2535 - Diamond Standard
- ERC-6551 - Token Bound Accounts
- Safe Contracts
- Compound Protocol
- Solady LibClone
- Clones With Immutable Args