EIP712.sol 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. // SPDX-License-Identifier: MIT
  2. // OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/EIP712.sol)
  3. pragma solidity ^0.8.24;
  4. import {MessageHashUtils} from "./MessageHashUtils.sol";
  5. import {ShortStrings, ShortString} from "../ShortStrings.sol";
  6. import {IERC5267} from "../../interfaces/IERC5267.sol";
  7. /**
  8. * @dev https://eips.ethereum.org/EIPS/eip-712[EIP-712] is a standard for hashing and signing of typed structured data.
  9. *
  10. * The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
  11. * encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
  12. * does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
  13. * produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.
  14. *
  15. * This contract implements the EIP-712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
  16. * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
  17. * ({_hashTypedDataV4}).
  18. *
  19. * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
  20. * the chain id to protect against replay attacks on an eventual fork of the chain.
  21. *
  22. * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
  23. * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
  24. *
  25. * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
  26. * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the
  27. * separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
  28. *
  29. * @custom:oz-upgrades-unsafe-allow state-variable-immutable
  30. */
  31. abstract contract EIP712 is IERC5267 {
  32. using ShortStrings for *;
  33. bytes32 private constant TYPE_HASH =
  34. keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
  35. // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
  36. // invalidate the cached domain separator if the chain id changes.
  37. bytes32 private immutable _cachedDomainSeparator;
  38. uint256 private immutable _cachedChainId;
  39. address private immutable _cachedThis;
  40. bytes32 private immutable _hashedName;
  41. bytes32 private immutable _hashedVersion;
  42. ShortString private immutable _name;
  43. ShortString private immutable _version;
  44. // slither-disable-next-line constable-states
  45. string private _nameFallback;
  46. // slither-disable-next-line constable-states
  47. string private _versionFallback;
  48. /**
  49. * @dev Initializes the domain separator and parameter caches.
  50. *
  51. * The meaning of `name` and `version` is specified in
  52. * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP-712]:
  53. *
  54. * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
  55. * - `version`: the current major version of the signing domain.
  56. *
  57. * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
  58. * contract upgrade].
  59. */
  60. constructor(string memory name, string memory version) {
  61. _name = name.toShortStringWithFallback(_nameFallback);
  62. _version = version.toShortStringWithFallback(_versionFallback);
  63. _hashedName = keccak256(bytes(name));
  64. _hashedVersion = keccak256(bytes(version));
  65. _cachedChainId = block.chainid;
  66. _cachedDomainSeparator = _buildDomainSeparator();
  67. _cachedThis = address(this);
  68. }
  69. /**
  70. * @dev Returns the domain separator for the current chain.
  71. */
  72. function _domainSeparatorV4() internal view returns (bytes32) {
  73. if (address(this) == _cachedThis && block.chainid == _cachedChainId) {
  74. return _cachedDomainSeparator;
  75. } else {
  76. return _buildDomainSeparator();
  77. }
  78. }
  79. function _buildDomainSeparator() private view returns (bytes32) {
  80. return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));
  81. }
  82. /**
  83. * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
  84. * function returns the hash of the fully encoded EIP712 message for this domain.
  85. *
  86. * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
  87. *
  88. * ```solidity
  89. * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
  90. * keccak256("Mail(address to,string contents)"),
  91. * mailTo,
  92. * keccak256(bytes(mailContents))
  93. * )));
  94. * address signer = ECDSA.recover(digest, signature);
  95. * ```
  96. */
  97. function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
  98. return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
  99. }
  100. /// @inheritdoc IERC5267
  101. function eip712Domain()
  102. public
  103. view
  104. virtual
  105. returns (
  106. bytes1 fields,
  107. string memory name,
  108. string memory version,
  109. uint256 chainId,
  110. address verifyingContract,
  111. bytes32 salt,
  112. uint256[] memory extensions
  113. )
  114. {
  115. return (
  116. hex"0f", // 01111
  117. _EIP712Name(),
  118. _EIP712Version(),
  119. block.chainid,
  120. address(this),
  121. bytes32(0),
  122. new uint256[](0)
  123. );
  124. }
  125. /**
  126. * @dev The name parameter for the EIP712 domain.
  127. *
  128. * NOTE: By default this function reads _name which is an immutable value.
  129. * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
  130. */
  131. // solhint-disable-next-line func-name-mixedcase
  132. function _EIP712Name() internal view returns (string memory) {
  133. return _name.toStringWithFallback(_nameFallback);
  134. }
  135. /**
  136. * @dev The version parameter for the EIP712 domain.
  137. *
  138. * NOTE: By default this function reads _version which is an immutable value.
  139. * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
  140. */
  141. // solhint-disable-next-line func-name-mixedcase
  142. function _EIP712Version() internal view returns (string memory) {
  143. return _version.toStringWithFallback(_versionFallback);
  144. }
  145. }