1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^ 0.8.4 ;
3
+
4
+ /// @notice Contract for EIP-712 typed structured data hashing and signing.
5
+ /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/EIP712.sol)
6
+ /// @author Modified from Solbase (https://github.com/Sol-DAO/solbase/blob/main/src/utils/EIP712.sol)
7
+ /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol)
8
+ ///
9
+ /// @dev Note, this implementation:
10
+ /// - Uses `address(this)` for the `verifyingContract` field.
11
+ /// - Does NOT use the optional EIP-712 salt.
12
+ /// - Does NOT use any EIP-712 extensions.
13
+ /// This is for simplicity and to save gas.
14
+ /// If you need to customize, please fork / modify accordingly.
15
+ abstract contract EIP712 {
16
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
17
+ /* CONSTANTS AND IMMUTABLES */
18
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
19
+
20
+ /// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
21
+ bytes32 internal constant _DOMAIN_TYPEHASH =
22
+ 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f ;
23
+
24
+ uint256 private immutable _cachedThis;
25
+ uint256 private immutable _cachedChainId;
26
+ bytes32 private immutable _cachedNameHash;
27
+ bytes32 private immutable _cachedVersionHash;
28
+ bytes32 private immutable _cachedDomainSeparator;
29
+
30
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
31
+ /* CONSTRUCTOR */
32
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
33
+
34
+ /// @dev Cache the hashes for cheaper runtime gas costs.
35
+ /// In the case of upgradeable contracts (i.e. proxies),
36
+ /// or if the chain id changes due to a hard fork,
37
+ /// the domain separator will be seamlessly calculated on-the-fly.
38
+ constructor () {
39
+ _cachedThis = uint256 (uint160 (address (this )));
40
+ _cachedChainId = block .chainid ;
41
+
42
+ string memory name;
43
+ string memory version;
44
+ if (! _domainNameAndVersionMayChange ()) (name, version) = _domainNameAndVersion ();
45
+ bytes32 nameHash = _domainNameAndVersionMayChange () ? bytes32 (0 ) : keccak256 (bytes (name));
46
+ bytes32 versionHash =
47
+ _domainNameAndVersionMayChange () ? bytes32 (0 ) : keccak256 (bytes (version));
48
+ _cachedNameHash = nameHash;
49
+ _cachedVersionHash = versionHash;
50
+
51
+ bytes32 separator;
52
+ if (! _domainNameAndVersionMayChange ()) {
53
+ /// @solidity memory-safe-assembly
54
+ assembly {
55
+ let m := mload (0x40 ) // Load the free memory pointer.
56
+ mstore (m, _DOMAIN_TYPEHASH)
57
+ mstore (add (m, 0x20 ), nameHash)
58
+ mstore (add (m, 0x40 ), versionHash)
59
+ mstore (add (m, 0x60 ), chainid ())
60
+ mstore (add (m, 0x80 ), address ())
61
+ separator := keccak256 (m, 0xa0 )
62
+ }
63
+ }
64
+ _cachedDomainSeparator = separator;
65
+ }
66
+
67
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
68
+ /* FUNCTIONS TO OVERRIDE */
69
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
70
+
71
+ /// @dev Please override this function to return the domain name and version.
72
+ /// ```
73
+ /// function _domainNameAndVersion()
74
+ /// internal
75
+ /// pure
76
+ /// virtual
77
+ /// returns (string memory name, string memory version)
78
+ /// {
79
+ /// name = "Solady";
80
+ /// version = "1";
81
+ /// }
82
+ /// ```
83
+ ///
84
+ /// Note: If the returned result may change after the contract has been deployed,
85
+ /// you must override `_domainNameAndVersionMayChange()` to return true.
86
+ function _domainNameAndVersion ()
87
+ internal
88
+ view
89
+ virtual
90
+ returns (string memory name , string memory version );
91
+
92
+ /// @dev Returns if `_domainNameAndVersion()` may change
93
+ /// after the contract has been deployed (i.e. after the constructor).
94
+ /// Default: false.
95
+ function _domainNameAndVersionMayChange () internal pure virtual returns (bool result ) {}
96
+
97
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
98
+ /* HASHING OPERATIONS */
99
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
100
+
101
+ /// @dev Returns the EIP-712 domain separator.
102
+ function _domainSeparator () internal view virtual returns (bytes32 separator ) {
103
+ if (_domainNameAndVersionMayChange ()) {
104
+ separator = _buildDomainSeparator ();
105
+ } else {
106
+ separator = _cachedDomainSeparator;
107
+ if (_cachedDomainSeparatorInvalidated ()) separator = _buildDomainSeparator ();
108
+ }
109
+ }
110
+
111
+ /// @dev Returns the hash of the fully encoded EIP-712 message for this domain,
112
+ /// given `structHash`, as defined in
113
+ /// https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct.
114
+ ///
115
+ /// The hash can be used together with {ECDSA-recover} to obtain the signer of a message:
116
+ /// ```
117
+ /// bytes32 digest = _hashTypedData(keccak256(abi.encode(
118
+ /// keccak256("Mail(address to,string contents)"),
119
+ /// mailTo,
120
+ /// keccak256(bytes(mailContents))
121
+ /// )));
122
+ /// address signer = ECDSA.recover(digest, signature);
123
+ /// ```
124
+ function _hashTypedData (bytes32 structHash ) internal view virtual returns (bytes32 digest ) {
125
+ // We will use `digest` to store the domain separator to save a bit of gas.
126
+ if (_domainNameAndVersionMayChange ()) {
127
+ digest = _buildDomainSeparator ();
128
+ } else {
129
+ digest = _cachedDomainSeparator;
130
+ if (_cachedDomainSeparatorInvalidated ()) digest = _buildDomainSeparator ();
131
+ }
132
+ /// @solidity memory-safe-assembly
133
+ assembly {
134
+ // Compute the digest.
135
+ mstore (0x00 , 0x1901000000000000 ) // Store "\x19\x01".
136
+ mstore (0x1a , digest) // Store the domain separator.
137
+ mstore (0x3a , structHash) // Store the struct hash.
138
+ digest := keccak256 (0x18 , 0x42 )
139
+ // Restore the part of the free memory slot that was overwritten.
140
+ mstore (0x3a , 0 )
141
+ }
142
+ }
143
+
144
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
145
+ /* EIP-5267 OPERATIONS */
146
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
147
+
148
+ /// @dev See: https://eips.ethereum.org/EIPS/eip-5267
149
+ function eip712Domain ()
150
+ public
151
+ view
152
+ virtual
153
+ returns (
154
+ bytes1 fields ,
155
+ string memory name ,
156
+ string memory version ,
157
+ uint256 chainId ,
158
+ address verifyingContract ,
159
+ bytes32 salt ,
160
+ uint256 [] memory extensions
161
+ )
162
+ {
163
+ fields = hex "0f " ; // `0b01111`.
164
+ (name, version) = _domainNameAndVersion ();
165
+ chainId = block .chainid ;
166
+ verifyingContract = address (this );
167
+ salt = salt; // `bytes32(0)`.
168
+ extensions = extensions; // `new uint256[](0)`.
169
+ }
170
+
171
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
172
+ /* PRIVATE HELPERS */
173
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
174
+
175
+ /// @dev Returns the EIP-712 domain separator.
176
+ function _buildDomainSeparator () private view returns (bytes32 separator ) {
177
+ // We will use `separator` to store the name hash to save a bit of gas.
178
+ bytes32 versionHash;
179
+ if (_domainNameAndVersionMayChange ()) {
180
+ (string memory name , string memory version ) = _domainNameAndVersion ();
181
+ separator = keccak256 (bytes (name));
182
+ versionHash = keccak256 (bytes (version));
183
+ } else {
184
+ separator = _cachedNameHash;
185
+ versionHash = _cachedVersionHash;
186
+ }
187
+ /// @solidity memory-safe-assembly
188
+ assembly {
189
+ let m := mload (0x40 ) // Load the free memory pointer.
190
+ mstore (m, _DOMAIN_TYPEHASH)
191
+ mstore (add (m, 0x20 ), separator) // Name hash.
192
+ mstore (add (m, 0x40 ), versionHash)
193
+ mstore (add (m, 0x60 ), chainid ())
194
+ mstore (add (m, 0x80 ), address ())
195
+ separator := keccak256 (m, 0xa0 )
196
+ }
197
+ }
198
+
199
+ /// @dev Returns if the cached domain separator has been invalidated.
200
+ function _cachedDomainSeparatorInvalidated () private view returns (bool result ) {
201
+ uint256 cachedChainId = _cachedChainId;
202
+ uint256 cachedThis = _cachedThis;
203
+ /// @solidity memory-safe-assembly
204
+ assembly {
205
+ result := iszero (and (eq (chainid (), cachedChainId), eq (address (), cachedThis)))
206
+ }
207
+ }
208
+ }
0 commit comments