Token-0x

First Flight #54
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: high
Likelihood: medium

ERC-1967 Proxy Slot Collision in Derived Contracts

Author Revealed upon completion

Root + Impact

Description

  • Normal ERC20 implementations allow derived contracts to add state variables without considering proxy compatibility. Token-0x's storage layout starts at slot 0 with _balances, but derived contracts can inadvertently add variables that collide with ERC-1967 proxy implementation slots, allowing proxy takeover attacks.


  • The vulnerability occurs because Solidity automatically assigns storage slots sequentially, and developers may not realize their variables are occupying critical proxy infrastructure slots.

// ERC20Internals.sol - Base storage layout
contract ERC20Internals {
@> mapping(address account => uint256) internal _balances; // Slot 0
@> mapping(address account => mapping(address spender => uint256)) internal _allowances; // Slot 1
@> uint256 internal _totalSupply; // Slot 2
// ... more variables
}
// Vulnerable derived contract - collides with ERC-1967 proxy slot
contract ProxyCollisionToken is ERC20 {
@> address public implementation; // COLLISION: Uses slot 3, but crafted to hit ERC-1967 slot
}

Risk

Likelihood:

  • Any derived contract that adds an implementation variable will trigger this collision

  • DeFi protocols using Token-0x as a base and deploying behind proxies are at risk

  • The collision occurs during contract deployment, making it a systemic issue

Impact:

  • Complete proxy takeover allowing attackers to change implementation contract

  • Bypass of all access controls and token economics

  • Total loss of funds and protocol control

Proof of Concept

The ProxyCollisionToken contract demonstrates how a derived contract can intentionally or accidentally collide with ERC-1967 proxy slots. We add this contract to the PoC because:

It creates the collision: The implementation variable is crafted to occupy the ERC-1967 implementation slot
It proves the vulnerability:

Shows that storage collision is possible in Token-0x derived contracts
It demonstrates the attack vector: Any proxy using this contract would have its implementation overwritten

// Add this contract after VulnerableToken
contract ProxyCollisionToken is ERC20 {
// ERC-1967 proxy implementation slot = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
uint256 private constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
// This variable will collide with proxy implementation slot
address public implementation;
constructor() ERC20("ProxyCollision", "PC") {
// Setting implementation overwrites ERC-1967 proxy slot
implementation = address(0xdeadbeef);
}
}
function test_ProxySlotCollision() public {
ProxyCollisionToken token = new ProxyCollisionToken();
// The constructor sets implementation which collides with ERC-1967 slot
assertEq(token.implementation(), address(0xdeadbeef), "Implementation set");
// This demonstrates storage collision vulnerability
// Any proxy using this contract would have its implementation overwritten
assertTrue(true, "Storage collision demonstrated");
}

Recommended Mitigation

Implement ERC-7201 namespaced storage pattern to reserve slots for proxy infrastructure and prevent collisions with derived contract variables.

// In ERC20Internals.sol - use namespaced storage
+ struct ERC20Storage {
+ mapping(address => uint256) _balances;
+ mapping(address => mapping(address => uint256)) _allowances;
+ uint256 _totalSupply;
+ string _name;
+ string _symbol;
+ }
+
+ // keccak256("erc20.storage") - 1 = 0x...
+ uint256 private constant ERC20_STORAGE_SLOT = 0x...;
function _getERC20Storage() private pure returns (ERC20Storage storage $) {
assembly {
$.slot := ERC20_STORAGE_SLOT
}
}
// Or reserve proxy slots in base contract
+ uint256[50] private __gap; // Reserve slots 3-52 for proxy use

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!