diff --git a/src/create3/Create3Factory.sol b/src/create3/Create3Factory.sol index 4ef70e3..b4298aa 100644 --- a/src/create3/Create3Factory.sol +++ b/src/create3/Create3Factory.sol @@ -9,6 +9,9 @@ import { UUPSUpgradeable } from "../dependencies/proxy/utils/UUPSUpgradeable.sol import { Ownable } from "../dependencies/access/Ownable.sol"; import { IERC20 } from "../dependencies/token/interfaces/IERC20.sol"; +/// @title Intent-based CREATE3 factory: no nonce tracking, no salt storage. +/// @author chainrule.eth +/// @notice Salts are signature-derived and child address is constructor argument independent. contract Create3Factory is Initializable, UUPSUpgradeable, Ownable { // Private Constants: no SLOAD to save users gas address private constant CONTRACT_DEPLOYER = 0x0a5B347509621337cDDf44CBCf6B6E7C9C908CD2; @@ -24,8 +27,8 @@ contract Create3Factory is Initializable, UUPSUpgradeable, Ownable { event Deploy( address indexed principal, address indexed child, - bytes32 indexed hashedStrippedBytecode, - bytes constructorArgsBytecode, + bytes32 indexed hashedCreationCode, + bytes constructorArgsCode, uint256 nonce ); @@ -57,24 +60,24 @@ contract Create3Factory is Initializable, UUPSUpgradeable, Ownable { /** * @dev Computes the expected message hash. * @param _principal The address of the account for whom this factory contract will deploy a child contract. - * @param _strippedBytecode The bytecode of the contract to be deployed without the constructor arguments. + * @param _creationCode The bytecode of the contract to be deployed without the constructor arguments. * @return messageHash The expected signed message hash. */ - function _computeMessageHash(address _principal, bytes memory _strippedBytecode) internal view returns (bytes32) { - bytes32 txHash = getTransactionHash(_principal, _strippedBytecode); + function _computeMessageHash(address _principal, bytes memory _creationCode) internal view returns (bytes32) { + bytes32 txHash = getTransactionHash(_principal, _creationCode); return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", txHash)); } /** * @dev Returns the unique deployment transaction hash to be signed. * @param _principal The address of the account for whom this factory contract will deploy a child contract. - * @param _strippedBytecode The bytecode of the contract to be deployed without the constructor arguments. + * @param _creationCode The bytecode of the contract to be deployed without the constructor arguments. * @return txHash The unique deployment transaction hash to be signed. */ - function getTransactionHash(address _principal, bytes memory _strippedBytecode) public view returns (bytes32) { + function getTransactionHash(address _principal, bytes memory _creationCode) public view returns (bytes32) { return keccak256( abi.encodePacked( - domainSeparator, _principal, _strippedBytecode, userNonces[_principal][keccak256(_strippedBytecode)] + domainSeparator, _principal, _creationCode, userNonces[_principal][keccak256(_creationCode)] ) ); } @@ -82,23 +85,22 @@ contract Create3Factory is Initializable, UUPSUpgradeable, Ownable { /** * @dev Returns the predicted address of a contract to be deployed before it's deployed. * @param _principal The address of the account that signed the message hash. - * @param _strippedBytecode The bytecode of the contract to be deployed without the constructor arguments. + * @param _creationCode The bytecode of the contract to be deployed without the constructor arguments. * @return child The predicted address of the contract to be deployed. */ - function getAddress(address _principal, bytes memory _strippedBytecode) public view returns (address) { - bytes32 salt = keccak256( - abi.encodePacked(_principal, _strippedBytecode, userNonces[_principal][keccak256(_strippedBytecode)]) - ); + function getAddress(address _principal, bytes memory _creationCode) public view returns (address) { + bytes32 salt = + keccak256(abi.encodePacked(_principal, _creationCode, userNonces[_principal][keccak256(_creationCode)])); return CREATE3.getDeployed(salt); } /** * @dev Returns the keccak256 hash of the provided stripped bytecode. - * @param _strippedBytecode The bytecode of the contract to be deployed without the constructor arguments. + * @param _creationCode The bytecode of the contract to be deployed without the constructor arguments. * @return hash The keccak256 hash of the provided stripped bytecode. */ - function getBytecodeHash(bytes memory _strippedBytecode) public pure returns (bytes32) { - return keccak256(_strippedBytecode); + function getBytecodeHash(bytes memory _creationCode) public pure returns (bytes32) { + return keccak256(_creationCode); } /** @@ -114,35 +116,35 @@ contract Create3Factory is Initializable, UUPSUpgradeable, Ownable { * @dev Deploys arbitrary child contracts at predictable addresses, derived from account signatures. * @param _principal The address of the account that signed the message hash. * @param _signature The resulting signature from the principal account signing the messahge hash. - * @param _strippedBytecode The bytecode of the contract to be deployed without the constructor arguments. - * @param _constructorArgsBytecode The encoded constructor arguments of the contract to be deployed. + * @param _creationCode The bytecode of the contract to be deployed without the constructor arguments. + * @param _constructorArgsCode The encoded constructor arguments of the contract to be deployed. */ function deploy( address _principal, bytes memory _signature, - bytes memory _strippedBytecode, - bytes memory _constructorArgsBytecode + bytes memory _creationCode, + bytes memory _constructorArgsCode ) public payable { - bytes32 hashedStrippedBytecode = keccak256(_strippedBytecode); - uint256 currentNonce = userNonces[_principal][hashedStrippedBytecode]; - bytes32 expectedMessageHash = _computeMessageHash(_principal, _strippedBytecode); + bytes32 hashedCreationCode = keccak256(_creationCode); + uint256 currentNonce = userNonces[_principal][hashedCreationCode]; + bytes32 expectedMessageHash = _computeMessageHash(_principal, _creationCode); // Ensure the provided principal signed the expected message hash if (ECDSA.recover(expectedMessageHash, _signature) != _principal) revert Unauthorized(); // Update nonce state - userNonces[_principal][hashedStrippedBytecode]++; + userNonces[_principal][hashedCreationCode]++; // Calculate salt - bytes32 salt = keccak256(abi.encodePacked(_principal, _strippedBytecode, currentNonce)); + bytes32 salt = keccak256(abi.encodePacked(_principal, _creationCode, currentNonce)); // Deploy - address child = CREATE3.deploy(salt, abi.encodePacked(_strippedBytecode, _constructorArgsBytecode), msg.value); + address child = CREATE3.deploy(salt, abi.encodePacked(_creationCode, _constructorArgsCode), msg.value); // Update deployment history _deploymentHistory[_principal].push(child); - emit Deploy(_principal, child, hashedStrippedBytecode, _constructorArgsBytecode, currentNonce); + emit Deploy(_principal, child, hashedCreationCode, _constructorArgsCode, currentNonce); } receive() external payable { } // solhint-disable-line no-empty-blocks diff --git a/src/create3/interfaces/ICreate3Factory.sol b/src/create3/interfaces/ICreate3Factory.sol index d47faa2..a30d380 100644 --- a/src/create3/interfaces/ICreate3Factory.sol +++ b/src/create3/interfaces/ICreate3Factory.sol @@ -2,15 +2,15 @@ pragma solidity ^0.8.21; interface ICreate3Factory { - function userNonces(address _principal, bytes32 hashedStrippedBytecode) external returns (uint256); - function getTransactionHash(address _principal, bytes memory _strippedBytecode) external returns (bytes32); - function getAddress(address _principal, bytes memory _strippedBytecode) external returns (address); - function getBytecodeHash(bytes memory _strippedBytecode) external pure returns (bytes32); + function userNonces(address _principal, bytes32 hashedCreationCode) external returns (uint256); + function getTransactionHash(address _principal, bytes memory _creationCode) external returns (bytes32); + function getAddress(address _principal, bytes memory _creationCode) external returns (address); + function getBytecodeHash(bytes memory _creationCode) external pure returns (bytes32); function getDeploymentHistory(address _principal) external returns (address[] memory); function deploy( address _principal, bytes memory _signature, - bytes memory _strippedBytecode, - bytes memory _constructorArgsBytecode + bytes memory _creationCode, + bytes memory _constructorArgsCode ) external payable; } diff --git a/test/common/Child.t.sol b/test/common/Child.t.sol deleted file mode 100644 index 8dd99d9..0000000 --- a/test/common/Child.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.21; - -contract ChildA { - uint256 public number; - address public admin; - - constructor(address _admin) { - admin = _admin; - } - - function increment() public { - require(msg.sender == admin, "Unauthorized."); - number++; - } -} - -contract ChildB { - uint256 public number; - - function increment() public { - number++; - } -} diff --git a/test/create2/Create2Factory.admin.t.sol b/test/create2/Create2Factory.admin.t.sol index 16963f1..5f23a25 100644 --- a/test/create2/Create2Factory.admin.t.sol +++ b/test/create2/Create2Factory.admin.t.sol @@ -6,8 +6,8 @@ import { Create2Factory } from "../../src/create2/Create2Factory.sol"; import { ERC1967Proxy } from "../../src/dependencies/proxy/ERC1967Proxy.sol"; import { IERC20 } from "../../src/dependencies/token/interfaces/IERC20.sol"; import { ICreate2FactoryAdmin } from "../../src/create2/interfaces/ICreate2FactoryAdmin.sol"; -import { TestSetup } from "./common/TestSetup.t.sol"; -import { CONTRACT_DEPLOYER, TEST_ERC20_TOKEN } from "../common/Constants.t.sol"; +import { TestSetup } from "./common/contracts/TestSetup.t.sol"; +import { CONTRACT_DEPLOYER, TEST_ERC20_TOKEN } from "./common/Constants.t.sol"; contract Create2FactoryTest is TestSetup { /* solhint-disable func-name-mixedcase */ diff --git a/test/create2/Create2Factory.t.sol b/test/create2/Create2Factory.t.sol index 93e5baf..7490307 100644 --- a/test/create2/Create2Factory.t.sol +++ b/test/create2/Create2Factory.t.sol @@ -5,10 +5,10 @@ import { VmSafe } from "forge-std/Vm.sol"; import { Create2Factory } from "../../src/create2/Create2Factory.sol"; import { ERC1967Proxy } from "../../src/dependencies/proxy/ERC1967Proxy.sol"; import { ICreate2Factory } from "../../src/create2/interfaces/ICreate2Factory.sol"; -import { TestSetup } from "./common/TestSetup.t.sol"; -import { AddressLib } from "../common/libraries/AddressLib.t.sol"; +import { TestSetup } from "./common/contracts/TestSetup.t.sol"; +import { AddressLib } from "./common/libraries/AddressLib.t.sol"; import { DeploymentHelper } from "./helpers/DeploymentHelper.t.sol"; -import { CONTRACT_DEPLOYER } from "../common/Constants.t.sol"; +import { CONTRACT_DEPLOYER } from "./common/Constants.t.sol"; contract Create2FactoryTest is DeploymentHelper, TestSetup { /* solhint-disable func-name-mixedcase */ @@ -19,10 +19,11 @@ contract Create2FactoryTest is DeploymentHelper, TestSetup { // Setup VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); - uint256 currentNonce = ICreate2Factory(address(proxy)).userNonces(wallet.addr, hashedBytecodeChildA); + uint256 currentNonce = ICreate2Factory(address(proxy)).userNonces(wallet.addr, hashedBytecodeChildWithArgs); // Get signature information - bytes32 txHash = ICreate2Factory(address(proxy)).getTransactionHash(wallet.addr, bytecodeChildA, currentNonce); + bytes32 txHash = + ICreate2Factory(address(proxy)).getTransactionHash(wallet.addr, bytecodeChildWithArgs, currentNonce); bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", txHash)); @@ -33,13 +34,13 @@ contract Create2FactoryTest is DeploymentHelper, TestSetup { // Expectation vm.startPrank(sender); uint256 snapShot = vm.snapshot(); - address expectedChild = ICreate2Factory(address(proxy)).deploy(wallet.addr, signature, bytecodeChildA); + address expectedChild = ICreate2Factory(address(proxy)).deploy(wallet.addr, signature, bytecodeChildWithArgs); // Set chain state to what it was before the deployment vm.revertTo(snapShot); // Act - address actualChild = ICreate2Factory(address(proxy)).getAddress(wallet.addr, bytecodeChildA); + address actualChild = ICreate2Factory(address(proxy)).getAddress(wallet.addr, bytecodeChildWithArgs); vm.stopPrank(); // Assertions @@ -50,10 +51,11 @@ contract Create2FactoryTest is DeploymentHelper, TestSetup { // Setup VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); - uint256 preDeployNonce = ICreate2Factory(address(proxy)).userNonces(wallet.addr, hashedBytecodeChildA); + uint256 preDeployNonce = ICreate2Factory(address(proxy)).userNonces(wallet.addr, hashedBytecodeChildWithArgs); // Get signature information - bytes32 txHash = ICreate2Factory(address(proxy)).getTransactionHash(wallet.addr, bytecodeChildA, preDeployNonce); + bytes32 txHash = + ICreate2Factory(address(proxy)).getTransactionHash(wallet.addr, bytecodeChildWithArgs, preDeployNonce); bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", txHash)); @@ -62,12 +64,12 @@ contract Create2FactoryTest is DeploymentHelper, TestSetup { // Expectations vm.startPrank(sender); - address expectedChild = ICreate2Factory(address(proxy)).getAddress(wallet.addr, bytecodeChildA); + address expectedChild = ICreate2Factory(address(proxy)).getAddress(wallet.addr, bytecodeChildWithArgs); vm.expectEmit(true, true, true, true, address(proxy)); - emit Deploy(wallet.addr, expectedChild, keccak256(bytecodeChildA), preDeployNonce); + emit Deploy(wallet.addr, expectedChild, keccak256(bytecodeChildWithArgs), preDeployNonce); // Act - address actualChild = ICreate2Factory(address(proxy)).deploy(wallet.addr, signature, bytecodeChildA); + address actualChild = ICreate2Factory(address(proxy)).deploy(wallet.addr, signature, bytecodeChildWithArgs); vm.stopPrank(); // Assertions @@ -79,10 +81,10 @@ contract Create2FactoryTest is DeploymentHelper, TestSetup { VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); // First Deployment - address actualChild1 = deployChild(address(proxy), wallet, bytecodeChildA); + address actualChild1 = deployChild(address(proxy), wallet, bytecodeChildWithArgs); // Second Deployment - address actualChild2 = deployChild(address(proxy), wallet, bytecodeChildA); + address actualChild2 = deployChild(address(proxy), wallet, bytecodeChildWithArgs); // Assertions assertTrue(actualChild1 != address(0) && actualChild2 != address(0)); @@ -95,15 +97,15 @@ contract Create2FactoryTest is DeploymentHelper, TestSetup { // First deployment set uint256 snapShot = vm.snapshot(); - address setOneChildA = deployChild(address(proxy), wallet, bytecodeChildA); - address setOneChildB = deployChild(address(proxy), wallet, bytecodeChildB); + address setOneChildA = deployChild(address(proxy), wallet, bytecodeChildWithArgs); + address setOneChildB = deployChild(address(proxy), wallet, bytecodeChildNoArgs); // Set chain state to what it was before first deployment set vm.revertTo(snapShot); // Second deployment set (reverse order) - address setTwoChildB = deployChild(address(proxy), wallet, bytecodeChildB); - address setTwoChildA = deployChild(address(proxy), wallet, bytecodeChildA); + address setTwoChildB = deployChild(address(proxy), wallet, bytecodeChildNoArgs); + address setTwoChildA = deployChild(address(proxy), wallet, bytecodeChildWithArgs); // Assertions assertEq(setOneChildA, setTwoChildA); @@ -113,11 +115,11 @@ contract Create2FactoryTest is DeploymentHelper, TestSetup { function testFuzz_DeployNonceUpdate(uint256 pkNum) public { // Setup VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); - uint256 preDeployNonce = ICreate2Factory(address(proxy)).userNonces(wallet.addr, hashedBytecodeChildA); + uint256 preDeployNonce = ICreate2Factory(address(proxy)).userNonces(wallet.addr, hashedBytecodeChildWithArgs); // Act - deployChild(address(proxy), wallet, bytecodeChildA); - uint256 postDeployNonce = ICreate2Factory(address(proxy)).userNonces(wallet.addr, hashedBytecodeChildA); + deployChild(address(proxy), wallet, bytecodeChildWithArgs); + uint256 postDeployNonce = ICreate2Factory(address(proxy)).userNonces(wallet.addr, hashedBytecodeChildWithArgs); // Assertions assertEq(postDeployNonce, preDeployNonce + 1); @@ -133,14 +135,14 @@ contract Create2FactoryTest is DeploymentHelper, TestSetup { assertEq(deploymentHistory.length, 0); // Act - address child1 = deployChild(address(proxy), wallet, bytecodeChildA); + address child1 = deployChild(address(proxy), wallet, bytecodeChildWithArgs); deploymentHistory = ICreate2Factory(address(proxy)).getDeploymentHistory(wallet.addr); // Assertions assertEq(deploymentHistory.length, 1); assertTrue(deploymentHistory.includes(child1)); - address child2 = deployChild(address(proxy), wallet, bytecodeChildA); + address child2 = deployChild(address(proxy), wallet, bytecodeChildWithArgs); deploymentHistory = ICreate2Factory(address(proxy)).getDeploymentHistory(wallet.addr); // Assertions @@ -152,10 +154,11 @@ contract Create2FactoryTest is DeploymentHelper, TestSetup { // Setup VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); - uint256 currentNonce = ICreate2Factory(address(proxy)).userNonces(wallet.addr, hashedBytecodeChildA); + uint256 currentNonce = ICreate2Factory(address(proxy)).userNonces(wallet.addr, hashedBytecodeChildWithArgs); // Get signature information - bytes32 txHash = ICreate2Factory(address(proxy)).getTransactionHash(wallet.addr, bytecodeChildA, currentNonce); + bytes32 txHash = + ICreate2Factory(address(proxy)).getTransactionHash(wallet.addr, bytecodeChildWithArgs, currentNonce); bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", txHash)); @@ -163,11 +166,11 @@ contract Create2FactoryTest is DeploymentHelper, TestSetup { bytes memory signature = abi.encodePacked(r, s, v); // Deploy once - ICreate2Factory(address(proxy)).deploy(wallet.addr, signature, bytecodeChildA); + ICreate2Factory(address(proxy)).deploy(wallet.addr, signature, bytecodeChildWithArgs); // Act: attempt replay vm.expectRevert(Create2Factory.Unauthorized.selector); - ICreate2Factory(address(proxy)).deploy(wallet.addr, signature, bytecodeChildA); + ICreate2Factory(address(proxy)).deploy(wallet.addr, signature, bytecodeChildWithArgs); } function testFuzz_CannotDeployWithoutApproval(uint256 pkNum, address invalidPrincipal) public { @@ -177,10 +180,11 @@ contract Create2FactoryTest is DeploymentHelper, TestSetup { // Failing condition: principal is not the signer vm.assume(invalidPrincipal != wallet.addr); - uint256 currentNonce = ICreate2Factory(address(proxy)).userNonces(wallet.addr, hashedBytecodeChildA); + uint256 currentNonce = ICreate2Factory(address(proxy)).userNonces(wallet.addr, hashedBytecodeChildWithArgs); // Get signature information - bytes32 txHash = ICreate2Factory(address(proxy)).getTransactionHash(wallet.addr, bytecodeChildA, currentNonce); + bytes32 txHash = + ICreate2Factory(address(proxy)).getTransactionHash(wallet.addr, bytecodeChildWithArgs, currentNonce); bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", txHash)); @@ -189,7 +193,7 @@ contract Create2FactoryTest is DeploymentHelper, TestSetup { // Act: attempt with invalid principal vm.expectRevert(Create2Factory.Unauthorized.selector); - ICreate2Factory(address(proxy)).deploy(invalidPrincipal, signature, bytecodeChildA); + ICreate2Factory(address(proxy)).deploy(invalidPrincipal, signature, bytecodeChildWithArgs); } function testFuzz_GetBytecodeHash(bytes memory _childBytecode) public { diff --git a/test/common/Constants.t.sol b/test/create2/common/Constants.t.sol similarity index 100% rename from test/common/Constants.t.sol rename to test/create2/common/Constants.t.sol diff --git a/test/create2/common/TestSetup.t.sol b/test/create2/common/TestSetup.t.sol deleted file mode 100644 index 69f6d14..0000000 --- a/test/create2/common/TestSetup.t.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.21; - -import { Test } from "forge-std/Test.sol"; -import { ICreate2Factory } from "../../../src/create2/interfaces/ICreate2Factory.sol"; -import { Create2Factory } from "../../../src/create2/Create2Factory.sol"; -import { ERC1967Proxy } from "../../../src/dependencies/proxy/ERC1967Proxy.sol"; -import { ChildA, ChildB } from "../../common/Child.t.sol"; -import { CONTRACT_DEPLOYER } from "../../common/Constants.t.sol"; - -abstract contract TestSetup is Test { - Create2Factory public implementation; - ERC1967Proxy public proxy; - ChildA public childA; - ChildB public childB; - bytes public bytecodeChildA; - bytes public bytecodeChildB; - bytes32 public hashedBytecodeChildA; - bytes32 public hashedBytecodeChildB; - - // Events - event Deploy(address indexed sender, address indexed childA, bytes32 hashedBytecode, uint256 nonce); - - function setUp() public { - implementation = new Create2Factory(); - vm.prank(CONTRACT_DEPLOYER); - proxy = new ERC1967Proxy(address(implementation), abi.encodeWithSignature("initialize()")); - childA = new ChildA(address(this)); - childB = new ChildB(); - - // Get Bytecode - bytecodeChildA = abi.encodePacked(type(ChildA).creationCode, abi.encode(address(this))); - hashedBytecodeChildA = keccak256(bytecodeChildA); - bytecodeChildB = abi.encodePacked(type(ChildB).creationCode, abi.encode(address(this))); - hashedBytecodeChildB = keccak256(bytecodeChildB); - } -} diff --git a/test/create2/common/contracts/Child.args.t.sol b/test/create2/common/contracts/Child.args.t.sol new file mode 100644 index 0000000..39eaa4f --- /dev/null +++ b/test/create2/common/contracts/Child.args.t.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import { IChild } from "../interfaces/IChild.t.sol"; + +contract ChildArgs is IChild { + address public arg; + + constructor(address _arg) { + arg = _arg; + } +} diff --git a/test/create2/common/contracts/Child.noargs.t.sol b/test/create2/common/contracts/Child.noargs.t.sol new file mode 100644 index 0000000..db0613b --- /dev/null +++ b/test/create2/common/contracts/Child.noargs.t.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import { IChild } from "../interfaces/IChild.t.sol"; + +contract ChildNoArgs is IChild { } diff --git a/test/create2/common/contracts/TestSetup.t.sol b/test/create2/common/contracts/TestSetup.t.sol new file mode 100644 index 0000000..859dd77 --- /dev/null +++ b/test/create2/common/contracts/TestSetup.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import { Test } from "forge-std/Test.sol"; +import { ICreate2Factory } from "../../../../src/create2/interfaces/ICreate2Factory.sol"; +import { Create2Factory } from "../../../../src/create2/Create2Factory.sol"; +import { ERC1967Proxy } from "../../../../src/dependencies/proxy/ERC1967Proxy.sol"; +import { ChildArgs } from "./Child.args.t.sol"; +import { ChildNoArgs } from "./Child.noargs.t.sol"; +import { CONTRACT_DEPLOYER } from "../../common/Constants.t.sol"; + +abstract contract TestSetup is Test { + Create2Factory public implementation; + ERC1967Proxy public proxy; + ChildArgs public childWithArgs; + ChildNoArgs public childNoArgs; + bytes public bytecodeChildWithArgs; + bytes public bytecodeChildNoArgs; + bytes32 public hashedBytecodeChildWithArgs; + bytes32 public hashedBytecodeChildNoArgs; + + // Events + event Deploy(address indexed sender, address indexed childA, bytes32 hashedBytecode, uint256 nonce); + + function setUp() public { + implementation = new Create2Factory(); + vm.prank(CONTRACT_DEPLOYER); + proxy = new ERC1967Proxy(address(implementation), abi.encodeWithSignature("initialize()")); + + // Get Bytecode + bytecodeChildWithArgs = abi.encodePacked(type(ChildArgs).creationCode, abi.encode(address(this))); + hashedBytecodeChildWithArgs = keccak256(bytecodeChildWithArgs); + bytecodeChildNoArgs = abi.encodePacked(type(ChildNoArgs).creationCode, ""); + hashedBytecodeChildNoArgs = keccak256(bytecodeChildNoArgs); + } +} diff --git a/test/create2/common/interfaces/IChild.t.sol b/test/create2/common/interfaces/IChild.t.sol new file mode 100644 index 0000000..6240cc6 --- /dev/null +++ b/test/create2/common/interfaces/IChild.t.sol @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +interface IChild { } diff --git a/test/common/libraries/AddressLib.t.sol b/test/create2/common/libraries/AddressLib.t.sol similarity index 100% rename from test/common/libraries/AddressLib.t.sol rename to test/create2/common/libraries/AddressLib.t.sol diff --git a/test/create3/Create3Factory.admin.t.sol b/test/create3/Create3Factory.admin.t.sol index fc9f19d..c4c49f7 100644 --- a/test/create3/Create3Factory.admin.t.sol +++ b/test/create3/Create3Factory.admin.t.sol @@ -6,8 +6,8 @@ import { Create3Factory } from "../../src/create3/Create3Factory.sol"; import { ERC1967Proxy } from "../../src/dependencies/proxy/ERC1967Proxy.sol"; import { IERC20 } from "../../src/dependencies/token/interfaces/IERC20.sol"; import { ICreate3FactoryAdmin } from "../../src/create3/interfaces/ICreate3FactoryAdmin.sol"; -import { TestSetup } from "./common/TestSetup.t.sol"; -import { CONTRACT_DEPLOYER, TEST_ERC20_TOKEN } from "../common/Constants.t.sol"; +import { TestSetup } from "./common/contracts/TestSetup.t.sol"; +import { CONTRACT_DEPLOYER, TEST_ERC20_TOKEN } from "./common/Constants.t.sol"; contract Create3FactoryAdminTest is TestSetup { /* solhint-disable func-name-mixedcase */ diff --git a/test/create3/Create3Factory.t.sol b/test/create3/Create3Factory.t.sol index 19de865..a7a4d7c 100644 --- a/test/create3/Create3Factory.t.sol +++ b/test/create3/Create3Factory.t.sol @@ -5,11 +5,12 @@ import { VmSafe } from "forge-std/Vm.sol"; import { Create3Factory } from "../../src/create3/Create3Factory.sol"; import { ERC1967Proxy } from "../../src/dependencies/proxy/ERC1967Proxy.sol"; import { ICreate3Factory } from "../../src/create3/interfaces/ICreate3Factory.sol"; -import { TestSetup } from "./common/TestSetup.t.sol"; -import { AddressLib } from "../common/libraries/AddressLib.t.sol"; +import { TestSetup, ChildrenWithConstructorArgs } from "./common/contracts/TestSetup.t.sol"; +import { AddressLib } from "./common/libraries/AddressLib.t.sol"; import { DeploymentHelper } from "./helpers/DeploymentHelper.t.sol"; +import { CONTRACT_DEPLOYER } from "./common/Constants.t.sol"; -contract Create3FactoryTest is DeploymentHelper, TestSetup { +contract Create3FactoryTest is TestSetup, DeploymentHelper { /* solhint-disable func-name-mixedcase */ using AddressLib for address[]; @@ -18,26 +19,21 @@ contract Create3FactoryTest is DeploymentHelper, TestSetup { // Setup VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); - // Get signature information - bytes32 txHash = ICreate3Factory(address(proxy)).getTransactionHash(wallet.addr, strippedBytecodeChildA); + (bytes memory creationCode, bytes memory constructorArgsCode) = getDeploymentBytecode(); - bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", txHash)); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(wallet.privateKey, messageHash); - - bytes memory signature = abi.encodePacked(r, s, v); + bytes memory signature = getSignature(address(proxy), wallet, creationCode); // Expectation vm.startPrank(sender); uint256 snapShot = vm.snapshot(); - ICreate3Factory(address(proxy)).deploy(wallet.addr, signature, strippedBytecodeChildA, abi.encode(wallet.addr)); + ICreate3Factory(address(proxy)).deploy(wallet.addr, signature, creationCode, constructorArgsCode); address expectedChild = ICreate3Factory(address(proxy)).getDeploymentHistory(wallet.addr)[0]; // Set chain state to what it was before the deployment vm.revertTo(snapShot); // Act - address actualChild = ICreate3Factory(address(proxy)).getAddress(wallet.addr, strippedBytecodeChildA); + address actualChild = ICreate3Factory(address(proxy)).getAddress(wallet.addr, creationCode); vm.stopPrank(); // Assertions @@ -47,30 +43,20 @@ contract Create3FactoryTest is DeploymentHelper, TestSetup { function testFuzz_Deploy(uint256 pkNum, address sender) public { // Setup VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); - bytes memory constructorArgsBytecodeChildA = abi.encode(wallet.addr); - - uint256 preDeployNonce = ICreate3Factory(address(proxy)).userNonces(wallet.addr, hashedStrippedBytecodeChildA); - // Get signature information - bytes32 txHash = ICreate3Factory(address(proxy)).getTransactionHash(wallet.addr, strippedBytecodeChildA); + (bytes memory creationCode, bytes memory constructorArgsCode) = getDeploymentBytecode(); - bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", txHash)); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(wallet.privateKey, messageHash); - bytes memory signature = abi.encodePacked(r, s, v); + bytes memory signature = getSignature(address(proxy), wallet, creationCode); // Expectations vm.startPrank(sender); - address expectedChild = ICreate3Factory(address(proxy)).getAddress(wallet.addr, strippedBytecodeChildA); + uint256 preDeployNonce = ICreate3Factory(address(proxy)).userNonces(wallet.addr, keccak256(creationCode)); + address expectedChild = ICreate3Factory(address(proxy)).getAddress(wallet.addr, creationCode); vm.expectEmit(true, true, true, true, address(proxy)); - emit Deploy( - wallet.addr, expectedChild, keccak256(strippedBytecodeChildA), constructorArgsBytecodeChildA, preDeployNonce - ); + emit Deploy(wallet.addr, expectedChild, keccak256(creationCode), constructorArgsCode, preDeployNonce); // Act - ICreate3Factory(address(proxy)).deploy( - wallet.addr, signature, strippedBytecodeChildA, constructorArgsBytecodeChildA - ); + ICreate3Factory(address(proxy)).deploy(wallet.addr, signature, creationCode, constructorArgsCode); address actualChild = ICreate3Factory(address(proxy)).getDeploymentHistory(wallet.addr)[0]; vm.stopPrank(); @@ -83,11 +69,13 @@ contract Create3FactoryTest is DeploymentHelper, TestSetup { // Setup VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); + (bytes memory creationCode, bytes memory constructorArgsCode) = getDeploymentBytecode(); + // First Deployment - deployChild(address(proxy), wallet, strippedBytecodeChildA); + deployChild(address(proxy), wallet, creationCode, constructorArgsCode); // Second Deployment - deployChild(address(proxy), wallet, strippedBytecodeChildA); + deployChild(address(proxy), wallet, creationCode, constructorArgsCode); // Fetch Deployment History address[] memory deployementHistory = ICreate3Factory(address(proxy)).getDeploymentHistory(wallet.addr); @@ -106,37 +94,79 @@ contract Create3FactoryTest is DeploymentHelper, TestSetup { // Setup VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); + (bytes memory creationCode, bytes memory constructorArgsCode) = getDeploymentBytecode(); + // First deployment set uint256 snapShot = vm.snapshot(); - deployChild(address(proxy), wallet, strippedBytecodeChildA); - deployChild(address(proxy), wallet, strippedBytecodeChildB); + deployChild(address(proxy), wallet, creationCode, constructorArgsCode); + deployChild(address(proxy), wallet, noArgsChildCreationCode, ""); address[] memory setOneDeployementHistory = ICreate3Factory(address(proxy)).getDeploymentHistory(wallet.addr); - address setOneChildA = setOneDeployementHistory[0]; - address setOneChildB = setOneDeployementHistory[1]; + address setOneChildWithArgs = setOneDeployementHistory[0]; + address setOneChildWithoutArgs = setOneDeployementHistory[1]; // Set chain state to what it was before first deployment set vm.revertTo(snapShot); // Second deployment set (reverse order) - deployChild(address(proxy), wallet, strippedBytecodeChildB); - deployChild(address(proxy), wallet, strippedBytecodeChildA); + deployChild(address(proxy), wallet, noArgsChildCreationCode, ""); + deployChild(address(proxy), wallet, creationCode, constructorArgsCode); address[] memory setTwoDeployementHistory = ICreate3Factory(address(proxy)).getDeploymentHistory(wallet.addr); - address setTwoChildB = setTwoDeployementHistory[0]; - address setTwoChildA = setTwoDeployementHistory[1]; + address setTwoChildWithoutArgs = setTwoDeployementHistory[0]; + address setTwoChildWithArgs = setTwoDeployementHistory[1]; // Assertions - assertEq(setOneChildA, setTwoChildA, "Equivalence violation: setOneChildA != setTwoChildA"); - assertEq(setOneChildB, setTwoChildB, "Equivalence violation: setOneChildB != setTwoChildB"); + assertEq( + setOneChildWithArgs, + setTwoChildWithArgs, + "Equivalence violation: setOneChildWithArgs != setTwoChildWithArgs" + ); + assertEq( + setOneChildWithoutArgs, + setTwoChildWithoutArgs, + "Equivalence violation: setOneChildWithoutArgs != setTwoChildWithoutArgs" + ); + } + + function testFuzz_DeployBytecodeVarianceIndependence(uint256 pkNum) public { + // Setup + VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); + assertTrue(wallet.addr != CONTRACT_DEPLOYER, "Truth Violation: wallet.addr != CONTRACT_DEPLOYER"); + + ChildrenWithConstructorArgs.ChildContractData[] memory childrenData = + childrenWithConstructorArgs.getChildrenData(); + + for (uint256 i = 0; i < childrenData.length; i++) { + // First deployment + uint256 snapShot = vm.snapshot(); + deployChild(address(proxy), wallet, childrenData[i].creationCode, childrenData[i].constructorArgsCode1); + address[] memory setOneDeployementHistory = + ICreate3Factory(address(proxy)).getDeploymentHistory(wallet.addr); + address childOne = setOneDeployementHistory[0]; + + // Set chain state to what it was before first deployment + vm.revertTo(snapShot); + + // Second deployment (different constructor args) + deployChild(address(proxy), wallet, childrenData[i].creationCode, childrenData[i].constructorArgsCode2); + address[] memory setTwoDeployementHistory = + ICreate3Factory(address(proxy)).getDeploymentHistory(wallet.addr); + address childTwo = setTwoDeployementHistory[0]; + + // Assertions + assertEq(childOne, childTwo, "Equivalence violation: childOne != childTwo"); + } } function testFuzz_DeployNonceUpdate(uint256 pkNum) public { // Setup - VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encode(uint256(pkNum))))); - uint256 preDeployNonce = ICreate3Factory(address(proxy)).userNonces(wallet.addr, hashedStrippedBytecodeChildA); + VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); + + (bytes memory creationCode, bytes memory constructorArgsCode) = getDeploymentBytecode(); + uint256 preDeployNonce = ICreate3Factory(address(proxy)).userNonces(wallet.addr, keccak256(creationCode)); // Act - deployChild(address(proxy), wallet, strippedBytecodeChildA); - uint256 postDeployNonce = ICreate3Factory(address(proxy)).userNonces(wallet.addr, hashedStrippedBytecodeChildA); + deployChild(address(proxy), wallet, creationCode, constructorArgsCode); + uint256 postDeployNonce = ICreate3Factory(address(proxy)).userNonces(wallet.addr, keccak256(creationCode)); // Assertions assertEq(postDeployNonce, preDeployNonce + 1, "Equivalence violation: postDeployNonce != preDeployNonce + 1"); @@ -145,17 +175,17 @@ contract Create3FactoryTest is DeploymentHelper, TestSetup { function testFuzz_DeployHistoryUpdate(uint256 pkNum) public { // Setup VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); - + (bytes memory creationCode, bytes memory constructorArgsCode) = getDeploymentBytecode(); address[] memory deploymentHistory = ICreate3Factory(address(proxy)).getDeploymentHistory(wallet.addr); // Pre-act assertions assertEq(deploymentHistory.length, 0); // Expectation 1 - address child1 = ICreate3Factory(address(proxy)).getAddress(wallet.addr, strippedBytecodeChildA); + address child1 = ICreate3Factory(address(proxy)).getAddress(wallet.addr, creationCode); // Act 1 - deployChild(address(proxy), wallet, strippedBytecodeChildA); + deployChild(address(proxy), wallet, creationCode, constructorArgsCode); deploymentHistory = ICreate3Factory(address(proxy)).getDeploymentHistory(wallet.addr); // Assertions 1 @@ -163,10 +193,10 @@ contract Create3FactoryTest is DeploymentHelper, TestSetup { assertTrue(deploymentHistory.includes(child1), "Truth Violation: deploymentHistory.includes(child1)"); // Expectation 2 - address child2 = ICreate3Factory(address(proxy)).getAddress(wallet.addr, strippedBytecodeChildA); + address child2 = ICreate3Factory(address(proxy)).getAddress(wallet.addr, creationCode); // Act 2 - deployChild(address(proxy), wallet, strippedBytecodeChildA); + deployChild(address(proxy), wallet, creationCode, constructorArgsCode); deploymentHistory = ICreate3Factory(address(proxy)).getDeploymentHistory(wallet.addr); // Assertions 2 @@ -178,20 +208,16 @@ contract Create3FactoryTest is DeploymentHelper, TestSetup { // Setup VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); - // Get signature information - bytes32 txHash = ICreate3Factory(address(proxy)).getTransactionHash(wallet.addr, strippedBytecodeChildA); - - bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", txHash)); + (bytes memory creationCode, bytes memory constructorArgsCode) = getDeploymentBytecode(); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(wallet.privateKey, messageHash); - bytes memory signature = abi.encodePacked(r, s, v); + bytes memory signature = getSignature(address(proxy), wallet, creationCode); // Deploy once - ICreate3Factory(address(proxy)).deploy(wallet.addr, signature, strippedBytecodeChildA, abi.encode(wallet.addr)); + ICreate3Factory(address(proxy)).deploy(wallet.addr, signature, creationCode, constructorArgsCode); // Act: attempt replay vm.expectRevert(Create3Factory.Unauthorized.selector); - ICreate3Factory(address(proxy)).deploy(wallet.addr, signature, strippedBytecodeChildA, abi.encode(wallet.addr)); + ICreate3Factory(address(proxy)).deploy(wallet.addr, signature, creationCode, constructorArgsCode); } function testFuzz_CannotDeployWithoutApproval(uint256 pkNum, address invalidPrincipal) public { @@ -201,29 +227,23 @@ contract Create3FactoryTest is DeploymentHelper, TestSetup { // Failing condition: principal is not the signer vm.assume(invalidPrincipal != wallet.addr); - // Get signature information - bytes32 txHash = ICreate3Factory(address(proxy)).getTransactionHash(wallet.addr, strippedBytecodeChildA); + (bytes memory creationCode, bytes memory constructorArgsCode) = getDeploymentBytecode(); - bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", txHash)); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(wallet.privateKey, messageHash); - bytes memory signature = abi.encodePacked(r, s, v); + bytes memory signature = getSignature(address(proxy), wallet, creationCode); // Act: attempt with invalid principal vm.expectRevert(Create3Factory.Unauthorized.selector); - ICreate3Factory(address(proxy)).deploy( - invalidPrincipal, signature, strippedBytecodeChildA, abi.encode(wallet.addr) - ); + ICreate3Factory(address(proxy)).deploy(invalidPrincipal, signature, creationCode, constructorArgsCode); } - function testFuzz_GetBytecodeHash(bytes memory _childStrippedBytecode) public { - vm.assume(_childStrippedBytecode.length > 0 && _childStrippedBytecode.length <= 24576); // max contract size + function testFuzz_GetBytecodeHash(bytes memory _childCreationCode) public { + vm.assume(_childCreationCode.length > 0 && _childCreationCode.length <= 24576); // max contract size // Act - bytes32 actualHash = ICreate3Factory(address(proxy)).getBytecodeHash(_childStrippedBytecode); + bytes32 actualHash = ICreate3Factory(address(proxy)).getBytecodeHash(_childCreationCode); // Expected - bytes32 expectedHash = keccak256(_childStrippedBytecode); + bytes32 expectedHash = keccak256(_childCreationCode); // Assertions assertEq(actualHash, expectedHash, "Equivalence Violation: actualHash != expectedHash"); diff --git a/test/create3/common/Constants.t.sol b/test/create3/common/Constants.t.sol new file mode 100644 index 0000000..1319563 --- /dev/null +++ b/test/create3/common/Constants.t.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +address constant CONTRACT_DEPLOYER = 0x0a5B347509621337cDDf44CBCf6B6E7C9C908CD2; + +address constant TEST_ERC20_TOKEN = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // USDC on Ethereum diff --git a/test/create3/common/TestSetup.t.sol b/test/create3/common/TestSetup.t.sol deleted file mode 100644 index 40e92c2..0000000 --- a/test/create3/common/TestSetup.t.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.21; - -import { Test } from "forge-std/Test.sol"; -import { ICreate3Factory } from "../../../src/create3/interfaces/ICreate3Factory.sol"; -import { Create3Factory } from "../../../src/create3/Create3Factory.sol"; -import { ERC1967Proxy } from "../../../src/dependencies/proxy/ERC1967Proxy.sol"; -import { ChildA, ChildB } from "../../common/Child.t.sol"; -import { CONTRACT_DEPLOYER } from "../../common/Constants.t.sol"; - -abstract contract TestSetup is Test { - Create3Factory public implementation; - ERC1967Proxy public proxy; - ChildA public childA; - ChildB public childB; - bytes public strippedBytecodeChildA; - bytes public strippedBytecodeChildB; - bytes32 public hashedStrippedBytecodeChildA; - bytes32 public hashedStrippedBytecodeChildB; - - // Events - event Deploy( - address indexed principal, - address indexed child, - bytes32 indexed hashedStrippedBytecode, - bytes constructorArgsBytecode, - uint256 nonce - ); - - function setUp() public { - implementation = new Create3Factory(); - vm.prank(CONTRACT_DEPLOYER); - proxy = new ERC1967Proxy(address(implementation), abi.encodeWithSignature("initialize()")); - childA = new ChildA(address(this)); - childB = new ChildB(); - - strippedBytecodeChildA = type(ChildA).creationCode; - hashedStrippedBytecodeChildA = keccak256(strippedBytecodeChildA); - strippedBytecodeChildB = type(ChildB).creationCode; - hashedStrippedBytecodeChildB = keccak256(strippedBytecodeChildB); - } -} diff --git a/test/create3/common/contracts/Child.args.t.sol b/test/create3/common/contracts/Child.args.t.sol new file mode 100644 index 0000000..3513612 --- /dev/null +++ b/test/create3/common/contracts/Child.args.t.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import { IChild } from "../interfaces/IChild.t.sol"; + +// Value types +contract AddressChild is IChild { + address public arg; + + constructor(address _arg) { + arg = _arg; + } +} + +contract UintChild is IChild { + uint256 public arg; + + constructor(uint256 _arg) { + arg = _arg; + } +} + +contract IntChild is IChild { + int256 public arg; + + constructor(int256 _arg) { + arg = _arg; + } +} + +contract StringChild is IChild { + string public arg; + + constructor(string memory _arg) { + arg = _arg; + } +} + +contract BytesChild is IChild { + bytes public arg; + + constructor(bytes memory _arg) { + arg = _arg; + } +} + +contract Bytes32Child is IChild { + bytes32 public arg; + + constructor(bytes32 _arg) { + arg = _arg; + } +} + +contract BoolChild is IChild { + bool public arg; + + constructor(bool _arg) { + arg = _arg; + } +} + +// Referrence types +contract AddressArrayChild is IChild { + address[] public arg; + + constructor(address[] memory _arg) { + arg = _arg; + } +} + +contract UintArrayChild is IChild { + uint256[] public arg; + + constructor(uint256[] memory _arg) { + arg = _arg; + } +} + +contract IntArrayChild is IChild { + int256[] public arg; + + constructor(int256[] memory _arg) { + arg = _arg; + } +} + +contract StringArrayChild is IChild { + string[] public arg; + + constructor(string[] memory _arg) { + arg = _arg; + } +} + +contract BytesArrayChild is IChild { + bytes[] public arg; + + constructor(bytes[] memory _arg) { + arg = _arg; + } +} + +contract Bytes32ArrayChild is IChild { + bytes32[] public arg; + + constructor(bytes32[] memory _arg) { + arg = _arg; + } +} + +contract BoolArrayChild is IChild { + bool[] public arg; + + constructor(bool[] memory _arg) { + arg = _arg; + } +} diff --git a/test/create3/common/contracts/Child.noargs.t.sol b/test/create3/common/contracts/Child.noargs.t.sol new file mode 100644 index 0000000..db0613b --- /dev/null +++ b/test/create3/common/contracts/Child.noargs.t.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import { IChild } from "../interfaces/IChild.t.sol"; + +contract ChildNoArgs is IChild { } diff --git a/test/create3/common/contracts/Children.args.t.sol b/test/create3/common/contracts/Children.args.t.sol new file mode 100644 index 0000000..a503868 --- /dev/null +++ b/test/create3/common/contracts/Children.args.t.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import { + AddressChild, + UintChild, + IntChild, + StringChild, + BytesChild, + Bytes32Child, + BoolChild, + AddressArrayChild, + UintArrayChild, + IntArrayChild, + StringArrayChild, + BytesArrayChild, + Bytes32ArrayChild, + BoolArrayChild +} from "./Child.args.t.sol"; +import { IChild } from "../interfaces/IChild.t.sol"; +import { CONTRACT_DEPLOYER } from "../Constants.t.sol"; + +/** + * @title ChildrenWithConstructorArgs + * @dev Constructs arrays of children with different types and different construct args for each type. + */ +contract ChildrenWithConstructorArgs { + struct ChildContractData { + bytes creationCode; + bytes constructorArgsCode1; + bytes constructorArgsCode2; + } + + ChildContractData[] public childrenData; + + constructor() { + __constructChildren(); + } + + /// @dev Constructs children with different constructor arguments for testing. + function __constructChildren() private { + address defaultAddress = address(this); + address variantAddress = address(uint160(uint256(keccak256(abi.encodePacked("variant"))))); + + // value types + __addChildData(type(AddressChild).creationCode, abi.encode(defaultAddress), abi.encode(variantAddress)); + __addChildData(type(UintChild).creationCode, abi.encode(uint256(1)), abi.encode(uint256(2))); + __addChildData(type(IntChild).creationCode, abi.encode(int256(-1)), abi.encode(int256(-2))); + __addChildData(type(StringChild).creationCode, abi.encode("hello world"), abi.encode("variant")); + __addChildData(type(BytesChild).creationCode, abi.encode(hex"01"), abi.encode(hex"02")); + __addChildData( + type(Bytes32Child).creationCode, abi.encode(bytes32(uint256(1))), abi.encode(bytes32(uint256(2))) + ); + __addChildData(type(BoolChild).creationCode, abi.encode(true), abi.encode(false)); + + // reference types + // Address Arrays + address[] memory defaultAddresses = new address[](1); + defaultAddresses[0] = defaultAddress; + address[] memory variantAddresses = new address[](1); + variantAddresses[0] = variantAddress; + __addChildData(type(AddressArrayChild).creationCode, abi.encode(defaultAddresses), abi.encode(variantAddresses)); + + // Uints Arrays + uint256[] memory defaultUints = new uint256[](1); + defaultUints[0] = 1; + uint256[] memory variantUints = new uint256[](1); + variantUints[0] = 2; + __addChildData(type(UintArrayChild).creationCode, abi.encode(defaultUints), abi.encode(variantUints)); + + // Ints Arrays + int256[] memory defaultInts = new int256[](1); + defaultInts[0] = -1; + int256[] memory variantInts = new int256[](1); + variantInts[0] = -2; + __addChildData(type(IntArrayChild).creationCode, abi.encode(defaultInts), abi.encode(variantInts)); + + // Strings Arrays + string[] memory defaultStrings = new string[](1); + defaultStrings[0] = "hello world"; + string[] memory variantStrings = new string[](1); + variantStrings[0] = "variant"; + __addChildData(type(StringArrayChild).creationCode, abi.encode(defaultStrings), abi.encode(variantStrings)); + + // Bytes Arrays + bytes[] memory defaultBytes = new bytes[](1); + defaultBytes[0] = hex"01"; + bytes[] memory variantBytes = new bytes[](1); + variantBytes[0] = hex"02"; + __addChildData(type(BytesArrayChild).creationCode, abi.encode(defaultBytes), abi.encode(variantBytes)); + + // Bytes32 Arrays + bytes32[] memory defaultBytes32 = new bytes32[](1); + defaultBytes32[0] = bytes32(uint256(1)); + bytes32[] memory variantBytes32 = new bytes32[](1); + variantBytes32[0] = bytes32(uint256(2)); + __addChildData(type(Bytes32ArrayChild).creationCode, abi.encode(defaultBytes32), abi.encode(variantBytes32)); + + // Bools Arrays + bool[] memory defaultBools = new bool[](1); + defaultBools[0] = true; + bool[] memory variantBools = new bool[](1); + variantBools[0] = false; + __addChildData(type(BoolArrayChild).creationCode, abi.encode(defaultBools), abi.encode(variantBools)); + } + + /// @dev Helper function to add child data to the array. + function __addChildData( + bytes memory creationCode, + bytes memory constructorArgsCode1, + bytes memory constructorArgsCode2 + ) private { + childrenData.push( + ChildContractData({ + creationCode: creationCode, + constructorArgsCode1: constructorArgsCode1, + constructorArgsCode2: constructorArgsCode2 + }) + ); + } + + function getChildrenData() public view returns (ChildContractData[] memory) { + return childrenData; + } +} diff --git a/test/create3/common/contracts/TestSetup.t.sol b/test/create3/common/contracts/TestSetup.t.sol new file mode 100644 index 0000000..8ff9c04 --- /dev/null +++ b/test/create3/common/contracts/TestSetup.t.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import { Test } from "forge-std/Test.sol"; +import { ICreate3Factory } from "../../../../src/create3/interfaces/ICreate3Factory.sol"; +import { Create3Factory } from "../../../../src/create3/Create3Factory.sol"; +import { ERC1967Proxy } from "../../../../src/dependencies/proxy/ERC1967Proxy.sol"; +import { ChildNoArgs } from "./Child.noargs.t.sol"; +import { ChildrenWithConstructorArgs } from "./Children.args.t.sol"; + +import { CONTRACT_DEPLOYER } from "../Constants.t.sol"; + +abstract contract TestSetup is Test { + Create3Factory public implementation; + ERC1967Proxy public proxy; + + ChildrenWithConstructorArgs public childrenWithConstructorArgs; + bytes public noArgsChildCreationCode; + + // Events + event Deploy( + address indexed principal, + address indexed child, + bytes32 indexed hashedCreationCode, + bytes constructorArgsCode, + uint256 nonce + ); + + function setUp() public { + implementation = new Create3Factory(); + vm.prank(CONTRACT_DEPLOYER); + proxy = new ERC1967Proxy(address(implementation), abi.encodeWithSignature("initialize()")); + + noArgsChildCreationCode = type(ChildNoArgs).creationCode; + childrenWithConstructorArgs = new ChildrenWithConstructorArgs(); + } +} diff --git a/test/create3/common/interfaces/IChild.t.sol b/test/create3/common/interfaces/IChild.t.sol new file mode 100644 index 0000000..6240cc6 --- /dev/null +++ b/test/create3/common/interfaces/IChild.t.sol @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +interface IChild { } diff --git a/test/create3/common/libraries/AddressLib.t.sol b/test/create3/common/libraries/AddressLib.t.sol new file mode 100644 index 0000000..f420790 --- /dev/null +++ b/test/create3/common/libraries/AddressLib.t.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +library AddressLib { + function includes(address[] memory _array, address _address) internal pure returns (bool) { + for (uint256 i; i < _array.length; i++) { + if (_array[i] == _address) { + return true; + } + } + return false; + } +} diff --git a/test/create3/helpers/DeploymentHelper.t.sol b/test/create3/helpers/DeploymentHelper.t.sol index a2828f2..d2fc732 100644 --- a/test/create3/helpers/DeploymentHelper.t.sol +++ b/test/create3/helpers/DeploymentHelper.t.sol @@ -3,21 +3,54 @@ pragma solidity ^0.8.21; import { Test } from "forge-std/Test.sol"; import { VmSafe } from "forge-std/Vm.sol"; +import { ChildrenWithConstructorArgs } from "../common/contracts/Children.args.t.sol"; +import { TestSetup } from "../common/contracts/TestSetup.t.sol"; import { ICreate3Factory } from "../../../src/create3/interfaces/ICreate3Factory.sol"; -abstract contract DeploymentHelper is Test { +abstract contract DeploymentHelper is Test, TestSetup { /** * @dev Reusable function for deploying childA contracts. * @param _contract The address of the PredictveDeployer proxy contract. * @param _wallet The principal's evm account that signs off on a childA contract deployment. - * @param _strippedBytecode The to-be-deployed contract bytecode. + * @param _creationCode The to-be-deployed contract bytecode. */ - function deployChild(address _contract, VmSafe.Wallet memory _wallet, bytes memory _strippedBytecode) internal { - bytes32 txHash = ICreate3Factory(_contract).getTransactionHash(_wallet.addr, _strippedBytecode); + function deployChild( + address _contract, + VmSafe.Wallet memory _wallet, + bytes memory _creationCode, + bytes memory _constructorArgsCode + ) internal { + bytes memory signature = getSignature(_contract, _wallet, _creationCode); + ICreate3Factory(_contract).deploy(_wallet.addr, signature, _creationCode, _constructorArgsCode); + } + + /** + * @dev Reusable function for returning generic bytcode contract creation code and constructor args code. + */ + function getDeploymentBytecode() internal view returns (bytes memory, bytes memory) { + ChildrenWithConstructorArgs.ChildContractData[] memory childrenData = + childrenWithConstructorArgs.getChildrenData(); + ChildrenWithConstructorArgs.ChildContractData memory child = childrenData[0]; + return (child.creationCode, child.constructorArgsCode1); + } + + /** + * @dev Reusable function for returning a signature to be used for deployment. + * @param _contract The address of the PredictveDeployer proxy contract. + * @param _wallet The principal's evm account that signs off on a childA contract deployment. + * @param _creationCode The to-be-deployed contract bytecode. + * @return signature The signature to be used for deployment. + */ + function getSignature(address _contract, VmSafe.Wallet memory _wallet, bytes memory _creationCode) + internal + returns (bytes memory) + { + bytes32 txHash = ICreate3Factory(_contract).getTransactionHash(_wallet.addr, _creationCode); + bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", txHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(_wallet.privateKey, messageHash); - bytes memory signature = abi.encodePacked(r, s, v); - bytes memory constructorArgsBytecode = abi.encode(_wallet.addr); - ICreate3Factory(_contract).deploy(_wallet.addr, signature, _strippedBytecode, constructorArgsBytecode); + + return abi.encodePacked(r, s, v); } } diff --git a/test/create2/test_proxy/ERC1967Proxy.t.sol b/test/proxy/ERC1967Proxy.t.sol similarity index 78% rename from test/create2/test_proxy/ERC1967Proxy.t.sol rename to test/proxy/ERC1967Proxy.t.sol index a41471e..9e94c7b 100644 --- a/test/create2/test_proxy/ERC1967Proxy.t.sol +++ b/test/proxy/ERC1967Proxy.t.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.21; -import { Create2Factory } from "../../../src/create2/Create2Factory.sol"; -import { ERC1967Proxy } from "../../../src/dependencies/proxy/ERC1967Proxy.sol"; -import { ICreate2FactoryAdmin } from "../../../src/create2/interfaces/ICreate2FactoryAdmin.sol"; -import { TestSetup } from "../common/TestSetup.t.sol"; -import { CONTRACT_DEPLOYER } from "../../common/Constants.t.sol"; +import { Create2Factory } from "../../src/create2/Create2Factory.sol"; +import { ERC1967Proxy } from "../../src/dependencies/proxy/ERC1967Proxy.sol"; +import { ICreate2FactoryAdmin } from "../../src/create2/interfaces/ICreate2FactoryAdmin.sol"; +import { TestSetup } from "../create2/common/contracts/TestSetup.t.sol"; +import { CONTRACT_DEPLOYER } from "../create2/common/Constants.t.sol"; contract ERC1967ProxyTest is TestSetup { /* solhint-disable func-name-mixedcase */