I've deployed a contract using the UUPS pattern and the file has become really long, so I want to refactor it and make it more modular and readable. In order to do so, I've decided to pull my storage variables out of the main contract and inherit them from another contract.
But I'm questioning whether I'm architecting this correctly. For simplicity, I've made some pseudo-code to explain my situation.
Version 1 of "Main" that's already deployed
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { EnternalInterface } from "./EnternalInterface.sol";
contract Main is Initializable, UUPSUpgradeable {
string firstName;
ExternalContractInterface externalContract;
function initialize(string memory _firstName, address externalContractAddress) initializer public {
__UUPSUpgradeable_init();
firstName = _firstName;
externalContract = EnternalInterface(externalContractAddress)
}
function getFirstName() public returns(string memory){
return firstName;
}
function _authorizeUpgrade(address newImplementation)
internal
override
{}
}
Me, one month later: " now that this file is >300 lines of code, I should pull out the storage variables and create a "data holder" contract. I'll deploy a new implementation
Version 2 of "Main" (which inherits the data holder contract)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { DataHolder } from "./DataHolder.sol";
import { ExternalInterface } from "./ExternalInterface.sol";
contract Main is Initializable, UUPSUpgradeable, DataHolder {
function getFirstName() public returns(string memory){
return firstName;
}
function getExternalContractsAddress() public {
return address(externalContract)
}
function _authorizeUpgrade(address newImplementation)
internal
override
{}
}
Data holder contract
pragma solidity ^0.8.16;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {ExternalInterface} from "./ExternalInterface.sol";
contract DataHolder is Initializable, UUPSUpgradeable {
string firstName;
ExternalInterface externalContract;
function initialize(string memory _firstName, address externalContractAddress) initializer public {
__UUPSUpgradeable_init();
firstName = _firstName;
externalContract = ExternalInterface(externalContractAddress)
}
function _authorizeUpgrade(address newImplementation)
internal
override
{}
}
Questions:
- Whenever I want to abstract data out of a contract and have another contract inherit it - in the new contract, do I always need to create an initializer in the new contract? (it seems like if I don't, I'll lose upgradeability)
- In the DataHolder, if I don't use the initializer to set
firstName
andexternalContract
wouldn't I lose access to those variables? Or are they still stored in the proxy?
I'd love any other general feedback on how to separate storage from a main contract!