Understanding Storage and Storage Collisions¶
Let's try a set of two Smart Contracts, where one is a proxy pattern. You'll see in a second how the storage clashes in the proxy with the first variable.
Copy and paste this contract to Remix:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.1;
contract LostStorage {
address public myAddress;
uint public myUint;
function setAddress(address _address) public {
myAddress = _address;
}
function setMyUint(uint _uint) public {
myUint = _uint;
}
}
contract ProxyClash {
address public otherContractAddress;
constructor(address _otherContract) {
otherContractAddress = _otherContract;
}
function setOtherAddress(address _otherContract) public {
otherContractAddress = _otherContract;
}
fallback() external {
address _impl = otherContractAddress;
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize())
let result := delegatecall(gas(), _impl, ptr, calldatasize(), 0, 0)
let size := returndatasize()
returndatacopy(ptr, 0, size)
switch result
case 0 { revert(ptr, size) }
default { return(ptr, size) }
}
}
}
This fallback function looks slightly more complicated than the previous one, but it does essentially the same thing. Here it also can return values and throw exceptions if there were any in the target contract. A lot happened since Solidity 0.4 and 0.8...
One major difference is that the LostStorage is not inheriting the Proxy. So, internally they have separated storage layout and both start from storage slot 0.
What are we going to do with this?
- Deploy the LostStorage Contract
- Deploy the Proxy, setting the LostStorage contract address as the constructor argument
- Tell Remix that the LostStorage is running on the Proxy address
- Call
myAddress()
. It surprisingly returns a non-zero address. BAM! Collision.
That is exactly why we do inheritance with a Storage Contract, so that the Solidity compiler knows where the Storage slots are used. And we will later see that there's an elegant solution around that.
Let's start with the first EIP for Proxies!