Cannot upgrade transparent proxy with ProxyAdmin

Hello there,

I am using open-zeppelin Transparent Proxy contract to upgrade the contracts, Here is my deployment script.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {MachineRegistry} from "../src/MachineRegistry.sol";
import {IMachineRegistry} from "../src/IMachineRegistry.sol";

import "forge-std/Test.sol";
import "forge-std/Script.sol";
import "forge-std/StdJson.sol";
import "forge-std/console.sol";

// forge script script/DeployMachineRegistry.s.sol:DeployMachineRegistry --rpc-url http://localhost:8545  --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --broadcast -vvvv
contract DeployMachineRegistry is Script {
    function run() external {
        vm.startBroadcast();

        address owner = msg.sender;
        ProxyAdmin proxyAdmin = new ProxyAdmin(owner);

        MachineRegistry machineRegistryImplementation = new MachineRegistry();

        MachineRegistry machineRegistryProxy = MachineRegistry(
            address(
                new TransparentUpgradeableProxy(
                    address(machineRegistryImplementation),
                    address(proxyAdmin),
                    ""
                )
            )
        );

        console.log("ProxyAdmin: ", address(proxyAdmin));
        console.log("MachineRegistryProxy: ", address(machineRegistryProxy));

        proxyAdmin.upgradeAndCall(
            ITransparentUpgradeableProxy(address(machineRegistryProxy)),
            address(machineRegistryImplementation),
            abi.encodeWithSelector(
                machineRegistryProxy.initialize.selector,
                owner
            )
        );

        console.log("ProxyAdmin: ", address(proxyAdmin));
        console.log("MachineRegistryProxy: ", address(machineRegistryProxy));
        console.log(
            "MachineRegistryImplementation: ",
            address(machineRegistryImplementation)
        );
    }
}

It fails with empty reward data

    ├─ [3042] ProxyAdmin::upgradeAndCall(TransparentUpgradeableProxy: [0xF5b81Fe0B6F378f9E6A3fb6A6cD1921FCeA11799], MachineRegistry: [0x74ef2B06A1D2035C33244A4a263FF00B84504865], 0xc4d66de8000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266)
    │   ├─ [1382] TransparentUpgradeableProxy::upgradeToAndCall(MachineRegistry: [0x74ef2B06A1D2035C33244A4a263FF00B84504865], 0xc4d66de8000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266)
    │   │   ├─ [0] console::log(ProxyAdmin: [0xe14058B1c3def306e2cb37535647A04De03Db092]) [staticcall]
    │   │   │   └─ ← [Stop] 
    │   │   ├─ [0] console::log(ProxyAdmin: [0xd33Dca619a78F1E120d2d1e5759b5F8D47C34529]) [staticcall]
    │   │   │   └─ ← [Stop] 
    │   │   ├─ [171] MachineRegistry::upgradeToAndCall(MachineRegistry: [0x74ef2B06A1D2035C33244A4a263FF00B84504865], 0xc4d66de8000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266) [delegatecall]
    │   │   │   └─ ← [Revert] EvmError: Revert
    │   │   └─ ← [Revert] EvmError: Revert
    │   └─ ← [Revert] EvmError: Revert
    └─ ← [Revert] EvmError: Revert

== Logs ==
  ProxyAdmin line 21:  0xd33Dca619a78F1E120d2d1e5759b5F8D47C34529
  ProxyAdmin line 22:  0xe14058B1c3def306e2cb37535647A04De03Db092
  ProxyAdmin:  0xe14058B1c3def306e2cb37535647A04De03Db092
  MachineRegistryProxy:  0xF5b81Fe0B6F378f9E6A3fb6A6cD1921FCeA11799
  0xe14058B1c3def306e2cb37535647A04De03Db092
  0xd33Dca619a78F1E120d2d1e5759b5F8D47C34529
Error: 
script failed: <empty revert data>

Then i had deep dive and found msg.sender == _proxyAdmin() not same. Because of TransparentUpgradeableProxy constructor creating a new ProxyAdmin instance. If i am remove instance new ProxyAdmin from the constructor it deploys.

    constructor(address _logic, address initialOwner, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
        _admin = address(new ProxyAdmin(initialOwner));
contract TransparentUpgradeableProxy is ERC1967Proxy {
...
    function _fallback() internal virtual override {
        console.log(msg.sender);
        console.log(_proxyAdmin());
        if (msg.sender == _proxyAdmin()) {
            if (msg.sig != ITransparentUpgradeableProxy.upgradeToAndCall.selector) {
                revert ProxyDeniedAdminAccess();
            } else {
                _dispatchUpgradeToAndCall();
            }
        } else {
            super._fallback();
        }
    }

Please help to deploy, and upgrade.

Library version: v5.0.1

Hi @bhautik,

In OpenZeppelin Contracts v5, the second parameter of the TransparentUpgradeableProxy constructor should be the owner address that you want to use for the proxy admin that it creates. Do not pass in an actual ProxyAdmin address for this parameter.

For example, your script can be changed to look like:

        address owner = msg.sender;

        MachineRegistry machineRegistryImplementation = new MachineRegistry();

        MachineRegistry machineRegistryProxy = MachineRegistry(
            address(
                new TransparentUpgradeableProxy(
                    address(machineRegistryImplementation),
                    owner,
                    ""
                )
            )
        );

or you could directly use the deployTransparentProxy function from Upgrades.sol, for example:

address owner = msg.sender;

MachineRegistry machineRegistryProxy = MachineRegistry(
  Upgrades.deployTransparentProxy(
    "MachineRegistry.sol",
    owner,
    ""
);

Also consider specifying the actual owner address in address owner = ... instead of using msg.sender, if you want to set a specific owner.

Hola @ericglau!

But now i want to upgrade, how can i becase now i don't have ProxyAdmin instance.

If you deployed the proxy like in my example, the TransparentUpgradeableProxy creates its own instance of ProxyAdmin. You can get the address of that ProxyAdmin using Upgrades.getAdminAddress()