Skip to content

v2 implementation with constructor() { _disableInitializer(); } is flagged as unsafe #102

@CJ42

Description

@CJ42

Description of the issue

I have the following two contracts: MyContractV1 and MyContractV2 as shown below.

When I run the Upgrades plugin from the "openzeppelin-foundry-upgrades/LegacyUpgrades.sol" library, the plugin warns that the new implementation MyContractV2 is unsafe because it contains a constructor. However, this seems incorrect as this new implementation simply contains a constructor to prevent the implementation from being initialized (the standard pattern with proxy + logic contracts).

Is this a false positive? Or is there something I am missing here? If I add in the Option the options.unsafeAllow = "constructor";, the error goes away.

Image

Configurations

  • using @openzeppelin/contracts v4.9.6
  • using openzeppelin-foundry-upgrades v0.4.0

Examples contracts to reproduce the issue

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

import {
    OwnableUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

contract MyContractV1 is OwnableUpgradeable {
    uint256 public value;

    constructor() {
        _disableInitializers();
    }

    function initialize() public initializer {
        __Ownable_init();
    }

    function setValue(uint256 _value) public onlyOwner {
        value = _value;
    }
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;

import {
    OwnableUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

contract MyContractV2 is OwnableUpgradeable {
    uint256 public value;
    string public name;

    constructor() {
        _disableInitializers();
    }

    function initialize() public initializer {
        __Ownable_init();
    }

    function setValue(uint256 _value) public onlyOwner {
        value = _value;
    }

    function setName(string memory _name) public onlyOwner {
        name = _name;
    }
}

Test to run to reproduce the issue

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

import {Test, console} from "forge-std/Test.sol";

import {
    Upgrades,
    Options
} from "openzeppelin-foundry-upgrades/LegacyUpgrades.sol";

import {
    TransparentUpgradeableProxy,
    ITransparentUpgradeableProxy as IProxy
} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

import {MyContractV1} from "../src/MyContractV1.sol";

contract OZUpgradeExampleTest is Test {
    address proxyAdmin;
    MyContractV1 implementationV1;

    IProxy proxy;

    function setUp() public {
        proxyAdmin = address(11);
        implementationV1 = new MyContractV1();

        bytes memory initializeCalldata = abi.encodeCall(
            implementationV1.initialize,
            ()
        );

        proxy = IProxy(
            address(
                new TransparentUpgradeableProxy(
                    address(implementationV1),
                    proxyAdmin,
                    initializeCalldata
                )
            )
        );
    }

    function test_deployNewImplementationAndUpgrade() public {
        Options memory options;

        options.referenceContract = "MyContractV1.sol";

        // uncommenting this make the error goes away
        // options.unsafeAllow = "constructor";

        Upgrades.upgradeProxy(
            address(proxy),
            "MyContractV2.sol",
            "",
            options,
            proxyAdmin
        );
    }
}

Command to run:

forge test --match-contract OZUpgradeExampleTest -vvv

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions