Hardhat – How to Perform Deterministic Deployment with Hardhat

hardhathardhat-deployproxy-contractsselfdestruct

I'm trying to deploy a contract, delete it, and then deploy it again using CREATE2 with hardhat-deploy's deterministic feature.

However, I can't seem to get my contracts to have the same address. Here are my two contracts:

Box:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;

contract Box{
    function version() public pure returns (uint256) {
        return 1;
    }
    function destroy() public {
        selfdestruct(payable(msg.sender));
    }
}

BoxV2:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;

contract BoxV2{
    function version() public pure returns (uint256) {
        return 2;
    }
}

And here is my deploy and "upgrade" (destroy, and re-deploy deterministically) script:

module.exports = async ({ getNamedAccounts, deployments }) => {
    const { deploy } = deployments
    const { deployer } = await getNamedAccounts()

    const box = await deploy("Box", {
        from: deployer,
        args: [],
        deterministicDeployment: "0x1234",
    })

    const boxContract = await ethers.getContract("Box")
    await boxContract.destroy()
    
    const boxV2 = await deploy("BoxV2", {
        from: deployer,
        args: [],
        deterministicDeployment: "0x1234",
    })
    console.log(`Box address ${box.address}`)
    console.log(`BoxV2 address ${boxV2.address}`)
}

However, I get different addresses:

Box address 0x5c5457177d92B27981809C6B5a3C960E69a2Cb23
BoxV2 address 0xBCf2C4fE08453C6898E7C18eFE4AF24521196622

Best Answer

In order to use Create2 and get the same address, you have to pass the same variables:

create2(someValueToPreventCollisionWithCreate, msg.sender, salt, contractBytecode)

So if your contractBytecode is different in create2, you'll not get the same address. To make a metamorphic contract, you'll have to deploy a proxy contract that will point to your different code. Something like this:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;

// See import "@openzeppelin/contracts/proxy/Proxy.sol"; for full example
import "@openzeppelin/contracts/proxy/Proxy.sol";

contract MetamorphicProxy is Proxy {
    // Don't do this!!
    // You'll get storage clashes!
    // address public s_implementation;

    // This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
    bytes32 private constant _IMPLEMENTATION_SLOT =
        0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

    function setImplementation(address newImplementation) public {
        assembly {
            sstore(_IMPLEMENTATION_SLOT, newImplementation)
        }
    }

    function _implementation() internal view override returns (address implementationAddress) {
        assembly {
            implementationAddress := sload(_IMPLEMENTATION_SLOT)
        }
    }

    function destroy() public {
        selfdestruct(payable(msg.sender));
    }
}

And then, destroy the proxy contract and redeploy it with a different implementation address to wipe storage and redeploy.

Related Topic