Hey @NethDote, if I'm understanding correctly, you're trying to make clones of a single implementation.
This pattern is usually called a "Factory" and can be accomplished by using the Clones library with an upgradeable contract although it's not required to be upgradeable. The reason for using an upgradeable contract behind for clones is that it allows initializing the contract in the way you're trying to make.
The code for creating a token factory that uses Clones
and leverages ERC20Upgradeable
's initializer:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import "@openzeppelin/contracts/proxy/Clones.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
contract Token is ERC20Upgradeable {
constructor() {
_disableInitializers();
}
function initialize(
string memory name_,
string memory symbol_
) public initializer {
__ERC20_init(name_, symbol_);
}
}
contract Factory {
Token private _token;
event TokenCreated(address indexed token, string name, string symbol);
constructor() {
_token = new Token();
}
function create(
string memory name,
string memory symbol
) external returns (Token) {
Token token = Token(Clones.clone(address(_token)));
token.initialize(name, symbol);
emit TokenCreated(address(token), _token.name(), _token.symbol());
return token;
}
}
And a Foundry test:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import "forge-std/Test.sol";
import "../src/Factory.sol";
contract FactoryTest is Test {
Factory private _factory;
function setUp() public {
_factory = new Factory();
}
function testMultipleClones() public {
// Clone A
Token a = _factory.create("A", "a");
// Clone B
Token b = _factory.create("B", "b");
// Assertions
assertNotEq(address(a), address(b));
assertEq(a.name(), "A");
assertEq(b.symbol(), "b");
assertEq(a.name(), "A");
assertEq(b.symbol(), "b");
}
}
In case you want to make the clones actually upgradeable as well, you may want to take a look to the BeaconProxy
pattern where a single upgradeable implementation lives in an UpgradeableBeacon
that allows upgrading all dependant clones.
I can see that a non-upgradeable minimal clone is available in "@openzeppelin/contracts/proxy/Clones.sol", but that doesn't have the same feature of calling an initialize function with (_logic, admin, data)
like the TransparentUpgradeableProxy
does, so I'd have to code that all myself.
Would you mind clarifying this? There's no reason for having an initialize
function in the proxy, perhaps you're referring to the constructor but I don't see how that'd be an issue
Perhaps it will be easiest to retain the upgradeable contracts just as they are, and I just make sure nobody can call .upgradeToAndCall(implementation, data)
any time after the first deployment of the proxy (which is very easy to do).
Every upgradeable pattern we support has its own way of ensuring nobody can call the upgradeToAndCall
function arbitrarily.
TransparentUpgradeableProxy
: It requires a ProxyAdmin
that (after oz contracts 5.0) will be deployed with each transparent proxy, that instance is the only address able to upgrade and it's an ownable contract
BeaconProxy
: It requires an UpgradeableBeacon
that's also ownable and follows a similar pattern to the ProxyAdmin.
- UUPS: This is the most flexible pattern since the upgrade functions live in the implementation and you need to make sure to keep permissions on each upgrade.
My recommendation is to take a look to our upgrades plugins, where you can find documentation and tools to deploy upgradeable contracts securely without much hassle. Also you can find examples of how to use with a Beacon proxy.