That's correct, my plan was to not have any storage variables in the implementation itself. ALL storage would live in the StorageV1 style contracts. That way there we don't have to think about __gaps. Leave that to the geniuses at OpenZeppelin :-).
I think I found a possible solution to this which leans mainly on the Compound code-copy approach and abstract contracts to keep inheritance ordered.
// Inherit all things onto an abstract contract, and add whatever storage vars you need after that
abstract contract OpenQStorageV0 is
OwnableUpgradeable,
UUPSUpgradeable,
ReentrancyGuardUpgradeable,
Oraclize
{
BountyFactory public bountyFactory;
OpenQTokenWhitelist public openQTokenWhitelist;
}
// Inherit the abstract contract onto your main implementation
// This works, as OpenQV0 overrides any necessary methods from the base contracts
contract OpenQV0 is OpenQStorageV0 {...}
// ----- MANY YEARS AND HACKS LATER -----
// Later, there's a New Base Contract you want to inherit
contract NewBaseContract {
uint256 public foo;
function setFoo(uint256 _foo) public {
foo = _foo;
}
}
// We Inherit the old storage contract, THEN we inherit from NewBaseContract, THEN we add any new custom storage variables
// I believe this maintains the storage order from before, and adds new ones after them.
// UNLESS there are dependency interactions between NewBaseContract and the old ones resulting from C3 linearization?
abstract contract OpenQStorageV1 is OpenQStorageV0, NewBaseContract {
uint256 public newStorageVar = 456;
}
// Inherit the newer abstract contract onto your main implementation
contract OpenQV1 is OpenQStorageV1 {...}
I just tried this upgrade approach using code copy + abstract contracts for storage, and it worked without messing up storage layout . I could update foo
and newStorageVar
.
So after this sequence of inheritance (NEVER putting storage vars directly in the implementation contract), I believe the storage layout of OpenQV1 would look something like this after upgrade:
[OpenZeppelin upgradable inherited contracts]
[2 OpenQStorageV0 storage variables]
after upgrade append....
[1 foo from NewBaseContract]
[1 newStorageVar from OpenQStorageV1]
Then I'd continue this chaining into the bright and storied future of the dApp...
Thoughts? Is this awful? What gotchas am I missing, or will this let me safely add both NEW CUSTOM STORAGE VARIABLES and NEW 3rd PARTY BASE CONTRACTS?