From 972fdfedd2c47bd7ca9fa1fed5a50da57169cbf2 Mon Sep 17 00:00:00 2001 From: Mazen Khalil Date: Thu, 7 Apr 2022 15:16:52 +0300 Subject: [PATCH 1/6] ERC3156 Flash loans extension #3316 --- .../mocks/ERC20FlashMintFeeReceiverMock.sol | 35 +++++++++++ contracts/mocks/ERC20FlashMintMock.sol | 4 ++ .../token/ERC20/extensions/ERC20FlashMint.sol | 21 ++++++- .../ERC20/extensions/ERC20FlashMint.test.js | 6 ++ .../ERC20FlashMintFeeReceiver.test.js | 61 +++++++++++++++++++ 5 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 contracts/mocks/ERC20FlashMintFeeReceiverMock.sol create mode 100644 test/token/ERC20/extensions/ERC20FlashMintFeeReceiver.test.js diff --git a/contracts/mocks/ERC20FlashMintFeeReceiverMock.sol b/contracts/mocks/ERC20FlashMintFeeReceiverMock.sol new file mode 100644 index 00000000000..2012e7792ef --- /dev/null +++ b/contracts/mocks/ERC20FlashMintFeeReceiverMock.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../token/ERC20/extensions/ERC20FlashMint.sol"; + +contract ERC20FlashMintFeeReceiverMock is ERC20FlashMint { + constructor( + string memory name, + string memory symbol, + address initialAccount, + uint256 initialBalance + ) ERC20(name, symbol) { + _mint(initialAccount, initialBalance); + } + + function mint(address account, uint256 amount) public { + _mint(account, amount); + } + + function flashFee(address token, uint256 amount) public view virtual override returns (uint256) { + token; + amount; + return 5000; + } + + function mockFlashFeeReceiver(address token) public view returns (address) { + return _flashFeeReceiver(token); + } + + function _flashFeeReceiver(address token) internal view override returns (address) { + token; + return address(this); + } +} diff --git a/contracts/mocks/ERC20FlashMintMock.sol b/contracts/mocks/ERC20FlashMintMock.sol index 0bb7871fc70..942a1da8849 100644 --- a/contracts/mocks/ERC20FlashMintMock.sol +++ b/contracts/mocks/ERC20FlashMintMock.sol @@ -13,4 +13,8 @@ contract ERC20FlashMintMock is ERC20FlashMint { ) ERC20(name, symbol) { _mint(initialAccount, initialBalance); } + + function mockFlashFeeReceiver(address token) public view returns (address) { + return _flashFeeReceiver(token); + } } diff --git a/contracts/token/ERC20/extensions/ERC20FlashMint.sol b/contracts/token/ERC20/extensions/ERC20FlashMint.sol index 3d526c2d7d7..4dc0bb28afe 100644 --- a/contracts/token/ERC20/extensions/ERC20FlashMint.sol +++ b/contracts/token/ERC20/extensions/ERC20FlashMint.sol @@ -43,6 +43,19 @@ abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { return 0; } + /** + * @dev Returns the reciever address of the flash fee. By default this + * implementation returns the address(0) which means the fee amount will be burnt. + * This function can be overloaded to change the fee reciever. + * @param token The token to be flash loaned. + * @return The address for which the flash fee will be sent to. + */ + function _flashFeeReceiver(address token) internal view virtual returns (address) { + // silence warning about unused variable without the addition of bytecode. + token; + return address(0); + } + /** * @dev Performs a flash loan. New tokens are minted and sent to the * `receiver`, who is required to implement the {IERC3156FlashBorrower} @@ -73,8 +86,14 @@ abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { receiver.onFlashLoan(msg.sender, token, amount, fee, data) == _RETURN_VALUE, "ERC20FlashMint: invalid return value" ); + address flashFeeReceiver = _flashFeeReceiver(token); _spendAllowance(address(receiver), address(this), amount + fee); - _burn(address(receiver), amount + fee); + if (fee == 0 || flashFeeReceiver == address(0)) { + _burn(address(receiver), amount + fee); + } else { + _burn(address(receiver), amount); + _transfer(address(receiver), flashFeeReceiver, fee); + } return true; } } diff --git a/test/token/ERC20/extensions/ERC20FlashMint.test.js b/test/token/ERC20/extensions/ERC20FlashMint.test.js index 0ecc056d667..e8fae8fa518 100644 --- a/test/token/ERC20/extensions/ERC20FlashMint.test.js +++ b/test/token/ERC20/extensions/ERC20FlashMint.test.js @@ -40,6 +40,12 @@ contract('ERC20FlashMint', function (accounts) { }); }); + describe('mockFlashFeeReceiver', function () { + it('default receiver', async function () { + expect(await this.token.mockFlashFeeReceiver(this.token.address)).to.be.eq(ZERO_ADDRESS); + }); + }); + describe('flashLoan', function () { it('success', async function () { const receiver = await ERC3156FlashBorrowerMock.new(true, true); diff --git a/test/token/ERC20/extensions/ERC20FlashMintFeeReceiver.test.js b/test/token/ERC20/extensions/ERC20FlashMintFeeReceiver.test.js new file mode 100644 index 00000000000..64edb8659bc --- /dev/null +++ b/test/token/ERC20/extensions/ERC20FlashMintFeeReceiver.test.js @@ -0,0 +1,61 @@ +/* eslint-disable */ + +const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); +const { MAX_UINT256, ZERO_ADDRESS, ZERO_BYTES32 } = constants; + +const ERC20FlashMintFeeReceiverMock = artifacts.require('ERC20FlashMintFeeReceiverMock'); +const ERC3156FlashBorrowerMock = artifacts.require('ERC3156FlashBorrowerMock'); + +contract('ERC20FlashMintFeeReceiver', function (accounts) { + const [ initialHolder, other ] = accounts; + + const name = 'My Token'; + const symbol = 'MTKN'; + + const initialSupply = new BN(100); + const loanAmount = new BN(10000000000000); + + const flashFee = new BN(5000); + + beforeEach(async function () { + this.token = await ERC20FlashMintFeeReceiverMock.new(name, symbol, initialHolder, initialSupply); + }); + + describe('flashFee', function () { + it('token amount', async function () { + expect(await this.token.flashFee(this.token.address, loanAmount)).to.be.bignumber.equal(flashFee); + }); + }); + + describe('mockFlashFeeReceiver', function () { + it('fee receiver', async function () { + expect(await this.token.mockFlashFeeReceiver(this.token.address)).to.be.eq(this.token.address); + }); + }); + + describe('flashLoan', function () { + it('transfered fee', async function () { + const receiver = await ERC3156FlashBorrowerMock.new(true, true); + const receiverInitialBalance = new BN(200000); + + const receipt = await this.token.mint(receiver.address, receiverInitialBalance); + await expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: receiverInitialBalance }); + + expect(await this.token.balanceOf(this.token.address)).to.be.bignumber.equal('0'); + expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal(receiverInitialBalance); + + const { tx } = await this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: loanAmount }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: ZERO_ADDRESS, value: loanAmount }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: this.token.address, value: flashFee }); + await expectEvent.inTransaction(tx, receiver, 'BalanceOf', { token: this.token.address, account: receiver.address, value: receiverInitialBalance.add(loanAmount) }); + await expectEvent.inTransaction(tx, receiver, 'TotalSupply', { token: this.token.address, value: initialSupply.add(receiverInitialBalance).add(loanAmount) }); + + expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply.add(receiverInitialBalance)); + expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal(receiverInitialBalance.sub(flashFee)); + expect(await this.token.balanceOf(this.token.address)).to.be.bignumber.equal(flashFee); + expect(await this.token.allowance(receiver.address, this.token.address)).to.be.bignumber.equal('0'); + }); + }); +}); From 463c9a892fe7b2bf659efa79837e5565a3a7964e Mon Sep 17 00:00:00 2001 From: Mazen Khalil Date: Fri, 8 Apr 2022 15:52:02 +0300 Subject: [PATCH 2/6] Resolve review comments #3316 --- .../mocks/ERC20FlashMintFeeReceiverMock.sol | 35 ----------- contracts/mocks/ERC20FlashMintMock.sol | 28 ++++++++- .../token/ERC20/extensions/ERC20FlashMint.sol | 11 ++-- .../ERC20/extensions/ERC20FlashMint.test.js | 60 +++++++++++++++++- .../ERC20FlashMintFeeReceiver.test.js | 61 ------------------- 5 files changed, 88 insertions(+), 107 deletions(-) delete mode 100644 contracts/mocks/ERC20FlashMintFeeReceiverMock.sol delete mode 100644 test/token/ERC20/extensions/ERC20FlashMintFeeReceiver.test.js diff --git a/contracts/mocks/ERC20FlashMintFeeReceiverMock.sol b/contracts/mocks/ERC20FlashMintFeeReceiverMock.sol deleted file mode 100644 index 2012e7792ef..00000000000 --- a/contracts/mocks/ERC20FlashMintFeeReceiverMock.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "../token/ERC20/extensions/ERC20FlashMint.sol"; - -contract ERC20FlashMintFeeReceiverMock is ERC20FlashMint { - constructor( - string memory name, - string memory symbol, - address initialAccount, - uint256 initialBalance - ) ERC20(name, symbol) { - _mint(initialAccount, initialBalance); - } - - function mint(address account, uint256 amount) public { - _mint(account, amount); - } - - function flashFee(address token, uint256 amount) public view virtual override returns (uint256) { - token; - amount; - return 5000; - } - - function mockFlashFeeReceiver(address token) public view returns (address) { - return _flashFeeReceiver(token); - } - - function _flashFeeReceiver(address token) internal view override returns (address) { - token; - return address(this); - } -} diff --git a/contracts/mocks/ERC20FlashMintMock.sol b/contracts/mocks/ERC20FlashMintMock.sol index 942a1da8849..c7772601b79 100644 --- a/contracts/mocks/ERC20FlashMintMock.sol +++ b/contracts/mocks/ERC20FlashMintMock.sol @@ -5,6 +5,9 @@ pragma solidity ^0.8.0; import "../token/ERC20/extensions/ERC20FlashMint.sol"; contract ERC20FlashMintMock is ERC20FlashMint { + uint256 _flashFeeAmount; + address _flashFeeReceiverAddress; + constructor( string memory name, string memory symbol, @@ -14,7 +17,28 @@ contract ERC20FlashMintMock is ERC20FlashMint { _mint(initialAccount, initialBalance); } - function mockFlashFeeReceiver(address token) public view returns (address) { - return _flashFeeReceiver(token); + function mint(address account, uint256 amount) public { + _mint(account, amount); + } + + function setFlashFee(uint256 amount) public { + _flashFeeAmount = amount; + } + + function flashFee(address token, uint256 amount) public view virtual override returns (uint256) { + super.flashFee(token, amount); + return _flashFeeAmount; + } + + function setFlashFeeReceiver(address receiver) public { + _flashFeeReceiverAddress = receiver; + } + + function flashFeeReceiver() public view returns (address) { + return _flashFeeReceiver(); + } + + function _flashFeeReceiver() internal view override returns (address) { + return _flashFeeReceiverAddress; } } diff --git a/contracts/token/ERC20/extensions/ERC20FlashMint.sol b/contracts/token/ERC20/extensions/ERC20FlashMint.sol index 4dc0bb28afe..ca88cf03a51 100644 --- a/contracts/token/ERC20/extensions/ERC20FlashMint.sol +++ b/contracts/token/ERC20/extensions/ERC20FlashMint.sol @@ -44,15 +44,12 @@ abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { } /** - * @dev Returns the reciever address of the flash fee. By default this + * @dev Returns the receiver address of the flash fee. By default this * implementation returns the address(0) which means the fee amount will be burnt. - * This function can be overloaded to change the fee reciever. - * @param token The token to be flash loaned. + * This function can be overloaded to change the fee receiver. * @return The address for which the flash fee will be sent to. */ - function _flashFeeReceiver(address token) internal view virtual returns (address) { - // silence warning about unused variable without the addition of bytecode. - token; + function _flashFeeReceiver() internal view virtual returns (address) { return address(0); } @@ -86,7 +83,7 @@ abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { receiver.onFlashLoan(msg.sender, token, amount, fee, data) == _RETURN_VALUE, "ERC20FlashMint: invalid return value" ); - address flashFeeReceiver = _flashFeeReceiver(token); + address flashFeeReceiver = _flashFeeReceiver(); _spendAllowance(address(receiver), address(this), amount + fee); if (fee == 0 || flashFeeReceiver == address(0)) { _burn(address(receiver), amount + fee); diff --git a/test/token/ERC20/extensions/ERC20FlashMint.test.js b/test/token/ERC20/extensions/ERC20FlashMint.test.js index e8fae8fa518..ed6e1d5c820 100644 --- a/test/token/ERC20/extensions/ERC20FlashMint.test.js +++ b/test/token/ERC20/extensions/ERC20FlashMint.test.js @@ -40,9 +40,9 @@ contract('ERC20FlashMint', function (accounts) { }); }); - describe('mockFlashFeeReceiver', function () { + describe('flashFeeReceiver', function () { it('default receiver', async function () { - expect(await this.token.mockFlashFeeReceiver(this.token.address)).to.be.eq(ZERO_ADDRESS); + expect(await this.token.flashFeeReceiver()).to.be.eq(ZERO_ADDRESS); }); }); @@ -92,5 +92,61 @@ contract('ERC20FlashMint', function (accounts) { // _mint overflow reverts using a panic code. No reason string. await expectRevert.unspecified(this.token.flashLoan(receiver.address, this.token.address, MAX_UINT256, data)); }); + + it('custom flash fee', async function () { + const receiver = await ERC3156FlashBorrowerMock.new(true, true); + const receiverInitialBalance = new BN(200000); + const flashFee = new BN(5000); + + const receipt = await this.token.mint(receiver.address, receiverInitialBalance); + await expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: receiverInitialBalance }); + + expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal(receiverInitialBalance); + + await this.token.setFlashFee(flashFee); + expect(await this.token.flashFee(this.token.address, loanAmount)).to.be.bignumber.equal(flashFee); + + const { tx } = await this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: loanAmount }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: ZERO_ADDRESS, value: loanAmount.add(flashFee)}); + await expectEvent.inTransaction(tx, receiver, 'BalanceOf', { token: this.token.address, account: receiver.address, value: receiverInitialBalance.add(loanAmount) }); + await expectEvent.inTransaction(tx, receiver, 'TotalSupply', { token: this.token.address, value: initialSupply.add(receiverInitialBalance).add(loanAmount) }); + + expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply.add(receiverInitialBalance).sub(flashFee)); + expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal(receiverInitialBalance.sub(flashFee)); + expect(await this.token.balanceOf(await this.token.flashFeeReceiver())).to.be.bignumber.equal('0'); + expect(await this.token.allowance(receiver.address, this.token.address)).to.be.bignumber.equal('0'); + }); + + it('custom flash fee and custom fee receiver', async function () { + const receiver = await ERC3156FlashBorrowerMock.new(true, true); + const receiverInitialBalance = new BN(200000); + const flashFee = new BN(5000); + const flashFeeReceiverAddress = this.token.address; + + const receipt = await this.token.mint(receiver.address, receiverInitialBalance); + await expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: receiverInitialBalance }); + + expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal(receiverInitialBalance); + expect(await this.token.balanceOf(flashFeeReceiverAddress)).to.be.bignumber.equal('0'); + + await this.token.setFlashFee(flashFee); + expect(await this.token.flashFee(this.token.address, loanAmount)).to.be.bignumber.equal(flashFee); + + await this.token.setFlashFeeReceiver(flashFeeReceiverAddress); + expect(await this.token.flashFeeReceiver()).to.be.eq(flashFeeReceiverAddress); + + const { tx } = await this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: loanAmount }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: ZERO_ADDRESS, value: loanAmount }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: flashFeeReceiverAddress, value: flashFee }); + await expectEvent.inTransaction(tx, receiver, 'BalanceOf', { token: this.token.address, account: receiver.address, value: receiverInitialBalance.add(loanAmount) }); + await expectEvent.inTransaction(tx, receiver, 'TotalSupply', { token: this.token.address, value: initialSupply.add(receiverInitialBalance).add(loanAmount) }); + + expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply.add(receiverInitialBalance)); + expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal(receiverInitialBalance.sub(flashFee)); + expect(await this.token.balanceOf(flashFeeReceiverAddress)).to.be.bignumber.equal(flashFee); + expect(await this.token.allowance(receiver.address, flashFeeReceiverAddress)).to.be.bignumber.equal('0'); + }); }); }); diff --git a/test/token/ERC20/extensions/ERC20FlashMintFeeReceiver.test.js b/test/token/ERC20/extensions/ERC20FlashMintFeeReceiver.test.js deleted file mode 100644 index 64edb8659bc..00000000000 --- a/test/token/ERC20/extensions/ERC20FlashMintFeeReceiver.test.js +++ /dev/null @@ -1,61 +0,0 @@ -/* eslint-disable */ - -const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); -const { expect } = require('chai'); -const { MAX_UINT256, ZERO_ADDRESS, ZERO_BYTES32 } = constants; - -const ERC20FlashMintFeeReceiverMock = artifacts.require('ERC20FlashMintFeeReceiverMock'); -const ERC3156FlashBorrowerMock = artifacts.require('ERC3156FlashBorrowerMock'); - -contract('ERC20FlashMintFeeReceiver', function (accounts) { - const [ initialHolder, other ] = accounts; - - const name = 'My Token'; - const symbol = 'MTKN'; - - const initialSupply = new BN(100); - const loanAmount = new BN(10000000000000); - - const flashFee = new BN(5000); - - beforeEach(async function () { - this.token = await ERC20FlashMintFeeReceiverMock.new(name, symbol, initialHolder, initialSupply); - }); - - describe('flashFee', function () { - it('token amount', async function () { - expect(await this.token.flashFee(this.token.address, loanAmount)).to.be.bignumber.equal(flashFee); - }); - }); - - describe('mockFlashFeeReceiver', function () { - it('fee receiver', async function () { - expect(await this.token.mockFlashFeeReceiver(this.token.address)).to.be.eq(this.token.address); - }); - }); - - describe('flashLoan', function () { - it('transfered fee', async function () { - const receiver = await ERC3156FlashBorrowerMock.new(true, true); - const receiverInitialBalance = new BN(200000); - - const receipt = await this.token.mint(receiver.address, receiverInitialBalance); - await expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: receiverInitialBalance }); - - expect(await this.token.balanceOf(this.token.address)).to.be.bignumber.equal('0'); - expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal(receiverInitialBalance); - - const { tx } = await this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: loanAmount }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: ZERO_ADDRESS, value: loanAmount }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: this.token.address, value: flashFee }); - await expectEvent.inTransaction(tx, receiver, 'BalanceOf', { token: this.token.address, account: receiver.address, value: receiverInitialBalance.add(loanAmount) }); - await expectEvent.inTransaction(tx, receiver, 'TotalSupply', { token: this.token.address, value: initialSupply.add(receiverInitialBalance).add(loanAmount) }); - - expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply.add(receiverInitialBalance)); - expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal(receiverInitialBalance.sub(flashFee)); - expect(await this.token.balanceOf(this.token.address)).to.be.bignumber.equal(flashFee); - expect(await this.token.allowance(receiver.address, this.token.address)).to.be.bignumber.equal('0'); - }); - }); -}); From 453b86c1dcd75caaf374ac3afdc33a3c109115f6 Mon Sep 17 00:00:00 2001 From: Mazen Khalil Date: Sat, 9 Apr 2022 04:49:29 +0300 Subject: [PATCH 3/6] Resolve review comments --- .../ERC20/extensions/ERC20FlashMint.test.js | 90 +++++++++---------- 1 file changed, 41 insertions(+), 49 deletions(-) diff --git a/test/token/ERC20/extensions/ERC20FlashMint.test.js b/test/token/ERC20/extensions/ERC20FlashMint.test.js index ed6e1d5c820..37158a9c261 100644 --- a/test/token/ERC20/extensions/ERC20FlashMint.test.js +++ b/test/token/ERC20/extensions/ERC20FlashMint.test.js @@ -93,60 +93,52 @@ contract('ERC20FlashMint', function (accounts) { await expectRevert.unspecified(this.token.flashLoan(receiver.address, this.token.address, MAX_UINT256, data)); }); - it('custom flash fee', async function () { + describe('custom flash fee & receiver', async function () { const receiver = await ERC3156FlashBorrowerMock.new(true, true); const receiverInitialBalance = new BN(200000); const flashFee = new BN(5000); - - const receipt = await this.token.mint(receiver.address, receiverInitialBalance); - await expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: receiverInitialBalance }); - expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal(receiverInitialBalance); - - await this.token.setFlashFee(flashFee); - expect(await this.token.flashFee(this.token.address, loanAmount)).to.be.bignumber.equal(flashFee); - - const { tx } = await this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: loanAmount }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: ZERO_ADDRESS, value: loanAmount.add(flashFee)}); - await expectEvent.inTransaction(tx, receiver, 'BalanceOf', { token: this.token.address, account: receiver.address, value: receiverInitialBalance.add(loanAmount) }); - await expectEvent.inTransaction(tx, receiver, 'TotalSupply', { token: this.token.address, value: initialSupply.add(receiverInitialBalance).add(loanAmount) }); - - expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply.add(receiverInitialBalance).sub(flashFee)); - expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal(receiverInitialBalance.sub(flashFee)); - expect(await this.token.balanceOf(await this.token.flashFeeReceiver())).to.be.bignumber.equal('0'); - expect(await this.token.allowance(receiver.address, this.token.address)).to.be.bignumber.equal('0'); - }); - - it('custom flash fee and custom fee receiver', async function () { - const receiver = await ERC3156FlashBorrowerMock.new(true, true); - const receiverInitialBalance = new BN(200000); - const flashFee = new BN(5000); - const flashFeeReceiverAddress = this.token.address; - - const receipt = await this.token.mint(receiver.address, receiverInitialBalance); - await expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: receiverInitialBalance }); + before('init reciever balance & set flash fee',async function () { + const receipt = await this.token.mint(receiver.address, receiverInitialBalance); + await expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: receiverInitialBalance }); + expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal(receiverInitialBalance); + + await this.token.setFlashFee(flashFee); + expect(await this.token.flashFee(this.token.address, loanAmount)).to.be.bignumber.equal(flashFee); + }); - expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal(receiverInitialBalance); - expect(await this.token.balanceOf(flashFeeReceiverAddress)).to.be.bignumber.equal('0'); - - await this.token.setFlashFee(flashFee); - expect(await this.token.flashFee(this.token.address, loanAmount)).to.be.bignumber.equal(flashFee); - - await this.token.setFlashFeeReceiver(flashFeeReceiverAddress); - expect(await this.token.flashFeeReceiver()).to.be.eq(flashFeeReceiverAddress); - - const { tx } = await this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: loanAmount }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: ZERO_ADDRESS, value: loanAmount }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: flashFeeReceiverAddress, value: flashFee }); - await expectEvent.inTransaction(tx, receiver, 'BalanceOf', { token: this.token.address, account: receiver.address, value: receiverInitialBalance.add(loanAmount) }); - await expectEvent.inTransaction(tx, receiver, 'TotalSupply', { token: this.token.address, value: initialSupply.add(receiverInitialBalance).add(loanAmount) }); - - expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply.add(receiverInitialBalance)); - expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal(receiverInitialBalance.sub(flashFee)); - expect(await this.token.balanceOf(flashFeeReceiverAddress)).to.be.bignumber.equal(flashFee); - expect(await this.token.allowance(receiver.address, flashFeeReceiverAddress)).to.be.bignumber.equal('0'); + it('default flash fee receiver', async function () { + const { tx } = await this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: loanAmount }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: ZERO_ADDRESS, value: loanAmount.add (flashFee)}); + await expectEvent.inTransaction(tx, receiver, 'BalanceOf', { token: this.token.address, account: receiver.address, value: receiverInitialBalance.add(loanAmount) }); + await expectEvent.inTransaction(tx, receiver, 'TotalSupply', { token: this.token.address, value: initialSupply.add (receiverInitialBalance).add(loanAmount) }); + + expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply.add(receiverInitialBalance).sub(flashFee)); + expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal(receiverInitialBalance.sub(flashFee)); + expect(await this.token.balanceOf(await this.token.flashFeeReceiver())).to.be.bignumber.equal('0'); + expect(await this.token.allowance(receiver.address, this.token.address)).to.be.bignumber.equal('0'); + }); + + it('custom flash fee receiver', async function () { + const flashFeeReceiverAddress = this.token.address; + await this.token.setFlashFeeReceiver(flashFeeReceiverAddress); + expect(await this.token.flashFeeReceiver()).to.be.eq(flashFeeReceiverAddress); + + expect(await this.token.balanceOf(flashFeeReceiverAddress)).to.be.bignumber.equal('0'); + + const { tx } = await this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: loanAmount }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: ZERO_ADDRESS, value: loanAmount }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: flashFeeReceiverAddress, value: flashFee }); + await expectEvent.inTransaction(tx, receiver, 'BalanceOf', { token: this.token.address, account: receiver.address, value: receiverInitialBalance.add(loanAmount) }); + await expectEvent.inTransaction(tx, receiver, 'TotalSupply', { token: this.token.address, value: initialSupply.add (receiverInitialBalance).add(loanAmount) }); + + expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply.add(receiverInitialBalance)); + expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal(receiverInitialBalance.sub(flashFee)); + expect(await this.token.balanceOf(flashFeeReceiverAddress)).to.be.bignumber.equal(flashFee); + expect(await this.token.allowance(receiver.address, flashFeeReceiverAddress)).to.be.bignumber.equal('0'); + }); }); }); }); From 88b6eb99879534ecb613abd8ef52c0554990e81e Mon Sep 17 00:00:00 2001 From: Mazen Khalil Date: Sat, 9 Apr 2022 14:02:58 +0300 Subject: [PATCH 4/6] Resolve review comments --- .../ERC20/extensions/ERC20FlashMint.test.js | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/test/token/ERC20/extensions/ERC20FlashMint.test.js b/test/token/ERC20/extensions/ERC20FlashMint.test.js index 37158a9c261..47b3b936fa1 100644 --- a/test/token/ERC20/extensions/ERC20FlashMint.test.js +++ b/test/token/ERC20/extensions/ERC20FlashMint.test.js @@ -93,31 +93,31 @@ contract('ERC20FlashMint', function (accounts) { await expectRevert.unspecified(this.token.flashLoan(receiver.address, this.token.address, MAX_UINT256, data)); }); - describe('custom flash fee & receiver', async function () { - const receiver = await ERC3156FlashBorrowerMock.new(true, true); + describe('custom flash fee & custom fee receiver', function () { const receiverInitialBalance = new BN(200000); const flashFee = new BN(5000); - before('init reciever balance & set flash fee',async function () { - const receipt = await this.token.mint(receiver.address, receiverInitialBalance); - await expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: receiverInitialBalance }); - expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal(receiverInitialBalance); + beforeEach('init reciever balance & set flash fee',async function () { + this.receiver = await ERC3156FlashBorrowerMock.new(true, true); + const receipt = await this.token.mint(this.receiver.address, receiverInitialBalance); + await expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: this.receiver.address, value: receiverInitialBalance }); + expect(await this.token.balanceOf(this.receiver.address)).to.be.bignumber.equal(receiverInitialBalance); await this.token.setFlashFee(flashFee); expect(await this.token.flashFee(this.token.address, loanAmount)).to.be.bignumber.equal(flashFee); }); it('default flash fee receiver', async function () { - const { tx } = await this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: loanAmount }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: ZERO_ADDRESS, value: loanAmount.add (flashFee)}); - await expectEvent.inTransaction(tx, receiver, 'BalanceOf', { token: this.token.address, account: receiver.address, value: receiverInitialBalance.add(loanAmount) }); - await expectEvent.inTransaction(tx, receiver, 'TotalSupply', { token: this.token.address, value: initialSupply.add (receiverInitialBalance).add(loanAmount) }); + const { tx } = await this.token.flashLoan(this.receiver.address, this.token.address, loanAmount, '0x'); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, to: this.receiver.address, value: loanAmount }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: this.receiver.address, to: ZERO_ADDRESS, value: loanAmount.add (flashFee)}); + await expectEvent.inTransaction(tx, this.receiver, 'BalanceOf', { token: this.token.address, account: this.receiver.address, value: receiverInitialBalance.add(loanAmount) }); + await expectEvent.inTransaction(tx, this.receiver, 'TotalSupply', { token: this.token.address, value: initialSupply.add (receiverInitialBalance).add(loanAmount) }); expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply.add(receiverInitialBalance).sub(flashFee)); - expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal(receiverInitialBalance.sub(flashFee)); + expect(await this.token.balanceOf(this.receiver.address)).to.be.bignumber.equal(receiverInitialBalance.sub(flashFee)); expect(await this.token.balanceOf(await this.token.flashFeeReceiver())).to.be.bignumber.equal('0'); - expect(await this.token.allowance(receiver.address, this.token.address)).to.be.bignumber.equal('0'); + expect(await this.token.allowance(this.receiver.address, this.token.address)).to.be.bignumber.equal('0'); }); it('custom flash fee receiver', async function () { @@ -127,17 +127,17 @@ contract('ERC20FlashMint', function (accounts) { expect(await this.token.balanceOf(flashFeeReceiverAddress)).to.be.bignumber.equal('0'); - const { tx } = await this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: loanAmount }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: ZERO_ADDRESS, value: loanAmount }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: flashFeeReceiverAddress, value: flashFee }); - await expectEvent.inTransaction(tx, receiver, 'BalanceOf', { token: this.token.address, account: receiver.address, value: receiverInitialBalance.add(loanAmount) }); - await expectEvent.inTransaction(tx, receiver, 'TotalSupply', { token: this.token.address, value: initialSupply.add (receiverInitialBalance).add(loanAmount) }); + const { tx } = await this.token.flashLoan(this.receiver.address, this.token.address, loanAmount, '0x'); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, to: this.receiver.address, value: loanAmount }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: this.receiver.address, to: ZERO_ADDRESS, value: loanAmount }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: this.receiver.address, to: flashFeeReceiverAddress, value: flashFee }); + await expectEvent.inTransaction(tx, this.receiver, 'BalanceOf', { token: this.token.address, account: this.receiver.address, value: receiverInitialBalance.add(loanAmount) }); + await expectEvent.inTransaction(tx, this.receiver, 'TotalSupply', { token: this.token.address, value: initialSupply.add (receiverInitialBalance).add(loanAmount) }); expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply.add(receiverInitialBalance)); - expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal(receiverInitialBalance.sub(flashFee)); + expect(await this.token.balanceOf(this.receiver.address)).to.be.bignumber.equal(receiverInitialBalance.sub(flashFee)); expect(await this.token.balanceOf(flashFeeReceiverAddress)).to.be.bignumber.equal(flashFee); - expect(await this.token.allowance(receiver.address, flashFeeReceiverAddress)).to.be.bignumber.equal('0'); + expect(await this.token.allowance(this.receiver.address, flashFeeReceiverAddress)).to.be.bignumber.equal('0'); }); }); }); From 9d5f7644a75cc62fa3d38f2d2faf2f4cffb256b3 Mon Sep 17 00:00:00 2001 From: Mazen Khalil Date: Tue, 12 Apr 2022 02:05:56 +0300 Subject: [PATCH 5/6] change flash fee reciever address --- test/token/ERC20/extensions/ERC20FlashMint.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/token/ERC20/extensions/ERC20FlashMint.test.js b/test/token/ERC20/extensions/ERC20FlashMint.test.js index 47b3b936fa1..01c08db598d 100644 --- a/test/token/ERC20/extensions/ERC20FlashMint.test.js +++ b/test/token/ERC20/extensions/ERC20FlashMint.test.js @@ -8,7 +8,7 @@ const ERC20FlashMintMock = artifacts.require('ERC20FlashMintMock'); const ERC3156FlashBorrowerMock = artifacts.require('ERC3156FlashBorrowerMock'); contract('ERC20FlashMint', function (accounts) { - const [ initialHolder, other ] = accounts; + const [ initialHolder, other, anotherAccount ] = accounts; const name = 'My Token'; const symbol = 'MTKN'; @@ -121,7 +121,7 @@ contract('ERC20FlashMint', function (accounts) { }); it('custom flash fee receiver', async function () { - const flashFeeReceiverAddress = this.token.address; + const flashFeeReceiverAddress = anotherAccount; await this.token.setFlashFeeReceiver(flashFeeReceiverAddress); expect(await this.token.flashFeeReceiver()).to.be.eq(flashFeeReceiverAddress); From ca60378a58f7d06163950c5b5eeb391b57b6520f Mon Sep 17 00:00:00 2001 From: Mazen Khalil Date: Tue, 12 Apr 2022 03:16:08 +0300 Subject: [PATCH 6/6] Add change log entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b483f71fd41..053ef4c75b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * `ERC2981`: make `royaltiInfo` public to allow super call in overrides. ([#3305](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3305)) * `Clones`: optimize clone creation ([#3329](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3329)) * `TimelockController`: Migrate `_call` to `_execute` and allow inheritance and overriding similar to `Governor`. ([#3317](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3317)) + * `ERC20FlashMint`: Add customizable flash fee receiver. ([#3327](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3327)) ## Unreleased