Skip to content

Commit

Permalink
✨ DeploylessPredeployQueryer (#867)
Browse files Browse the repository at this point in the history
  • Loading branch information
Vectorized authored Mar 26, 2024
1 parent 3a4e387 commit 032066c
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ DateTimeLibTest:testNthWeekdayInMonthOfYearTimestamp() (gas: 12031)
DateTimeLibTest:testNthWeekdayInMonthOfYearTimestamp(uint256,uint256,uint256,uint256) (runs: 268, μ: 3460, ~: 3500)
DateTimeLibTest:testWeekday() (gas: 705)
DateTimeLibTest:test__codesize() (gas: 20080)
DeploylessPredeployQueryerTest:testPredeployQueryer(bytes32) (runs: 268, μ: 215590, ~: 227249)
DeploylessPredeployQueryerTest:test__codesize() (gas: 6836)
DynamicBufferLibTest:testClear(uint256) (runs: 268, μ: 171018, ~: 171180)
DynamicBufferLibTest:testDynamicBuffer(bytes[],uint256) (runs: 268, μ: 983393, ~: 908603)
DynamicBufferLibTest:testDynamicBuffer(uint256) (runs: 268, μ: 945149, ~: 732824)
Expand Down
50 changes: 50 additions & 0 deletions DeploylessPredeployQueryer.yul
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
object "DeploylessPredeployQueryer" {
code {
codecopy(returndatasize(), dataoffset("runtime"), codesize())
let target := mload(returndatasize())
let targetQueryCalldata := mload(0x20)
let factoryCalldata := mload(0x60)
// If the target does not exist, deploy it.
if iszero(extcodesize(target)) {
if iszero(
call(
gas(),
mload(0x40),
returndatasize(),
add(factoryCalldata, 0x20),
mload(factoryCalldata),
0x00,
0x20
)
) {
returndatacopy(0x00, 0x00, returndatasize())
revert(0x00, returndatasize())
}
if iszero(and(gt(returndatasize(), 0x1f), eq(mload(0x00), target))) {
mstore(0x00, 0xd1f6b812) // `ReturnedAddressMismatch()`.
revert(0x1c, 0x04)
}
}
if iszero(
call(
gas(),
target,
callvalue(),
add(targetQueryCalldata, 0x20),
mload(targetQueryCalldata),
codesize(),
0x00
)
) {
returndatacopy(0x00, 0x00, returndatasize())
revert(0x00, returndatasize())
}
mstore(0x00, 0x20)
mstore(0x20, returndatasize())
returndatacopy(0x40, 0x00, returndatasize())
return(0x00, add(0x60, returndatasize()))
}
object "runtime" {
code {}
}
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ utils
├─ SignatureCheckerLib — "Library for verification of ECDSA and ERC1271 signatures"
├─ ECDSA — "Library for verification of ECDSA signatures"
├─ EIP712 — "Contract for EIP-712 typed structured data hashing and signing"
├─ DeploylessPredeployQueryer — "Deployless queryer for predeploys"
├─ ERC1967Factory — "Factory for deploying and managing ERC1967 proxy contracts"
├─ ERC1967FactoryConstants — "The address and bytecode of the canonical ERC1967Factory"
├─ JSONParserLib — "Library for parsing JSONs"
Expand Down
86 changes: 86 additions & 0 deletions src/utils/DeploylessPredeployQueryer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Deployless queryer for predeploys.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/DeploylessPredeployQueryer.sol)
///
/// @dev
/// This contract is not meant to ever actually be deployed,
/// only mock deployed and used via a static `eth_call`.
///
/// Creation code (hex-encoded):
/// `38607d3d393d5160208051606051833b156045575b506000928391389184825192019034905af115603c578082523d90523d8160403e3d60600190f35b503d81803e3d90fd5b8260008281935190833d9101906040515af11560745783815114601f3d111660145763d1f6b81290526004601cfd5b3d81803e3d90fdfe`
/// See: https://gist.github.com/Vectorized/f77fce00a03dfa99aee526d2a77fd2aa
///
/// May be useful for generating ERC-6492 compliant signatures.
/// Inspired by Ambire's DeploylessUniversalSigValidator
/// (https://github.com/AmbireTech/signature-validator/blob/main/contracts/DeploylessUniversalSigValidator.sol)
contract DeploylessPredeployQueryer {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev The returned address by the factory does not match the provided address.
error ReturnedAddressMismatch();

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTRUCTOR */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev The code of the deployed contract can be `abi.decoded` into bytes,
/// which can be `abi.decoded` into the required variables.
///
/// For example, if `targetQueryCalldata` is expected to return a `uint256`,
/// you will use `abi.decode(abi.decode(deployed.code, (bytes)), (uint256))` to
/// get the returned `uint256`.
constructor(
address target,
bytes memory targetQueryCalldata,
address factory,
bytes memory factoryCalldata
) payable {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
// If the target does not exist, deploy it.
if iszero(extcodesize(target)) {
if iszero(
call(
gas(),
factory,
returndatasize(),
add(factoryCalldata, 0x20),
mload(factoryCalldata),
m,
0x20
)
) {
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
if iszero(and(gt(returndatasize(), 0x1f), eq(mload(m), target))) {
mstore(0x00, 0xd1f6b812) // `ReturnedAddressMismatch()`.
revert(0x1c, 0x04)
}
}
if iszero(
call(
gas(),
target,
callvalue(),
add(targetQueryCalldata, 0x20),
mload(targetQueryCalldata),
codesize(),
0x00
)
) {
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
mstore(m, 0x20)
mstore(add(m, 0x20), returndatasize())
returndatacopy(add(m, 0x40), 0x00, returndatasize())
return(m, add(0x60, returndatasize()))
}
}
}
138 changes: 138 additions & 0 deletions test/DeploylessPredeployQueryer.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "./utils/SoladyTest.sol";
import {LibClone} from "../src/utils/LibClone.sol";
import {DeploylessPredeployQueryer} from "../src/utils/DeploylessPredeployQueryer.sol";

library RandomBytesGeneratorLib {
function generate(uint256 seed) internal pure returns (bytes memory result) {
/// @solidity memory-safe-assembly
assembly {
result := add(0x20, mload(0x40))
mstore(0x00, seed)
let n := mod(keccak256(0x00, 0x20), 50)
mstore(result, n)
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } {
mstore(0x20, i)
mstore(add(i, add(result, 0x20)), keccak256(0x00, 0x40))
}
mstore(0x40, add(n, add(result, 0x20)))
}
}

function next(uint256 seed) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, seed)
result := keccak256(0x00, 0x20)
}
}
}

