Skip to content

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

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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions