Function Selector Collision Remediation
Overview
The EVM dispatches external calls using a 4-byte selector derived from the function signature. With only 2^32 selectors, collisions can be found by brute force. In a proxy, a collision between an admin/upgrade function and an implementation function means a call meant for one executes the other, potentially handing an attacker admin control or running user logic in the wrong storage context.
Related Detector: Function Selector Collision
Recommended Fix
Before (Vulnerable)
// Proxy admin and implementation share selector 0x12345678
// Proxy: function upgradeAdmin(address) -> 0x12345678
// Impl: function collideFunc(address) -> 0x12345678
After (Fixed)
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
// The proxy routes calls by msg.sender:
// admin -> proxy admin functions
// others -> delegated to implementation
// Admin and implementation selectors can never be reached by the wrong caller.
A transparent proxy decides whether to run proxy logic or delegate based on msg.sender. The admin address reaches admin functions; everyone else is always delegated to the implementation. This makes selector overlap between the two surfaces irrelevant, because the dispatch decision no longer depends on the selector alone.
Alternative Mitigations
- If a non-proxy contract has two functions sharing a selector, rename one until the 4-byte selectors differ. Compute selectors during the build and fail the build on any duplicate.
- For diamond (EIP-2535) deployments, enforce a registry check that rejects adding a facet function whose selector already exists.
- Keep upgrade and admin functions on a separate contract from user-facing logic so their selector spaces never mix.
Common Mistakes
- Relying on a UUPS or minimal proxy that dispatches purely by selector, which does not resolve admin/implementation collisions.
- Assuming a collision is impossible because it is rare; selectors can be deliberately mined to collide.
- Renaming a function to dodge a collision without checking that the new name does not collide with a third function.