Skip to content

Commit

Permalink
refactor: update UserNonces (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
rahul0eth authored Oct 26, 2023
1 parent 6526a75 commit 2e3be14
Show file tree
Hide file tree
Showing 12 changed files with 264 additions and 82 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
File renamed without changes.
56 changes: 44 additions & 12 deletions src/PredictiveDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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));
}

/**
Expand All @@ -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.
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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
}
8 changes: 6 additions & 2 deletions src/interfaces/IPredictiveDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
15 changes: 3 additions & 12 deletions test/PredictiveDeployer.admin.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 2e3be14

Please sign in to comment.