I am trying to find some way for users to deploy an erc721 contract and would like to clone an implementation contract through a contract factory like eip-1167. However when the contract factory deploys a new contract, the interaction with the transparent proxy and erc721 do not work. The interaction schema i am trying to implement is below:
My thought is that a user would be able to call the factory to create a new proxy contract and that contract would point to the transparent proxy of my erc721. I want the transparent proxy so that I can upgrade the erc721 if needed. When my contracts are deployed you are unable to interact with the proxies created from the factory. I think this is because the erc721 has an initializer that only allows it to be called once but I am not sure how to get around that…
I’m pretty new to all of this but I think my questions are:
Are two layer proxies possible?
Is metadata stored in the transparent proxy or in the contract itself?
Is there a pattern to clone erc721 contracts but modify name and symbol for each proxy created by the factory? (eip-3448?)
I am not set on this layout or implementation and would like suggestions for a better schema to achieve the same result or fixes to my current code.
I would also appreciate any clarifying explanation on the topic of proxies as I am sure I have more to learn. I also put my code below as it might help understand what I am trying to do.
ERC721
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol";
contract ERC721ControlledUpgradeable is ERC721Upgradeable, ERC721URIStorageUpgradeable,
PausableUpgradeable, AccessControlUpgradeable, ERC721BurnableUpgradeable {
using CountersUpgradeable for CountersUpgradeable.Counter;
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
CountersUpgradeable.Counter private _tokenIdCounter;
function initialize(string memory name, string memory symbol) public {
__ERC721_init(name, symbol);
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
_setupRole(PAUSER_ROLE, _msgSender());
_setupRole(MINTER_ROLE, _msgSender());
_setupRole(BURNER_ROLE, _msgSender());
}
function safeMint(address to) public {
require(hasRole(MINTER_ROLE, _msgSender()));
_safeMint(to, _tokenIdCounter.current());
_tokenIdCounter.increment();
}
function pause() public {
require(hasRole(PAUSER_ROLE, _msgSender()));
_pause();
}
function unpause() public {
require(hasRole(PAUSER_ROLE, _msgSender()));
_unpause();
}
function _baseURI() internal pure override returns (string memory) {
return "";
}
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal whenNotPaused override{
super._beforeTokenTransfer(from, to, tokenId);
}
function _burn(uint256 tokenId) internal override(ERC721Upgradeable, ERC721URIStorageUpgradeable) {
require(hasRole(BURNER_ROLE, _msgSender()));
super._burn(tokenId);
}
function tokenURI(uint256 tokenId) public view override(ERC721Upgradeable, ERC721URIStorageUpgradeable)
returns (string memory){
return super.tokenURI(tokenId);
}
function supportsInterface(bytes4 interfaceId) public view override(ERC721Upgradeable, AccessControlUpgradeable)
returns (bool){
return super.supportsInterface(interfaceId);
}
}
Contract Factory
// USES ERC 1167
pragma solidity >=0.8.0;
import "./ERC721ControlledUpgradeable.sol";
contract Factory{
address public implementation;
address[] public clonedContracts;
constructor(address _implementationAddress) {
implementation = _implementationAddress;
}
function createProxy() internal returns (address result) {
bytes20 targetBytes = bytes20(implementation);
assembly {
let clone := mload(0x40)
mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
mstore(add(clone, 0x14), targetBytes)
mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
result := create(0, clone, 0x37)
}
}
function createERC721Controlled(string memory name, string memory symbol) public {
address clone = createProxy();
ERC721ControlledUpgradeable(clone).initialize(name, symbol); // THIS IS A LITTLE FUNNY
//emit ClonedContract(clone)
clonedContracts.push(clone);
}
function getAddress(uint i) view external returns(address) {
return clonedContracts[i];
}
}
I am using truffle to deploy the erc721 and then the factory.