From 50db94a4492b3af99a2ac5047f8a572a7732cd0c Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Thu, 24 Nov 2022 15:12:13 -0400 Subject: [PATCH 01/42] remove beforeTokenTranser and afterTokenTransfer hooks --- contracts/mocks/ERC20VotesMock.sol | 4 +- contracts/token/ERC20/ERC20.sol | 123 ++++-------------- .../token/ERC20/extensions/ERC20Capped.sol | 6 +- .../token/ERC20/extensions/ERC20Pausable.sol | 5 +- .../token/ERC20/extensions/ERC20Snapshot.sol | 10 +- .../token/ERC20/extensions/ERC20Votes.sol | 30 ++--- .../ERC20/presets/ERC20PresetMinterPauser.sol | 4 +- .../ERC20/extensions/ERC20FlashMint.test.js | 6 +- 8 files changed, 52 insertions(+), 136 deletions(-) diff --git a/contracts/mocks/ERC20VotesMock.sol b/contracts/mocks/ERC20VotesMock.sol index 0975e8b9f7e..6719f64b102 100644 --- a/contracts/mocks/ERC20VotesMock.sol +++ b/contracts/mocks/ERC20VotesMock.sol @@ -8,11 +8,11 @@ contract ERC20VotesMock is ERC20Votes { constructor(string memory name, string memory symbol) ERC20(name, symbol) ERC20Permit(name) {} function mint(address account, uint256 amount) public { - _mint(account, amount); + _transfer(address(0), account, amount); } function burn(address account, uint256 amount) public { - _burn(account, amount); + _transfer(account, address(0), amount); } function getChainId() external view returns (uint256) { diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 492e261018d..94b4011a0d1 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -215,89 +215,60 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * 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. + * Will mint (or burn) if `from` (or `to`) is address(0). * - * Requirements: + * Emits a {Transfer} event. * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. */ function _transfer( address from, address to, uint256 amount ) internal virtual { - require(from != address(0), "ERC20: transfer from the zero address"); - require(to != address(0), "ERC20: transfer to the zero address"); - - _beforeTokenTransfer(from, to, amount); + require(from != address(0) || to != address(0), "ERC721: mint to the zero address"); uint256 fromBalance = _balances[from]; - require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); unchecked { - _balances[from] = fromBalance - amount; - // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by - // decrementing then incrementing. - _balances[to] += amount; + if (from == address(0)) { + _balances[to] += amount; + _totalSupply += amount; + } else if (to == address(0)) { + require(fromBalance >= amount, "ERC20: burn amount exceeds balance"); + _balances[from] -= amount; + _totalSupply -= amount; + } else { + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + _balances[from] -= amount; + _balances[to] += amount; + } } - + emit Transfer(from, to, amount); - - _afterTokenTransfer(from, to, amount); } - /** @dev Creates `amount` tokens and assigns them to `account`, increasing - * the total supply. + /** @dev Creates `amount` tokens and assigns them to `account`, by transfering it from address(0). + * Relies on the `_transfer` mechanism * * Emits a {Transfer} event with `from` set to the zero address. * - * Requirements: + * Note: this function is not virtual, {_transfer} should be overriden instead. * - * - `account` cannot be the zero address. */ - function _mint(address account, uint256 amount) internal virtual { - require(account != address(0), "ERC20: mint to the zero address"); - - _beforeTokenTransfer(address(0), account, amount); - - _totalSupply += amount; - unchecked { - // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. - _balances[account] += amount; - } - emit Transfer(address(0), account, amount); - - _afterTokenTransfer(address(0), account, amount); + function _mint(address account, uint256 amount) internal { + _transfer(address(0), account, amount); } /** - * @dev Destroys `amount` tokens from `account`, reducing the - * total supply. + * @dev Destroys `amount` tokens from `account`, by transfering it to address(0). + * Relies on the `_transfer` mechanism. * - * Emits a {Transfer} event with `to` set to the zero address. + * Note: this function is not virtual, {_transfer} should be overriden instead * - * Requirements: + * Emits a {Transfer} event with `to` set to the zero address. * - * - `account` cannot be the zero address. - * - `account` must have at least `amount` tokens. */ - function _burn(address account, uint256 amount) internal virtual { - require(account != address(0), "ERC20: burn from the zero address"); - - _beforeTokenTransfer(account, address(0), amount); - - uint256 accountBalance = _balances[account]; - require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); - unchecked { - _balances[account] = accountBalance - amount; - // Overflow not possible: amount <= accountBalance <= totalSupply. - _totalSupply -= amount; - } - - emit Transfer(account, address(0), amount); - - _afterTokenTransfer(account, address(0), amount); + function _burn(address account, uint256 amount) internal { + _transfer(account, address(0), amount); } /** @@ -346,44 +317,4 @@ contract ERC20 is Context, IERC20, IERC20Metadata { } } } - - /** - * @dev Hook that is called before any transfer of tokens. This includes - * minting and burning. - * - * Calling conditions: - * - * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * will be transferred to `to`. - * - when `from` is zero, `amount` tokens will be minted for `to`. - * - when `to` is zero, `amount` of ``from``'s tokens will be burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _beforeTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual {} - - /** - * @dev Hook that is called after any transfer of tokens. This includes - * minting and burning. - * - * Calling conditions: - * - * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * has been transferred to `to`. - * - when `from` is zero, `amount` tokens have been minted for `to`. - * - when `to` is zero, `amount` of ``from``'s tokens have been burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _afterTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual {} } diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol index 16f830d18a5..24d52166301 100644 --- a/contracts/token/ERC20/extensions/ERC20Capped.sol +++ b/contracts/token/ERC20/extensions/ERC20Capped.sol @@ -28,10 +28,10 @@ abstract contract ERC20Capped is ERC20 { } /** - * @dev See {ERC20-_mint}. + * @dev See {ERC20-_transfer}. */ - function _mint(address account, uint256 amount) internal virtual override { + function _transfer(address, address account, uint256 amount) internal virtual override { require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded"); - super._mint(account, amount); + super._transfer(address(0), account, amount); } } diff --git a/contracts/token/ERC20/extensions/ERC20Pausable.sol b/contracts/token/ERC20/extensions/ERC20Pausable.sol index e448e96a640..e314cba5b1c 100644 --- a/contracts/token/ERC20/extensions/ERC20Pausable.sol +++ b/contracts/token/ERC20/extensions/ERC20Pausable.sol @@ -21,13 +21,12 @@ abstract contract ERC20Pausable is ERC20, Pausable { * * - the contract must not be paused. */ - function _beforeTokenTransfer( + function _transfer( address from, address to, uint256 amount ) internal virtual override { - super._beforeTokenTransfer(from, to, amount); - require(!paused(), "ERC20Pausable: token transfer while paused"); + super._transfer(from, to, amount); } } diff --git a/contracts/token/ERC20/extensions/ERC20Snapshot.sol b/contracts/token/ERC20/extensions/ERC20Snapshot.sol index 0b46fc660bf..6db172f3c9d 100644 --- a/contracts/token/ERC20/extensions/ERC20Snapshot.sol +++ b/contracts/token/ERC20/extensions/ERC20Snapshot.sol @@ -118,18 +118,15 @@ abstract contract ERC20Snapshot is ERC20 { return snapshotted ? value : totalSupply(); } - // Update balance and/or total supply snapshots before the values are modified. This is implemented - // in the _beforeTokenTransfer hook, which is executed for _mint, _burn, and _transfer operations. - function _beforeTokenTransfer( + // Update balance and/or total supply snapshots before the values are modified. This is executed + // for _mint, _burn, and _transfer operations. + function _transfer( address from, address to, uint256 amount ) internal virtual override { - super._beforeTokenTransfer(from, to, amount); - if (from == address(0)) { // mint - _updateAccountSnapshot(to); _updateTotalSupplySnapshot(); } else if (to == address(0)) { // burn @@ -140,6 +137,7 @@ abstract contract ERC20Snapshot is ERC20 { _updateAccountSnapshot(from); _updateAccountSnapshot(to); } + super._transfer(from, to, amount); } function _valueAt(uint256 snapshotId, Snapshots storage snapshots) private view returns (bool, uint256) { diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index bf319c26f84..8e66c86ecbd 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -173,36 +173,24 @@ abstract contract ERC20Votes is IVotes, ERC20Permit { return type(uint224).max; } - /** - * @dev Snapshots the totalSupply after it has been increased. - */ - function _mint(address account, uint256 amount) internal virtual override { - super._mint(account, amount); - require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes"); - - _writeCheckpoint(_totalSupplyCheckpoints, _add, amount); - } - - /** - * @dev Snapshots the totalSupply after it has been decreased. - */ - function _burn(address account, uint256 amount) internal virtual override { - super._burn(account, amount); - - _writeCheckpoint(_totalSupplyCheckpoints, _subtract, amount); - } - /** * @dev Move voting power when tokens are transferred. * * Emits a {IVotes-DelegateVotesChanged} event. */ - function _afterTokenTransfer( + function _transfer( address from, address to, uint256 amount ) internal virtual override { - super._afterTokenTransfer(from, to, amount); + super._transfer(from, to, amount); + if(from == address(0)){ + require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes"); + _writeCheckpoint(_totalSupplyCheckpoints, _add, amount); + } + if(to == address(0)){ + _writeCheckpoint(_totalSupplyCheckpoints, _subtract, amount); + } _moveVotingPower(delegates(from), delegates(to), amount); } diff --git a/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol b/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol index e711a894f0c..8ab23590e90 100644 --- a/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol +++ b/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol @@ -84,11 +84,11 @@ contract ERC20PresetMinterPauser is Context, AccessControlEnumerable, ERC20Burna _unpause(); } - function _beforeTokenTransfer( + function _transfer( address from, address to, uint256 amount ) internal virtual override(ERC20, ERC20Pausable) { - super._beforeTokenTransfer(from, to, amount); + super._transfer(from, to, amount); } } diff --git a/test/token/ERC20/extensions/ERC20FlashMint.test.js b/test/token/ERC20/extensions/ERC20FlashMint.test.js index d08464decfb..a375c214c09 100644 --- a/test/token/ERC20/extensions/ERC20FlashMint.test.js +++ b/test/token/ERC20/extensions/ERC20FlashMint.test.js @@ -1,8 +1,8 @@ /* eslint-disable */ -const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); +const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); -const { MAX_UINT256, ZERO_ADDRESS, ZERO_BYTES32 } = constants; +const { MAX_UINT256, ZERO_ADDRESS } = constants; const ERC20FlashMintMock = artifacts.require('ERC20FlashMintMock'); const ERC3156FlashBorrowerMock = artifacts.require('ERC3156FlashBorrowerMock'); @@ -77,7 +77,7 @@ contract('ERC20FlashMint', function (accounts) { ); }); - it('unavailable funds', async function () { + it.only('unavailable funds', async function () { const receiver = await ERC3156FlashBorrowerMock.new(true, true); const data = this.token.contract.methods.transfer(other, 10).encodeABI(); await expectRevert( From 2e68978965e3dec65da64ecd6f980846de5637f0 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Fri, 25 Nov 2022 08:44:51 -0400 Subject: [PATCH 02/42] Update contracts using the hooks --- contracts/token/ERC20/ERC20.sol | 8 +++++-- .../token/ERC20/extensions/ERC20Capped.sol | 6 ++++- .../token/ERC20/extensions/ERC20Votes.sol | 4 ++-- test/token/ERC20/ERC20.behavior.js | 23 ------------------- test/token/ERC20/ERC20.test.js | 12 ++-------- .../ERC20/extensions/ERC20FlashMint.test.js | 2 +- 6 files changed, 16 insertions(+), 39 deletions(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 94b4011a0d1..69ceb3831c8 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -6,6 +6,7 @@ pragma solidity ^0.8.0; import "./IERC20.sol"; import "./extensions/IERC20Metadata.sol"; import "../../utils/Context.sol"; +import "hardhat/console.sol"; /** * @dev Implementation of the {IERC20} interface. @@ -225,24 +226,27 @@ contract ERC20 is Context, IERC20, IERC20Metadata { address to, uint256 amount ) internal virtual { - require(from != address(0) || to != address(0), "ERC721: mint to the zero address"); + require(from != address(0) || to != address(0), "ERC20: invalid transfer operation"); uint256 fromBalance = _balances[from]; unchecked { if (from == address(0)) { + console.log("ERC mint", amount); _balances[to] += amount; _totalSupply += amount; } else if (to == address(0)) { + console.log("erc burn", amount); require(fromBalance >= amount, "ERC20: burn amount exceeds balance"); _balances[from] -= amount; _totalSupply -= amount; } else { + console.log("erc transfer", amount); require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); _balances[from] -= amount; _balances[to] += amount; } } - + emit Transfer(from, to, amount); } diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol index 24d52166301..559123b15cb 100644 --- a/contracts/token/ERC20/extensions/ERC20Capped.sol +++ b/contracts/token/ERC20/extensions/ERC20Capped.sol @@ -30,7 +30,11 @@ abstract contract ERC20Capped is ERC20 { /** * @dev See {ERC20-_transfer}. */ - function _transfer(address, address account, uint256 amount) internal virtual override { + function _transfer( + address, + address account, + uint256 amount + ) internal virtual override { require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded"); super._transfer(address(0), account, amount); } diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index 8e66c86ecbd..fa791b16c10 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -184,11 +184,11 @@ abstract contract ERC20Votes is IVotes, ERC20Permit { uint256 amount ) internal virtual override { super._transfer(from, to, amount); - if(from == address(0)){ + if (from == address(0)) { require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes"); _writeCheckpoint(_totalSupplyCheckpoints, _add, amount); } - if(to == address(0)){ + if (to == address(0)) { _writeCheckpoint(_totalSupplyCheckpoints, _subtract, amount); } diff --git a/test/token/ERC20/ERC20.behavior.js b/test/token/ERC20/ERC20.behavior.js index 8bc54762ac8..96c30aeda92 100644 --- a/test/token/ERC20/ERC20.behavior.js +++ b/test/token/ERC20/ERC20.behavior.js @@ -148,21 +148,6 @@ function shouldBehaveLikeERC20 (errorPrefix, initialSupply, initialHolder, recip }); }); }); - - describe('when the recipient is the zero address', function () { - const amount = initialSupply; - const to = ZERO_ADDRESS; - - beforeEach(async function () { - await this.token.approve(spender, amount, { from: tokenOwner }); - }); - - it('reverts', async function () { - await expectRevert(this.token.transferFrom( - tokenOwner, to, amount, { from: spender }), `${errorPrefix}: transfer to the zero address`, - ); - }); - }); }); describe('when the token owner is the zero address', function () { @@ -240,14 +225,6 @@ function shouldBehaveLikeERC20Transfer (errorPrefix, from, to, balance, transfer }); }); }); - - describe('when the recipient is the zero address', function () { - it('reverts', async function () { - await expectRevert(transfer.call(this, from, ZERO_ADDRESS, balance), - `${errorPrefix}: transfer to the zero address`, - ); - }); - }); } function shouldBehaveLikeERC20Approve (errorPrefix, owner, spender, supply, approve) { diff --git a/test/token/ERC20/ERC20.test.js b/test/token/ERC20/ERC20.test.js index 992edf93b17..54417933844 100644 --- a/test/token/ERC20/ERC20.test.js +++ b/test/token/ERC20/ERC20.test.js @@ -203,7 +203,7 @@ contract('ERC20', function (accounts) { const amount = new BN(50); it('rejects a null account', async function () { await expectRevert( - this.token.mint(ZERO_ADDRESS, amount), 'ERC20: mint to the zero address', + this.token.mint(ZERO_ADDRESS, amount), 'ERC20: invalid transfer operation', ); }); @@ -236,7 +236,7 @@ contract('ERC20', function (accounts) { describe('_burn', function () { it('rejects a null account', async function () { await expectRevert(this.token.burn(ZERO_ADDRESS, new BN(1)), - 'ERC20: burn from the zero address'); + 'ERC20: invalid transfer operation'); }); describe('for a non zero account', function () { @@ -283,14 +283,6 @@ contract('ERC20', function (accounts) { shouldBehaveLikeERC20Transfer('ERC20', initialHolder, recipient, initialSupply, function (from, to, amount) { return this.token.transferInternal(from, to, amount); }); - - describe('when the sender is the zero address', function () { - it('reverts', async function () { - await expectRevert(this.token.transferInternal(ZERO_ADDRESS, recipient, initialSupply), - 'ERC20: transfer from the zero address', - ); - }); - }); }); describe('_approve', function () { diff --git a/test/token/ERC20/extensions/ERC20FlashMint.test.js b/test/token/ERC20/extensions/ERC20FlashMint.test.js index a375c214c09..4354dd90c2b 100644 --- a/test/token/ERC20/extensions/ERC20FlashMint.test.js +++ b/test/token/ERC20/extensions/ERC20FlashMint.test.js @@ -77,7 +77,7 @@ contract('ERC20FlashMint', function (accounts) { ); }); - it.only('unavailable funds', async function () { + it('unavailable funds', async function () { const receiver = await ERC3156FlashBorrowerMock.new(true, true); const data = this.token.contract.methods.transfer(other, 10).encodeABI(); await expectRevert( From bdd94d131240abe66c141bdf8abb2d9c46aa0c0a Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Fri, 25 Nov 2022 08:45:25 -0400 Subject: [PATCH 03/42] ERC20Snapshot changes --- contracts/mocks/ERC20SnapshotMock.sol | 2 ++ contracts/token/ERC20/extensions/ERC20Snapshot.sol | 4 ++++ test/token/ERC20/extensions/ERC20Snapshot.test.js | 5 ++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/contracts/mocks/ERC20SnapshotMock.sol b/contracts/mocks/ERC20SnapshotMock.sol index cb3048322bf..cc150788c07 100644 --- a/contracts/mocks/ERC20SnapshotMock.sol +++ b/contracts/mocks/ERC20SnapshotMock.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import "../token/ERC20/extensions/ERC20Snapshot.sol"; +import "hardhat/console.sol"; contract ERC20SnapshotMock is ERC20Snapshot { constructor( @@ -11,6 +12,7 @@ contract ERC20SnapshotMock is ERC20Snapshot { address initialAccount, uint256 initialBalance ) ERC20(name, symbol) { + console.log("we'll mint"); _mint(initialAccount, initialBalance); } diff --git a/contracts/token/ERC20/extensions/ERC20Snapshot.sol b/contracts/token/ERC20/extensions/ERC20Snapshot.sol index 6db172f3c9d..9782a20210b 100644 --- a/contracts/token/ERC20/extensions/ERC20Snapshot.sol +++ b/contracts/token/ERC20/extensions/ERC20Snapshot.sol @@ -6,6 +6,7 @@ pragma solidity ^0.8.0; import "../ERC20.sol"; import "../../../utils/Arrays.sol"; import "../../../utils/Counters.sol"; +import "hardhat/console.sol"; /** * @dev This contract extends an ERC20 token with a snapshot mechanism. When a snapshot is created, the balances and @@ -127,13 +128,16 @@ abstract contract ERC20Snapshot is ERC20 { ) internal virtual override { if (from == address(0)) { // mint + console.log("mint", amount); _updateTotalSupplySnapshot(); } else if (to == address(0)) { // burn + console.log("burn", amount); _updateAccountSnapshot(from); _updateTotalSupplySnapshot(); } else { // transfer + console.log("transfer", amount); _updateAccountSnapshot(from); _updateAccountSnapshot(to); } diff --git a/test/token/ERC20/extensions/ERC20Snapshot.test.js b/test/token/ERC20/extensions/ERC20Snapshot.test.js index 64d922706d7..241ffc71b69 100644 --- a/test/token/ERC20/extensions/ERC20Snapshot.test.js +++ b/test/token/ERC20/extensions/ERC20Snapshot.test.js @@ -129,10 +129,13 @@ contract('ERC20Snapshot', function (accounts) { }); }); - context('with balance changes after the snapshot', function () { + context.only('with balance changes after the snapshot', function () { beforeEach(async function () { + console.log('transfer 10'); await this.token.transfer(recipient, new BN('10'), { from: initialHolder }); + console.log('mint 50'); await this.token.mint(other, new BN('50')); + console.log('burn 20'); await this.token.burn(initialHolder, new BN('20')); }); From e03c773376a90f5a0a965e9b9a4ad1f6de0b54ca Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Mon, 28 Nov 2022 10:37:25 -0400 Subject: [PATCH 04/42] Update ERC20Snapshot mock --- contracts/mocks/ERC20SnapshotMock.sol | 6 ++---- contracts/token/ERC20/ERC20.sol | 4 ---- contracts/token/ERC20/extensions/ERC20Snapshot.sol | 5 +---- test/token/ERC20/extensions/ERC20Snapshot.test.js | 5 +---- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/contracts/mocks/ERC20SnapshotMock.sol b/contracts/mocks/ERC20SnapshotMock.sol index cc150788c07..c319af872ad 100644 --- a/contracts/mocks/ERC20SnapshotMock.sol +++ b/contracts/mocks/ERC20SnapshotMock.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.0; import "../token/ERC20/extensions/ERC20Snapshot.sol"; -import "hardhat/console.sol"; contract ERC20SnapshotMock is ERC20Snapshot { constructor( @@ -12,7 +11,6 @@ contract ERC20SnapshotMock is ERC20Snapshot { address initialAccount, uint256 initialBalance ) ERC20(name, symbol) { - console.log("we'll mint"); _mint(initialAccount, initialBalance); } @@ -21,10 +19,10 @@ contract ERC20SnapshotMock is ERC20Snapshot { } function mint(address account, uint256 amount) public { - _mint(account, amount); + _transfer(address(0), account, amount); } function burn(address account, uint256 amount) public { - _burn(account, amount); + _transfer(account, address(0), amount); } } diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 69ceb3831c8..13fca961784 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -6,7 +6,6 @@ pragma solidity ^0.8.0; import "./IERC20.sol"; import "./extensions/IERC20Metadata.sol"; import "../../utils/Context.sol"; -import "hardhat/console.sol"; /** * @dev Implementation of the {IERC20} interface. @@ -231,16 +230,13 @@ contract ERC20 is Context, IERC20, IERC20Metadata { uint256 fromBalance = _balances[from]; unchecked { if (from == address(0)) { - console.log("ERC mint", amount); _balances[to] += amount; _totalSupply += amount; } else if (to == address(0)) { - console.log("erc burn", amount); require(fromBalance >= amount, "ERC20: burn amount exceeds balance"); _balances[from] -= amount; _totalSupply -= amount; } else { - console.log("erc transfer", amount); require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); _balances[from] -= amount; _balances[to] += amount; diff --git a/contracts/token/ERC20/extensions/ERC20Snapshot.sol b/contracts/token/ERC20/extensions/ERC20Snapshot.sol index 9782a20210b..fcb65d09f53 100644 --- a/contracts/token/ERC20/extensions/ERC20Snapshot.sol +++ b/contracts/token/ERC20/extensions/ERC20Snapshot.sol @@ -6,7 +6,6 @@ pragma solidity ^0.8.0; import "../ERC20.sol"; import "../../../utils/Arrays.sol"; import "../../../utils/Counters.sol"; -import "hardhat/console.sol"; /** * @dev This contract extends an ERC20 token with a snapshot mechanism. When a snapshot is created, the balances and @@ -128,16 +127,14 @@ abstract contract ERC20Snapshot is ERC20 { ) internal virtual override { if (from == address(0)) { // mint - console.log("mint", amount); + _updateAccountSnapshot(to); _updateTotalSupplySnapshot(); } else if (to == address(0)) { // burn - console.log("burn", amount); _updateAccountSnapshot(from); _updateTotalSupplySnapshot(); } else { // transfer - console.log("transfer", amount); _updateAccountSnapshot(from); _updateAccountSnapshot(to); } diff --git a/test/token/ERC20/extensions/ERC20Snapshot.test.js b/test/token/ERC20/extensions/ERC20Snapshot.test.js index 241ffc71b69..64d922706d7 100644 --- a/test/token/ERC20/extensions/ERC20Snapshot.test.js +++ b/test/token/ERC20/extensions/ERC20Snapshot.test.js @@ -129,13 +129,10 @@ contract('ERC20Snapshot', function (accounts) { }); }); - context.only('with balance changes after the snapshot', function () { + context('with balance changes after the snapshot', function () { beforeEach(async function () { - console.log('transfer 10'); await this.token.transfer(recipient, new BN('10'), { from: initialHolder }); - console.log('mint 50'); await this.token.mint(other, new BN('50')); - console.log('burn 20'); await this.token.burn(initialHolder, new BN('20')); }); From 616c148fa1ae7483675338495986d3f3ee752729 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Mon, 28 Nov 2022 11:00:32 -0400 Subject: [PATCH 05/42] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dafdf499018..f121e3ab8aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased (Breaking) * `TimelockController`: Changed the role architecture to use `DEFAULT_ADMIN_ROLE` as the admin for all roles, instead of the bespoke `TIMELOCK_ADMIN_ROLE` that was used previously. This aligns with the general recommendation for `AccessControl` and makes the addition of new roles easier. Accordingly, the `admin` parameter and timelock will now be granted `DEFAULT_ADMIN_ROLE` instead of `TIMELOCK_ADMIN_ROLE`. ([#3799](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3799)) + * `ERC20`: Deleted `_beforeTokenTransfer` and `_afterTokenTransfer` hooks, and refactor all extensions using those hooks for customization, by using `transfer` instead. ([#3838](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3838)) ## Unreleased From a81f1a5b01569c1729ac0d581e4010d39faeceec Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Mon, 28 Nov 2022 12:45:45 -0400 Subject: [PATCH 06/42] update documentation --- contracts/token/ERC20/ERC20.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 0be9d897742..07be9cb37e8 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -245,12 +245,12 @@ contract ERC20 is Context, IERC20, IERC20Metadata { emit Transfer(from, to, amount); } - /** @dev Creates `amount` tokens and assigns them to `account`, by transfering it from address(0). + /** @dev Creates `amount` tokens and assigns them to `account`, by transferring it from address(0). * Relies on the `_transfer` mechanism * * Emits a {Transfer} event with `from` set to the zero address. * - * Note: this function is not virtual, {_transfer} should be overriden instead. + * Note: this function is not virtual, {_transfer} should be overridden instead. * */ function _mint(address account, uint256 amount) internal { @@ -258,10 +258,10 @@ contract ERC20 is Context, IERC20, IERC20Metadata { } /** - * @dev Destroys `amount` tokens from `account`, by transfering it to address(0). + * @dev Destroys `amount` tokens from `account`, by transferring it to address(0). * Relies on the `_transfer` mechanism. * - * Note: this function is not virtual, {_transfer} should be overriden instead + * Note: this function is not virtual, {_transfer} should be overridden instead * * Emits a {Transfer} event with `to` set to the zero address. * From 4f3adcbb35cf83505eb6c160c14a9d9f46a5c1c3 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Tue, 29 Nov 2022 16:07:11 -0400 Subject: [PATCH 07/42] update documentation --- .../token/ERC20/extensions/ERC20Pausable.sol | 2 +- .../ERC20/presets/ERC20PresetMinterPauser.sol | 94 ------------------- 2 files changed, 1 insertion(+), 95 deletions(-) delete mode 100644 contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol diff --git a/contracts/token/ERC20/extensions/ERC20Pausable.sol b/contracts/token/ERC20/extensions/ERC20Pausable.sol index e314cba5b1c..97410fb9320 100644 --- a/contracts/token/ERC20/extensions/ERC20Pausable.sol +++ b/contracts/token/ERC20/extensions/ERC20Pausable.sol @@ -15,7 +15,7 @@ import "../../../security/Pausable.sol"; */ abstract contract ERC20Pausable is ERC20, Pausable { /** - * @dev See {ERC20-_beforeTokenTransfer}. + * @dev See {ERC20-_transfer}. * * Requirements: * diff --git a/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol b/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol deleted file mode 100644 index 8ab23590e90..00000000000 --- a/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/presets/ERC20PresetMinterPauser.sol) - -pragma solidity ^0.8.0; - -import "../ERC20.sol"; -import "../extensions/ERC20Burnable.sol"; -import "../extensions/ERC20Pausable.sol"; -import "../../../access/AccessControlEnumerable.sol"; -import "../../../utils/Context.sol"; - -/** - * @dev {ERC20} token, including: - * - * - ability for holders to burn (destroy) their tokens - * - a minter role that allows for token minting (creation) - * - a pauser role that allows to stop all token transfers - * - * This contract uses {AccessControl} to lock permissioned functions using the - * different roles - head to its documentation for details. - * - * The account that deploys the contract will be granted the minter and pauser - * roles, as well as the default admin role, which will let it grant both minter - * and pauser roles to other accounts. - * - * _Deprecated in favor of https://wizard.openzeppelin.com/[Contracts Wizard]._ - */ -contract ERC20PresetMinterPauser is Context, AccessControlEnumerable, ERC20Burnable, ERC20Pausable { - bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); - - /** - * @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the - * account that deploys the contract. - * - * See {ERC20-constructor}. - */ - constructor(string memory name, string memory symbol) ERC20(name, symbol) { - _setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); - - _setupRole(MINTER_ROLE, _msgSender()); - _setupRole(PAUSER_ROLE, _msgSender()); - } - - /** - * @dev Creates `amount` new tokens for `to`. - * - * See {ERC20-_mint}. - * - * Requirements: - * - * - the caller must have the `MINTER_ROLE`. - */ - function mint(address to, uint256 amount) public virtual { - require(hasRole(MINTER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have minter role to mint"); - _mint(to, amount); - } - - /** - * @dev Pauses all token transfers. - * - * See {ERC20Pausable} and {Pausable-_pause}. - * - * Requirements: - * - * - the caller must have the `PAUSER_ROLE`. - */ - function pause() public virtual { - require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to pause"); - _pause(); - } - - /** - * @dev Unpauses all token transfers. - * - * See {ERC20Pausable} and {Pausable-_unpause}. - * - * Requirements: - * - * - the caller must have the `PAUSER_ROLE`. - */ - function unpause() public virtual { - require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to unpause"); - _unpause(); - } - - function _transfer( - address from, - address to, - uint256 amount - ) internal virtual override(ERC20, ERC20Pausable) { - super._transfer(from, to, amount); - } -} From 054777ec69c5a56eef1c5ef8875f884e381e7614 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Tue, 29 Nov 2022 16:20:42 -0400 Subject: [PATCH 08/42] Post merge fix --- contracts/token/ERC20/extensions/ERC20Votes.sol | 17 ++--------------- test/token/ERC20/extensions/ERC20Votes.test.js | 2 +- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index 989a5cdfa0e..b47f366ada7 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -41,15 +41,10 @@ abstract contract ERC20Votes is ERC20, Votes { uint256 amount ) internal virtual override { super._transfer(from, to, amount); - if (from == address(0)) { + if(from == address(0)) { require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes"); - _writeCheckpoint(_totalSupplyCheckpoints, _add, amount); } - if (to == address(0)) { - _writeCheckpoint(_totalSupplyCheckpoints, _subtract, amount); - } - - _moveVotingPower(delegates(from), delegates(to), amount); + _transferVotingUnits(from, to, amount); } /** @@ -72,12 +67,4 @@ abstract contract ERC20Votes is ERC20, Votes { function _getVotingUnits(address account) internal view virtual override returns (uint256) { return balanceOf(account); } - - /** - * @dev Snapshots the totalSupply after it has been increased. - */ - function _mint(address account, uint256 amount) internal virtual override { - super._mint(account, amount); - require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes"); - } } diff --git a/test/token/ERC20/extensions/ERC20Votes.test.js b/test/token/ERC20/extensions/ERC20Votes.test.js index 0d3ab74aadc..d89a7b9fb54 100644 --- a/test/token/ERC20/extensions/ERC20Votes.test.js +++ b/test/token/ERC20/extensions/ERC20Votes.test.js @@ -53,7 +53,7 @@ contract('ERC20Votes', function (accounts) { const amount = new BN('2').pow(new BN('224')); await expectRevert( this.token.mint(holder, amount), - "SafeCast: value doesn't fit in 224 bits", + "ERC20Votes: total supply risks overflowing votes", ); }); From b3da557582c9d08db662f6d3c57c2898a7086216 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Tue, 29 Nov 2022 16:23:07 -0400 Subject: [PATCH 09/42] Update ERC20Votes --- contracts/token/ERC20/extensions/ERC20Votes.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index b47f366ada7..0a7bb70385b 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -41,7 +41,7 @@ abstract contract ERC20Votes is ERC20, Votes { uint256 amount ) internal virtual override { super._transfer(from, to, amount); - if(from == address(0)) { + if (from == address(0)) { require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes"); } _transferVotingUnits(from, to, amount); From 27c7090480c2dc26f361def7b4a62960a591f55b Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Wed, 30 Nov 2022 14:31:05 -0400 Subject: [PATCH 10/42] add overflow test --- contracts/token/ERC20/ERC20.sol | 23 +++++++++++++---------- test/token/ERC20/ERC20.test.js | 7 +++++++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 07be9cb37e8..5e90d63d9d9 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -227,19 +227,22 @@ contract ERC20 is Context, IERC20, IERC20Metadata { require(from != address(0) || to != address(0), "ERC20: invalid transfer operation"); uint256 fromBalance = _balances[from]; - unchecked { - if (from == address(0)) { + if (from == address(0)) { + _totalSupply += amount; + unchecked { + // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. _balances[to] += amount; - _totalSupply += amount; - } else if (to == address(0)) { - require(fromBalance >= amount, "ERC20: burn amount exceeds balance"); - _balances[from] -= amount; - _totalSupply -= amount; - } else { - require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + } + } else if (to == address(0)) { + require(fromBalance >= amount, "ERC20: burn amount exceeds balance"); + _totalSupply -= amount; + unchecked { _balances[from] -= amount; - _balances[to] += amount; } + } else { + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + _balances[from] -= amount; + _balances[to] += amount; } emit Transfer(from, to, amount); diff --git a/test/token/ERC20/ERC20.test.js b/test/token/ERC20/ERC20.test.js index 54417933844..42b24cc4abb 100644 --- a/test/token/ERC20/ERC20.test.js +++ b/test/token/ERC20/ERC20.test.js @@ -207,6 +207,13 @@ contract('ERC20', function (accounts) { ); }); + it('rejects overflow', async function () { + const maxUint256 = new BN('2').pow(new BN(256)).subn(1); + await expectRevert.unspecified( + this.token.mint(recipient, maxUint256), + ); + }); + describe('for a non zero account', function () { beforeEach('minting', async function () { this.receipt = await this.token.mint(recipient, amount); From b4ab7c7a442047704fcefe6328e558a5987c2d36 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Fri, 2 Dec 2022 11:10:59 -0400 Subject: [PATCH 11/42] Update contracts/token/ERC20/ERC20.sol Co-authored-by: Francisco --- contracts/token/ERC20/ERC20.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 5e90d63d9d9..4caf1c81214 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -214,7 +214,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * This internal function is equivalent to {transfer}, and can be used to * e.g. implement automatic token fees, slashing mechanisms, etc. * - * Will mint (or burn) if `from` (or `to`) is address(0). + * Will mint (or burn) if `from` (or `to`) is the zero address. * * Emits a {Transfer} event. * From dcbc27514478324316dd82008e7964a9615797f9 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Fri, 2 Dec 2022 11:11:09 -0400 Subject: [PATCH 12/42] Update contracts/token/ERC20/ERC20.sol Co-authored-by: Francisco --- contracts/token/ERC20/ERC20.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 4caf1c81214..73fe624f397 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -224,7 +224,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { address to, uint256 amount ) internal virtual { - require(from != address(0) || to != address(0), "ERC20: invalid transfer operation"); + require(!(from == address(0) && to == address(0)), "ERC20: invalid transfer operation"); uint256 fromBalance = _balances[from]; if (from == address(0)) { From c3350bd34eef8b058fc36c8584f3fa3efa9039c3 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Mon, 5 Dec 2022 08:43:59 -0400 Subject: [PATCH 13/42] Update ERC20 --- contracts/mocks/ERC20SnapshotMock.sol | 4 ++-- contracts/token/ERC20/ERC20.sol | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/contracts/mocks/ERC20SnapshotMock.sol b/contracts/mocks/ERC20SnapshotMock.sol index c319af872ad..cb3048322bf 100644 --- a/contracts/mocks/ERC20SnapshotMock.sol +++ b/contracts/mocks/ERC20SnapshotMock.sol @@ -19,10 +19,10 @@ contract ERC20SnapshotMock is ERC20Snapshot { } function mint(address account, uint256 amount) public { - _transfer(address(0), account, amount); + _mint(account, amount); } function burn(address account, uint256 amount) public { - _transfer(account, address(0), amount); + _burn(account, amount); } } diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 5e90d63d9d9..c148648076d 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -237,11 +237,12 @@ contract ERC20 is Context, IERC20, IERC20Metadata { require(fromBalance >= amount, "ERC20: burn amount exceeds balance"); _totalSupply -= amount; unchecked { - _balances[from] -= amount; + // Overflow not possible: balance - amount is at most totalSupply - amount, which is checked above. + _balances[from] = fromBalance - amount; } } else { require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); - _balances[from] -= amount; + _balances[from] = fromBalance - amount; _balances[to] += amount; } From a56afc463af5130e1cf9ace96e22d49b8e8e703a Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Mon, 5 Dec 2022 08:50:40 -0400 Subject: [PATCH 14/42] update ERC20Capped --- contracts/token/ERC20/extensions/ERC20Capped.sol | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol index 559123b15cb..63b7f630819 100644 --- a/contracts/token/ERC20/extensions/ERC20Capped.sol +++ b/contracts/token/ERC20/extensions/ERC20Capped.sol @@ -31,11 +31,14 @@ abstract contract ERC20Capped is ERC20 { * @dev See {ERC20-_transfer}. */ function _transfer( - address, - address account, + address from, + address to, uint256 amount ) internal virtual override { - require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded"); - super._transfer(address(0), account, amount); + if(from == address(0)){ + require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded"); + } + + super._transfer(from, to, amount); } } From 3ec8abfc8862c96c8b99ba38036c097b06084139 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Mon, 5 Dec 2022 08:51:06 -0400 Subject: [PATCH 15/42] Add format --- contracts/token/ERC20/extensions/ERC20Capped.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol index 63b7f630819..8acc7248c2c 100644 --- a/contracts/token/ERC20/extensions/ERC20Capped.sol +++ b/contracts/token/ERC20/extensions/ERC20Capped.sol @@ -35,10 +35,10 @@ abstract contract ERC20Capped is ERC20 { address to, uint256 amount ) internal virtual override { - if(from == address(0)){ + if (from == address(0)) { require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded"); } - + super._transfer(from, to, amount); } } From 4213df3d41ee753d07da8b570d68ba3cc24894a1 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Mon, 5 Dec 2022 10:09:10 -0400 Subject: [PATCH 16/42] update revert reason --- test/token/ERC20/ERC20.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/token/ERC20/ERC20.test.js b/test/token/ERC20/ERC20.test.js index 42b24cc4abb..dcb1363987d 100644 --- a/test/token/ERC20/ERC20.test.js +++ b/test/token/ERC20/ERC20.test.js @@ -203,14 +203,14 @@ contract('ERC20', function (accounts) { const amount = new BN(50); it('rejects a null account', async function () { await expectRevert( - this.token.mint(ZERO_ADDRESS, amount), 'ERC20: invalid transfer operation', + this.token.mint(ZERO_ADDRESS, amount), 'ERC20: invalid transfer operation' ); }); it('rejects overflow', async function () { const maxUint256 = new BN('2').pow(new BN(256)).subn(1); - await expectRevert.unspecified( - this.token.mint(recipient, maxUint256), + await expectRevert( + this.token.mint(recipient, maxUint256),'reverted with panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)' ); }); From 0fc5d3a0e8bd687faba7a7453ec26b9d6375f126 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Mon, 5 Dec 2022 11:39:50 -0400 Subject: [PATCH 17/42] run lint --- test/token/ERC20/ERC20.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/token/ERC20/ERC20.test.js b/test/token/ERC20/ERC20.test.js index dcb1363987d..e8fa042dd1e 100644 --- a/test/token/ERC20/ERC20.test.js +++ b/test/token/ERC20/ERC20.test.js @@ -203,14 +203,15 @@ contract('ERC20', function (accounts) { const amount = new BN(50); it('rejects a null account', async function () { await expectRevert( - this.token.mint(ZERO_ADDRESS, amount), 'ERC20: invalid transfer operation' + this.token.mint(ZERO_ADDRESS, amount), 'ERC20: invalid transfer operation', ); }); it('rejects overflow', async function () { const maxUint256 = new BN('2').pow(new BN(256)).subn(1); await expectRevert( - this.token.mint(recipient, maxUint256),'reverted with panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)' + this.token.mint(recipient, maxUint256), + 'reverted with panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)', ); }); From 975ed992aa93bedfd60c9564b998f1341f28e4cb Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Mon, 5 Dec 2022 13:40:25 -0400 Subject: [PATCH 18/42] add checks for external transfer calls --- contracts/token/ERC20/ERC20.sol | 3 +++ test/token/ERC20/ERC20.behavior.js | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 60a57d7db56..8586d9984d2 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -110,6 +110,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * - the caller must have a balance of at least `amount`. */ function transfer(address to, uint256 amount) public virtual override returns (bool) { + require(to != address(0), "ERC20: transfer to the zero address"); address owner = _msgSender(); _transfer(owner, to, amount); return true; @@ -159,6 +160,8 @@ contract ERC20 is Context, IERC20, IERC20Metadata { address to, uint256 amount ) public virtual override returns (bool) { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); address spender = _msgSender(); _spendAllowance(from, spender, amount); _transfer(from, to, amount); diff --git a/test/token/ERC20/ERC20.behavior.js b/test/token/ERC20/ERC20.behavior.js index 96c30aeda92..a1884a72ddf 100644 --- a/test/token/ERC20/ERC20.behavior.js +++ b/test/token/ERC20/ERC20.behavior.js @@ -148,6 +148,21 @@ function shouldBehaveLikeERC20 (errorPrefix, initialSupply, initialHolder, recip }); }); }); + + describe('when the recipient is the zero address', function () { + const amount = initialSupply; + const to = ZERO_ADDRESS; + + beforeEach(async function () { + await this.token.approve(spender, amount, { from: tokenOwner }); + }); + + it('reverts', async function () { + await expectRevert(this.token.transferFrom( + tokenOwner, to, amount, { from: spender }), `${errorPrefix}: transfer to the zero address`, + ); + }); + }); }); describe('when the token owner is the zero address', function () { @@ -223,8 +238,8 @@ function shouldBehaveLikeERC20Transfer (errorPrefix, from, to, balance, transfer { from, to, value: amount }, ); }); - }); - }); + }); + }); } function shouldBehaveLikeERC20Approve (errorPrefix, owner, spender, supply, approve) { From 00ed155e3f75587235c1404e50b88a0f9a23ddc6 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Mon, 5 Dec 2022 13:52:22 -0400 Subject: [PATCH 19/42] update ERC20Test --- test/token/ERC20/ERC20.behavior.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/token/ERC20/ERC20.behavior.js b/test/token/ERC20/ERC20.behavior.js index a1884a72ddf..0404a2941bf 100644 --- a/test/token/ERC20/ERC20.behavior.js +++ b/test/token/ERC20/ERC20.behavior.js @@ -152,11 +152,11 @@ function shouldBehaveLikeERC20 (errorPrefix, initialSupply, initialHolder, recip describe('when the recipient is the zero address', function () { const amount = initialSupply; const to = ZERO_ADDRESS; - + beforeEach(async function () { await this.token.approve(spender, amount, { from: tokenOwner }); }); - + it('reverts', async function () { await expectRevert(this.token.transferFrom( tokenOwner, to, amount, { from: spender }), `${errorPrefix}: transfer to the zero address`, @@ -238,8 +238,8 @@ function shouldBehaveLikeERC20Transfer (errorPrefix, from, to, balance, transfer { from, to, value: amount }, ); }); - }); - }); + }); + }); } function shouldBehaveLikeERC20Approve (errorPrefix, owner, spender, supply, approve) { From 0d74d6622ee08114d181176571aba27cb073986c Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Mon, 5 Dec 2022 17:49:33 -0400 Subject: [PATCH 20/42] Update changelog --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79005af6bf2..645c6a7e375 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,31 @@ * `ProxyAdmin`: Removed `getProxyAdmin` and `getProxyImplementation` getters. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) * `ERC20`: Deleted `_beforeTokenTransfer` and `_afterTokenTransfer` hooks, and refactor all extensions using those hooks for customization, by using `transfer` instead. ([#3838](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3838)) +### How to upgrade from 4.x +These breaking changes will require additional modifications in your implementation of ERC20, ERC721, and ERC1155, since the `afterTokenTransfer` and `beforeTokenTransfer` were removed. Any customization made through those hooks should now use the `transfer` function instead, keeping in mind that `_mint` and `_burn` are no longer virtual, so they are not overrideable. + +For example, a contract using `ERC20`'s `afterTokenTransfer` hook would have to be changed in the following way. + +```diff +-function _beforeTokenTransfer( +- address from, +- address to, +- uint256 amount +- ) internal virtual override { +- super._beforeTokenTransfer(from, to, amount); +- +- require(!condition(), "ERC20: wrong condition"); +- } ++function _transfer( ++ address from, ++ address to, ++ uint256 amount ++ ) internal virtual override { ++ require(!condition(), "ERC20: wrong condition"); ++ super._transfer(from, to, amount); ++ } +``` + ## Unreleased * `ReentrancyGuard`: Add a `_reentrancyGuardEntered` function to expose the guard status. ([#3714](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3714)) From 7110896b5a7a45f43c4a6d64ac3c0296a69cf6d2 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Mon, 5 Dec 2022 17:52:18 -0400 Subject: [PATCH 21/42] Update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 645c6a7e375..039b0ed2b51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ * `ERC20`: Deleted `_beforeTokenTransfer` and `_afterTokenTransfer` hooks, and refactor all extensions using those hooks for customization, by using `transfer` instead. ([#3838](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3838)) ### How to upgrade from 4.x -These breaking changes will require additional modifications in your implementation of ERC20, ERC721, and ERC1155, since the `afterTokenTransfer` and `beforeTokenTransfer` were removed. Any customization made through those hooks should now use the `transfer` function instead, keeping in mind that `_mint` and `_burn` are no longer virtual, so they are not overrideable. +These breaking changes will require additional modifications in your implementation of ERC20, ERC721, and ERC1155, since the `afterTokenTransfer` and `beforeTokenTransfer` were removed. Any customization made through those hooks should now use the `transfer` function instead, keeping in mind that `_mint` and `_burn` are no longer virtual, so they are not overridable. For example, a contract using `ERC20`'s `afterTokenTransfer` hook would have to be changed in the following way. From 4e09ef84170e5ce83085efa5c078e85b9adc0632 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Fri, 9 Dec 2022 10:13:49 -0400 Subject: [PATCH 22/42] Update CHANGELOG.md Co-authored-by: Francisco --- CHANGELOG.md | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 039b0ed2b51..c92c0d90469 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,23 +16,16 @@ These breaking changes will require additional modifications in your implementat For example, a contract using `ERC20`'s `afterTokenTransfer` hook would have to be changed in the following way. ```diff --function _beforeTokenTransfer( -- address from, -- address to, -- uint256 amount -- ) internal virtual override { -- super._beforeTokenTransfer(from, to, amount); -- -- require(!condition(), "ERC20: wrong condition"); -- } -+function _transfer( -+ address from, -+ address to, -+ uint256 amount -+ ) internal virtual override { -+ require(!condition(), "ERC20: wrong condition"); -+ super._transfer(from, to, amount); -+ } +- function _beforeTokenTransfer( ++ function _transfer( + address from, + address to, + uint256 amount + ) internal virtual override { +- super._beforeTokenTransfer(from, to, amount); + require(!condition(), "ERC20: wrong condition"); ++ super._transfer(from, to, amount); + } ``` ## Unreleased From ed853fae9494731bca535f1911c93ac2782f94ad Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Fri, 9 Dec 2022 10:14:03 -0400 Subject: [PATCH 23/42] Update CHANGELOG.md Co-authored-by: Francisco --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c92c0d90469..cb851e2074f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ ### How to upgrade from 4.x These breaking changes will require additional modifications in your implementation of ERC20, ERC721, and ERC1155, since the `afterTokenTransfer` and `beforeTokenTransfer` were removed. Any customization made through those hooks should now use the `transfer` function instead, keeping in mind that `_mint` and `_burn` are no longer virtual, so they are not overridable. -For example, a contract using `ERC20`'s `afterTokenTransfer` hook would have to be changed in the following way. +For example, a contract using `ERC20`'s `_afterTokenTransfer` hook would have to be changed in the following way. ```diff - function _beforeTokenTransfer( From a2bc30c23158d8a35846bf3d518c527e7cf0cce9 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Fri, 9 Dec 2022 10:14:45 -0400 Subject: [PATCH 24/42] Update CHANGELOG.md Co-authored-by: Francisco --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb851e2074f..776262b16d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,12 @@ * `ERC20`: Deleted `_beforeTokenTransfer` and `_afterTokenTransfer` hooks, and refactor all extensions using those hooks for customization, by using `transfer` instead. ([#3838](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3838)) ### How to upgrade from 4.x -These breaking changes will require additional modifications in your implementation of ERC20, ERC721, and ERC1155, since the `afterTokenTransfer` and `beforeTokenTransfer` were removed. Any customization made through those hooks should now use the `transfer` function instead, keeping in mind that `_mint` and `_burn` are no longer virtual, so they are not overridable. + +#### ERC20, ERC721, and ERC1155 + +These breaking changes will require modifications to ERC20, ERC721, and ERC1155 contracts, since the `_afterTokenTransfer` and `_beforeTokenTransfer` functions were removed. Any customization made through those hooks should now be done overriding the `_transfer` function instead. + +Minting and burning are implemented by `_transfer` and customizations should be done by overriding this function as well. `_mint` and `_burn` are no longer virtual (meaning they are not overridable) to guard against possible inconsistencies. For example, a contract using `ERC20`'s `_afterTokenTransfer` hook would have to be changed in the following way. From ed66c580397a2f87ee778bde58db918c42661f82 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Mon, 12 Dec 2022 13:43:17 -0400 Subject: [PATCH 25/42] update test --- test/token/ERC20/ERC20.behavior.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/token/ERC20/ERC20.behavior.js b/test/token/ERC20/ERC20.behavior.js index 0404a2941bf..701ec2e6d5e 100644 --- a/test/token/ERC20/ERC20.behavior.js +++ b/test/token/ERC20/ERC20.behavior.js @@ -158,8 +158,9 @@ function shouldBehaveLikeERC20 (errorPrefix, initialSupply, initialHolder, recip }); it('reverts', async function () { - await expectRevert(this.token.transferFrom( - tokenOwner, to, amount, { from: spender }), `${errorPrefix}: transfer to the zero address`, + await expectRevert( + this.token.transferFrom(tokenOwner, to, amount, { from: spender }), + `${errorPrefix}: transfer to the zero address`, ); }); }); From 5e671709f0c0d99083908aa52967e2d2a6d44fa9 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Mon, 12 Dec 2022 13:48:52 -0400 Subject: [PATCH 26/42] update ERC20 --- contracts/token/ERC20/ERC20.sol | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 8586d9984d2..dd33cb85c29 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -245,8 +245,11 @@ contract ERC20 is Context, IERC20, IERC20Metadata { } } else { require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); - _balances[from] = fromBalance - amount; - _balances[to] += amount; + unchecked { + // Overflow not possible: fromBalance - amount is at most 0, which is checked above. + _balances[from] = fromBalance - amount; + _balances[to] += amount; + } } emit Transfer(from, to, amount); From fe5abf40e76e3a96e27cc2bbc03a370d48855194 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Mon, 12 Dec 2022 16:19:06 -0400 Subject: [PATCH 27/42] Handle zero address parameters --- contracts/mocks/ERC20VotesMock.sol | 4 ++-- contracts/token/ERC20/ERC20.sol | 24 ++++++++++++++++--- .../token/ERC20/extensions/ERC20Capped.sol | 4 ++-- .../token/ERC20/extensions/ERC20Pausable.sol | 6 ++--- .../token/ERC20/extensions/ERC20Snapshot.sol | 4 ++-- .../token/ERC20/extensions/ERC20Votes.sol | 4 ++-- test/token/ERC20/ERC20.test.js | 4 ++-- 7 files changed, 34 insertions(+), 16 deletions(-) diff --git a/contracts/mocks/ERC20VotesMock.sol b/contracts/mocks/ERC20VotesMock.sol index d60e13be842..c52c5b073a7 100644 --- a/contracts/mocks/ERC20VotesMock.sol +++ b/contracts/mocks/ERC20VotesMock.sol @@ -8,11 +8,11 @@ contract ERC20VotesMock is ERC20Votes { constructor(string memory name, string memory symbol) ERC20(name, symbol) EIP712(name, "1") {} function mint(address account, uint256 amount) public { - _transfer(address(0), account, amount); + _update(address(0), account, amount); } function burn(address account, uint256 amount) public { - _transfer(account, address(0), amount); + _update(account, address(0), amount); } function getChainId() external view returns (uint256) { diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index dd33cb85c29..c2e3c2dc572 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -227,8 +227,24 @@ contract ERC20 is Context, IERC20, IERC20Metadata { address to, uint256 amount ) internal virtual { - require(!(from == address(0) && to == address(0)), "ERC20: invalid transfer operation"); + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + _update(from, to, amount); + } + /** + * @dev Moves `amount` of tokens from `from` to `to`. + * + * Will mint (or burn) if `from` (or `to`) is the zero address. + * + * Emits a {Transfer} event. + * + */ + function _update( + address from, + address to, + uint256 amount + ) internal virtual { uint256 fromBalance = _balances[from]; if (from == address(0)) { _totalSupply += amount; @@ -264,7 +280,8 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * */ function _mint(address account, uint256 amount) internal { - _transfer(address(0), account, amount); + require(account != address(0), "ERC20: mint to the zero address"); + _update(address(0), account, amount); } /** @@ -277,7 +294,8 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * */ function _burn(address account, uint256 amount) internal { - _transfer(account, address(0), amount); + require(account != address(0), "ERC20: burn from the zero address"); + _update(account, address(0), amount); } /** diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol index 8acc7248c2c..3be9ff805d7 100644 --- a/contracts/token/ERC20/extensions/ERC20Capped.sol +++ b/contracts/token/ERC20/extensions/ERC20Capped.sol @@ -30,7 +30,7 @@ abstract contract ERC20Capped is ERC20 { /** * @dev See {ERC20-_transfer}. */ - function _transfer( + function _update( address from, address to, uint256 amount @@ -39,6 +39,6 @@ abstract contract ERC20Capped is ERC20 { require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded"); } - super._transfer(from, to, amount); + super._update(from, to, amount); } } diff --git a/contracts/token/ERC20/extensions/ERC20Pausable.sol b/contracts/token/ERC20/extensions/ERC20Pausable.sol index 97410fb9320..92d764932a9 100644 --- a/contracts/token/ERC20/extensions/ERC20Pausable.sol +++ b/contracts/token/ERC20/extensions/ERC20Pausable.sol @@ -15,18 +15,18 @@ import "../../../security/Pausable.sol"; */ abstract contract ERC20Pausable is ERC20, Pausable { /** - * @dev See {ERC20-_transfer}. + * @dev See {ERC20-_update}. * * Requirements: * * - the contract must not be paused. */ - function _transfer( + function _update( address from, address to, uint256 amount ) internal virtual override { require(!paused(), "ERC20Pausable: token transfer while paused"); - super._transfer(from, to, amount); + super._update(from, to, amount); } } diff --git a/contracts/token/ERC20/extensions/ERC20Snapshot.sol b/contracts/token/ERC20/extensions/ERC20Snapshot.sol index fcb65d09f53..2c34e226809 100644 --- a/contracts/token/ERC20/extensions/ERC20Snapshot.sol +++ b/contracts/token/ERC20/extensions/ERC20Snapshot.sol @@ -120,7 +120,7 @@ abstract contract ERC20Snapshot is ERC20 { // Update balance and/or total supply snapshots before the values are modified. This is executed // for _mint, _burn, and _transfer operations. - function _transfer( + function _update( address from, address to, uint256 amount @@ -138,7 +138,7 @@ abstract contract ERC20Snapshot is ERC20 { _updateAccountSnapshot(from); _updateAccountSnapshot(to); } - super._transfer(from, to, amount); + super._update(from, to, amount); } function _valueAt(uint256 snapshotId, Snapshots storage snapshots) private view returns (bool, uint256) { diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index 0a7bb70385b..7f9f36b7603 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -35,12 +35,12 @@ abstract contract ERC20Votes is ERC20, Votes { * * Emits a {IVotes-DelegateVotesChanged} event. */ - function _transfer( + function _update( address from, address to, uint256 amount ) internal virtual override { - super._transfer(from, to, amount); + super._update(from, to, amount); if (from == address(0)) { require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes"); } diff --git a/test/token/ERC20/ERC20.test.js b/test/token/ERC20/ERC20.test.js index e8fa042dd1e..46ee5ab6373 100644 --- a/test/token/ERC20/ERC20.test.js +++ b/test/token/ERC20/ERC20.test.js @@ -203,7 +203,7 @@ contract('ERC20', function (accounts) { const amount = new BN(50); it('rejects a null account', async function () { await expectRevert( - this.token.mint(ZERO_ADDRESS, amount), 'ERC20: invalid transfer operation', + this.token.mint(ZERO_ADDRESS, amount), 'ERC20: mint to the zero address', ); }); @@ -244,7 +244,7 @@ contract('ERC20', function (accounts) { describe('_burn', function () { it('rejects a null account', async function () { await expectRevert(this.token.burn(ZERO_ADDRESS, new BN(1)), - 'ERC20: invalid transfer operation'); + 'ERC20: burn from the zero address'); }); describe('for a non zero account', function () { From 1083de9520fae091f28ad0b3145fd4361eb71b98 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Tue, 13 Dec 2022 15:45:29 -0400 Subject: [PATCH 28/42] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 776262b16d9..60ce13b1954 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ * Removed presets in favor of [OpenZeppelin Contracts Wizard](https://wizard.openzeppelin.com/). * `TransparentUpgradeableProxy`: Removed `admin` and `implementation` getters, which were only callable by the proxy owner and thus not very useful. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) * `ProxyAdmin`: Removed `getProxyAdmin` and `getProxyImplementation` getters. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) - * `ERC20`: Deleted `_beforeTokenTransfer` and `_afterTokenTransfer` hooks, and refactor all extensions using those hooks for customization, by using `transfer` instead. ([#3838](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3838)) + * `ERC20`: Deleted `_beforeTokenTransfer` and `_afterTokenTransfer` hooks, and refactor all extensions using those hooks for customization, by using `_update` instead. ([#3838](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3838)) ### How to upgrade from 4.x From a7a9ff16d9f42fb58ad5351c47ce2d7a7e39dffc Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Wed, 14 Dec 2022 08:01:40 -0400 Subject: [PATCH 29/42] Update CHANGELOG.md Co-authored-by: Francisco --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60ce13b1954..3fdf6c09d1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,14 +22,14 @@ For example, a contract using `ERC20`'s `_afterTokenTransfer` hook would have to ```diff - function _beforeTokenTransfer( -+ function _transfer( ++ function _update( address from, address to, uint256 amount ) internal virtual override { - super._beforeTokenTransfer(from, to, amount); require(!condition(), "ERC20: wrong condition"); -+ super._transfer(from, to, amount); ++ super._update(from, to, amount); } ``` From d6fdf53df4ad15f43d19e9691796a40c8400e6d0 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Wed, 14 Dec 2022 08:01:48 -0400 Subject: [PATCH 30/42] Update CHANGELOG.md Co-authored-by: Francisco --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fdf6c09d1b..a0848ba6d99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,9 @@ #### ERC20, ERC721, and ERC1155 -These breaking changes will require modifications to ERC20, ERC721, and ERC1155 contracts, since the `_afterTokenTransfer` and `_beforeTokenTransfer` functions were removed. Any customization made through those hooks should now be done overriding the `_transfer` function instead. +These breaking changes will require modifications to ERC20, ERC721, and ERC1155 contracts, since the `_afterTokenTransfer` and `_beforeTokenTransfer` functions were removed. Any customization made through those hooks should now be done overriding the new `_update` function instead. -Minting and burning are implemented by `_transfer` and customizations should be done by overriding this function as well. `_mint` and `_burn` are no longer virtual (meaning they are not overridable) to guard against possible inconsistencies. +Minting and burning are implemented by `_update` and customizations should be done by overriding this function as well. `_mint` and `_burn` are no longer virtual (meaning they are not overridable) to guard against possible inconsistencies. For example, a contract using `ERC20`'s `_afterTokenTransfer` hook would have to be changed in the following way. From 5e0688b92860e778d66e8bea3857c8ae3bfc5b20 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Wed, 14 Dec 2022 08:02:09 -0400 Subject: [PATCH 31/42] Update contracts/token/ERC20/ERC20.sol Co-authored-by: Francisco --- contracts/token/ERC20/ERC20.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index c2e3c2dc572..0fdeeeb8727 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -262,7 +262,6 @@ contract ERC20 is Context, IERC20, IERC20Metadata { } else { require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); unchecked { - // Overflow not possible: fromBalance - amount is at most 0, which is checked above. _balances[from] = fromBalance - amount; _balances[to] += amount; } From b66eb88f93ddeb029952896ab49422ca62ad4e7f Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Wed, 14 Dec 2022 08:02:15 -0400 Subject: [PATCH 32/42] Update contracts/token/ERC20/ERC20.sol Co-authored-by: Francisco --- contracts/token/ERC20/ERC20.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 0fdeeeb8727..4dbbc574877 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -110,7 +110,6 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * - the caller must have a balance of at least `amount`. */ function transfer(address to, uint256 amount) public virtual override returns (bool) { - require(to != address(0), "ERC20: transfer to the zero address"); address owner = _msgSender(); _transfer(owner, to, amount); return true; From d55e714fd4f3a21585ef835ac5ebed59ca1a1b26 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Wed, 14 Dec 2022 08:02:25 -0400 Subject: [PATCH 33/42] Update contracts/token/ERC20/ERC20.sol Co-authored-by: Francisco --- contracts/token/ERC20/ERC20.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 4dbbc574877..bd1b2138f2e 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -159,8 +159,6 @@ contract ERC20 is Context, IERC20, IERC20Metadata { address to, uint256 amount ) public virtual override returns (bool) { - require(from != address(0), "ERC20: transfer from the zero address"); - require(to != address(0), "ERC20: transfer to the zero address"); address spender = _msgSender(); _spendAllowance(from, spender, amount); _transfer(from, to, amount); From 8abfed7eae032a74078e037fda8220b978b0e5eb Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Wed, 14 Dec 2022 08:05:31 -0400 Subject: [PATCH 34/42] Update unchecked reason --- contracts/token/ERC20/ERC20.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index bd1b2138f2e..12cabe32af8 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -253,7 +253,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { require(fromBalance >= amount, "ERC20: burn amount exceeds balance"); _totalSupply -= amount; unchecked { - // Overflow not possible: balance - amount is at most totalSupply - amount, which is checked above. + // Overflow not possible: amount <= accountBalance <= totalSupply. _balances[from] = fromBalance - amount; } } else { From 278c6d0c43b4f44e69043141f77ac58980205af6 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Wed, 14 Dec 2022 12:34:09 -0400 Subject: [PATCH 35/42] Update contracts/token/ERC20/ERC20.sol Co-authored-by: Francisco --- contracts/token/ERC20/ERC20.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 12cabe32af8..cb73e7b6479 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -253,7 +253,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { require(fromBalance >= amount, "ERC20: burn amount exceeds balance"); _totalSupply -= amount; unchecked { - // Overflow not possible: amount <= accountBalance <= totalSupply. + // Overflow not possible: amount <= fromBalance <= totalSupply. _balances[from] = fromBalance - amount; } } else { From 56e648aad08578c01b142ee276ae173712d53fc3 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Wed, 14 Dec 2022 12:36:06 -0400 Subject: [PATCH 36/42] Update ERC20.sol --- contracts/token/ERC20/ERC20.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index cb73e7b6479..b3cabfa5454 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -242,7 +242,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { address to, uint256 amount ) internal virtual { - uint256 fromBalance = _balances[from]; + if (from == address(0)) { _totalSupply += amount; unchecked { @@ -250,6 +250,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { _balances[to] += amount; } } else if (to == address(0)) { + uint256 fromBalance = _balances[from]; require(fromBalance >= amount, "ERC20: burn amount exceeds balance"); _totalSupply -= amount; unchecked { @@ -257,6 +258,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { _balances[from] = fromBalance - amount; } } else { + uint256 fromBalance = _balances[from]; require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); unchecked { _balances[from] = fromBalance - amount; From 2fc018eaf1ab7acc9196199e63625f63845f8cc8 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Wed, 14 Dec 2022 12:42:46 -0400 Subject: [PATCH 37/42] run lint --- contracts/token/ERC20/ERC20.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index b3cabfa5454..529f5936484 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -242,7 +242,6 @@ contract ERC20 is Context, IERC20, IERC20Metadata { address to, uint256 amount ) internal virtual { - if (from == address(0)) { _totalSupply += amount; unchecked { From fa58b35ff5fb113c3411b5c2b28755f63a2fdd94 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Wed, 14 Dec 2022 14:25:41 -0400 Subject: [PATCH 38/42] Add unchecked comment --- contracts/token/ERC20/ERC20.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 529f5936484..978da3a431e 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -261,6 +261,8 @@ contract ERC20 is Context, IERC20, IERC20Metadata { require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); unchecked { _balances[from] = fromBalance - amount; + // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by + // decrementing then incrementing. _balances[to] += amount; } } From 58a28b9d0b16c9084e1109ca4926dcd4e3585007 Mon Sep 17 00:00:00 2001 From: Francisco Date: Wed, 14 Dec 2022 15:43:14 -0300 Subject: [PATCH 39/42] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0848ba6d99..83ba8498d14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ * Removed presets in favor of [OpenZeppelin Contracts Wizard](https://wizard.openzeppelin.com/). * `TransparentUpgradeableProxy`: Removed `admin` and `implementation` getters, which were only callable by the proxy owner and thus not very useful. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) * `ProxyAdmin`: Removed `getProxyAdmin` and `getProxyImplementation` getters. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) - * `ERC20`: Deleted `_beforeTokenTransfer` and `_afterTokenTransfer` hooks, and refactor all extensions using those hooks for customization, by using `_update` instead. ([#3838](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3838)) + * `ERC20`: Deleted `_beforeTokenTransfer` and `_afterTokenTransfer` hooks, added a new internal `_update` function for customizations, and refactored all extensions using those hooks to use `_update` instead. ([#3838](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3838)) ### How to upgrade from 4.x From a6ce9e1a6a8166805d7c588f81a4f8e6c0a90dbb Mon Sep 17 00:00:00 2001 From: Francisco Date: Wed, 14 Dec 2022 15:55:13 -0300 Subject: [PATCH 40/42] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83ba8498d14..9450563faf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ These breaking changes will require modifications to ERC20, ERC721, and ERC1155 Minting and burning are implemented by `_update` and customizations should be done by overriding this function as well. `_mint` and `_burn` are no longer virtual (meaning they are not overridable) to guard against possible inconsistencies. -For example, a contract using `ERC20`'s `_afterTokenTransfer` hook would have to be changed in the following way. +For example, a contract using `ERC20`'s `_beforeTokenTransfer` hook would have to be changed in the following way. ```diff - function _beforeTokenTransfer( From ee4b86a2f8c6a603bfb0052d84ffff5a10f091b8 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Wed, 14 Dec 2022 15:00:52 -0400 Subject: [PATCH 41/42] update mock --- contracts/mocks/ERC20VotesMock.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/mocks/ERC20VotesMock.sol b/contracts/mocks/ERC20VotesMock.sol index c52c5b073a7..654a089acf1 100644 --- a/contracts/mocks/ERC20VotesMock.sol +++ b/contracts/mocks/ERC20VotesMock.sol @@ -8,11 +8,11 @@ contract ERC20VotesMock is ERC20Votes { constructor(string memory name, string memory symbol) ERC20(name, symbol) EIP712(name, "1") {} function mint(address account, uint256 amount) public { - _update(address(0), account, amount); + _mint(account, amount); } function burn(address account, uint256 amount) public { - _update(account, address(0), amount); + _burn(account, amount); } function getChainId() external view returns (uint256) { From 30876ac29a8b263f2d3376b46dfa6ec0639ea82b Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Wed, 14 Dec 2022 16:27:51 -0300 Subject: [PATCH 42/42] update docs --- contracts/token/ERC20/ERC20.sol | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 978da3a431e..c10f659d019 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -214,28 +214,25 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * This internal function is equivalent to {transfer}, and can be used to * e.g. implement automatic token fees, slashing mechanisms, etc. * - * Will mint (or burn) if `from` (or `to`) is the zero address. - * * Emits a {Transfer} event. * + * NOTE: This function is not virtual, {_update} should be overridden instead. */ function _transfer( address from, address to, uint256 amount - ) internal virtual { + ) internal { require(from != address(0), "ERC20: transfer from the zero address"); require(to != address(0), "ERC20: transfer to the zero address"); _update(from, to, amount); } /** - * @dev Moves `amount` of tokens from `from` to `to`. - * - * Will mint (or burn) if `from` (or `to`) is the zero address. + * @dev Transfers `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( address from, @@ -270,13 +267,13 @@ contract ERC20 is Context, IERC20, IERC20Metadata { emit Transfer(from, to, amount); } - /** @dev Creates `amount` tokens and assigns them to `account`, by transferring it from address(0). - * Relies on the `_transfer` mechanism + /** + * @dev Creates `amount` 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, {_transfer} should be overridden instead. - * + * NOTE: This function is not virtual, {_update} should be overridden instead. */ function _mint(address account, uint256 amount) internal { require(account != address(0), "ERC20: mint to the zero address"); @@ -285,12 +282,11 @@ contract ERC20 is Context, IERC20, IERC20Metadata { /** * @dev Destroys `amount` tokens from `account`, by transferring it to address(0). - * Relies on the `_transfer` mechanism. - * - * Note: this function is not virtual, {_transfer} should be overridden instead + * 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(address account, uint256 amount) internal { require(account != address(0), "ERC20: burn from the zero address");