-
Notifications
You must be signed in to change notification settings - Fork 372
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3a4e387
commit 032066c
Showing
5 changed files
with
277 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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())) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
); | ||
} | ||
} |