EIP712.sol 6.3 KB

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