From 8a775cd8d428595804bb1cc77a5e77f9c39f8c6d Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 23 Jun 2021 00:14:40 +0200 Subject: [PATCH] Emit DelegateVotesChanged events after Transfer (#2733) --- contracts/token/ERC20/ERC20.sol | 28 ++++++++++++++++++- .../token/ERC20/extensions/ERC20Votes.sol | 4 ++- .../token/ERC20/extensions/ERC20Votes.test.js | 9 ++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index ecb9adb6647..b4dcfb19a2b 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -234,6 +234,8 @@ contract ERC20 is Context, IERC20, IERC20Metadata { _balances[recipient] += amount; emit Transfer(sender, recipient, amount); + + _afterTokenTransfer(sender, recipient, amount); } /** @dev Creates `amount` tokens and assigns them to `account`, increasing @@ -253,6 +255,8 @@ contract ERC20 is Context, IERC20, IERC20Metadata { _totalSupply += amount; _balances[account] += amount; emit Transfer(address(0), account, amount); + + _afterTokenTransfer(address(0), account, amount); } /** @@ -279,6 +283,8 @@ contract ERC20 is Context, IERC20, IERC20Metadata { _totalSupply -= amount; emit Transfer(account, address(0), amount); + + _afterTokenTransfer(account, address(0), amount); } /** @@ -313,7 +319,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * Calling conditions: * * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * will be to transferred to `to`. + * 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. @@ -325,4 +331,24 @@ contract ERC20 is Context, IERC20, IERC20Metadata { 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/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index 9f765ccb077..0747d39945c 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -190,11 +190,13 @@ abstract contract ERC20Votes is ERC20Permit { * * Emits a {DelegateVotesChanged} event. */ - function _beforeTokenTransfer( + function _afterTokenTransfer( address from, address to, uint256 amount ) internal virtual override { + super._afterTokenTransfer(from, to, amount); + _moveVotingPower(delegates(from), delegates(to), amount); } diff --git a/test/token/ERC20/extensions/ERC20Votes.test.js b/test/token/ERC20/extensions/ERC20Votes.test.js index 4fc674503be..a0ab60abe77 100644 --- a/test/token/ERC20/extensions/ERC20Votes.test.js +++ b/test/token/ERC20/extensions/ERC20Votes.test.js @@ -306,6 +306,9 @@ contract('ERC20Votes', function (accounts) { expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: holder, previousBalance: supply, newBalance: supply.subn(1) }); + const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); + expect(receipt.logs.filter(({ event }) => event == 'DelegateVotesChanged').every(({ logIndex }) => transferLogIndex < logIndex)).to.be.equal(true); + this.holderVotes = supply.subn(1); this.recipientVotes = '0'; }); @@ -317,6 +320,9 @@ contract('ERC20Votes', function (accounts) { expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousBalance: '0', newBalance: '1' }); + const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); + expect(receipt.logs.filter(({ event }) => event == 'DelegateVotesChanged').every(({ logIndex }) => transferLogIndex < logIndex)).to.be.equal(true); + this.holderVotes = '0'; this.recipientVotes = '1'; }); @@ -330,6 +336,9 @@ contract('ERC20Votes', function (accounts) { expectEvent(receipt, 'DelegateVotesChanged', { delegate: holder, previousBalance: supply, newBalance: supply.subn(1) }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousBalance: '0', newBalance: '1' }); + const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); + expect(receipt.logs.filter(({ event }) => event == 'DelegateVotesChanged').every(({ logIndex }) => transferLogIndex < logIndex)).to.be.equal(true); + this.holderVotes = supply.subn(1); this.recipientVotes = '1'; });