Last active
March 8, 2023 19:22
-
-
Save Agusx1211/9e70e397a34f12a1ed240852284b91b5 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
pragma solidity ^0.8.18; | |
// As per ERC-1271 | |
interface IERC1271Wallet { | |
function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4 magicValue); | |
} | |
error ERC1271Error(string reason); | |
enum NoSideEffectsResult { | |
Invalid, | |
Valid, | |
Revert | |
} | |
contract UniversalSigValidator { | |
bytes32 private constant ERC6492_DETECTION_SUFFIX = 0x6492649264926492649264926492649264926492649264926492649264926492; | |
bytes4 private constant ERC1271_SUCCESS = 0x1626ba7e; | |
function isValidSigWithSideEffects2( | |
address _signer, | |
bytes32 _hash, | |
bytes calldata _signature | |
) public returns (bool, bool) { | |
uint contractCodeLen = address(_signer).code.length; | |
bool hadSideEffects = false; | |
// The order here is striclty 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 so as to not invalidate old sigs | |
// - ERC-1271 verification if there's contract code | |
// - finally, ecrecover | |
bool isCounterfactual = bytes32(_signature[_signature.length-32:_signature.length]) == ERC6492_DETECTION_SUFFIX; | |
if (isCounterfactual) { | |
// resize the sig to remove the magic suffix | |
_signature = _signature[0:_signature.length-32]; | |
address create2Factory; | |
bytes memory factoryCalldata; | |
bytes memory originalSig; | |
(create2Factory, factoryCalldata, originalSig) = abi.decode(_signature, (address, bytes, bytes)); | |
if (contractCodeLen == 0) { | |
(bool success, ) = create2Factory.call(factoryCalldata); | |
require(success, 'SignatureValidator: deployment'); | |
hadSideEffects = true; | |
} | |
} | |
if (isCounterfactual || contractCodeLen > 0) { | |
bytes4 magicValue = IERC1271Wallet(_signer).isValidSignature(_hash, _signature); | |
return (magicValue == ERC1271_SUCCESS, hadSideEffects); | |
} | |
// 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#recoverSigner: invalid signature v value'); | |
} | |
return (ecrecover(_hash, v, r, s) == _signer, hadSideEffects); | |
} | |
function isValidSigWithSideEffects( | |
address _signer, | |
bytes32 _hash, | |
bytes calldata _signature | |
) external returns (bool) { | |
(bool isValid,) = isValidSigWithSideEffects2(_signer, _hash, _signature); | |
return isValid; | |
} | |
function isValidSigNoSideEffects( | |
address _signer, | |
bytes32 _hash, | |
bytes calldata _signature | |
) external returns (NoSideEffectsResult, bytes memory) { | |
(bool success, bytes memory data) = (address(this)).call( | |
abi.encodeWithSelector( | |
this.isValidSigWithSideEffects2.selector, | |
_signer, | |
_hash, | |
_signature | |
) | |
); | |
if (!success) { | |
// if the call failed, we need to return the | |
// data, so we can bubble up the revert reason | |
return (NoSideEffectsResult.Revert, data); | |
} | |
(bool isValid, bool hadSideEffects) = abi.decode(data, (bool, bool)); | |
NoSideEffectsResult result = isValid ? NoSideEffectsResult.Valid : NoSideEffectsResult.Invalid; | |
if (hadSideEffects) { | |
// if the call had side effects we need to return the | |
// result using a `revert` (to undo the state changes) | |
assembly { | |
// if the result is valid or invalid, the data is empty | |
// it's never decoded, so we can just skip it | |
mstore(0, result) | |
revert(0, 32) | |
} | |
} | |
return (result, bytes("")); | |
} | |
// In order to have a no side effects function, we need to call something that always reverts and then parse it's result | |
function isValidSig( | |
address _signer, | |
bytes32 _hash, | |
bytes calldata _signature | |
) external returns (bool) { | |
(,bytes memory data) = address(this).call( | |
abi.encodeWithSelector( | |
this.isValidSigNoSideEffects.selector, | |
_signer, | |
_hash, | |
_signature | |
) | |
); | |
NoSideEffectsResult result = abi.decode(data, (NoSideEffectsResult)); | |
if (result == NoSideEffectsResult.Revert) { | |
// bubble up the revert | |
// so it behaves like a normal eth_call | |
(,bytes memory reason) = abi.decode(data, (NoSideEffectsResult, bytes)); | |
assembly { | |
revert(add(32, reason), mload(reason)) | |
} | |
} | |
return result == NoSideEffectsResult.Valid; | |
} | |
} | |
// this is a helper so we can perform validation in a single eth_call without pre-deploying a singleton | |
contract ValidateSig { | |
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) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment