107 lines
3.1 KiB
Solidity
107 lines
3.1 KiB
Solidity
|
// SPDX-License-Identifier: MIT
|
||
|
|
||
|
pragma solidity ^0.6.0;
|
||
|
|
||
|
// Adapted copy from https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237/files
|
||
|
|
||
|
import { ERC20 } from "@openzeppelin/contracts-v3/token/ERC20/ERC20.sol";
|
||
|
import { ECDSA } from "./ECDSA.sol";
|
||
|
|
||
|
/**
|
||
|
* @dev Extension of {ERC20} that allows token holders to use their tokens
|
||
|
* without sending any transactions by setting {IERC20-allowance} with a
|
||
|
* signature using the {permit} method, and then spend them via
|
||
|
* {IERC20-transferFrom}.
|
||
|
*
|
||
|
* The {permit} signature mechanism conforms to the {IERC2612Permit} interface.
|
||
|
*/
|
||
|
abstract contract ERC20Permit is ERC20 {
|
||
|
mapping(address => uint256) private _nonces;
|
||
|
|
||
|
bytes32 private constant _PERMIT_TYPEHASH = keccak256(
|
||
|
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
|
||
|
);
|
||
|
|
||
|
// Mapping of ChainID to domain separators. This is a very gas efficient way
|
||
|
// to not recalculate the domain separator on every call, while still
|
||
|
// automatically detecting ChainID changes.
|
||
|
mapping(uint256 => bytes32) private _domainSeparators;
|
||
|
|
||
|
constructor() internal {
|
||
|
_updateDomainSeparator();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @dev See {IERC2612Permit-permit}.
|
||
|
*
|
||
|
* If https://eips.ethereum.org/EIPS/eip-1344[ChainID] ever changes, the
|
||
|
* EIP712 Domain Separator is automatically recalculated.
|
||
|
*/
|
||
|
function permit(
|
||
|
address owner,
|
||
|
address spender,
|
||
|
uint256 amount,
|
||
|
uint256 deadline,
|
||
|
uint8 v,
|
||
|
bytes32 r,
|
||
|
bytes32 s
|
||
|
) public {
|
||
|
require(blockTimestamp() <= deadline, "ERC20Permit: expired deadline");
|
||
|
|
||
|
bytes32 hashStruct = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, amount, _nonces[owner], deadline));
|
||
|
|
||
|
bytes32 hash = keccak256(abi.encodePacked(uint16(0x1901), _domainSeparator(), hashStruct));
|
||
|
|
||
|
address signer = ECDSA.recover(hash, v, r, s);
|
||
|
require(signer == owner, "ERC20Permit: invalid signature");
|
||
|
|
||
|
_nonces[owner]++;
|
||
|
_approve(owner, spender, amount);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @dev See {IERC2612Permit-nonces}.
|
||
|
*/
|
||
|
function nonces(address owner) public view returns (uint256) {
|
||
|
return _nonces[owner];
|
||
|
}
|
||
|
|
||
|
function _updateDomainSeparator() private returns (bytes32) {
|
||
|
uint256 _chainID = chainID();
|
||
|
|
||
|
bytes32 newDomainSeparator = keccak256(
|
||
|
abi.encode(
|
||
|
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
|
||
|
keccak256(bytes(name())),
|
||
|
keccak256(bytes("1")), // Version
|
||
|
_chainID,
|
||
|
address(this)
|
||
|
)
|
||
|
);
|
||
|
|
||
|
_domainSeparators[_chainID] = newDomainSeparator;
|
||
|
|
||
|
return newDomainSeparator;
|
||
|
}
|
||
|
|
||
|
// Returns the domain separator, updating it if chainID changes
|
||
|
function _domainSeparator() private returns (bytes32) {
|
||
|
bytes32 domainSeparator = _domainSeparators[chainID()];
|
||
|
if (domainSeparator != 0x00) {
|
||
|
return domainSeparator;
|
||
|
} else {
|
||
|
return _updateDomainSeparator();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function chainID() public view virtual returns (uint256 _chainID) {
|
||
|
assembly {
|
||
|
_chainID := chainid()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function blockTimestamp() public view virtual returns (uint256) {
|
||
|
return block.timestamp;
|
||
|
}
|
||
|
}
|