// SPDX-License-Identifier: MIT pragma solidity 0.6.12; import { IERC20Internal } from "./IERC20Internal.sol"; import { EIP712Domain } from "./EIP712Domain.sol"; import { EIP712 } from "./EIP712.sol"; abstract contract ERC3009 is IERC20Internal, EIP712Domain { // keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)") bytes32 public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267; // keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)") bytes32 public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH = 0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8; // keccak256("CancelAuthorization(address authorizer,bytes32 nonce)") bytes32 public constant CANCEL_AUTHORIZATION_TYPEHASH = 0x158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429; /** * @dev authorizer address => nonce => state (true = used / false = unused) */ mapping(address => mapping(bytes32 => bool)) internal _authorizationStates; event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce); event AuthorizationCanceled( address indexed authorizer, bytes32 indexed nonce ); string internal constant _INVALID_SIGNATURE_ERROR = "ERC3009: invalid signature"; string internal constant _AUTHORIZATION_USED_ERROR = "ERC3009: authorization is used"; /** * @notice Returns the state of an authorization * @dev Nonces are randomly generated 32-byte data unique to the authorizer's * address * @param authorizer Authorizer's address * @param nonce Nonce of the authorization * @return True if the nonce is used */ function authorizationState(address authorizer, bytes32 nonce) external view returns (bool) { return _authorizationStates[authorizer][nonce]; } /** * @notice Execute a transfer with a signed authorization * @param from Payer's address (Authorizer) * @param to Payee's address * @param value Amount to be transferred * @param validAfter The time after which this is valid (unix time) * @param validBefore The time before which this is valid (unix time) * @param nonce Unique nonce * @param v v of the signature * @param r r of the signature * @param s s of the signature */ function transferWithAuthorization( address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s ) external { _transferWithAuthorization( TRANSFER_WITH_AUTHORIZATION_TYPEHASH, from, to, value, validAfter, validBefore, nonce, v, r, s ); } /** * @notice Receive a transfer with a signed authorization from the payer * @dev This has an additional check to ensure that the payee's address matches * the caller of this function to prevent front-running attacks. (See security * considerations) * @param from Payer's address (Authorizer) * @param to Payee's address * @param value Amount to be transferred * @param validAfter The time after which this is valid (unix time) * @param validBefore The time before which this is valid (unix time) * @param nonce Unique nonce * @param v v of the signature * @param r r of the signature * @param s s of the signature */ function receiveWithAuthorization( address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s ) external { require(to == msg.sender, "ERC3009: caller must be the payee"); _transferWithAuthorization( RECEIVE_WITH_AUTHORIZATION_TYPEHASH, from, to, value, validAfter, validBefore, nonce, v, r, s ); } /** * @notice Attempt to cancel an authorization * @param authorizer Authorizer's address * @param nonce Nonce of the authorization * @param v v of the signature * @param r r of the signature * @param s s of the signature */ function cancelAuthorization( address authorizer, bytes32 nonce, uint8 v, bytes32 r, bytes32 s ) external { require( !_authorizationStates[authorizer][nonce], _AUTHORIZATION_USED_ERROR ); bytes memory data = abi.encode( CANCEL_AUTHORIZATION_TYPEHASH, authorizer, nonce ); require( EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == authorizer, _INVALID_SIGNATURE_ERROR ); _authorizationStates[authorizer][nonce] = true; emit AuthorizationCanceled(authorizer, nonce); } function _transferWithAuthorization( bytes32 typeHash, address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s ) internal { require(now > validAfter, "ERC3009: authorization is not yet valid"); require(now < validBefore, "ERC3009: authorization is expired"); require(!_authorizationStates[from][nonce], _AUTHORIZATION_USED_ERROR); bytes memory data = abi.encode( typeHash, from, to, value, validAfter, validBefore, nonce ); require( EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == from, _INVALID_SIGNATURE_ERROR ); _authorizationStates[from][nonce] = true; emit AuthorizationUsed(from, nonce); _transfer(from, to, value); } }