diff --git a/contracts/token/ERC20/README.adoc b/contracts/token/ERC20/README.adoc index b3f68e5436f..4d1cc0f237d 100644 --- a/contracts/token/ERC20/README.adoc +++ b/contracts/token/ERC20/README.adoc @@ -15,11 +15,11 @@ There are a few core contracts that implement the behavior specified in the EIP: Additionally there are multiple custom extensions, including: +* {ERC20Permit}: gasless approval of tokens (standardized as ERC2612). * {ERC20Burnable}: destruction of own tokens. * {ERC20Capped}: enforcement of a cap to the total supply when minting tokens. * {ERC20Pausable}: ability to pause token transfers. * {ERC20Snapshot}: efficient storage of past token balances to be later queried at any point in time. -* {ERC20Permit}: gasless approval of tokens (standardized as ERC2612). * {ERC20FlashMint}: token level support for flash loans through the minting and burning of ephemeral tokens (standardized as ERC3156). * {ERC20Votes}: support for voting and vote delegation. * {ERC20VotesComp}: support for voting and vote delegation (compatible with Compound's token, with uint96 restrictions). @@ -43,14 +43,16 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel == Extensions +{{IERC20Permit}} + +{{ERC20Permit}} + {{ERC20Burnable}} {{ERC20Capped}} {{ERC20Pausable}} -{{ERC20Permit}} - {{ERC20Snapshot}} {{ERC20Votes}} diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol index 7ee7331f717..bc60e3a5255 100644 --- a/contracts/token/ERC20/extensions/ERC20Permit.sol +++ b/contracts/token/ERC20/extensions/ERC20Permit.sol @@ -44,7 +44,7 @@ abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 { constructor(string memory name) EIP712(name, "1") {} /** - * @dev See {IERC20Permit-permit}. + * @inheritdoc IERC20Permit */ function permit( address owner, @@ -68,14 +68,14 @@ abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 { } /** - * @dev See {IERC20Permit-nonces}. + * @inheritdoc IERC20Permit */ function nonces(address owner) public view virtual override returns (uint256) { return _nonces[owner].current(); } /** - * @dev See {IERC20Permit-DOMAIN_SEPARATOR}. + * @inheritdoc IERC20Permit */ // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view override returns (bytes32) { diff --git a/contracts/token/ERC20/extensions/IERC20Permit.sol b/contracts/token/ERC20/extensions/IERC20Permit.sol index 0deb54b143b..3ffafcc38a5 100644 --- a/contracts/token/ERC20/extensions/IERC20Permit.sol +++ b/contracts/token/ERC20/extensions/IERC20Permit.sol @@ -10,6 +10,34 @@ pragma solidity ^0.8.0; * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't * need to send a transaction, and thus is not required to hold Ether at all. + * + * ==== Security Considerations + * + * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature + * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be + * considered as an intention to spend the allowance in any specific way. The second is that because permits have + * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should + * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be + * generally recommended is: + * + * ```solidity + * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public { + * try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {} + * doThing(..., value); + * } + * + * function doThing(..., uint256 value) public { + * token.safeTransferFrom(msg.sender, address(this), value); + * ... + * } + * ``` + * + * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of + * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also + * {SafeERC20-safeTransferFrom}). + * + * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so + * contracts should have entry points that don't rely on permit. */ interface IERC20Permit { /** @@ -32,6 +60,8 @@ interface IERC20Permit { * For more information on the signature format, see the * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP * section]. + * + * CAUTION: See Security Considerations above. */ function permit( address owner,