diff --git a/EIPS/eip-6492.md b/EIPS/eip-6492.md index 78d7f4f6fb6697..b67ff06c6ea34e 100644 --- a/EIPS/eip-6492.md +++ b/EIPS/eip-6492.md @@ -53,6 +53,7 @@ The signing contract will normally be a contract wallet, but it could be any con - If the contract is deployed, produce a normal [ERC-1271](./eip-1271.md) signature - If the contract is not deployed yet, wrap the signature as follows: `concat(abi.encode((create2Factory, factoryCalldata, originalERC1271Signature), (address, bytes, bytes)), magicBytes)` +- If the contract is deployed but not ready to verify using [ERC-1271](./eip-1271.md), wrap the signature as follows: `concat(abi.encode((prepareTo, prepareData, originalERC1271Signature), (address, bytes, bytes)), magicBytes)`; `prepareTo` and `prepareData` must contain the necessary transaction that will make the contract ready to verify using [ERC-1271](./eip-1271.md) (e.g. a call to `migrate` or `update`) Note that we're passing `factoryCalldata` instead of `salt` and `bytecode`. We do this in order to make verification compliant with any factory interface. We do not need to calculate the address based on `create2Factory`/`salt`/`bytecode`, because [ERC-1271](./eip-1271.md) verification presumes we already know the account address we're verifying the signature for. @@ -62,6 +63,7 @@ Full signature verification MUST be performed in the following order: - check if the signature ends with magic bytes, in which case do an `eth_call` to a multicall contract that will call the factory first with the `factoryCalldata` and deploy the contract if it isn't already deployed; Then, call `contract.isValidSignature` as usual with the unwrapped signature - check if there's contract code at the address. If so perform [ERC-1271](./eip-1271.md) verification as usual by invoking `isValidSignature` +- if the [ERC-1271](./eip-1271.md) verification fails, and the deploy call to the `factory` was skipped due to the wallet already having code, execute the `factoryCalldata` transaction and try `isValidSignature` again - if there is no contract code at the address, try `ecrecover` verification ## Rationale @@ -76,6 +78,8 @@ The order to ensure correct verification is based on the following rules: - checking for `magicBytes` MUST happen before `ecrecover` in order to avoid trying to verify a counterfactual contract signature via `ecrecover` if such is clearly identifiable - checking `ecrecover` MUST NOT happen before [ERC-1271](./eip-1271.md) verification, because a contract may use a signature format that also happens to be a valid `ecrecover` signature for an EOA with a different address. One such example is a contract that's a wallet controlled by said EOA. +We can't determine the reason why a signature was encoded with a "deploy prefix" when the corresponding wallet already has code. It could be due to the signature being created before the contract was deployed, or it could be because the contract was deployed but not ready to verify signatures yet. As such, we need to try both options. + ## Backwards Compatibility This ERC is backward compatible with previous work on signature validation, including [ERC-1271](./eip-1271.md) and allows for easy verification of all signature types, including EOA signatures and typed data ([EIP-712](./eip-712.md)). @@ -108,7 +112,8 @@ contract UniversalSigValidator { address _signer, bytes32 _hash, bytes calldata _signature, - bool allowSideEffects + bool allowSideEffects, + bool tryPrepare ) public returns (bool) { uint contractCodeLen = address(_signer).code.length; bytes memory sigToValidate; @@ -122,7 +127,7 @@ contract UniversalSigValidator { bytes memory factoryCalldata; (create2Factory, factoryCalldata, sigToValidate) = abi.decode(_signature[0:_signature.length-32], (address, bytes, bytes)); - if (contractCodeLen == 0) { + if (contractCodeLen == 0 || tryPrepare) { (bool success, bytes memory err) = create2Factory.call(factoryCalldata); if (!success) revert ERC6492DeployFailed(err); } @@ -135,6 +140,11 @@ contract UniversalSigValidator { 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) @@ -145,7 +155,14 @@ contract UniversalSigValidator { } return isValid; - } catch (bytes memory err) { revert ERC1271Revert(err); } + } catch (bytes memory err) { + // retry, but this time assume the prefix is a prepare call + if (!isValid && !tryPrepare && contractCodeLen > 0) { + return isValidSigImpl(_signer, _hash, _signature, allowSideEffects, true); + } + + revert ERC1271Revert(err); + } } // ecrecover verification @@ -162,13 +179,13 @@ contract UniversalSigValidator { function isValidSigWithSideEffects(address _signer, bytes32 _hash, bytes calldata _signature) external returns (bool) { - return this.isValidSigImpl(_signer, _hash, _signature, true); + 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) returns (bool isValid) { return isValid; } + 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;