Bubble up returndata from reverted Create2 deployments (#5052)
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com> Co-authored-by: ernestognw <ernestognw@gmail.com>
This commit is contained in:
committed by
GitHub
parent
52e0e3e783
commit
984233dcad
5
.changeset/nervous-eyes-teach.md
Normal file
5
.changeset/nervous-eyes-teach.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'openzeppelin-solidity': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
`Create2`: Bubbles up returndata from a deployed contract that reverted during construction.
|
||||||
34
contracts/mocks/ConstructorMock.sol
Normal file
34
contracts/mocks/ConstructorMock.sol
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -44,6 +44,11 @@ library Create2 {
|
|||||||
/// @solidity memory-safe-assembly
|
/// @solidity memory-safe-assembly
|
||||||
assembly {
|
assembly {
|
||||||
addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
|
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)) {
|
if (addr == address(0)) {
|
||||||
revert Errors.FailedDeployment();
|
revert Errors.FailedDeployment();
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
const { ethers } = require('hardhat');
|
const { ethers } = require('hardhat');
|
||||||
const { expect } = require('chai');
|
const { expect } = require('chai');
|
||||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||||
|
const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
|
||||||
|
|
||||||
|
const { RevertType } = require('../helpers/enums');
|
||||||
|
|
||||||
async function fixture() {
|
async function fixture() {
|
||||||
const [deployer, other] = await ethers.getSigners();
|
const [deployer, other] = await ethers.getSigners();
|
||||||
@ -19,7 +22,9 @@ async function fixture() {
|
|||||||
.getContractFactory('$Create2')
|
.getContractFactory('$Create2')
|
||||||
.then(({ bytecode, interface }) => ethers.concat([bytecode, interface.encodeDeploy([])]));
|
.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 () {
|
describe('Create2', function () {
|
||||||
@ -130,5 +135,56 @@ describe('Create2', function () {
|
|||||||
.to.be.revertedWithCustomError(this.factory, 'InsufficientBalance')
|
.to.be.revertedWithCustomError(this.factory, 'InsufficientBalance')
|
||||||
.withArgs(0n, 1n);
|
.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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user