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 and
sload in structions 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 function,
TokenProxy 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 in
AkropolisToken containing a
pause function. Why there's no collision still?
We'll have to concentrate and make a detailed map of
TokenProxy and
AkropolisToken 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: