Mapping has similar mechanics with only difference that every value is located separately and hash computation involves a corresponding key. You may think of possible data collisions but these
collisions are neglected as well as 256-bytes hash collisions. Contract inheritance doesn't add up to the current situation. For contracts that use inheritance, the ordering of state variables is determined by the C3-linearized order of contracts starting with the most base-ward contract.
The above-described rules are called "Layout of State Variables in Storage" (later referred to as "Storage Layout"), the details can be found and should be checked here
. Modifying these rules would eliminate backward compatibility, therefore we are unlikely to see any changes affecting smart contracts and libraries in the future.
Now that we are familiar with a proxy smart contract operation and storage layout, let's find out what can go wrong.
A particular code version, that records data in the storage of a proxy, has its own variables and storage layout. The following version will also have its storage layout and it must be capable of handling the data formed in accordance with the previous storage layout. That's half the trouble. Don't forget about the proxy code that also has a storage layout and operates in parallel with the current smart contract version that gains control. Thus, the proxy code storage layout and the current smart contract version should not interact, i.e. they cannot use the same slots for different data.
The easiest solution is to store proxy data sidestepping the usual Solidity storage layout mechanics, using EVM sstore
instructions to read and write data into the slots with pseudorandom numbers, for instance, returned by the following hash-function keccak256(my.proxy.version)
. We managed to escape collision.
Another approach is to use identical storage layouts and high-level data dispute resolution, as in github
Let's figure out what may happen if we miss two "competing" storage layouts. Have a look at the commit 3ad8eaa6f2849dceb125c8c614d5d61e90d465a2 from the AkropolisToken repository
. We performed a token sale contract security audit for that company.
We noticed that both TokenProxy
and current AkropolisToken
version have their own state variables (AkropolisToken ones are located in basic contracts
). We are expecting a collision to happen and, consequently, a big trouble ahead. However, a quick test deflates this guess. If we change the paused
flag (that is present in the AkropolisToken
inherited from Pausable
) after calling the pause
state variables won't change. TokenProxy
function calls perform correctly - calling getter functions of the TokenProxy
contract happens after addressing the token as they are defined in the proxy contract. As proxy doesn't have a pause
function, it is called via calling the default function of the basic UpgradeabilityProxy
contract that in turn executes a delegatecall
containing a pause
function. Why there's no collision still?
We'll have to concentrate and make a detailed map of TokenProxy
slots, taking into account state variable location rules described above. We have to find out the correct order of basic contracts and bear in mind possible packing of several state variables in one slot. You can test your results in Remix
: send a transaction of some record, debug it and track storage changes.
The results are as follows: