Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: update UserNonces #6

Merged
merged 3 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
0xernesto marked this conversation as resolved.
Show resolved Hide resolved
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.
*/
cucupac marked this conversation as resolved.
Show resolved Hide resolved
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.
rahul0eth marked this conversation as resolved.
Show resolved Hide resolved
* @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) {
cucupac marked this conversation as resolved.
Show resolved Hide resolved
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
Loading