ERC20FlashMint.test.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
  4. const name = 'My Token';
  5. const symbol = 'MTKN';
  6. const initialSupply = 100n;
  7. const loanValue = 10_000_000_000_000n;
  8. async function fixture() {
  9. const [holder, other] = await ethers.getSigners();
  10. const token = await ethers.deployContract('$ERC20FlashMintMock', [name, symbol]);
  11. await token.$_mint(holder, initialSupply);
  12. return { holder, other, token };
  13. }
  14. describe('ERC20FlashMint', function () {
  15. beforeEach(async function () {
  16. Object.assign(this, await loadFixture(fixture));
  17. });
  18. describe('maxFlashLoan', function () {
  19. it('token match', async function () {
  20. expect(await this.token.maxFlashLoan(this.token)).to.equal(ethers.MaxUint256 - initialSupply);
  21. });
  22. it('token mismatch', async function () {
  23. expect(await this.token.maxFlashLoan(ethers.ZeroAddress)).to.equal(0n);
  24. });
  25. });
  26. describe('flashFee', function () {
  27. it('token match', async function () {
  28. expect(await this.token.flashFee(this.token, loanValue)).to.equal(0n);
  29. });
  30. it('token mismatch', async function () {
  31. await expect(this.token.flashFee(ethers.ZeroAddress, loanValue))
  32. .to.be.revertedWithCustomError(this.token, 'ERC3156UnsupportedToken')
  33. .withArgs(ethers.ZeroAddress);
  34. });
  35. });
  36. describe('flashFeeReceiver', function () {
  37. it('default receiver', async function () {
  38. expect(await this.token.$_flashFeeReceiver()).to.equal(ethers.ZeroAddress);
  39. });
  40. });
  41. describe('flashLoan', function () {
  42. it('success', async function () {
  43. const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, true]);
  44. const tx = await this.token.flashLoan(receiver, this.token, loanValue, '0x');
  45. await expect(tx)
  46. .to.emit(this.token, 'Transfer')
  47. .withArgs(ethers.ZeroAddress, receiver, loanValue)
  48. .to.emit(this.token, 'Transfer')
  49. .withArgs(receiver, ethers.ZeroAddress, loanValue)
  50. .to.emit(receiver, 'BalanceOf')
  51. .withArgs(this.token, receiver, loanValue)
  52. .to.emit(receiver, 'TotalSupply')
  53. .withArgs(this.token, initialSupply + loanValue);
  54. await expect(tx).to.changeTokenBalance(this.token, receiver, 0);
  55. expect(await this.token.totalSupply()).to.equal(initialSupply);
  56. expect(await this.token.allowance(receiver, this.token)).to.equal(0n);
  57. });
  58. it('missing return value', async function () {
  59. const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [false, true]);
  60. await expect(this.token.flashLoan(receiver, this.token, loanValue, '0x'))
  61. .to.be.revertedWithCustomError(this.token, 'ERC3156InvalidReceiver')
  62. .withArgs(receiver);
  63. });
  64. it('missing approval', async function () {
  65. const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, false]);
  66. await expect(this.token.flashLoan(receiver, this.token, loanValue, '0x'))
  67. .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientAllowance')
  68. .withArgs(this.token, 0, loanValue);
  69. });
  70. it('unavailable funds', async function () {
  71. const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, true]);
  72. const data = this.token.interface.encodeFunctionData('transfer', [this.other.address, 10]);
  73. await expect(this.token.flashLoan(receiver, this.token, loanValue, data))
  74. .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance')
  75. .withArgs(receiver, loanValue - 10n, loanValue);
  76. });
  77. it('more than maxFlashLoan', async function () {
  78. const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, true]);
  79. const data = this.token.interface.encodeFunctionData('transfer', [this.other.address, 10]);
  80. await expect(this.token.flashLoan(receiver, this.token, ethers.MaxUint256, data))
  81. .to.be.revertedWithCustomError(this.token, 'ERC3156ExceededMaxLoan')
  82. .withArgs(ethers.MaxUint256 - initialSupply);
  83. });
  84. describe('custom flash fee & custom fee receiver', function () {
  85. const receiverInitialBalance = 200_000n;
  86. const flashFee = 5_000n;
  87. beforeEach('init receiver balance & set flash fee', async function () {
  88. this.receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, true]);
  89. const tx = await this.token.$_mint(this.receiver, receiverInitialBalance);
  90. await expect(tx)
  91. .to.emit(this.token, 'Transfer')
  92. .withArgs(ethers.ZeroAddress, this.receiver, receiverInitialBalance);
  93. await expect(tx).to.changeTokenBalance(this.token, this.receiver, receiverInitialBalance);
  94. await this.token.setFlashFee(flashFee);
  95. expect(await this.token.flashFee(this.token, loanValue)).to.equal(flashFee);
  96. });
  97. it('default flash fee receiver', async function () {
  98. const tx = await this.token.flashLoan(this.receiver, this.token, loanValue, '0x');
  99. await expect(tx)
  100. .to.emit(this.token, 'Transfer')
  101. .withArgs(ethers.ZeroAddress, this.receiver, loanValue)
  102. .to.emit(this.token, 'Transfer')
  103. .withArgs(this.receiver, ethers.ZeroAddress, loanValue + flashFee)
  104. .to.emit(this.receiver, 'BalanceOf')
  105. .withArgs(this.token, this.receiver, receiverInitialBalance + loanValue)
  106. .to.emit(this.receiver, 'TotalSupply')
  107. .withArgs(this.token, initialSupply + receiverInitialBalance + loanValue);
  108. await expect(tx).to.changeTokenBalances(this.token, [this.receiver, ethers.ZeroAddress], [-flashFee, 0]);
  109. expect(await this.token.totalSupply()).to.equal(initialSupply + receiverInitialBalance - flashFee);
  110. expect(await this.token.allowance(this.receiver, this.token)).to.equal(0n);
  111. });
  112. it('custom flash fee receiver', async function () {
  113. const flashFeeReceiverAddress = this.other;
  114. await this.token.setFlashFeeReceiver(flashFeeReceiverAddress);
  115. expect(await this.token.$_flashFeeReceiver()).to.equal(flashFeeReceiverAddress);
  116. const tx = await this.token.flashLoan(this.receiver, this.token, loanValue, '0x');
  117. await expect(tx)
  118. .to.emit(this.token, 'Transfer')
  119. .withArgs(ethers.ZeroAddress, this.receiver, loanValue)
  120. .to.emit(this.token, 'Transfer')
  121. .withArgs(this.receiver, ethers.ZeroAddress, loanValue)
  122. .to.emit(this.token, 'Transfer')
  123. .withArgs(this.receiver, flashFeeReceiverAddress, flashFee)
  124. .to.emit(this.receiver, 'BalanceOf')
  125. .withArgs(this.token, this.receiver, receiverInitialBalance + loanValue)
  126. .to.emit(this.receiver, 'TotalSupply')
  127. .withArgs(this.token, initialSupply + receiverInitialBalance + loanValue);
  128. await expect(tx).to.changeTokenBalances(
  129. this.token,
  130. [this.receiver, flashFeeReceiverAddress],
  131. [-flashFee, flashFee],
  132. );
  133. expect(await this.token.totalSupply()).to.equal(initialSupply + receiverInitialBalance);
  134. expect(await this.token.allowance(this.receiver, flashFeeReceiverAddress)).to.equal(0n);
  135. });
  136. });
  137. });
  138. });