diff --git a/README.md b/README.md index a6e34d9..3510ccd 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,12 @@ WE MANDATE THAT ONLY SIGNER AND XSAFE CAN SUBMIT SIGNATURES FOR DEPLOYMENT DEPLOYMENT REQUIRES SIGNATURE, NOT ADDRESS DIRECTLY -- SALT IS A FUNCTION OF ADDRESS - ANY ONE CAN SUBMIT AN ADDRESS. ONLY PRIVATE KEY HOLDER CAN SUBMIT SIGNATURE +- SALT IS A FUNCTION OF ADDRESS - ANYONE CAN SUBMIT AN ADDRESS. ONLY PRIVATE KEYHOLDER CAN SUBMIT SIGNATURE - WITH SIGNATURE METHOD, THE ONLY WAY TO OBTAIN A SALT, TIED TO AN ADDRESS, IS TO OWN THAT ADDRESS' PRIVATE KEY - ONLY RIGHTFUL OWNERS CAN DEPLOY THEIR CONTRACTS AT THEIR DETERMINISTIC ADDRESSES + +USER NONCE IS DEPENDENT ON THE BYTECODE OF THE FUNCTION +- RATIONALE: USER DOES NOT NEED TO TRACK THE ORDER IN WHICH THEY DEPLOY CONTRACTS A, B, C... ON CHAIN 1 | B, A, C... ON CHAIN 2 +- IN AN EFFORT TO KEEP THE SIGNABLE TRANSACTION HASH UNIQUE FOR EACH TRANSACTION, BYTECODE IS INCLUDED IN THE HAS +- WE CHOSE NOT TO INCLUDE BYTECODE IN THE SALT BECAUSE CREATE2 IS A HASH OF THE BYTECODE AND THE SALT. + - EVEN IF THE SALT IS THE SAME FOR CONTRACT A AND B, THEY WILL HAVE DIFFERENT BYTECODES AND THUS DIFFERENT ADDRESSES. \ No newline at end of file diff --git a/script/PredictiveDeployer.sol b/script/PredictiveDeployer.s.sol similarity index 100% rename from script/PredictiveDeployer.sol rename to script/PredictiveDeployer.s.sol diff --git a/src/PredictiveDeployer.sol b/src/PredictiveDeployer.sol index a753437..ba89a95 100644 --- a/src/PredictiveDeployer.sol +++ b/src/PredictiveDeployer.sol @@ -16,7 +16,8 @@ contract PredictiveDeployer is Initializable, UUPSUpgradeable, Ownable { bytes32 internal domainSeparator; // Factory Storage - mapping(address => uint256) public userNonces; + mapping(address => mapping(bytes32 => uint256)) public userNonces; + mapping(address => address[]) internal _deploymentHistory; // Events event Deploy(address indexed principal, address indexed child, bytes32 hashedBytecode, uint256 nonce); @@ -52,19 +53,28 @@ contract PredictiveDeployer is Initializable, UUPSUpgradeable, Ownable { * @param _nonce The principal account's current nonce on this factory contract. * @return messageHash The expected signed message hash. */ - function _computeMessageHash(address _principal, uint256 _nonce) internal view returns (bytes32) { - bytes32 txHash = getTransactionHash(_principal, _nonce); + function _computeMessageHash(address _principal, bytes memory _bytecode, uint256 _nonce) + internal + view + returns (bytes32) + { + bytes32 txHash = getTransactionHash(_principal, _bytecode, _nonce); 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 _bytecode The bytecode of the contract to be deployed. * @param _nonce The principal account's current nonce on this factory contract. * @return txHash The unique deployment transaction hash to be signed. */ - function getTransactionHash(address _principal, uint256 _nonce) public view returns (bytes32) { - return keccak256(abi.encodePacked(domainSeparator, _principal, _nonce)); + function getTransactionHash(address _principal, bytes memory _bytecode, uint256 _nonce) + public + view + returns (bytes32) + { + return keccak256(abi.encodePacked(domainSeparator, _principal, _bytecode, _nonce)); } /** @@ -74,11 +84,29 @@ contract PredictiveDeployer is Initializable, UUPSUpgradeable, Ownable { * @return child The predicted address of the contract to be deployed. */ function getAddress(address _principal, bytes memory _bytecode) public view returns (address) { - uint256 salt = uint256(uint160(_principal)) + userNonces[_principal]; + uint256 salt = uint256(uint160(_principal)) + userNonces[_principal][keccak256(_bytecode)]; bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(_bytecode))); return address(uint160(uint256(hash))); } + /** + * @dev Returns the keccak256 hash of the provided bytecode. + * @param _bytecode The bytecode of the contract to be deployed. + * @return hash The keccak256 hash of the provided bytecode. + */ + function getBytecodeHash(bytes memory _bytecode) public pure returns (bytes32) { + return keccak256(_bytecode); + } + + /** + * @dev Returns a list of the provided principal's previously deployed child contracts. + * @param _principal The address of the account that signed the message hash. + * @return deploymentHistory A list of the provided principal's previously deployed child contracts. + */ + function getDeploymentHistory(address _principal) public view returns (address[] memory) { + return _deploymentHistory[_principal]; + } + /** * @dev Deploys arbitrary child contracts at predictable addresses, derived from account signatures. * @param _principal The address of the account that signed the message hash. @@ -87,8 +115,9 @@ contract PredictiveDeployer is Initializable, UUPSUpgradeable, Ownable { * @return child The address of the deployed contract. */ function deploy(address _principal, bytes memory _signature, bytes memory _bytecode) public returns (address) { - uint256 currentNonce = userNonces[_principal]; - bytes32 expectedMessageHash = _computeMessageHash(_principal, currentNonce); + bytes32 hashedByteCode = keccak256(_bytecode); + uint256 currentNonce = userNonces[_principal][hashedByteCode]; + bytes32 expectedMessageHash = _computeMessageHash(_principal, _bytecode, currentNonce); // Recover principal address address recoveredSigner = ECDSA.recover(expectedMessageHash, _signature); @@ -100,7 +129,7 @@ contract PredictiveDeployer is Initializable, UUPSUpgradeable, Ownable { uint256 salt = uint256(uint160(_principal)) + currentNonce; // Update nonce state - userNonces[_principal]++; + userNonces[_principal][hashedByteCode]++; // Deploy contract address child; @@ -109,10 +138,15 @@ contract PredictiveDeployer is Initializable, UUPSUpgradeable, Ownable { if iszero(extcodesize(child)) { revert(0, 0) } } - emit Deploy(_principal, child, keccak256(_bytecode), currentNonce); + // Update deployment history + _deploymentHistory[_principal].push(child); + + emit Deploy(_principal, child, hashedByteCode, currentNonce); return child; } + receive() external payable { } // solhint-disable-line no-empty-blocks + /* **************************************************************************** ** ** Admin Functions @@ -135,6 +169,4 @@ contract PredictiveDeployer is Initializable, UUPSUpgradeable, Ownable { SafeTransferLib.safeTransfer(ERC20(_token), msg.sender, balance); } - - receive() external payable { } // solhint-disable-line no-empty-blocks } diff --git a/src/interfaces/IPredictiveDeployer.sol b/src/interfaces/IPredictiveDeployer.sol index 25e7353..cf3820f 100644 --- a/src/interfaces/IPredictiveDeployer.sol +++ b/src/interfaces/IPredictiveDeployer.sol @@ -2,8 +2,12 @@ pragma solidity ^0.8.21; interface IPredictiveDeployer { - function userNonces(address _principal) external returns (uint256); - function getTransactionHash(address _principal, uint256 _nonce) external returns (bytes32); + function userNonces(address _principal, bytes32 _hashedBytecode) external returns (uint256); + function getDeploymentHistory(address _principal) external returns (address[] memory); + function getTransactionHash(address _principal, bytes memory _bytecode, uint256 _nonce) + external + returns (bytes32); + function getBytecodeHash(bytes memory _bytecode) external pure returns (bytes32); function getAddress(address _principal, bytes memory _bytecode) external returns (address); function deploy(address _principal, bytes memory _signature, bytes memory _bytecode) external returns (address); } diff --git a/test/PredictiveDeployer.admin.t.sol b/test/PredictiveDeployer.admin.t.sol index f889598..dbe0302 100644 --- a/test/PredictiveDeployer.admin.t.sol +++ b/test/PredictiveDeployer.admin.t.sol @@ -6,21 +6,12 @@ import { PredictiveDeployer } from "../src/PredictiveDeployer.sol"; import { ERC1967Proxy } from "../src/dependencies/proxy/ERC1967Proxy.sol"; import { IERC20 } from "../src/dependencies/token/interfaces/IERC20.sol"; import { IPredictiveDeployerAdmin } from "../src/interfaces/IPredictiveDeployerAdmin.sol"; -import { CONTRACT_DEPLOYER, TEST_ERC20_TOKEN } from "./common/constants.t.sol"; +import { TestSetup } from "./common/TestSetup.t.sol"; +import { CONTRACT_DEPLOYER, TEST_ERC20_TOKEN } from "./common/Constants.t.sol"; -contract PredictiveDeployerTest is Test { +contract PredictiveDeployerTest is TestSetup { /* solhint-disable func-name-mixedcase */ - PredictiveDeployer public implementation; - ERC1967Proxy public proxy; - - function setUp() public { - // Instantiate contracts - implementation = new PredictiveDeployer(); - vm.prank(CONTRACT_DEPLOYER); - proxy = new ERC1967Proxy(address(implementation), abi.encodeWithSignature("initialize()")); - } - function test_ExtractNative(uint256 amount) public { // Setup vm.assume(amount > 0 && amount < 1e22); diff --git a/test/PredictiveDeployer.t.sol b/test/PredictiveDeployer.t.sol index df0de82..0b7154c 100644 --- a/test/PredictiveDeployer.t.sol +++ b/test/PredictiveDeployer.t.sol @@ -1,44 +1,29 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.21; -import { Test } from "forge-std/Test.sol"; import { VmSafe } from "forge-std/Vm.sol"; import { PredictiveDeployer } from "../src/PredictiveDeployer.sol"; import { ERC1967Proxy } from "../src/dependencies/proxy/ERC1967Proxy.sol"; -import { Child } from "./Child.t.sol"; import { IPredictiveDeployer } from "../src/interfaces/IPredictiveDeployer.sol"; -import { CONTRACT_DEPLOYER } from "./common/constants.t.sol"; +import { TestSetup } from "./common/TestSetup.t.sol"; +import { AddressLib } from "./helpers/libraries/AddressLib.t.sol"; +import { DeploymentHelper } from "./helpers/DeploymentHelper.t.sol"; +import { CONTRACT_DEPLOYER } from "./common/Constants.t.sol"; -contract PredictiveDeployerTest is Test { +contract PredictiveDeployerTest is DeploymentHelper, TestSetup { /* solhint-disable func-name-mixedcase */ - PredictiveDeployer public implementation; - ERC1967Proxy public proxy; - Child public child; - bytes public childBytecode; - - // Events - event Deploy(address indexed sender, address indexed child, bytes32 hashedBytecode, uint256 nonce); - - function setUp() public { - implementation = new PredictiveDeployer(); - vm.prank(CONTRACT_DEPLOYER); - proxy = new ERC1967Proxy(address(implementation), abi.encodeWithSignature("initialize()")); - child = new Child(address(this)); - - // Get Bytecode - bytes memory bytecode = type(Child).creationCode; - childBytecode = abi.encodePacked(bytecode, abi.encode(address(this))); - } + using AddressLib for address[]; function testFuzz_GetAddress(uint256 pkNum, address sender) public { // Setup VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); - uint256 currentNonce = IPredictiveDeployer(address(proxy)).userNonces(wallet.addr); + uint256 currentNonce = IPredictiveDeployer(address(proxy)).userNonces(wallet.addr, hashedBytecodeChildA); // Get signature information - bytes32 txHash = IPredictiveDeployer(address(proxy)).getTransactionHash(wallet.addr, currentNonce); + bytes32 txHash = + IPredictiveDeployer(address(proxy)).getTransactionHash(wallet.addr, bytecodeChildA, currentNonce); bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", txHash)); @@ -49,13 +34,13 @@ contract PredictiveDeployerTest is Test { // Expectation vm.startPrank(sender); uint256 snapShot = vm.snapshot(); - address expectedChild = IPredictiveDeployer(address(proxy)).deploy(wallet.addr, signature, childBytecode); + address expectedChild = IPredictiveDeployer(address(proxy)).deploy(wallet.addr, signature, bytecodeChildA); // Set chain state to what it was before the deployment vm.revertTo(snapShot); // Act - address actualChild = IPredictiveDeployer(address(proxy)).getAddress(wallet.addr, childBytecode); + address actualChild = IPredictiveDeployer(address(proxy)).getAddress(wallet.addr, bytecodeChildA); vm.stopPrank(); // Assertions @@ -66,10 +51,11 @@ contract PredictiveDeployerTest is Test { // Setup VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); - uint256 currentNonce = IPredictiveDeployer(address(proxy)).userNonces(wallet.addr); + uint256 preDeployNonce = IPredictiveDeployer(address(proxy)).userNonces(wallet.addr, hashedBytecodeChildA); // Get signature information - bytes32 txHash = IPredictiveDeployer(address(proxy)).getTransactionHash(wallet.addr, currentNonce); + bytes32 txHash = + IPredictiveDeployer(address(proxy)).getTransactionHash(wallet.addr, bytecodeChildA, preDeployNonce); bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", txHash)); @@ -78,26 +64,101 @@ contract PredictiveDeployerTest is Test { // Expectations vm.startPrank(sender); - address expectedChild = IPredictiveDeployer(address(proxy)).getAddress(wallet.addr, childBytecode); + address expectedChild = IPredictiveDeployer(address(proxy)).getAddress(wallet.addr, bytecodeChildA); vm.expectEmit(true, true, true, true, address(proxy)); - emit Deploy(wallet.addr, expectedChild, keccak256(childBytecode), currentNonce); + emit Deploy(wallet.addr, expectedChild, keccak256(bytecodeChildA), preDeployNonce); // Act - address actualChild = IPredictiveDeployer(address(proxy)).deploy(wallet.addr, signature, childBytecode); + address actualChild = IPredictiveDeployer(address(proxy)).deploy(wallet.addr, signature, bytecodeChildA); vm.stopPrank(); // Assertions assertEq(actualChild, expectedChild); } - function test_CannotDeployReplay(uint256 pkNum) public { + function testFuzz_DeployTwice(uint256 pkNum) public { + // Setup + VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); + + // First Deployment + address actualChild1 = deployChild(address(proxy), wallet, bytecodeChildA); + + // Second Deployment + address actualChild2 = deployChild(address(proxy), wallet, bytecodeChildA); + + // Assertions + assertTrue(actualChild1 != address(0) && actualChild2 != address(0)); + assertTrue(actualChild1 != actualChild2); + } + + function testFuzz_DeployOrderIndependence(uint256 pkNum) public { + // Setup + VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); + + // First deployment set + uint256 snapShot = vm.snapshot(); + address setOneChildA = deployChild(address(proxy), wallet, bytecodeChildA); + address setOneChildB = deployChild(address(proxy), wallet, bytecodeChildB); + + // 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); + + // Assertions + assertEq(setOneChildA, setTwoChildA); + assertEq(setOneChildB, setTwoChildB); + } + + function testFuzz_DeployNonceUpdate(uint256 pkNum) public { // Setup VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); + uint256 preDeployNonce = IPredictiveDeployer(address(proxy)).userNonces(wallet.addr, hashedBytecodeChildA); - uint256 currentNonce = IPredictiveDeployer(address(proxy)).userNonces(wallet.addr); + // Act + deployChild(address(proxy), wallet, bytecodeChildA); + uint256 postDeployNonce = IPredictiveDeployer(address(proxy)).userNonces(wallet.addr, hashedBytecodeChildA); + + // Assertions + assertEq(postDeployNonce, preDeployNonce + 1); + } + + function testFuzz_DeployHistoryUpdate(uint256 pkNum) public { + // Setup + VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); + + address[] memory deploymentHistory = IPredictiveDeployer(address(proxy)).getDeploymentHistory(wallet.addr); + + // Pre-act assertions + assertEq(deploymentHistory.length, 0); + + // Act + address child1 = deployChild(address(proxy), wallet, bytecodeChildA); + deploymentHistory = IPredictiveDeployer(address(proxy)).getDeploymentHistory(wallet.addr); + + // Assertions + assertEq(deploymentHistory.length, 1); + assertTrue(deploymentHistory.includes(child1)); + + address child2 = deployChild(address(proxy), wallet, bytecodeChildA); + deploymentHistory = IPredictiveDeployer(address(proxy)).getDeploymentHistory(wallet.addr); + + // Assertions + assertEq(deploymentHistory.length, 2); + assertTrue(deploymentHistory.includes(child2)); + } + + function testFuzz_CannotDeployReplay(uint256 pkNum) public { + // Setup + VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); + + uint256 currentNonce = IPredictiveDeployer(address(proxy)).userNonces(wallet.addr, hashedBytecodeChildA); // Get signature information - bytes32 txHash = IPredictiveDeployer(address(proxy)).getTransactionHash(wallet.addr, currentNonce); + bytes32 txHash = + IPredictiveDeployer(address(proxy)).getTransactionHash(wallet.addr, bytecodeChildA, currentNonce); bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", txHash)); @@ -105,24 +166,25 @@ contract PredictiveDeployerTest is Test { bytes memory signature = abi.encodePacked(r, s, v); // Deploy once - IPredictiveDeployer(address(proxy)).deploy(wallet.addr, signature, childBytecode); + IPredictiveDeployer(address(proxy)).deploy(wallet.addr, signature, bytecodeChildA); // Act: attempt replay vm.expectRevert(PredictiveDeployer.Unauthorized.selector); - IPredictiveDeployer(address(proxy)).deploy(wallet.addr, signature, childBytecode); + IPredictiveDeployer(address(proxy)).deploy(wallet.addr, signature, bytecodeChildA); } - function test_CannotDeployWithoutApproval(uint256 pkNum, address invalidPrincipal) public { + function testFuzz_CannotDeployWithoutApproval(uint256 pkNum, address invalidPrincipal) public { // Setup VmSafe.Wallet memory wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(pkNum))))); // Failing condition: principal is not the signer vm.assume(invalidPrincipal != wallet.addr); - uint256 currentNonce = IPredictiveDeployer(address(proxy)).userNonces(wallet.addr); + uint256 currentNonce = IPredictiveDeployer(address(proxy)).userNonces(wallet.addr, hashedBytecodeChildA); // Get signature information - bytes32 txHash = IPredictiveDeployer(address(proxy)).getTransactionHash(wallet.addr, currentNonce); + bytes32 txHash = + IPredictiveDeployer(address(proxy)).getTransactionHash(wallet.addr, bytecodeChildA, currentNonce); bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", txHash)); @@ -131,10 +193,23 @@ contract PredictiveDeployerTest is Test { // Act: attempt with invalid principal vm.expectRevert(PredictiveDeployer.Unauthorized.selector); - IPredictiveDeployer(address(proxy)).deploy(invalidPrincipal, signature, childBytecode); + IPredictiveDeployer(address(proxy)).deploy(invalidPrincipal, signature, bytecodeChildA); + } + + function testFuzz_GetBytecodeHash(bytes memory _childBytecode) public { + vm.assume(_childBytecode.length > 0 && _childBytecode.length <= 24576); // max contract size + + // Act + bytes32 actualHash = IPredictiveDeployer(address(proxy)).getBytecodeHash(_childBytecode); + + // Expected + bytes32 expectedHash = keccak256(_childBytecode); + + // Assertions + assertEq(actualHash, expectedHash, "Hash mismatch."); } - function test_Receive(uint256 transferAmount) public { + function testFuzz_Receive(uint256 transferAmount) public { // Assumptions vm.assume(transferAmount != 0 && transferAmount <= 1000 ether); diff --git a/test/Child.t.sol b/test/common/Child.t.sol similarity index 70% rename from test/Child.t.sol rename to test/common/Child.t.sol index d38e6f5..f0b54ec 100644 --- a/test/Child.t.sol +++ b/test/common/Child.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.21; -contract Child { +contract ChildA { uint256 public number; address public admin; @@ -14,3 +14,11 @@ contract Child { number++; } } + +contract ChildB { + uint256 public number; + + function increment() public { + number++; + } +} diff --git a/test/common/constants.t.sol b/test/common/Constants.t.sol similarity index 100% rename from test/common/constants.t.sol rename to test/common/Constants.t.sol diff --git a/test/common/TestSetup.t.sol b/test/common/TestSetup.t.sol new file mode 100644 index 0000000..ce21051 --- /dev/null +++ b/test/common/TestSetup.t.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.21; + +import { Test } from "forge-std/Test.sol"; +import { IPredictiveDeployer } from "../../src/interfaces/IPredictiveDeployer.sol"; +import { PredictiveDeployer } from "../../src/PredictiveDeployer.sol"; +import { ERC1967Proxy } from "../../src/dependencies/proxy/ERC1967Proxy.sol"; +import { ChildA, ChildB } from "./Child.t.sol"; +import { CONTRACT_DEPLOYER } from "./../common/Constants.t.sol"; + +abstract contract TestSetup is Test { + PredictiveDeployer 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 PredictiveDeployer(); + 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/helpers/DeploymentHelper.t.sol b/test/helpers/DeploymentHelper.t.sol new file mode 100644 index 0000000..c08b73d --- /dev/null +++ b/test/helpers/DeploymentHelper.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.21; + +import { Test } from "forge-std/Test.sol"; +import { VmSafe } from "forge-std/Vm.sol"; +import { IPredictiveDeployer } from "../../src/interfaces/IPredictiveDeployer.sol"; + +abstract contract DeploymentHelper is Test { + /** + * @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 _bytecode The to-be-deployed contract bytecode. + */ + function deployChild(address _contract, VmSafe.Wallet memory _wallet, bytes memory _bytecode) + internal + returns (address) + { + uint256 currentNonce = IPredictiveDeployer(_contract).userNonces(_wallet.addr, keccak256(_bytecode)); + bytes32 txHash = IPredictiveDeployer(_contract).getTransactionHash(_wallet.addr, _bytecode, currentNonce); + 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); + return IPredictiveDeployer(_contract).deploy(_wallet.addr, signature, _bytecode); + } +} diff --git a/test/helpers/libraries/AddressLib.t.sol b/test/helpers/libraries/AddressLib.t.sol new file mode 100644 index 0000000..f420790 --- /dev/null +++ b/test/helpers/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/test_proxy/ERC1967Proxy.t.sol b/test/test_proxy/ERC1967Proxy.t.sol index e7bae1d..61bd995 100644 --- a/test/test_proxy/ERC1967Proxy.t.sol +++ b/test/test_proxy/ERC1967Proxy.t.sol @@ -1,25 +1,15 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.21; -import { Test } from "forge-std/Test.sol"; import { PredictiveDeployer } from "../../src/PredictiveDeployer.sol"; import { ERC1967Proxy } from "../../src/dependencies/proxy/ERC1967Proxy.sol"; import { IPredictiveDeployerAdmin } from "../../src/interfaces/IPredictiveDeployerAdmin.sol"; -import { CONTRACT_DEPLOYER } from "../common/constants.t.sol"; +import { TestSetup } from "../common/TestSetup.t.sol"; +import { CONTRACT_DEPLOYER } from "../common/Constants.t.sol"; -contract ERC1967ProxyTest is Test { +contract ERC1967ProxyTest is TestSetup { /* solhint-disable func-name-mixedcase */ - // Test Contracts - PredictiveDeployer public implementation; - ERC1967Proxy public proxy; - - function setUp() public { - implementation = new PredictiveDeployer(); - vm.prank(CONTRACT_DEPLOYER); - proxy = new ERC1967Proxy(address(implementation), abi.encodeWithSignature("initialize()")); - } - function test_upgradeToAndCall() public { // Setup bytes32 implentationStorageSlot = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;