ERC20.test.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
  2. const { expect } = require('chai');
  3. const { ZERO_ADDRESS } = constants;
  4. const {
  5. shouldBehaveLikeERC20,
  6. shouldBehaveLikeERC20Transfer,
  7. shouldBehaveLikeERC20Approve,
  8. } = require('./ERC20.behavior');
  9. const { expectRevertCustomError } = require('../../helpers/customError');
  10. const TOKENS = [
  11. { Token: artifacts.require('$ERC20') },
  12. { Token: artifacts.require('$ERC20ApprovalMock'), forcedApproval: true },
  13. ];
  14. contract('ERC20', function (accounts) {
  15. const [initialHolder, recipient] = accounts;
  16. const name = 'My Token';
  17. const symbol = 'MTKN';
  18. const initialSupply = new BN(100);
  19. for (const { Token, forcedApproval } of TOKENS) {
  20. describe(`using ${Token._json.contractName}`, function () {
  21. beforeEach(async function () {
  22. this.token = await Token.new(name, symbol);
  23. await this.token.$_mint(initialHolder, initialSupply);
  24. });
  25. shouldBehaveLikeERC20(initialSupply, accounts, { forcedApproval });
  26. it('has a name', async function () {
  27. expect(await this.token.name()).to.equal(name);
  28. });
  29. it('has a symbol', async function () {
  30. expect(await this.token.symbol()).to.equal(symbol);
  31. });
  32. it('has 18 decimals', async function () {
  33. expect(await this.token.decimals()).to.be.bignumber.equal('18');
  34. });
  35. describe('_mint', function () {
  36. const value = new BN(50);
  37. it('rejects a null account', async function () {
  38. await expectRevertCustomError(this.token.$_mint(ZERO_ADDRESS, value), 'ERC20InvalidReceiver', [ZERO_ADDRESS]);
  39. });
  40. it('rejects overflow', async function () {
  41. const maxUint256 = new BN('2').pow(new BN(256)).subn(1);
  42. await expectRevert(
  43. this.token.$_mint(recipient, maxUint256),
  44. 'reverted with panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)',
  45. );
  46. });
  47. describe('for a non zero account', function () {
  48. beforeEach('minting', async function () {
  49. this.receipt = await this.token.$_mint(recipient, value);
  50. });
  51. it('increments totalSupply', async function () {
  52. const expectedSupply = initialSupply.add(value);
  53. expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply);
  54. });
  55. it('increments recipient balance', async function () {
  56. expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(value);
  57. });
  58. it('emits Transfer event', async function () {
  59. const event = expectEvent(this.receipt, 'Transfer', { from: ZERO_ADDRESS, to: recipient });
  60. expect(event.args.value).to.be.bignumber.equal(value);
  61. });
  62. });
  63. });
  64. describe('_burn', function () {
  65. it('rejects a null account', async function () {
  66. await expectRevertCustomError(this.token.$_burn(ZERO_ADDRESS, new BN(1)), 'ERC20InvalidSender', [
  67. ZERO_ADDRESS,
  68. ]);
  69. });
  70. describe('for a non zero account', function () {
  71. it('rejects burning more than balance', async function () {
  72. await expectRevertCustomError(
  73. this.token.$_burn(initialHolder, initialSupply.addn(1)),
  74. 'ERC20InsufficientBalance',
  75. [initialHolder, initialSupply, initialSupply.addn(1)],
  76. );
  77. });
  78. const describeBurn = function (description, value) {
  79. describe(description, function () {
  80. beforeEach('burning', async function () {
  81. this.receipt = await this.token.$_burn(initialHolder, value);
  82. });
  83. it('decrements totalSupply', async function () {
  84. const expectedSupply = initialSupply.sub(value);
  85. expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply);
  86. });
  87. it('decrements initialHolder balance', async function () {
  88. const expectedBalance = initialSupply.sub(value);
  89. expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(expectedBalance);
  90. });
  91. it('emits Transfer event', async function () {
  92. const event = expectEvent(this.receipt, 'Transfer', { from: initialHolder, to: ZERO_ADDRESS });
  93. expect(event.args.value).to.be.bignumber.equal(value);
  94. });
  95. });
  96. };
  97. describeBurn('for entire balance', initialSupply);
  98. describeBurn('for less value than balance', initialSupply.subn(1));
  99. });
  100. });
  101. describe('_update', function () {
  102. const value = new BN(1);
  103. it('from is the zero address', async function () {
  104. const balanceBefore = await this.token.balanceOf(initialHolder);
  105. const totalSupply = await this.token.totalSupply();
  106. expectEvent(await this.token.$_update(ZERO_ADDRESS, initialHolder, value), 'Transfer', {
  107. from: ZERO_ADDRESS,
  108. to: initialHolder,
  109. value: value,
  110. });
  111. expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply.add(value));
  112. expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(balanceBefore.add(value));
  113. });
  114. it('to is the zero address', async function () {
  115. const balanceBefore = await this.token.balanceOf(initialHolder);
  116. const totalSupply = await this.token.totalSupply();
  117. expectEvent(await this.token.$_update(initialHolder, ZERO_ADDRESS, value), 'Transfer', {
  118. from: initialHolder,
  119. to: ZERO_ADDRESS,
  120. value: value,
  121. });
  122. expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply.sub(value));
  123. expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(balanceBefore.sub(value));
  124. });
  125. it('from and to are the zero address', async function () {
  126. const totalSupply = await this.token.totalSupply();
  127. await this.token.$_update(ZERO_ADDRESS, ZERO_ADDRESS, value);
  128. expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply);
  129. expectEvent(await this.token.$_update(ZERO_ADDRESS, ZERO_ADDRESS, value), 'Transfer', {
  130. from: ZERO_ADDRESS,
  131. to: ZERO_ADDRESS,
  132. value: value,
  133. });
  134. });
  135. });
  136. describe('_transfer', function () {
  137. shouldBehaveLikeERC20Transfer(initialHolder, recipient, initialSupply, function (from, to, value) {
  138. return this.token.$_transfer(from, to, value);
  139. });
  140. describe('when the sender is the zero address', function () {
  141. it('reverts', async function () {
  142. await expectRevertCustomError(
  143. this.token.$_transfer(ZERO_ADDRESS, recipient, initialSupply),
  144. 'ERC20InvalidSender',
  145. [ZERO_ADDRESS],
  146. );
  147. });
  148. });
  149. });
  150. describe('_approve', function () {
  151. shouldBehaveLikeERC20Approve(initialHolder, recipient, initialSupply, function (owner, spender, value) {
  152. return this.token.$_approve(owner, spender, value);
  153. });
  154. describe('when the owner is the zero address', function () {
  155. it('reverts', async function () {
  156. await expectRevertCustomError(
  157. this.token.$_approve(ZERO_ADDRESS, recipient, initialSupply),
  158. 'ERC20InvalidApprover',
  159. [ZERO_ADDRESS],
  160. );
  161. });
  162. });
  163. });
  164. });
  165. }
  166. });