ERC20.test.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. const { accounts, contract } = require('@openzeppelin/test-environment');
  2. const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
  3. const { expect } = require('chai');
  4. const { ZERO_ADDRESS } = constants;
  5. const {
  6. shouldBehaveLikeERC20,
  7. shouldBehaveLikeERC20Transfer,
  8. shouldBehaveLikeERC20Approve,
  9. } = require('./ERC20.behavior');
  10. const ERC20Mock = contract.fromArtifact('ERC20Mock');
  11. describe('ERC20', function () {
  12. const [ initialHolder, recipient, anotherAccount ] = accounts;
  13. const initialSupply = new BN(100);
  14. beforeEach(async function () {
  15. this.token = await ERC20Mock.new(initialHolder, initialSupply);
  16. });
  17. shouldBehaveLikeERC20('ERC20', initialSupply, initialHolder, recipient, anotherAccount);
  18. describe('decrease allowance', function () {
  19. describe('when the spender is not the zero address', function () {
  20. const spender = recipient;
  21. function shouldDecreaseApproval (amount) {
  22. describe('when there was no approved amount before', function () {
  23. it('reverts', async function () {
  24. await expectRevert(this.token.decreaseAllowance(
  25. spender, amount, { from: initialHolder }), 'ERC20: decreased allowance below zero'
  26. );
  27. });
  28. });
  29. describe('when the spender had an approved amount', function () {
  30. const approvedAmount = amount;
  31. beforeEach(async function () {
  32. ({ logs: this.logs } = await this.token.approve(spender, approvedAmount, { from: initialHolder }));
  33. });
  34. it('emits an approval event', async function () {
  35. const { logs } = await this.token.decreaseAllowance(spender, approvedAmount, { from: initialHolder });
  36. expectEvent.inLogs(logs, 'Approval', {
  37. owner: initialHolder,
  38. spender: spender,
  39. value: new BN(0),
  40. });
  41. });
  42. it('decreases the spender allowance subtracting the requested amount', async function () {
  43. await this.token.decreaseAllowance(spender, approvedAmount.subn(1), { from: initialHolder });
  44. expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal('1');
  45. });
  46. it('sets the allowance to zero when all allowance is removed', async function () {
  47. await this.token.decreaseAllowance(spender, approvedAmount, { from: initialHolder });
  48. expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal('0');
  49. });
  50. it('reverts when more than the full allowance is removed', async function () {
  51. await expectRevert(
  52. this.token.decreaseAllowance(spender, approvedAmount.addn(1), { from: initialHolder }),
  53. 'ERC20: decreased allowance below zero'
  54. );
  55. });
  56. });
  57. }
  58. describe('when the sender has enough balance', function () {
  59. const amount = initialSupply;
  60. shouldDecreaseApproval(amount);
  61. });
  62. describe('when the sender does not have enough balance', function () {
  63. const amount = initialSupply.addn(1);
  64. shouldDecreaseApproval(amount);
  65. });
  66. });
  67. describe('when the spender is the zero address', function () {
  68. const amount = initialSupply;
  69. const spender = ZERO_ADDRESS;
  70. it('reverts', async function () {
  71. await expectRevert(this.token.decreaseAllowance(
  72. spender, amount, { from: initialHolder }), 'ERC20: decreased allowance below zero'
  73. );
  74. });
  75. });
  76. });
  77. describe('increase allowance', function () {
  78. const amount = initialSupply;
  79. describe('when the spender is not the zero address', function () {
  80. const spender = recipient;
  81. describe('when the sender has enough balance', function () {
  82. it('emits an approval event', async function () {
  83. const { logs } = await this.token.increaseAllowance(spender, amount, { from: initialHolder });
  84. expectEvent.inLogs(logs, 'Approval', {
  85. owner: initialHolder,
  86. spender: spender,
  87. value: amount,
  88. });
  89. });
  90. describe('when there was no approved amount before', function () {
  91. it('approves the requested amount', async function () {
  92. await this.token.increaseAllowance(spender, amount, { from: initialHolder });
  93. expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount);
  94. });
  95. });
  96. describe('when the spender had an approved amount', function () {
  97. beforeEach(async function () {
  98. await this.token.approve(spender, new BN(1), { from: initialHolder });
  99. });
  100. it('increases the spender allowance adding the requested amount', async function () {
  101. await this.token.increaseAllowance(spender, amount, { from: initialHolder });
  102. expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount.addn(1));
  103. });
  104. });
  105. });
  106. describe('when the sender does not have enough balance', function () {
  107. const amount = initialSupply.addn(1);
  108. it('emits an approval event', async function () {
  109. const { logs } = await this.token.increaseAllowance(spender, amount, { from: initialHolder });
  110. expectEvent.inLogs(logs, 'Approval', {
  111. owner: initialHolder,
  112. spender: spender,
  113. value: amount,
  114. });
  115. });
  116. describe('when there was no approved amount before', function () {
  117. it('approves the requested amount', async function () {
  118. await this.token.increaseAllowance(spender, amount, { from: initialHolder });
  119. expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount);
  120. });
  121. });
  122. describe('when the spender had an approved amount', function () {
  123. beforeEach(async function () {
  124. await this.token.approve(spender, new BN(1), { from: initialHolder });
  125. });
  126. it('increases the spender allowance adding the requested amount', async function () {
  127. await this.token.increaseAllowance(spender, amount, { from: initialHolder });
  128. expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount.addn(1));
  129. });
  130. });
  131. });
  132. });
  133. describe('when the spender is the zero address', function () {
  134. const spender = ZERO_ADDRESS;
  135. it('reverts', async function () {
  136. await expectRevert(
  137. this.token.increaseAllowance(spender, amount, { from: initialHolder }), 'ERC20: approve to the zero address'
  138. );
  139. });
  140. });
  141. });
  142. describe('_mint', function () {
  143. const amount = new BN(50);
  144. it('rejects a null account', async function () {
  145. await expectRevert(
  146. this.token.mint(ZERO_ADDRESS, amount), 'ERC20: mint to the zero address'
  147. );
  148. });
  149. describe('for a non zero account', function () {
  150. beforeEach('minting', async function () {
  151. const { logs } = await this.token.mint(recipient, amount);
  152. this.logs = logs;
  153. });
  154. it('increments totalSupply', async function () {
  155. const expectedSupply = initialSupply.add(amount);
  156. expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply);
  157. });
  158. it('increments recipient balance', async function () {
  159. expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(amount);
  160. });
  161. it('emits Transfer event', async function () {
  162. const event = expectEvent.inLogs(this.logs, 'Transfer', {
  163. from: ZERO_ADDRESS,
  164. to: recipient,
  165. });
  166. expect(event.args.value).to.be.bignumber.equal(amount);
  167. });
  168. });
  169. });
  170. describe('_burn', function () {
  171. it('rejects a null account', async function () {
  172. await expectRevert(this.token.burn(ZERO_ADDRESS, new BN(1)),
  173. 'ERC20: burn from the zero address');
  174. });
  175. describe('for a non zero account', function () {
  176. it('rejects burning more than balance', async function () {
  177. await expectRevert(this.token.burn(
  178. initialHolder, initialSupply.addn(1)), 'ERC20: burn amount exceeds balance'
  179. );
  180. });
  181. const describeBurn = function (description, amount) {
  182. describe(description, function () {
  183. beforeEach('burning', async function () {
  184. const { logs } = await this.token.burn(initialHolder, amount);
  185. this.logs = logs;
  186. });
  187. it('decrements totalSupply', async function () {
  188. const expectedSupply = initialSupply.sub(amount);
  189. expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply);
  190. });
  191. it('decrements initialHolder balance', async function () {
  192. const expectedBalance = initialSupply.sub(amount);
  193. expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(expectedBalance);
  194. });
  195. it('emits Transfer event', async function () {
  196. const event = expectEvent.inLogs(this.logs, 'Transfer', {
  197. from: initialHolder,
  198. to: ZERO_ADDRESS,
  199. });
  200. expect(event.args.value).to.be.bignumber.equal(amount);
  201. });
  202. });
  203. };
  204. describeBurn('for entire balance', initialSupply);
  205. describeBurn('for less amount than balance', initialSupply.subn(1));
  206. });
  207. });
  208. describe('_transfer', function () {
  209. shouldBehaveLikeERC20Transfer('ERC20', initialHolder, recipient, initialSupply, function (from, to, amount) {
  210. return this.token.transferInternal(from, to, amount);
  211. });
  212. describe('when the sender is the zero address', function () {
  213. it('reverts', async function () {
  214. await expectRevert(this.token.transferInternal(ZERO_ADDRESS, recipient, initialSupply),
  215. 'ERC20: transfer from the zero address'
  216. );
  217. });
  218. });
  219. });
  220. describe('_approve', function () {
  221. shouldBehaveLikeERC20Approve('ERC20', initialHolder, recipient, initialSupply, function (owner, spender, amount) {
  222. return this.token.approveInternal(owner, spender, amount);
  223. });
  224. describe('when the owner is the zero address', function () {
  225. it('reverts', async function () {
  226. await expectRevert(this.token.approveInternal(ZERO_ADDRESS, recipient, initialSupply),
  227. 'ERC20: approve from the zero address'
  228. );
  229. });
  230. });
  231. });
  232. });