Skip to content

Commit

Permalink
test: test deployment for bytecode variance independence (#10)
Browse files Browse the repository at this point in the history
* test: test deployment for bytecode variance independence

* chore: add requested changes

* refactor: improve test coverage across all constructor arg types, refactory create2 tests

* chore: add requested changes

* refactor: move proxy tests in test directory
  • Loading branch information
cucupac authored Nov 7, 2023
1 parent c5e351f commit 112adea
Show file tree
Hide file tree
Showing 24 changed files with 575 additions and 252 deletions.
56 changes: 29 additions & 27 deletions src/create3/Create3Factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
);

Expand Down Expand Up @@ -57,48 +60,47 @@ 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)]
)
);
}

/**
* @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);
}

/**
Expand All @@ -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
Expand Down
12 changes: 6 additions & 6 deletions src/create3/interfaces/ICreate3Factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
24 changes: 0 additions & 24 deletions test/common/Child.t.sol

This file was deleted.

4 changes: 2 additions & 2 deletions test/create2/Create2Factory.admin.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
Loading

0 comments on commit 112adea

Please sign in to comment.