diff --git a/.forge-snapshots/Create2FactoryTest#test_deploy.snap b/.forge-snapshots/Create2FactoryTest#test_deploy.snap new file mode 100644 index 00000000..faa4979a --- /dev/null +++ b/.forge-snapshots/Create2FactoryTest#test_deploy.snap @@ -0,0 +1 @@ +1730265 \ No newline at end of file diff --git a/.forge-snapshots/Create2FactoryTest#test_deploy_NonDeterministic.snap b/.forge-snapshots/Create2FactoryTest#test_deploy_NonDeterministic.snap new file mode 100644 index 00000000..3fcd4b04 --- /dev/null +++ b/.forge-snapshots/Create2FactoryTest#test_deploy_NonDeterministic.snap @@ -0,0 +1 @@ +393380 \ No newline at end of file diff --git a/.forge-snapshots/Create3FactoryTest#test_deploy.snap b/.forge-snapshots/Create3FactoryTest#test_deploy.snap new file mode 100644 index 00000000..3dab876b --- /dev/null +++ b/.forge-snapshots/Create3FactoryTest#test_deploy.snap @@ -0,0 +1 @@ +1765131 \ No newline at end of file diff --git a/.forge-snapshots/Create3FactoryTest#test_deploy_NonDeterministic.snap b/.forge-snapshots/Create3FactoryTest#test_deploy_NonDeterministic.snap new file mode 100644 index 00000000..2101d347 --- /dev/null +++ b/.forge-snapshots/Create3FactoryTest#test_deploy_NonDeterministic.snap @@ -0,0 +1 @@ +405640 \ No newline at end of file diff --git a/test/Create2Factory.t.sol b/test/Create2Factory.t.sol new file mode 100644 index 00000000..0ab2213c --- /dev/null +++ b/test/Create2Factory.t.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import "forge-std/Test.sol"; +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; +import {Vault} from "../src/Vault.sol"; +import {CLPoolManager} from "../src/pool-cl/CLPoolManager.sol"; + +/// @notice Through the use of solmate create3, deploy contracts with deterministic addresses +contract Create2Factory { + function deploy(bytes32 salt, bytes memory creationCode) public payable returns (address deployed) { + // hash salt with the deployer address to give each deployer its own namespace + salt = keccak256(abi.encodePacked(msg.sender, salt)); + + return Create2.deploy(msg.value, salt, creationCode); + } + + function getDeployed(address deployer, bytes32 salt, bytes32 bytecodeHash) public view returns (address addr) { + // hash salt with the deployer address to give each deployer its own namespace + salt = keccak256(abi.encodePacked(deployer, salt)); + + return Create2.computeAddress(salt, bytecodeHash); + } + + /// @notice execute a call on a deployed contract + /// @dev if contract uses Ownable(msg.sender) - it would mean the owner is Create2Factory, so a call is required + function execute(bytes32 salt, bytes32 bytecodeHash, bytes calldata data) external { + address target = getDeployed(msg.sender, salt, bytecodeHash); + (bool success,) = target.call(data); + require(success, "Create2Factory: failed execute call"); + } +} + +contract Create2FactoryTest is Test, GasSnapshot { + Create2Factory create2Factory; + Vault vault; + CLPoolManager clPoolManager; + + function setUp() public { + create2Factory = new Create2Factory(); + } + + function test_deploy_NonDeterministic() public { + // deploy + vault = new Vault(); + snapLastCall("Create2FactoryTest#test_deploy_NonDeterministic"); + } + + function test_Create2_Deploy() public { + // deploy + bytes memory creationCode = type(Vault).creationCode; + bytes32 salt = bytes32(uint256(0x1234)); + address deployed = create2Factory.deploy(salt, creationCode); + snapLastCall("Create2FactoryTest#test_deploy"); + + vault = Vault(deployed); + assertEq(vault.owner(), address(create2Factory)); + } + + function test_Create2_GetDeployed() public { + // deploy + bytes memory creationCode = type(Vault).creationCode; + bytes32 salt = bytes32(uint256(0x1234)); + address deployed = create2Factory.deploy(salt, creationCode); + + // get deployed + address getDeployed = create2Factory.getDeployed(address(this), salt, keccak256(creationCode)); + + // assert + assertEq(deployed, getDeployed); + } + + function test_Create2_Execute() public { + // pre-req: deploy vault + bytes memory creationCode = type(Vault).creationCode; + bytes32 salt = bytes32(uint256(0x1234)); + address deployed = create2Factory.deploy(salt, creationCode); + vault = Vault(deployed); + assertEq(vault.owner(), address(create2Factory)); + + address alice = makeAddr("alice"); + bytes32 bytecodeHash = keccak256(creationCode); + bytes memory data = abi.encodeWithSignature("transferOwnership(address)", alice); + create2Factory.execute(salt, bytecodeHash, data); + assertEq(vault.owner(), alice); + } + + function test_Create2_Execute_FrontRun() public { + // pre-req: deploy vault + bytes memory creationCode = type(Vault).creationCode; + bytes32 salt = bytes32(uint256(0x1234)); + address deployed = create2Factory.deploy(salt, creationCode); + vault = Vault(deployed); + assertEq(vault.owner(), address(create2Factory)); + + // assume someone front-runs the transferOwnership call + address alice = makeAddr("alice"); + vm.startPrank(alice); + bytes32 bytecodeHash = keccak256(abi.encodePacked(creationCode)); + bytes memory data = abi.encodeWithSignature("transferOwnership(address)", alice); + create2Factory.execute(salt, bytecodeHash, data); + + // frontrun doesn't work, the owner is still create2Factory + assertEq(vault.owner(), address(create2Factory)); + } +} diff --git a/test/Create3Factory.t.sol b/test/Create3Factory.t.sol new file mode 100644 index 00000000..594a3a5f --- /dev/null +++ b/test/Create3Factory.t.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import "forge-std/Test.sol"; +import {CREATE3} from "solmate/src/utils/CREATE3.sol"; +import {Vault} from "../src/Vault.sol"; +import {CLPoolManager} from "../src/pool-cl/CLPoolManager.sol"; + +/// @notice Through the use of solmate create3, deploy contracts with deterministic addresses +contract Create3Factory { + /// @notice deploy a contract with a deterministic address based on `salt + msg.sender (deployer)` + /// @dev this means that two contract with different creationCode can be deployed on the same address on different chains + function deploy(bytes32 salt, bytes memory creationCode) external payable returns (address deployed) { + // hash salt with the deployer address to give each deployer its own namespace + salt = keccak256(abi.encodePacked(msg.sender, salt)); + return CREATE3.deploy(salt, creationCode, msg.value); + } + + /// @notice get the deployed address of a contract with a deterministic address based on `salt + deployer` + function getDeployed(address deployer, bytes32 salt) public view returns (address deployed) { + // hash salt with the deployer address to give each deployer its own namespace + salt = keccak256(abi.encodePacked(deployer, salt)); + return CREATE3.getDeployed(salt); + } +} + +contract Create3FactoryTest is Test, GasSnapshot { + Create3Factory create3Factory; + Vault vault; + CLPoolManager clPoolManager; + + function setUp() public { + create3Factory = new Create3Factory(); + } + + function test_deploy_NonDeterministic() public { + // deploy + vault = new Vault(); + snapLastCall("Create3FactoryTest#test_deploy_NonDeterministic"); + } + + /// @dev showcase a need to pass in owner address + function test_Create3_Deploy() public { + // deploy + bytes memory creationCode = type(Vault).creationCode; + bytes32 salt = bytes32(uint256(0x1234)); + address deployed = create3Factory.deploy(salt, creationCode); + snapLastCall("Create3FactoryTest#test_deploy"); + + vault = Vault(deployed); + // note equal as owner is the proxy contract, not factory + assertNotEq(vault.owner(), address(create3Factory)); + } + + function test_Create3_Deploy_CLPoolManager() public { + // deploy vault + bytes memory vaultCreationCode = type(Vault).creationCode; + bytes32 vaultSalt = bytes32(uint256(0x1234)); + address deployedVault = create3Factory.deploy(vaultSalt, vaultCreationCode); + vault = Vault(deployedVault); + + // deploy CLPoolManager + bytes memory pmCreationCode = type(CLPoolManager).creationCode; + bytes memory pmConstructorArgs = abi.encode(deployedVault); + bytes memory pmCreationcodeWithArgs = abi.encodePacked(pmCreationCode, pmConstructorArgs); + bytes32 pmSalt = bytes32(uint256(0x12345)); + address deployedCLPoolManager = create3Factory.deploy(pmSalt, pmCreationcodeWithArgs); + clPoolManager = CLPoolManager(deployedVault); + } +}