contract Target {
function generate(uint256 seed) public pure returns (bytes memory result) {
result = RandomBytesGeneratorLib.generate(seed);
/// @solidity memory-safe-assembly
assembly {
mstore(sub(result, 0x20), 0x20)
return(sub(result, 0x20), add(add(0x40, mod(seed, 50)), mload(result)))
}
}

function next(uint256 seed) public pure returns (uint256 result) {
result = RandomBytesGeneratorLib.next(seed);
}
}

contract Factory {
address public implementation;

constructor() {
implementation = address(new Target());
}

function deploy(bytes32 salt) public returns (address) {
if (predictDeployment(salt).code.length != 0) return predictDeployment(salt);
return LibClone.cloneDeterministic(implementation, salt);
}

function predictDeployment(bytes32 salt) public view returns (address) {
return LibClone.predictDeterministicAddress(implementation, salt, address(this));
}
}

contract DeploylessPredeployQueryerTest is SoladyTest {
Factory factory;

bytes internal constant _CREATION_CODE =
hex"38607d3d393d5160208051606051833b156045575b506000928391389184825192019034905af115603c578082523d90523d8160403e3d60600190f35b503d81803e3d90fd5b8260008281935190833d9101906040515af11560745783815114601f3d111660145763d1f6b81290526004601cfd5b3d81803e3d90fdfe";

function setUp() public {
factory = new Factory();
}

struct _TestTemps {
address target;
uint256 seed;
address deployed;
bytes factoryCalldata;
bytes targetQueryCalldata;
}

function _deployQuery(
address target,
bytes memory targetQueryCalldata,
bytes memory factoryCalldata
) internal returns (address result) {
if (_random() % 2 == 0) {
return address(
new DeploylessPredeployQueryer(
target, targetQueryCalldata, address(factory), factoryCalldata
)
);
}
bytes memory args =
abi.encode(target, targetQueryCalldata, address(factory), factoryCalldata);
bytes memory initcode;
if (_random() % 2 == 0) {
initcode = _CREATION_CODE;
} else {
initcode = type(DeploylessPredeployQueryer).creationCode;
}
initcode = abi.encodePacked(initcode, args);
/// @solidity memory-safe-assembly
assembly {
result := create(0, add(0x20, initcode), mload(initcode))
}
}

function testPredeployQueryer(bytes32 salt) public {
_TestTemps memory t;
t.target = factory.predictDeployment(salt);
if (_random() % 2 == 0) {
assertEq(factory.deploy(salt), t.target);
}
t.factoryCalldata = abi.encodeWithSignature("deploy(bytes32)", salt);
t.seed = _random();
if (_random() % 2 == 0) {
vm.expectRevert(DeploylessPredeployQueryer.ReturnedAddressMismatch.selector);
address wrongTarget = address(uint160(t.target) ^ 1);
t.deployed = _deployQuery(wrongTarget, t.targetQueryCalldata, t.factoryCalldata);
}
if (_random() % 2 == 0) {
t.targetQueryCalldata = abi.encodeWithSignature("generate(uint256)", t.seed);
t.deployed = _deployQuery(t.target, t.targetQueryCalldata, t.factoryCalldata);
assertEq(
abi.decode(abi.decode(t.deployed.code, (bytes)), (bytes)),
RandomBytesGeneratorLib.generate(t.seed)
);
}
t.targetQueryCalldata = abi.encodeWithSignature("next(uint256)", t.seed);
t.deployed = _deployQuery(t.target, t.targetQueryCalldata, t.factoryCalldata);
assertEq(
abi.decode(abi.decode(t.deployed.code, (bytes)), (uint256)),
RandomBytesGeneratorLib.next(t.seed)
);
}
}

0 comments on commit 032066c

Please sign in to comment.