Hello guys,
I would like to share with you my implementation and I would like to know if my assumption about the memory layout is correct.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
/// @title Content Distributor contract.
/// @notice Use this contract to handle all needed logic for distributors.
/// @dev This contract inherits from Ownable and ERC165, and implements the IDistributor interface.
/// Extending upgradeable contracts in a non-upgradeable contract to extend ERC-7201: Namespaced Storage Layout
/// Same as below with __gap the issue could happen using this contract as implementation and receiving delegated calls.
/// This contract can be deployed without needing to upgrade.
contract Distributor is
Initializable,
ERC165Upgradeable,
OwnableUpgradeable,
{
/// @notice The URL to the distribution.
/// Since this is a contract considered as implementation for beacon proxy,
/// we need to reserve a gap for endpoint to avoid memory layout getting mixed up.
/// https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable
string private endpoint;
/// @notice We use this method to initialize store from "BeaconProxy"
function initialize(
string memory _endpoint,
address _owner
) public initializer {
__ERC165_init();
__Ownable_init(_owner);
if (bytes(_endpoint).length == 0) revert InvalidEndpoint();
endpoint = _endpoint;
}
.....
// Reserved space for future storage variables to prevent storage conflicts
uint256[20] private __gap;
}
The factory
// SPDX-License-Identifier: MIT
// NatSpec format convention - https://docs.soliditylang.org/en/v0.5.10/natspec-format.html
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
import "./Distributor.sol";
// Each distributor has their own contract. The problem with this approach is that each contract
// has its own implementation. If in the future we need to improve the distributor contract,
// we can't deploy and upgrade each contract individually to update the implementation.
// Even worse, if the contract is not upgradeable, the implementation cannot be updated,
// requiring a new deployment, which is a significant hassle.
// The solution involves using a beacon proxy pattern:
// beaconProxy -> beacon
// -> beacon
// -> beacon -> implementation
// -> beacon
// -> beacon
contract DistributorFactory is UpgradeableBeacon, Pausable {
constructor(
address implementation,
address initialOwner
) UpgradeableBeacon(implementation, initialOwner) Pausable() {}
/// @notice Function to pause the contract, preventing the creation of new distributors.
/// @dev Can only be called by the owner of the contract.
function pause() external onlyOwner {
_pause();
}
/// @notice Function to unpause the contract, allowing the creation of new distributors.
/// @dev Can only be called by the owner of the contract.
function unpause() external onlyOwner {
_unpause();
}
function register(string calldata _endpoint) external whenNotPaused {
// not allowed duplicated endpoints
if (registry[_endpoint] != address(0))
revert DistributorAlreadyRegistered();
// initialize storage layout from Distributor contract..
bytes memory data = abi.encodeWithSignature(
"initialize(string,address)",
_endpoint, _msgSender()
);
address newContract = address(new BeaconProxy(address(this), data));
registry[_endpoint] = _msgSender();
}
}
Best Regards..