Function Selector Collision
Vulnerability where proxy and implementation contracts have identical 4-byte function signatures, causing unpredictable behavior.
Function selector collision occurs when a proxy contract and its implementation contract have functions with identical 4-byte signatures (selectors), causing ambiguity in which function should be executed. This can lead to unintended behavior, bypass of security controls, or complete system compromise.
Understanding Function Selectors
Ethereum uses the first 4 bytes of a function signature's keccak256 hash as the selector:
1// Function: transfer(address,uint256)2// Selector: bytes4(keccak256("transfer(address,uint256)")) = 0xa9059cbb34contract Example {5 function transfer(address to, uint256 amount) external {6 // Function body7 }8}
When a contract receives a call, it matches the first 4 bytes of calldata against its function selectors to determine which function to execute.
How Collisions Occur in Proxies
Proxy Management Functions
Proxy contracts typically have administrative functions like upgrade(), admin(), or implementation(). If the implementation accidentally includes functions with the same selectors, collisions occur:
1// Proxy contract2contract SimpleProxy {3 address public implementation;4 address public admin;56 function upgrade(address newImpl) external {7 require(msg.sender == admin, "Not admin");8 implementation = newImpl;9 }1011 fallback() external payable {12 _delegate(implementation);13 }14}1516// Implementation contract - COLLISION!17contract BadImplementation {18 mapping(address => uint256) private _balances;1920 // DANGEROUS: Same selector as proxy's upgrade function!21 function upgrade(address recipient) external {22 // This was meant to be "upgrade user status"23 // but collides with proxy upgrade function!24 _balances[recipient] = 1000000;25 }26}
In this example:
- Proxy's
upgrade(address)manages contract upgrades - Implementation's
upgrade(address)manages user upgrades - Both have the same selector:
bytes4(keccak256("upgrade(address)"))
The Collision Problem
When a user calls upgrade(address):
- The proxy receives the call
- It matches the selector to its own
upgradefunction - Instead of delegating to implementation, it executes proxy logic
- This bypasses the implementation's intended functionality
Types of Selector Collisions
1. Administrative Function Collisions
Most dangerous - attacker can call proxy admin functions through implementation interface:
1// Proxy admin function2function changeAdmin(address newAdmin) external;34// Implementation function (different purpose, same selector)5function changeAdmin(address userAddr) external {6 // Intended to change user admin status7 // But actually changes proxy admin!8}
2. Hidden Function Collisions
Functions with different names can have the same selector:
1// These functions have the same selector!2console.log(web3.utils.keccak256("func1(bytes)").slice(0,10)); // 0x47c0e3ab3console.log(web3.utils.keccak256("func2(bytes)").slice(0,10)); // 0x47c0e3ab45// Different function names, same selector!6function func1(bytes memory data) external {}7function func2(bytes memory info) external {}
3. Accidental Overloading Collisions
When implementation tries to override proxy functions:
1contract VulnerableImpl {2 // Trying to "override" proxy's admin function3 // But this creates a collision instead of override!4 function admin() external view returns (address) {5 return address(0x1234); // Hardcoded admin6 }7}
Real-World Collision Examples
Example 1: Parity Wallet Hack
The Parity multi-sig wallet had a collision between:
- Library's
initWallet()function - Implementation's initialization function
- Attacker called
initWallet()on the library, becoming owner - Then called
kill(), destroying the library and bricking 587 wallets
Example 2: Function Name Brute Force
Attackers can generate functions with specific selectors to create collisions:
1import itertools2from Crypto.Hash import keccak34target_selector = "0xa9059cbb" # transfer(address,uint256)56# Brute force function names with target selector7for length in range(1, 10):8 for chars in itertools.product('abcdefghijklmnopqrstuvwxyz', repeat=length):9 func_name = ''.join(chars)10 signature = f"{func_name}(address,uint256)"11 hash = keccak.new(digest_bits=256).update(signature.encode()).digest()12 selector = "0x" + hash[:4].hex()1314 if selector == target_selector:15 print(f"Found collision: {signature}")16 break
Transparent Proxy Solution
OpenZeppelin's Transparent Proxy pattern solves this by separating admin and user calls:
1contract TransparentUpgradeableProxy {2 bytes32 private constant _ADMIN_SLOT =3 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;45 modifier ifAdmin() {6 if (msg.sender == _getAdmin()) {7 _;8 } else {9 _fallback();10 }11 }1213 function upgrade(address newImplementation) external ifAdmin {14 _upgradeTo(newImplementation);15 }1617 function admin() external ifAdmin returns (address) {18 return _getAdmin();19 }2021 function implementation() external ifAdmin returns (address) {22 return _getImplementation();23 }2425 fallback() external payable {26 _fallback();27 }2829 function _fallback() internal {30 // Only delegate to implementation if not admin31 require(msg.sender != _getAdmin(), "Admin cannot call fallback");32 _delegate(_getImplementation());33 }34}
How it works:
- If caller is admin → execute proxy functions
- If caller is NOT admin → delegate to implementation
- Admin can never call implementation functions
- Users can never call proxy admin functions
Detection and Prevention
1. Automated Collision Detection
Tools to check for selector collisions:
1// Check for collisions between proxy and implementation2function checkCollisions(proxyABI, implABI) {3 const proxySelectors = new Set();4 const implSelectors = new Set();56 // Extract selectors from ABIs7 proxyABI.forEach(func => {8 if (func.type === 'function') {9 const selector = web3.utils.keccak256(func.signature).slice(0, 10);10 proxySelectors.add(selector);11 }12 });1314 implABI.forEach(func => {15 if (func.type === 'function') {16 const selector = web3.utils.keccak256(func.signature).slice(0, 10);17 if (proxySelectors.has(selector)) {18 console.log(`COLLISION DETECTED: ${func.signature}`);19 }20 }21 });22}
2. Safe Function Naming
Use prefixes or unique patterns for implementation functions:
1contract SafeImplementation {2 // Use prefixes to avoid collisions3 function impl_upgrade(address user) external {4 // Implementation-specific upgrade logic5 }67 function impl_admin() external view returns (address) {8 // Implementation-specific admin logic9 }10}
3. UUPS Pattern (User-Controlled Upgrades)
UUPS moves upgrade logic to implementation, avoiding proxy admin functions:
1contract UUPSImplementation is UUPSUpgradeable {2 function _authorizeUpgrade(address) internal override onlyOwner {3 // Upgrade authorization logic in implementation4 }56 // No collision with proxy admin functions since there are none!7 function admin() external view returns (address) {8 return owner();9 }10}
Testing for Collisions
1describe("Function Selector Collisions", function() {2 it("should detect collisions between proxy and implementation", async function() {3 const proxyInterface = await proxy.interface;4 const implInterface = await implementation.interface;56 const proxySelectors = Object.keys(proxyInterface.functions);7 const implSelectors = Object.keys(implInterface.functions);89 const collisions = proxySelectors.filter(10 selector => implSelectors.includes(selector)11 );1213 expect(collisions).to.have.length(0,14 `Found collisions: ${collisions.join(', ')}`);15 });1617 it("should route admin calls correctly", async function() {18 // Admin calling upgrade should work19 await proxy.connect(admin).upgrade(newImpl.address);2021 // User calling upgrade should delegate to implementation22 await proxy.connect(user).upgrade(user.address);23 // This should call implementation's upgrade function, not proxy's24 });25});
Mitigation Strategies
1. Use Established Proxy Patterns
- OpenZeppelin's Transparent Proxy
- UUPS (EIP-1822) pattern
- Diamond Standard (EIP-2535)
2. Function Signature Analysis
Always check for collisions during development:
1# Use tools like slither to detect collisions2slither . --detect function-selector-collisions
3. Clear Interface Separation
Keep proxy admin functions and implementation business logic completely separate:
1// Proxy admin interface2interface IProxyAdmin {3 function upgrade(address) external;4 function admin() external view returns (address);5}67// Implementation business interface8interface IToken {9 function transfer(address, uint256) external returns (bool);10 function balanceOf(address) external view returns (uint256);11}
Function selector collision is a subtle but critical vulnerability in proxy patterns. Proper detection, established patterns like Transparent Proxy, and careful interface design are essential to prevent these potentially devastating attacks.
Articles Using This Term
Learn more about Function Selector Collision in these articles:
Related Terms
Proxy Pattern
Smart contract design separating storage and logic, enabling upgrades by changing implementation while preserving state.
Delegatecall
EVM opcode that executes another contract's code in the calling contract's storage context, enabling proxy patterns and code reuse.
Implementation Contract
The logic contract containing actual business functions that executes in proxy storage context via delegatecall.
Need expert guidance on Function Selector Collision?
Our team at Zealynx has deep expertise in blockchain security and DeFi protocols. Whether you need an audit or consultation, we're here to help.
Get a Quote

