-
Notifications
You must be signed in to change notification settings - Fork 13
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
1319404
commit edd6b6b
Showing
16 changed files
with
917 additions
and
86 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
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
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,98 @@ | ||
use { | ||
serde_json::Value, | ||
std::process::{Command, Stdio}, | ||
}; | ||
|
||
fn main() { | ||
#[cfg(feature = "cacao")] | ||
build_contracts(); | ||
} | ||
|
||
fn build_contracts() { | ||
println!("cargo::rerun-if-changed=contracts"); | ||
install_foundry(); | ||
compile_contracts(); | ||
extract_bytecodes(); | ||
} | ||
|
||
fn format_foundry_dir(path: &str) -> String { | ||
format!( | ||
"{}/../../../../.foundry/{}", | ||
std::env::var("OUT_DIR").unwrap(), | ||
path | ||
) | ||
} | ||
|
||
fn install_foundry() { | ||
let bin_folder = format_foundry_dir("bin"); | ||
std::fs::remove_dir_all(&bin_folder).ok(); | ||
std::fs::create_dir_all(&bin_folder).unwrap(); | ||
let output = Command::new("bash") | ||
.args(["-c", &format!("curl https://raw.githubusercontent.com/foundry-rs/foundry/e0ea59cae26d945445d9cf21fdf22f4a18ac5bb2/foundryup/foundryup | FOUNDRY_DIR={} bash", format_foundry_dir(""))]) | ||
.stdout(Stdio::piped()) | ||
.stderr(Stdio::piped()) | ||
.spawn() | ||
.unwrap() | ||
.wait_with_output() | ||
.unwrap(); | ||
println!("foundryup status: {:?}", output.status); | ||
let stdout = String::from_utf8(output.stdout).unwrap(); | ||
println!("foundryup stdout: {stdout:?}"); | ||
let stderr = String::from_utf8(output.stderr).unwrap(); | ||
println!("foundryup stderr: {stderr:?}"); | ||
assert!(output.status.success()); | ||
} | ||
|
||
fn compile_contracts() { | ||
let output = Command::new(format_foundry_dir("bin/forge")) | ||
.args([ | ||
"build", | ||
"--contracts=relay_rpc/contracts", | ||
"--cache-path", | ||
&format_foundry_dir("forge/cache"), | ||
"--out", | ||
&format_foundry_dir("forge/out"), | ||
]) | ||
.stdout(Stdio::piped()) | ||
.stderr(Stdio::piped()) | ||
.spawn() | ||
.unwrap() | ||
.wait_with_output() | ||
.unwrap(); | ||
println!("forge status: {:?}", output.status); | ||
let stdout = String::from_utf8(output.stdout).unwrap(); | ||
println!("forge stdout: {stdout:?}"); | ||
let stderr = String::from_utf8(output.stderr).unwrap(); | ||
println!("forge stderr: {stderr:?}"); | ||
assert!(output.status.success()); | ||
} | ||
|
||
const EIP6492_FILE: &str = "forge/out/Eip6492.sol/ValidateSigOffchain.json"; | ||
const EIP6492_BYTECODE_FILE: &str = "forge/out/Eip6492.sol/ValidateSigOffchain.bytecode"; | ||
const EIP1271_MOCK_FILE: &str = "forge/out/Eip1271Mock.sol/Eip1271Mock.json"; | ||
const EIP1271_MOCK_BYTECODE_FILE: &str = "forge/out/Eip1271Mock.sol/Eip1271Mock.bytecode"; | ||
fn extract_bytecodes() { | ||
extract_bytecode( | ||
&format_foundry_dir(EIP6492_FILE), | ||
&format_foundry_dir(EIP6492_BYTECODE_FILE), | ||
); | ||
extract_bytecode( | ||
&format_foundry_dir(EIP1271_MOCK_FILE), | ||
&format_foundry_dir(EIP1271_MOCK_BYTECODE_FILE), | ||
); | ||
} | ||
|
||
fn extract_bytecode(input_file: &str, output_file: &str) { | ||
let contents = serde_json::from_slice::<Value>(&std::fs::read(input_file).unwrap()).unwrap(); | ||
let bytecode = contents | ||
.get("bytecode") | ||
.unwrap() | ||
.get("object") | ||
.unwrap() | ||
.as_str() | ||
.unwrap() | ||
.strip_prefix("0x") | ||
.unwrap(); | ||
let bytecode = hex::decode(bytecode).unwrap(); | ||
std::fs::write(output_file, bytecode).unwrap(); | ||
} |
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,49 @@ | ||
// https://github.com/Genesis3800/CREATE2Factory/blob/b202029eadc0299e6e5923dd90db4200c2f7955a/src/Create2.sol | ||
|
||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
contract Create2 { | ||
|
||
error Create2InsufficientBalance(uint256 received, uint256 minimumNeeded); | ||
|
||
error Create2EmptyBytecode(); | ||
|
||
error Create2FailedDeployment(); | ||
|
||
function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) external payable returns (address addr) { | ||
|
||
if (msg.value < amount) { | ||
revert Create2InsufficientBalance(msg.value, amount); | ||
} | ||
|
||
if (bytecode.length == 0) { | ||
revert Create2EmptyBytecode(); | ||
} | ||
|
||
assembly { | ||
addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt) | ||
} | ||
|
||
if (addr == address(0)) { | ||
revert Create2FailedDeployment(); | ||
} | ||
} | ||
|
||
function computeAddress(bytes32 salt, bytes32 bytecodeHash) external view returns (address addr) { | ||
|
||
address contractAddress = address(this); | ||
|
||
assembly { | ||
let ptr := mload(0x40) | ||
|
||
mstore(add(ptr, 0x40), bytecodeHash) | ||
mstore(add(ptr, 0x20), salt) | ||
mstore(ptr, contractAddress) | ||
let start := add(ptr, 0x0b) | ||
mstore8(start, 0xff) | ||
addr := keccak256(start, 85) | ||
} | ||
} | ||
|
||
} |
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,111 @@ | ||
// As per ERC-1271 | ||
interface IERC1271Wallet { | ||
function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4 magicValue); | ||
} | ||
|
||
error ERC1271Revert(bytes error); | ||
error ERC6492DeployFailed(bytes error); | ||
|
||
contract UniversalSigValidator { | ||
bytes32 private constant ERC6492_DETECTION_SUFFIX = 0x6492649264926492649264926492649264926492649264926492649264926492; | ||
bytes4 private constant ERC1271_SUCCESS = 0x1626ba7e; | ||
|
||
function isValidSigImpl( | ||
address _signer, | ||
bytes32 _hash, | ||
bytes calldata _signature, | ||
bool allowSideEffects, | ||
bool tryPrepare | ||
) public returns (bool) { | ||
uint contractCodeLen = address(_signer).code.length; | ||
bytes memory sigToValidate; | ||
// The order here is strictly defined in https://eips.ethereum.org/EIPS/eip-6492 | ||
// - ERC-6492 suffix check and verification first, while being permissive in case the contract is already deployed; if the contract is deployed we will check the sig against the deployed version, this allows 6492 signatures to still be validated while taking into account potential key rotation | ||
// - ERC-1271 verification if there's contract code | ||
// - finally, ecrecover | ||
bool isCounterfactual = bytes32(_signature[_signature.length-32:_signature.length]) == ERC6492_DETECTION_SUFFIX; | ||
if (isCounterfactual) { | ||
address create2Factory; | ||
bytes memory factoryCalldata; | ||
(create2Factory, factoryCalldata, sigToValidate) = abi.decode(_signature[0:_signature.length-32], (address, bytes, bytes)); | ||
|
||
if (contractCodeLen == 0 || tryPrepare) { | ||
(bool success, bytes memory err) = create2Factory.call(factoryCalldata); | ||
if (!success) revert ERC6492DeployFailed(err); | ||
} | ||
} else { | ||
sigToValidate = _signature; | ||
} | ||
|
||
// Try ERC-1271 verification | ||
if (isCounterfactual || contractCodeLen > 0) { | ||
try IERC1271Wallet(_signer).isValidSignature(_hash, sigToValidate) returns (bytes4 magicValue) { | ||
bool isValid = magicValue == ERC1271_SUCCESS; | ||
|
||
// retry, but this time assume the prefix is a prepare call | ||
if (!isValid && !tryPrepare && contractCodeLen > 0) { | ||
return isValidSigImpl(_signer, _hash, _signature, allowSideEffects, true); | ||
} | ||
|
||
if (contractCodeLen == 0 && isCounterfactual && !allowSideEffects) { | ||
// if the call had side effects we need to return the | ||
// result using a `revert` (to undo the state changes) | ||
assembly { | ||
mstore(0, isValid) | ||
revert(31, 1) | ||
} | ||
} | ||
|
||
return isValid; | ||
} catch (bytes memory err) { | ||
// retry, but this time assume the prefix is a prepare call | ||
if (!tryPrepare && contractCodeLen > 0) { | ||
return isValidSigImpl(_signer, _hash, _signature, allowSideEffects, true); | ||
} | ||
|
||
revert ERC1271Revert(err); | ||
} | ||
} | ||
|
||
// ecrecover verification | ||
require(_signature.length == 65, 'SignatureValidator#recoverSigner: invalid signature length'); | ||
bytes32 r = bytes32(_signature[0:32]); | ||
bytes32 s = bytes32(_signature[32:64]); | ||
uint8 v = uint8(_signature[64]); | ||
if (v != 27 && v != 28) { | ||
revert('SignatureValidator: invalid signature v value'); | ||
} | ||
return ecrecover(_hash, v, r, s) == _signer; | ||
} | ||
|
||
function isValidSigWithSideEffects(address _signer, bytes32 _hash, bytes calldata _signature) | ||
external returns (bool) | ||
{ | ||
return this.isValidSigImpl(_signer, _hash, _signature, true, false); | ||
} | ||
|
||
function isValidSig(address _signer, bytes32 _hash, bytes calldata _signature) | ||
external returns (bool) | ||
{ | ||
try this.isValidSigImpl(_signer, _hash, _signature, false, false) returns (bool isValid) { return isValid; } | ||
catch (bytes memory error) { | ||
// in order to avoid side effects from the contract getting deployed, the entire call will revert with a single byte result | ||
uint len = error.length; | ||
if (len == 1) return error[0] == 0x01; | ||
// all other errors are simply forwarded, but in custom formats so that nothing else can revert with a single byte in the call | ||
else assembly { revert(error, len) } | ||
} | ||
} | ||
} | ||
|
||
// this is a helper so we can perform validation in a single eth_call without pre-deploying a singleton | ||
contract ValidateSigOffchain { | ||
constructor (address _signer, bytes32 _hash, bytes memory _signature) { | ||
UniversalSigValidator validator = new UniversalSigValidator(); | ||
bool isValidSig = validator.isValidSigWithSideEffects(_signer, _hash, _signature); | ||
assembly { | ||
mstore(0, isValidSig) | ||
return(31, 1) | ||
} | ||
} | ||
} |
Oops, something went wrong.