You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
6.5 KiB
160 lines
6.5 KiB
// SPDX-License-Identifier: MIT
|
|
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/EIP712.sol)
|
|
|
|
pragma solidity ^0.8.20;
|
|
|
|
import {MessageHashUtils} from "./MessageHashUtils.sol";
|
|
import {ShortStrings, ShortString} from "../ShortStrings.sol";
|
|
import {IERC5267} from "../../interfaces/IERC5267.sol";
|
|
|
|
/**
|
|
* @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
|
|
*
|
|
* The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
|
|
* encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
|
|
* does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
|
|
* produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.
|
|
*
|
|
* This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
|
|
* scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
|
|
* ({_hashTypedDataV4}).
|
|
*
|
|
* The implementation of the domain separator was designed to be as efficient as possible while still properly updating
|
|
* the chain id to protect against replay attacks on an eventual fork of the chain.
|
|
*
|
|
* NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
|
|
* https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
|
|
*
|
|
* NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
|
|
* separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the
|
|
* separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
|
|
*
|
|
* @custom:oz-upgrades-unsafe-allow state-variable-immutable
|
|
*/
|
|
abstract contract EIP712 is IERC5267 {
|
|
using ShortStrings for *;
|
|
|
|
bytes32 private constant _TYPE_HASH =
|
|
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
|
|
|
|
// Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
|
|
// invalidate the cached domain separator if the chain id changes.
|
|
bytes32 private immutable _cachedDomainSeparator;
|
|
uint256 private immutable _cachedChainId;
|
|
address private immutable _cachedThis;
|
|
|
|
bytes32 private immutable _hashedName;
|
|
bytes32 private immutable _hashedVersion;
|
|
|
|
ShortString private immutable _name;
|
|
ShortString private immutable _version;
|
|
string private _nameFallback;
|
|
string private _versionFallback;
|
|
|
|
/**
|
|
* @dev Initializes the domain separator and parameter caches.
|
|
*
|
|
* The meaning of `name` and `version` is specified in
|
|
* https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
|
|
*
|
|
* - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
|
|
* - `version`: the current major version of the signing domain.
|
|
*
|
|
* NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
|
|
* contract upgrade].
|
|
*/
|
|
constructor(string memory name, string memory version) {
|
|
_name = name.toShortStringWithFallback(_nameFallback);
|
|
_version = version.toShortStringWithFallback(_versionFallback);
|
|
_hashedName = keccak256(bytes(name));
|
|
_hashedVersion = keccak256(bytes(version));
|
|
|
|
_cachedChainId = block.chainid;
|
|
_cachedDomainSeparator = _buildDomainSeparator();
|
|
_cachedThis = address(this);
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the domain separator for the current chain.
|
|
*/
|
|
function _domainSeparatorV4() internal view returns (bytes32) {
|
|
if (address(this) == _cachedThis && block.chainid == _cachedChainId) {
|
|
return _cachedDomainSeparator;
|
|
} else {
|
|
return _buildDomainSeparator();
|
|
}
|
|
}
|
|
|
|
function _buildDomainSeparator() private view returns (bytes32) {
|
|
return keccak256(abi.encode(_TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));
|
|
}
|
|
|
|
/**
|
|
* @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
|
|
* function returns the hash of the fully encoded EIP712 message for this domain.
|
|
*
|
|
* This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
|
|
*
|
|
* ```solidity
|
|
* bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
|
|
* keccak256("Mail(address to,string contents)"),
|
|
* mailTo,
|
|
* keccak256(bytes(mailContents))
|
|
* )));
|
|
* address signer = ECDSA.recover(digest, signature);
|
|
* ```
|
|
*/
|
|
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
|
|
return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC-5267}.
|
|
*/
|
|
function eip712Domain()
|
|
public
|
|
view
|
|
virtual
|
|
returns (
|
|
bytes1 fields,
|
|
string memory name,
|
|
string memory version,
|
|
uint256 chainId,
|
|
address verifyingContract,
|
|
bytes32 salt,
|
|
uint256[] memory extensions
|
|
)
|
|
{
|
|
return (
|
|
hex"0f", // 01111
|
|
_EIP712Name(),
|
|
_EIP712Version(),
|
|
block.chainid,
|
|
address(this),
|
|
bytes32(0),
|
|
new uint256[](0)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dev The name parameter for the EIP712 domain.
|
|
*
|
|
* NOTE: By default this function reads _name which is an immutable value.
|
|
* It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
|
|
*/
|
|
// solhint-disable-next-line func-name-mixedcase
|
|
function _EIP712Name() internal view returns (string memory) {
|
|
return _name.toStringWithFallback(_nameFallback);
|
|
}
|
|
|
|
/**
|
|
* @dev The version parameter for the EIP712 domain.
|
|
*
|
|
* NOTE: By default this function reads _version which is an immutable value.
|
|
* It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
|
|
*/
|
|
// solhint-disable-next-line func-name-mixedcase
|
|
function _EIP712Version() internal view returns (string memory) {
|
|
return _version.toStringWithFallback(_versionFallback);
|
|
}
|
|
}
|
|
|