Quellcode durchsuchen

Bubble up `returndata` from reverted Create2 deployments (#5052)

Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
Co-authored-by: ernestognw <ernestognw@gmail.com>
Dimitrios Papathanasiou vor 1 Jahr
Ursprung
Commit
984233dcad

+ 5 - 0
.changeset/nervous-eyes-teach.md

@@ -0,0 +1,5 @@
+---
+'openzeppelin-solidity': minor
+---
+
+`Create2`: Bubbles up returndata from a deployed contract that reverted during construction.

+ 34 - 0
contracts/mocks/ConstructorMock.sol

@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.20;
+
+contract ConstructorMock {
+    bool foo;
+
+    enum RevertType {
+        None,
+        RevertWithoutMessage,
+        RevertWithMessage,
+        RevertWithCustomError,
+        Panic
+    }
+
+    error CustomError();
+
+    constructor(RevertType error) {
+        // After transpilation to upgradeable contract, the constructor will become an initializer
+        // To silence the `... can be restricted to view` warning, we write to state
+        foo = true;
+
+        if (error == RevertType.RevertWithoutMessage) {
+            revert();
+        } else if (error == RevertType.RevertWithMessage) {
+            revert("ConstructorMock: reverting");
+        } else if (error == RevertType.RevertWithCustomError) {
+            revert CustomError();
+        } else if (error == RevertType.Panic) {
+            uint256 a = uint256(0) / uint256(0);
+            a;
+        }
+    }
+}

+ 5 - 0
contracts/utils/Create2.sol

@@ -44,6 +44,11 @@ library Create2 {
         /// @solidity memory-safe-assembly
         assembly {
             addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
+            // if no address was created, and returndata is not empty, bubble revert
+            if and(iszero(addr), not(iszero(returndatasize()))) {
+                returndatacopy(0, 0, returndatasize())
+                revert(0, returndatasize())
+            }
         }
         if (addr == address(0)) {
             revert Errors.FailedDeployment();

+ 57 - 1
test/utils/Create2.test.js

@@ -1,6 +1,9 @@
 const { ethers } = require('hardhat');
 const { expect } = require('chai');
 const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
+const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
+
+const { RevertType } = require('../helpers/enums');
 
 async function fixture() {
   const [deployer, other] = await ethers.getSigners();
@@ -19,7 +22,9 @@ async function fixture() {
     .getContractFactory('$Create2')
     .then(({ bytecode, interface }) => ethers.concat([bytecode, interface.encodeDeploy([])]));
 
-  return { deployer, other, factory, constructorByteCode, constructorLessBytecode };
+  const mockFactory = await ethers.getContractFactory('ConstructorMock');
+
+  return { deployer, other, factory, constructorByteCode, constructorLessBytecode, mockFactory };
 }
 
 describe('Create2', function () {
@@ -130,5 +135,56 @@ describe('Create2', function () {
         .to.be.revertedWithCustomError(this.factory, 'InsufficientBalance')
         .withArgs(0n, 1n);
     });
+
+    describe('reverts error thrown during contract creation', function () {
+      it('bubbles up without message', async function () {
+        await expect(
+          this.factory.$deploy(
+            0n,
+            saltHex,
+            ethers.concat([
+              this.mockFactory.bytecode,
+              this.mockFactory.interface.encodeDeploy([RevertType.RevertWithoutMessage]),
+            ]),
+          ),
+        ).to.be.revertedWithCustomError(this.factory, 'FailedDeployment');
+      });
+
+      it('bubbles up message', async function () {
+        await expect(
+          this.factory.$deploy(
+            0n,
+            saltHex,
+            ethers.concat([
+              this.mockFactory.bytecode,
+              this.mockFactory.interface.encodeDeploy([RevertType.RevertWithMessage]),
+            ]),
+          ),
+        ).to.be.revertedWith('ConstructorMock: reverting');
+      });
+
+      it('bubbles up custom error', async function () {
+        await expect(
+          this.factory.$deploy(
+            0n,
+            saltHex,
+            ethers.concat([
+              this.mockFactory.bytecode,
+              this.mockFactory.interface.encodeDeploy([RevertType.RevertWithCustomError]),
+            ]),
+          ),
+        ).to.be.revertedWithCustomError({ interface: this.mockFactory.interface }, 'CustomError');
+      });
+
+      it('bubbles up panic', async function () {
+        await expect(
+          this.factory.$deploy(
+            0n,
+            saltHex,
+            ethers.concat([this.mockFactory.bytecode, this.mockFactory.interface.encodeDeploy([RevertType.Panic])]),
+          ),
+        ).to.be.revertedWithPanic(PANIC_CODES.DIVISION_BY_ZERO);
+      });
+    });
   });
 });