diff --git a/contracts/Create2Deployer.sol b/contracts/Create2Deployer.sol index c4d8c7e7..f5b1a1eb 100644 --- a/contracts/Create2Deployer.sol +++ b/contracts/Create2Deployer.sol @@ -34,9 +34,15 @@ contract Create2Deployer is Ownable, Pausable { function deploy( uint256 value, bytes32 salt, - bytes memory code + bytes memory code, + bytes memory initCall ) public whenNotPaused { - Create2.deploy(value, salt, code); + address addr = Create2.deploy(value, salt, code); + + if (initCall.length > 0) { + (bool success, bytes memory reason) = addr.call(initCall); + require(success, string(reason)); + } } /** diff --git a/contracts/mocks/OwnableMock.sol b/contracts/mocks/OwnableMock.sol new file mode 100644 index 00000000..d01fa5ba --- /dev/null +++ b/contracts/mocks/OwnableMock.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/mocks/ERC20Mock.sol + +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts/access/Ownable.sol"; + +// Mock class using ERC20 +contract OwnableMock is Ownable { + constructor() {} +} diff --git a/test/Create2.test.ts b/test/Create2.test.ts index 32211c54..e982134e 100644 --- a/test/Create2.test.ts +++ b/test/Create2.test.ts @@ -15,6 +15,7 @@ const { expect } = require("chai"); const Create2Deployer = artifacts.require("Create2Deployer"); const ERC20Mock = artifacts.require("ERC20Mock"); +const OwnableMock = artifacts.require("OwnableMock"); const ERC1820Implementer = artifacts.require("ERC1820Implementer"); contract("Create2", function (accounts) { @@ -84,7 +85,7 @@ contract("Create2", function (accounts) { constructorByteCode ); - await this.factory.deploy(0, saltHex, constructorByteCode); + await this.factory.deploy(0, saltHex, constructorByteCode, "0x"); const erc20 = await ERC20Mock.at(offChainComputed); expect(await erc20.balanceOf(deployerAccount)).to.be.bignumber.equal( @@ -92,6 +93,54 @@ contract("Create2", function (accounts) { ); }); + it("deploy a Ownable Mock with correct initial call", async function () { + const offChainComputed = computeCreate2Address( + this.factory.address, + saltHex, + OwnableMock.bytecode + ); + + const transferOwnershipAbi = OwnableMock.abi.filter( + (f: any) => f.name === "transferOwnership" && f.inputs.length === 1 + )[0]; + + const transferOwnershipCall = web3.eth.abi.encodeFunctionCall( + transferOwnershipAbi, + [deployerAccount] + ); + + await this.factory.deploy( + 0, + saltHex, + OwnableMock.bytecode, + transferOwnershipCall + ); + + const ownable = await OwnableMock.at(offChainComputed); + expect(await ownable.owner()).to.be.equal(deployerAccount); + }); + + it("deploy a Ownable Mock with wrong init call", async function () { + const transferOwnershipAbi = OwnableMock.abi.filter( + (f: any) => f.name === "transferOwnership" && f.inputs.length === 1 + )[0]; + + const wrongTransferOwnershipCall = web3.eth.abi.encodeFunctionCall( + transferOwnershipAbi, + ["0x0000000000000000000000000000000000000000"] + ); + + expectRevert( + this.factory.deploy( + 0, + saltHex, + OwnableMock.bytecode, + wrongTransferOwnershipCall + ), + "Ownable: caller is not the owner" + ); + }); + it("deploys a contract with funds deposited in the factory", async function () { const deposit = ether("2"); await send.ether(deployerAccount, this.factory.address, deposit); @@ -105,18 +154,18 @@ contract("Create2", function (accounts) { this.factory.address ); - await this.factory.deploy(deposit, saltHex, constructorByteCode); + await this.factory.deploy(deposit, saltHex, constructorByteCode, "0x"); expect(await balance.current(onChainComputed)).to.be.bignumber.equal( deposit ); }); it("fails deploying a contract in an existent address", async function () { - await this.factory.deploy(0, saltHex, constructorByteCode, { + await this.factory.deploy(0, saltHex, constructorByteCode, "0x", { from: deployerAccount, }); await expectRevert( - this.factory.deploy(0, saltHex, constructorByteCode, { + this.factory.deploy(0, saltHex, constructorByteCode, "0x", { from: deployerAccount, }), "Create2: Failed on deploy" @@ -125,14 +174,14 @@ contract("Create2", function (accounts) { it("fails deploying a contract if the bytecode length is zero", async function () { await expectRevert( - this.factory.deploy(0, saltHex, "0x", { from: deployerAccount }), + this.factory.deploy(0, saltHex, "0x", "0x", { from: deployerAccount }), "Create2: bytecode length is zero" ); }); it("fails deploying a contract if factory contract does not have sufficient balance", async function () { await expectRevert( - this.factory.deploy(1, saltHex, constructorByteCode, { + this.factory.deploy(1, saltHex, constructorByteCode, "0x", { from: deployerAccount, }), "Create2: insufficient balance"