diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index cf7332858c9..05fa98aaad0 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -7,6 +7,7 @@ import {IERC20} from "./IERC20.sol"; import {IERC20Metadata} from "./extensions/IERC20Metadata.sol"; import {Context} from "../../utils/Context.sol"; import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol"; +import {ERC20Lib} from "./libs/ERC20Lib.sol"; /** * @dev Implementation of the {IERC20} interface. @@ -32,14 +33,9 @@ import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol"; * these events, as it isn't required by the specification. */ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { - mapping(address account => uint256) private _balances; + using ERC20Lib for ERC20Lib.Storage; - mapping(address account => mapping(address spender => uint256)) private _allowances; - - uint256 private _totalSupply; - - string private _name; - string private _symbol; + ERC20Lib.Storage private _erc20; /** * @dev Sets the values for {name} and {symbol}. @@ -48,15 +44,14 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * construction. */ constructor(string memory name_, string memory symbol_) { - _name = name_; - _symbol = symbol_; + _erc20.init(name_, symbol_); } /** * @dev Returns the name of the token. */ function name() public view virtual returns (string memory) { - return _name; + return _erc20.name(); } /** @@ -64,7 +59,7 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * name. */ function symbol() public view virtual returns (string memory) { - return _symbol; + return _erc20.symbol(); } /** @@ -88,14 +83,14 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * @dev See {IERC20-totalSupply}. */ function totalSupply() public view virtual returns (uint256) { - return _totalSupply; + return _erc20.totalSupply(); } /** * @dev See {IERC20-balanceOf}. */ function balanceOf(address account) public view virtual returns (uint256) { - return _balances[account]; + return _erc20.balanceOf(account); } /** @@ -107,16 +102,14 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * - the caller must have a balance of at least `value`. */ function transfer(address to, uint256 value) public virtual returns (bool) { - address owner = _msgSender(); - _transfer(owner, to, value); - return true; + return _erc20.transfer(_msgSender(), to, value); } /** * @dev See {IERC20-allowance}. */ function allowance(address owner, address spender) public view virtual returns (uint256) { - return _allowances[owner][spender]; + return _erc20.allowance(owner, spender); } /** @@ -130,9 +123,7 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * - `spender` cannot be the zero address. */ function approve(address spender, uint256 value) public virtual returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, value); - return true; + return _erc20.approve(_msgSender(), spender, value); } /** @@ -152,10 +143,7 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * `value`. */ function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { - address spender = _msgSender(); - _spendAllowance(from, spender, value); - _transfer(from, to, value); - return true; + return _erc20.transferFrom(_msgSender(), from, to, value); } /** @@ -169,13 +157,7 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * NOTE: This function is not virtual, {_update} should be overridden instead. */ function _transfer(address from, address to, uint256 value) internal { - if (from == address(0)) { - revert ERC20InvalidSender(address(0)); - } - if (to == address(0)) { - revert ERC20InvalidReceiver(address(0)); - } - _update(from, to, value); + _erc20._transfer(from, to, value); } /** @@ -186,33 +168,7 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * Emits a {Transfer} event. */ function _update(address from, address to, uint256 value) internal virtual { - if (from == address(0)) { - // Overflow check required: The rest of the code assumes that totalSupply never overflows - _totalSupply += value; - } else { - uint256 fromBalance = _balances[from]; - if (fromBalance < value) { - revert ERC20InsufficientBalance(from, fromBalance, value); - } - unchecked { - // Overflow not possible: value <= fromBalance <= totalSupply. - _balances[from] = fromBalance - value; - } - } - - if (to == address(0)) { - unchecked { - // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. - _totalSupply -= value; - } - } else { - unchecked { - // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. - _balances[to] += value; - } - } - - emit Transfer(from, to, value); + _erc20._update(from, to, value); } /** @@ -224,10 +180,7 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * NOTE: This function is not virtual, {_update} should be overridden instead. */ function _mint(address account, uint256 value) internal { - if (account == address(0)) { - revert ERC20InvalidReceiver(address(0)); - } - _update(address(0), account, value); + _erc20.mint(account, value); } /** @@ -239,10 +192,7 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * NOTE: This function is not virtual, {_update} should be overridden instead */ function _burn(address account, uint256 value) internal { - if (account == address(0)) { - revert ERC20InvalidSender(address(0)); - } - _update(account, address(0), value); + _erc20.burn(account, value); } /** @@ -261,7 +211,7 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. */ function _approve(address owner, address spender, uint256 value) internal { - _approve(owner, spender, value, true); + _erc20._approve(owner, spender, value, true); } /** @@ -282,16 +232,7 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * Requirements are the same as {_approve}. */ function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual { - if (owner == address(0)) { - revert ERC20InvalidApprover(address(0)); - } - if (spender == address(0)) { - revert ERC20InvalidSpender(address(0)); - } - _allowances[owner][spender] = value; - if (emitEvent) { - emit Approval(owner, spender, value); - } + _erc20._approve(owner, spender, value, emitEvent); } /** @@ -303,14 +244,6 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * Does not emit an {Approval} event. */ function _spendAllowance(address owner, address spender, uint256 value) internal virtual { - uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance != type(uint256).max) { - if (currentAllowance < value) { - revert ERC20InsufficientAllowance(spender, currentAllowance, value); - } - unchecked { - _approve(owner, spender, currentAllowance - value, false); - } - } + _erc20._spendAllowance(owner, spender, value); } } diff --git a/contracts/token/ERC20/libs/ERC20Lib.sol b/contracts/token/ERC20/libs/ERC20Lib.sol new file mode 100644 index 00000000000..f8adfd68dff --- /dev/null +++ b/contracts/token/ERC20/libs/ERC20Lib.sol @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.20; + +import {IERC20Errors} from "../../../interfaces/draft-IERC6093.sol"; + +library ERC20Lib { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + struct Storage { + mapping(address account => uint256) _balances; + mapping(address account => mapping(address spender => uint256)) _allowances; + uint256 _totalSupply; + string _name; + string _symbol; + } + + /** + * @dev Sets the values for {name} and {symbol}. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + function init(Storage storage $, string memory name_, string memory symbol_) internal { + $._name = name_; + $._symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name(Storage storage $) internal view returns (string memory) { + return $._name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol(Storage storage $) internal view returns (string memory) { + return $._symbol; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply(Storage storage $) internal view returns (uint256) { + return $._totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(Storage storage $, address account) internal view returns (uint256) { + return $._balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `value`. + */ + function transfer(Storage storage $, address owner, address to, uint256 value) internal returns (bool) { + _transfer($, owner, to, value); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(Storage storage $, address owner, address spender) internal view returns (uint256) { + return $._allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(Storage storage $, address owner, address spender, uint256 value) internal returns (bool) { + _approve($, owner, spender, value); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the ERC. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `value`. + * - the caller must have allowance for ``from``'s tokens of at least + * `value`. + */ + function transferFrom( + Storage storage $, + address owner, + address from, + address to, + uint256 value + ) internal returns (bool) { + _spendAllowance($, owner, from, value); + _transfer($, from, to, value); + return true; + } + + /** + * @dev Moves a `value` amount of tokens from `from` to `to`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * NOTE: This function is not virtual, {_update} should be overridden instead. + */ + function _transfer(Storage storage $, address from, address to, uint256 value) internal { + if (from == address(0)) { + revert IERC20Errors.ERC20InvalidSender(address(0)); + } + if (to == address(0)) { + revert IERC20Errors.ERC20InvalidReceiver(address(0)); + } + _update($, from, to, value); + } + + /** + * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` + * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding + * this function. + * + * Emits a {Transfer} event. + */ + function _update(Storage storage $, address from, address to, uint256 value) internal { + if (from == address(0)) { + // Overflow check required: The rest of the code assumes that totalSupply never overflows + $._totalSupply += value; + } else { + uint256 fromBalance = $._balances[from]; + if (fromBalance < value) { + revert IERC20Errors.ERC20InsufficientBalance(from, fromBalance, value); + } + unchecked { + // Overflow not possible: value <= fromBalance <= totalSupply. + $._balances[from] = fromBalance - value; + } + } + + if (to == address(0)) { + unchecked { + // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. + $._totalSupply -= value; + } + } else { + unchecked { + // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. + $._balances[to] += value; + } + } + + emit Transfer(from, to, value); + } + + /** + * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0). + * Relies on the `_update` mechanism + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * NOTE: This function is not virtual, {_update} should be overridden instead. + */ + function mint(Storage storage $, address account, uint256 value) internal { + if (account == address(0)) { + revert IERC20Errors.ERC20InvalidReceiver(address(0)); + } + _update($, address(0), account, value); + } + + /** + * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply. + * Relies on the `_update` mechanism. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * NOTE: This function is not virtual, {_update} should be overridden instead + */ + function burn(Storage storage $, address account, uint256 value) internal { + if (account == address(0)) { + revert IERC20Errors.ERC20InvalidSender(address(0)); + } + _update($, account, address(0), value); + } + + /** + * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + * + * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. + */ + function _approve(Storage storage $, address owner, address spender, uint256 value) internal { + _approve($, owner, spender, value, true); + } + + /** + * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event. + * + * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by + * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any + * `Approval` event during `transferFrom` operations. + * + * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to + * true using the following override: + * ``` + * function _approve(address owner, address spender, uint256 value, bool) internal override { + * super._approve(owner, spender, value, true); + * } + * ``` + * + * Requirements are the same as {_approve}. + */ + function _approve(Storage storage $, address owner, address spender, uint256 value, bool emitEvent) internal { + if (owner == address(0)) { + revert IERC20Errors.ERC20InvalidApprover(address(0)); + } + if (spender == address(0)) { + revert IERC20Errors.ERC20InvalidSpender(address(0)); + } + $._allowances[owner][spender] = value; + if (emitEvent) { + emit Approval(owner, spender, value); + } + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `value`. + * + * Does not update the allowance value in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Does not emit an {Approval} event. + */ + function _spendAllowance(Storage storage $, address owner, address spender, uint256 value) internal { + uint256 currentAllowance = allowance($, owner, spender); + if (currentAllowance != type(uint256).max) { + if (currentAllowance < value) { + revert IERC20Errors.ERC20InsufficientAllowance(spender, currentAllowance, value); + } + unchecked { + _approve($, owner, spender, currentAllowance - value, false); + } + } + } +}