Closed mrmemes-eth closed 1 year ago
Hello @mrmemes-eth
I'm sorry but I am not familiar with foundry's tooling and I don't fully understand what the conflit is here.
Can you point out which version version of OZ contracts you used to work with, and which one you updated to?
Hi @Amxx, thanks for the response. The top (above the ---
) is the contract's storage layout prior to adding EIP712Upgradeable
. The bottom (below the ---
) is the contract's storage layout after adding it. The third column is the storage slot number and you can see that the variable (column 1) and the type (column 2) are changed by the addition of EIP712Upgradeable
for the variables in storage slots 352 and 353.
Does that do a better job of explaining the output?
The contracts used v4.6.0 when they were originally created, but this storage slot conflict doesn't seem to be dependent on that version. I can deploy a new contract on 4.8.1, add EIP712Upgradeable
and will experience the same problem with conflicts at these slots.
Focusing solely on the ERC20, as the simpler case, adding EIP712Upgradeable
tries to store _HASHED_NAME
and _HASHED_VERSION
and 352 and 353, respectively. The layout of this contract has the following variables in those slots:
Which would be overwritten as a result.
Here's the commit in question that demonstrates the only changes necessary to cause this storage layout problem: https://github.com/JoinOrigami/crane/pull/5/commits/d45d0648de70da58c492a6688317f257168494bd
The top (above the ---) is the contract's storage layout prior to adding EIP712Upgradeable. The bottom (below the ---) is the contract's storage layout after adding it.
If I understand correctly, you are saying that you have
And A and B are not storage compatible?
That is probably because you updated you contract in a way that inserts EIP712Upgradeable in the middle of the storage of A.
The simplest option to achieve that is to keep A (the old version) in your codebase, and write B (the new version) as
contract B is A, EIP712Upgradeable {
…
}
Hi @Amxx, no, this is the same contract, just before and after adding EIP712Upgradeable
. Merely adding EIP712Upgradeable
as a base contract, at the end of the existing base contracts demonstrates this issue. Please see https://github.com/JoinOrigami/crane/pull/5/commits/d45d0648de70da58c492a6688317f257168494bd for the exact code change that introduces this issue.
Your contract includes some storage variable of its own, which are after all the storage variables from all the parents. When you add a new parent, its storage it placed BEFORE these variable which causes the issue.
To simplify lets imagine your have
contract A { uint256 a; }
contract B { uint256 b; }
contract C { uint256 c; }
contract T1 is A, B { uint256 t; }
contract T2 is A, B, C { uint256 t; }
the storage layout of T1 will be:
The storage layout of T2 will be
You see that adding the inheritance on C "pushed" the storage, and created the inconsistency
That is what I'm saying your need to keep the "old" version (T1), and you need to write the new version (T2) as
contract T2 is T1, C {}
Closing since this is expected Solidity behavior. Please apply the workaround shared by @Amxx.
Closing since this is expected Solidity behavior. Please apply the workaround shared by @Amxx.
I understand very well that the storage layout can get pushed. However, isn't the goal of the storage gaps in the OZ upgradeable contracts to prevent the addition of OZ dependencies from clobbering existing storage allocations? This seems like a problem that prevents the upgrade-safe contracts for actually being upgrade-safe.
The gaps are here so we can add variables in a contract while simultaneously shirking the gap. They are not here so we can add new contracts.
Is you case we would need to reduce the gap in Votes to accommodate the storage needs of EIP712Upgradeable. I'm sure you understand that we can't do such a change as it would affect everyone and would be breaking for users don't plan on doing the same changes as you do.
Introducing the EIP712Upgradeable base contract creates a storage layout conflict at slot 352.
💻 Environment
Openzeppelin is pegged to v4.8.1 and we use
foundry
for our tooling.📝 Details
We're using
forge inspect --pretty
for analyzing storage layout and comparing them with an existing known storage layout usingdiff -a --suppress-common-lines
.For an ERC721 NFT of ours, we see this conflict:
And for an ERC20 token of ours we see this conflict:
With both of these diffs, you can see conflicts in storage slot 352 (and 353 as well for the ERC721).
🔢 Code to reproduce bug
I have a Pull Request open on my own repository that reproduces the issue. The only change (as shown by the diff) is the addition of EIP712 as a contract base and the calls to its init.