ProxyFactory and UUPS Contract - unable to return proxy address and question about upgradability

Hi,

We would like to deploy a proxy factory for an upgradable contract.

We've chosen to go for the UUPS standard, and we used the OZ plugin. Everything seems to work fine. Then we discovered that we could not really use it in mainnet for our use case, as there is not a frontend option available for the OZ plugin.
After reading the forum, we kind of trying to come back to an old fashion way.

  1. My problem is I can not return the address of the ERC1967Proxy directly.
    The console.log(address(proxy)) in the solidity code returns address(proxy) : 0xb7a5bd0345ef1cc5e66bf61bdec17d2461fbd968 correctly. But I can not figure out why the return address(proxy) returns a transaction in test scripts and not an actual "address" as shown below.
{
  hash: '0x7571e94e1d2ff7be05066ed4bcd6926c73f5e36fd82f6b936a13016522dc4774',
  type: 2,
  accessList: [],
  blockHash: '0x3a2c4ac257e686674ff6e29d05909fa7f0f738f8964f634bc142d7b77a3cc5e5',
  blockNumber: 2,
  transactionIndex: 0,
  confirmations: 1,
  from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  gasPrice: BigNumber { _hex: '0x6aa396bc', _isBigNumber: true },
  maxPriorityFeePerGas: BigNumber { _hex: '0x3b9aca00', _isBigNumber: true },
  maxFeePerGas: BigNumber { _hex: '0x99ac6378', _isBigNumber: true },
  gasLimit: BigNumber { _hex: '0x01bae718', _isBigNumber: true },
  to: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
  value: BigNumber { _hex: '0x00', _isBigNumber: true },
  nonce: 1,
  data: '0xc55e8db1000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000bda5747bfd65f08deb54cb465eb87d40e51b197e000000000000000000000000dd2fd4581271e230360230f9337d5c0430bf44c00000000000000000000000008626f6940e2eb28930efb4cef49b2d1f2c9c1199',
  r: '0xaec2b95aeebb96a99543f6aca2166bd86e129a8aac78907bd5dfcfc03fc841c3',
  s: '0x39c1e1cebb8469358467367cf685cdf9455e75cddf40e59865d50db8c37471f4',
  v: 1,
  creates: null,
  chainId: 31337,
  wait: [Function (anonymous)]
}
    √ deploys a proxy from sign0 (108ms)

:1234: MyContract.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "hardhat/console.sol";

contract MyContract is Initializable, UUPSUpgradeable, OwnableUpgradeable  {

    //Constant can be inizialized even with Proxies
    string public constant VERSION = "0.0.1";
	uint8 public confirmationNeeded;
	address[] public owners; 
	
    function initialize(address[] memory _owners, uint8 _confirmationNeeded) public initializer {
        __Ownable_init();
        __UUPSUpgradeable_init();
		confirmationNeeded = _confirmationNeeded;
		for(uint i;i<_owners.length; i++){
			console.log(_owners[i]);
			owners.push(_owners[i]);
		}
	}

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer {}

    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}

:1234: MyProxyFactory.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "./MyContract.sol";
import "hardhat/console.sol";

contract MyProxyFactory {
	event ProxyCreated(address indexed adrs);
	address public immutable logic;

	constructor() {
		logic = address(new MyContract());
	}
	
	function createProxy(address[] memory _owners, uint8 _confirmationNeeded) public returns (address) {
		ERC1967Proxy proxy = new ERC1967Proxy(
            logic,
            abi.encodeWithSelector(MyContract(address(0)).initialize.selector, _owners, _confirmationNeeded)
        );

	    console.log("address(proxy) : ", address(proxy));
		emit ProxyCreated(address(proxy));
		return address(proxy);
	}
}

:computer: Environment

"@openzeppelin/contracts": "^4.5.0",
"@openzeppelin/contracts-upgradeable": "^4.5.2",
"@openzeppelin/hardhat-upgrades": "^1.17.0",
"@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers",
"@nomiclabs/hardhat-etherscan": "^3.0.0",
"@nomiclabs/hardhat-solhint": "^2.0.0",
"@nomiclabs/hardhat-waffle": "^2.0.0",
"hardhat": "^2.9.3",
  1. My second question is about upgrading.
    Assuming we manage to store the addresses of whole proxies in the Proxy Factory, is it possible to upgrade MyContrat with OZ plugin ? I mean, with the normal script like in tutorial ?
const myProxyUpdated = await upgrades.upgradeProxy(PROXY, MyContractV2); 
await myProxyV2.deployed();

It will probably cost a lot but still...

Thank you for having read me, and thanks for your help.

Hi @MarcOreliOoo,

Since you mention "frontend option", check out OpenZeppelin Defender (and particularly the Admin component) to see if that is what you are looking for.

For 1., what does your test script look like?

For 2., you can use the forceImport command to import a proxy (even if the proxy was deployed by a factory). Then you can use it with the upgrades plugin afterwards.

1 Like

Hi @ericglau,

Thx for the advice, I'll check out.

For 1. My bad, forget that one :

import { expect } from "chai";
import "@nomiclabs/hardhat-ethers";
import { ethers } from "hardhat";
import MyProxyFactory from "../src/artifacts/contracts/MyProxyFactory.sol/MyProxyFactory.json";
import MyContract from "../src/artifacts/contracts/MyContract.sol/MyContract.json";


describe('MyProxyFactory creationProxy', function () {

	it("deploys a proxy from sign0", async function(){	
		const addresses = ["0xbDA5747bFD65F08deb54cb465eB87D40e51B197E","0xdD2FD4581271e230360230F9337D5c0430Bf44C0","0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199"];
		const confirmation = 2;
		
		const [sign0, sign1, sign2] = await ethers.getSigners();
		const MyProxyFactory = await ethers.getContractFactory("MyProxyFactory");
		const proxyFactory = await MyProxyFactory.deploy();

		await proxyFactory.deployed();

		const proxyCreated = await proxyFactory.createProxy(addresses,confirmation);
		console.log(proxyCreated); //Returns a transaction

		//If I continue like this, with the true proxy address, it works fine

		// const MyContract = await ethers.getContractFactory("MyContract");
		// const contract = await MyContract.attach("0x9f1ac54bef0dd2f6f3462ea0fa94fc62300d3a8e");
		// expect(await proxyContract.confirmationNeeded()).to.equal(2); //true
		// expect(await proxyContract.owners(1)).to.equal("0xdD2FD4581271e230360230F9337D5c0430Bf44C0"); //False and dont know why yet
	});
});

For 2. thank you, I'll check that too.
One more question if I can, does these previous implementation seems legit for an OZ professional perpective ?

Thank you again !

Your createProxy function creates a transaction and does not return the value outside of Solidity. Instead, you can watch for the ProxyCreated event. See https://ethereum.stackexchange.com/a/109860 for an explanation and https://ethereum.stackexchange.com/a/93804 for a code example for Hardhat.

1 Like