@@ -7,8 +7,10 @@ contract PermittableToken is ERC677BridgeToken {
77
88 // EIP712 niceties
99 bytes32 public DOMAIN_SEPARATOR;
10- // bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)");
11- bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb ;
10+ // bytes32 public constant PERMIT_TYPEHASH_LEGACY = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)");
11+ bytes32 public constant PERMIT_TYPEHASH_LEGACY = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb ;
12+ // bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
13+ bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9 ;
1214
1315 mapping (address => uint256 ) public nonces;
1416 mapping (address => mapping (address => uint256 )) public expirations;
@@ -56,7 +58,7 @@ contract PermittableToken is ERC677BridgeToken {
5658 } else {
5759 // If allowance is unlimited by `permit`, `approve`, or `increaseAllowance`
5860 // function, don't adjust it. But the expiration date must be empty or in the future
59- require (expirations[_sender][msg .sender ] == 0 || expirations[_sender][msg .sender ] >= _now () );
61+ require (expirations[_sender][msg .sender ] == 0 || expirations[_sender][msg .sender ] >= now );
6062 }
6163 } else {
6264 // If `_sender` is `msg.sender`,
@@ -71,20 +73,16 @@ contract PermittableToken is ERC677BridgeToken {
7173 /// @param _to The address which will spend the funds.
7274 /// @param _value The amount of tokens to be spent.
7375 function approve (address _to , uint256 _value ) public returns (bool result ) {
74- result = super .approve (_to, _value);
75- if (_value == uint256 (- 1 )) {
76- delete expirations[msg .sender ][_to];
77- }
76+ _approveAndResetExpirations (msg .sender , _to, _value);
77+ return true ;
7878 }
7979
8080 /// @dev Atomically increases the allowance granted to spender by the caller.
8181 /// @param _to The address which will spend the funds.
8282 /// @param _addedValue The amount of tokens to increase the allowance by.
8383 function increaseAllowance (address _to , uint256 _addedValue ) public returns (bool result ) {
84- result = super .increaseAllowance (_to, _addedValue);
85- if (allowed[msg .sender ][_to] == uint256 (- 1 )) {
86- delete expirations[msg .sender ][_to];
87- }
84+ _approveAndResetExpirations (msg .sender , _to, allowed[msg .sender ][_to].add (_addedValue));
85+ return true ;
8886 }
8987
9088 /// @dev An alias for `transfer` function.
@@ -134,33 +132,107 @@ contract PermittableToken is ERC677BridgeToken {
134132 bytes32 _r ,
135133 bytes32 _s
136134 ) external {
137- require (_holder != address (0 ));
138- require (_spender != address (0 ));
139- require (_expiry == 0 || _now () <= _expiry);
140-
141- require (_v == 27 || _v == 28 );
142- require (uint256 (_s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 );
143-
144- bytes32 digest = keccak256 (
145- abi.encodePacked (
146- "\x19\x01 " ,
147- DOMAIN_SEPARATOR,
148- keccak256 (abi.encode (PERMIT_TYPEHASH, _holder, _spender, _nonce, _expiry, _allowed))
149- )
150- );
135+ require (_expiry == 0 || now <= _expiry);
151136
152- require (_holder == ecrecover (digest, _v, _r, _s));
137+ bytes32 digest = _digest (abi.encode (PERMIT_TYPEHASH_LEGACY, _holder, _spender, _nonce, _expiry, _allowed));
138+
139+ require (_holder == _recover (digest, _v, _r, _s));
153140 require (_nonce == nonces[_holder]++ );
154141
155142 uint256 amount = _allowed ? uint256 (- 1 ) : 0 ;
156143
157- allowed[_holder][_spender] = amount;
158144 expirations[_holder][_spender] = _allowed ? _expiry : 0 ;
159145
160- emit Approval (_holder, _spender, amount);
146+ _approve (_holder, _spender, amount);
161147 }
162148
163- function _now () internal view returns (uint256 ) {
164- return now ;
149+ /** @dev Allows to spend holder's unlimited amount by the specified spender according to EIP2612.
150+ * The function can be called by anyone, but requires having allowance parameters
151+ * signed by the holder according to EIP712.
152+ * @param _holder The holder's address.
153+ * @param _spender The spender's address.
154+ * @param _value Allowance value to set as a result of the call.
155+ * @param _deadline The deadline timestamp to call the permit function. Must be a timestamp in the future.
156+ * Note that timestamps are not precise, malicious miner/validator can manipulate them to some extend.
157+ * Assume that there can be a 900 seconds time delta between the desired timestamp and the actual expiration.
158+ * @param _v A final byte of signature (ECDSA component).
159+ * @param _r The first 32 bytes of signature (ECDSA component).
160+ * @param _s The second 32 bytes of signature (ECDSA component).
161+ */
162+ function permit (
163+ address _holder ,
164+ address _spender ,
165+ uint256 _value ,
166+ uint256 _deadline ,
167+ uint8 _v ,
168+ bytes32 _r ,
169+ bytes32 _s
170+ ) external {
171+ require (now <= _deadline);
172+
173+ uint256 nonce = nonces[_holder]++ ;
174+ bytes32 digest = _digest (abi.encode (PERMIT_TYPEHASH, _holder, _spender, _value, nonce, _deadline));
175+
176+ require (_holder == _recover (digest, _v, _r, _s));
177+
178+ _approveAndResetExpirations (_holder, _spender, _value);
179+ }
180+
181+ /**
182+ * @dev Sets a new allowance value for the given owner and spender addresses.
183+ * Resets expiration timestamp in case of unlimited approval.
184+ * @param _owner address tokens holder.
185+ * @param _spender address of tokens spender.
186+ * @param _amount amount of approved tokens.
187+ */
188+ function _approveAndResetExpirations (address _owner , address _spender , uint256 _amount ) internal {
189+ _approve (_owner, _spender, _amount);
190+
191+ // it is not necessary to reset _expirations in other cases, since it is only used together with infinite allowance
192+ if (_amount == uint256 (- 1 )) {
193+ delete expirations[_owner][_spender];
194+ }
195+ }
196+
197+ /**
198+ * @dev Internal function for issuing an allowance.
199+ * @param _owner address of the tokens owner.
200+ * @param _spender address of the approved tokens spender.
201+ * @param _amount amount of the approved tokens.
202+ */
203+ function _approve (address _owner , address _spender , uint256 _amount ) internal {
204+ require (_owner != address (0 ), "ERC20: approve from the zero address " );
205+ require (_spender != address (0 ), "ERC20: approve to the zero address " );
206+
207+ allowed[_owner][_spender] = _amount;
208+ emit Approval (_owner, _spender, _amount);
209+ }
210+
211+ /**
212+ * @dev Calculates the message digest for encoded EIP712 typed struct.
213+ * @param _typedStruct encoded payload.
214+ */
215+ function _digest (bytes memory _typedStruct ) internal view returns (bytes32 ) {
216+ return keccak256 (abi.encodePacked ("\x19\x01 " , DOMAIN_SEPARATOR, keccak256 (_typedStruct)));
217+ }
218+
219+ /**
220+ * @dev Derives the signer address for the given message digest and ECDSA signature params.
221+ * @param _digest signed message digest.
222+ * @param _v a final byte of signature (ECDSA component).
223+ * @param _r the first 32 bytes of the signature (ECDSA component).
224+ * @param _s the second 32 bytes of the signature (ECDSA component).
225+ */
226+ function _recover (bytes32 _digest , uint8 _v , bytes32 _r , bytes32 _s ) internal pure returns (address ) {
227+ require (_v == 27 || _v == 28 , "ECDSA: invalid signature 'v' value " );
228+ require (
229+ uint256 (_s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 ,
230+ "ECDSA: invalid signature 's' value "
231+ );
232+
233+ address signer = ecrecover (_digest, _v, _r, _s);
234+ require (signer != address (0 ), "ECDSA: invalid signature " );
235+
236+ return signer;
165237 }
166238}
0 commit comments