wighawag / hardhat-deploy

hardhat deployment plugin
MIT License
1.18k stars 286 forks source link

Support for contracts deployed from another contract's constructor #391

Open sebastianst opened 1 year ago

sebastianst commented 1 year ago

Problem Say I have a contract A that, inside it's constructor, deploys a contract B. Now if I deploy A using hardhat-deploy, artifacts are generated, the contract is verified on etherscan etc. but the deployment of B is not known to hardhat-deploy.

Proposed Solution It would be awesome if one could configure the deployment of A in a way to make it known to hardhat-deploy that this will also deploy a contract B, whose address can be read from A using function getB() and then have all the artifacts created etc as if hardhat-deploy deployed B itself. E.g. this could be configured by extending the DeployFunction with a field imlpicitDeployments of the form

func.imlpicitDeployments = [
  {
    contract: 'B',
    addressGetter: (hre) => {
      const a = await hre.deployments.get('A');
      return a.getB();
    }
  }
]

That is, each object in the implicitDeployments array would have a string field contract and a field addressGetter that is a function(HardhatRuntimeEnvironment): Promise<string>. Of course, this is just a rough sketch.

Alternatives I've considered I can use the usual contract dependencies to split the deployments of A and B. E.g. I first deploy B and make it a dependency of A, then in A's deployment I read the address of B and pass it to the constructor of A.

Additional context There are situations where two contacts depend on each other. E.g. take the following minimal example

contract A is Ownable {
    address public b;

    function setB(address _b) external onlyOwner {
        b = _b;
    }
}

contract B {
    address public immutable a;

    constructor(address _a) {
        a = _a;
    }
}

Now I have to 1. deploy A, 2. deploy B passing the address of A to its construtor and 3. call A.setB() with the address of B. This can also nicely be done with contract dependencies.

An alternative solution is to have the following alternative constructor for A:

contract AWithBDeployment is A {
    constructor() {
        b = address(new B(address(this));
    }
}

Now all I have to do it deploy A and B will be automatically deployed and both contracts are setup correctly. However, B would be invisible to hardhat-deploy and no artifacts generated, no etherscan verification etc.