ERC20.test.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. const { BN, constants, expectEvent, shouldFail } = require('openzeppelin-test-helpers');
  2. const { ZERO_ADDRESS } = constants;
  3. const ERC20Mock = artifacts.require('ERC20Mock');
  4. contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
  5. const initialSupply = new BN(100);
  6. beforeEach(async function () {
  7. this.token = await ERC20Mock.new(initialHolder, initialSupply);
  8. });
  9. describe('total supply', function () {
  10. it('returns the total amount of tokens', async function () {
  11. (await this.token.totalSupply()).should.be.bignumber.equal(initialSupply);
  12. });
  13. });
  14. describe('balanceOf', function () {
  15. describe('when the requested account has no tokens', function () {
  16. it('returns zero', async function () {
  17. (await this.token.balanceOf(anotherAccount)).should.be.bignumber.equal('0');
  18. });
  19. });
  20. describe('when the requested account has some tokens', function () {
  21. it('returns the total amount of tokens', async function () {
  22. (await this.token.balanceOf(initialHolder)).should.be.bignumber.equal(initialSupply);
  23. });
  24. });
  25. });
  26. describe('transfer', function () {
  27. describe('when the recipient is not the zero address', function () {
  28. const to = recipient;
  29. describe('when the sender does not have enough balance', function () {
  30. const amount = initialSupply.addn(1);
  31. it('reverts', async function () {
  32. await shouldFail.reverting.withMessage(this.token.transfer(to, amount, { from: initialHolder }),
  33. 'SafeMath: subtraction overflow'
  34. );
  35. });
  36. });
  37. describe('when the sender has enough balance', function () {
  38. const amount = initialSupply;
  39. it('transfers the requested amount', async function () {
  40. await this.token.transfer(to, amount, { from: initialHolder });
  41. (await this.token.balanceOf(initialHolder)).should.be.bignumber.equal('0');
  42. (await this.token.balanceOf(to)).should.be.bignumber.equal(amount);
  43. });
  44. it('emits a transfer event', async function () {
  45. const { logs } = await this.token.transfer(to, amount, { from: initialHolder });
  46. expectEvent.inLogs(logs, 'Transfer', {
  47. from: initialHolder,
  48. to: to,
  49. value: amount,
  50. });
  51. });
  52. });
  53. });
  54. describe('when the recipient is the zero address', function () {
  55. const to = ZERO_ADDRESS;
  56. it('reverts', async function () {
  57. await shouldFail.reverting.withMessage(this.token.transfer(to, initialSupply, { from: initialHolder }),
  58. 'ERC20: transfer to the zero address'
  59. );
  60. });
  61. });
  62. });
  63. describe('transfer from', function () {
  64. const spender = recipient;
  65. describe('when the recipient is not the zero address', function () {
  66. const to = anotherAccount;
  67. describe('when the spender has enough approved balance', function () {
  68. beforeEach(async function () {
  69. await this.token.approve(spender, initialSupply, { from: initialHolder });
  70. });
  71. describe('when the initial holder has enough balance', function () {
  72. const amount = initialSupply;
  73. it('transfers the requested amount', async function () {
  74. await this.token.transferFrom(initialHolder, to, amount, { from: spender });
  75. (await this.token.balanceOf(initialHolder)).should.be.bignumber.equal('0');
  76. (await this.token.balanceOf(to)).should.be.bignumber.equal(amount);
  77. });
  78. it('decreases the spender allowance', async function () {
  79. await this.token.transferFrom(initialHolder, to, amount, { from: spender });
  80. (await this.token.allowance(initialHolder, spender)).should.be.bignumber.equal('0');
  81. });
  82. it('emits a transfer event', async function () {
  83. const { logs } = await this.token.transferFrom(initialHolder, to, amount, { from: spender });
  84. expectEvent.inLogs(logs, 'Transfer', {
  85. from: initialHolder,
  86. to: to,
  87. value: amount,
  88. });
  89. });
  90. it('emits an approval event', async function () {
  91. const { logs } = await this.token.transferFrom(initialHolder, to, amount, { from: spender });
  92. expectEvent.inLogs(logs, 'Approval', {
  93. owner: initialHolder,
  94. spender: spender,
  95. value: await this.token.allowance(initialHolder, spender),
  96. });
  97. });
  98. });
  99. describe('when the initial holder does not have enough balance', function () {
  100. const amount = initialSupply.addn(1);
  101. it('reverts', async function () {
  102. await shouldFail.reverting.withMessage(this.token.transferFrom(
  103. initialHolder, to, amount, { from: spender }), 'SafeMath: subtraction overflow'
  104. );
  105. });
  106. });
  107. });
  108. describe('when the spender does not have enough approved balance', function () {
  109. beforeEach(async function () {
  110. await this.token.approve(spender, initialSupply.subn(1), { from: initialHolder });
  111. });
  112. describe('when the initial holder has enough balance', function () {
  113. const amount = initialSupply;
  114. it('reverts', async function () {
  115. await shouldFail.reverting.withMessage(this.token.transferFrom(
  116. initialHolder, to, amount, { from: spender }), 'SafeMath: subtraction overflow'
  117. );
  118. });
  119. });
  120. describe('when the initial holder does not have enough balance', function () {
  121. const amount = initialSupply.addn(1);
  122. it('reverts', async function () {
  123. await shouldFail.reverting.withMessage(this.token.transferFrom(
  124. initialHolder, to, amount, { from: spender }), 'SafeMath: subtraction overflow'
  125. );
  126. });
  127. });
  128. });
  129. });
  130. describe('when the recipient is the zero address', function () {
  131. const amount = initialSupply;
  132. const to = ZERO_ADDRESS;
  133. beforeEach(async function () {
  134. await this.token.approve(spender, amount, { from: initialHolder });
  135. });
  136. it('reverts', async function () {
  137. await shouldFail.reverting.withMessage(this.token.transferFrom(
  138. initialHolder, to, amount, { from: spender }), 'ERC20: transfer to the zero address'
  139. );
  140. });
  141. });
  142. });
  143. describe('decrease allowance', function () {
  144. describe('when the spender is not the zero address', function () {
  145. const spender = recipient;
  146. function shouldDecreaseApproval (amount) {
  147. describe('when there was no approved amount before', function () {
  148. it('reverts', async function () {
  149. await shouldFail.reverting.withMessage(this.token.decreaseAllowance(
  150. spender, amount, { from: initialHolder }), 'SafeMath: subtraction overflow'
  151. );
  152. });
  153. });
  154. describe('when the spender had an approved amount', function () {
  155. const approvedAmount = amount;
  156. beforeEach(async function () {
  157. ({ logs: this.logs } = await this.token.approve(spender, approvedAmount, { from: initialHolder }));
  158. });
  159. it('emits an approval event', async function () {
  160. const { logs } = await this.token.decreaseAllowance(spender, approvedAmount, { from: initialHolder });
  161. expectEvent.inLogs(logs, 'Approval', {
  162. owner: initialHolder,
  163. spender: spender,
  164. value: new BN(0),
  165. });
  166. });
  167. it('decreases the spender allowance subtracting the requested amount', async function () {
  168. await this.token.decreaseAllowance(spender, approvedAmount.subn(1), { from: initialHolder });
  169. (await this.token.allowance(initialHolder, spender)).should.be.bignumber.equal('1');
  170. });
  171. it('sets the allowance to zero when all allowance is removed', async function () {
  172. await this.token.decreaseAllowance(spender, approvedAmount, { from: initialHolder });
  173. (await this.token.allowance(initialHolder, spender)).should.be.bignumber.equal('0');
  174. });
  175. it('reverts when more than the full allowance is removed', async function () {
  176. await shouldFail.reverting.withMessage(
  177. this.token.decreaseAllowance(spender, approvedAmount.addn(1), { from: initialHolder }),
  178. 'SafeMath: subtraction overflow'
  179. );
  180. });
  181. });
  182. }
  183. describe('when the sender has enough balance', function () {
  184. const amount = initialSupply;
  185. shouldDecreaseApproval(amount);
  186. });
  187. describe('when the sender does not have enough balance', function () {
  188. const amount = initialSupply.addn(1);
  189. shouldDecreaseApproval(amount);
  190. });
  191. });
  192. describe('when the spender is the zero address', function () {
  193. const amount = initialSupply;
  194. const spender = ZERO_ADDRESS;
  195. it('reverts', async function () {
  196. await shouldFail.reverting.withMessage(this.token.decreaseAllowance(
  197. spender, amount, { from: initialHolder }, 'ERC20: approve to the zero address')
  198. );
  199. });
  200. });
  201. });
  202. describe('increase allowance', function () {
  203. const amount = initialSupply;
  204. describe('when the spender is not the zero address', function () {
  205. const spender = recipient;
  206. describe('when the sender has enough balance', function () {
  207. it('emits an approval event', async function () {
  208. const { logs } = await this.token.increaseAllowance(spender, amount, { from: initialHolder });
  209. expectEvent.inLogs(logs, 'Approval', {
  210. owner: initialHolder,
  211. spender: spender,
  212. value: amount,
  213. });
  214. });
  215. describe('when there was no approved amount before', function () {
  216. it('approves the requested amount', async function () {
  217. await this.token.increaseAllowance(spender, amount, { from: initialHolder });
  218. (await this.token.allowance(initialHolder, spender)).should.be.bignumber.equal(amount);
  219. });
  220. });
  221. describe('when the spender had an approved amount', function () {
  222. beforeEach(async function () {
  223. await this.token.approve(spender, new BN(1), { from: initialHolder });
  224. });
  225. it('increases the spender allowance adding the requested amount', async function () {
  226. await this.token.increaseAllowance(spender, amount, { from: initialHolder });
  227. (await this.token.allowance(initialHolder, spender)).should.be.bignumber.equal(amount.addn(1));
  228. });
  229. });
  230. });
  231. describe('when the sender does not have enough balance', function () {
  232. const amount = initialSupply.addn(1);
  233. it('emits an approval event', async function () {
  234. const { logs } = await this.token.increaseAllowance(spender, amount, { from: initialHolder });
  235. expectEvent.inLogs(logs, 'Approval', {
  236. owner: initialHolder,
  237. spender: spender,
  238. value: amount,
  239. });
  240. });
  241. describe('when there was no approved amount before', function () {
  242. it('approves the requested amount', async function () {
  243. await this.token.increaseAllowance(spender, amount, { from: initialHolder });
  244. (await this.token.allowance(initialHolder, spender)).should.be.bignumber.equal(amount);
  245. });
  246. });
  247. describe('when the spender had an approved amount', function () {
  248. beforeEach(async function () {
  249. await this.token.approve(spender, new BN(1), { from: initialHolder });
  250. });
  251. it('increases the spender allowance adding the requested amount', async function () {
  252. await this.token.increaseAllowance(spender, amount, { from: initialHolder });
  253. (await this.token.allowance(initialHolder, spender)).should.be.bignumber.equal(amount.addn(1));
  254. });
  255. });
  256. });
  257. });
  258. describe('when the spender is the zero address', function () {
  259. const spender = ZERO_ADDRESS;
  260. it('reverts', async function () {
  261. await shouldFail.reverting.withMessage(
  262. this.token.increaseAllowance(spender, amount, { from: initialHolder }), 'ERC20: approve to the zero address'
  263. );
  264. });
  265. });
  266. });
  267. describe('_mint', function () {
  268. const amount = new BN(50);
  269. it('rejects a null account', async function () {
  270. await shouldFail.reverting.withMessage(
  271. this.token.mint(ZERO_ADDRESS, amount), 'ERC20: mint to the zero address'
  272. );
  273. });
  274. describe('for a non zero account', function () {
  275. beforeEach('minting', async function () {
  276. const { logs } = await this.token.mint(recipient, amount);
  277. this.logs = logs;
  278. });
  279. it('increments totalSupply', async function () {
  280. const expectedSupply = initialSupply.add(amount);
  281. (await this.token.totalSupply()).should.be.bignumber.equal(expectedSupply);
  282. });
  283. it('increments recipient balance', async function () {
  284. (await this.token.balanceOf(recipient)).should.be.bignumber.equal(amount);
  285. });
  286. it('emits Transfer event', async function () {
  287. const event = expectEvent.inLogs(this.logs, 'Transfer', {
  288. from: ZERO_ADDRESS,
  289. to: recipient,
  290. });
  291. event.args.value.should.be.bignumber.equal(amount);
  292. });
  293. });
  294. });
  295. describe('_burn', function () {
  296. it('rejects a null account', async function () {
  297. await shouldFail.reverting.withMessage(this.token.burn(ZERO_ADDRESS, new BN(1)),
  298. 'ERC20: burn from the zero address');
  299. });
  300. describe('for a non zero account', function () {
  301. it('rejects burning more than balance', async function () {
  302. await shouldFail.reverting.withMessage(this.token.burn(
  303. initialHolder, initialSupply.addn(1)), 'SafeMath: subtraction overflow'
  304. );
  305. });
  306. const describeBurn = function (description, amount) {
  307. describe(description, function () {
  308. beforeEach('burning', async function () {
  309. const { logs } = await this.token.burn(initialHolder, amount);
  310. this.logs = logs;
  311. });
  312. it('decrements totalSupply', async function () {
  313. const expectedSupply = initialSupply.sub(amount);
  314. (await this.token.totalSupply()).should.be.bignumber.equal(expectedSupply);
  315. });
  316. it('decrements initialHolder balance', async function () {
  317. const expectedBalance = initialSupply.sub(amount);
  318. (await this.token.balanceOf(initialHolder)).should.be.bignumber.equal(expectedBalance);
  319. });
  320. it('emits Transfer event', async function () {
  321. const event = expectEvent.inLogs(this.logs, 'Transfer', {
  322. from: initialHolder,
  323. to: ZERO_ADDRESS,
  324. });
  325. event.args.value.should.be.bignumber.equal(amount);
  326. });
  327. });
  328. };
  329. describeBurn('for entire balance', initialSupply);
  330. describeBurn('for less amount than balance', initialSupply.subn(1));
  331. });
  332. });
  333. describe('_burnFrom', function () {
  334. const allowance = new BN(70);
  335. const spender = anotherAccount;
  336. beforeEach('approving', async function () {
  337. await this.token.approve(spender, allowance, { from: initialHolder });
  338. });
  339. it('rejects a null account', async function () {
  340. await shouldFail.reverting.withMessage(this.token.burnFrom(ZERO_ADDRESS, new BN(1)),
  341. 'ERC20: burn from the zero address'
  342. );
  343. });
  344. describe('for a non zero account', function () {
  345. it('rejects burning more than allowance', async function () {
  346. await shouldFail.reverting.withMessage(this.token.burnFrom(initialHolder, allowance.addn(1)),
  347. 'SafeMath: subtraction overflow'
  348. );
  349. });
  350. it('rejects burning more than balance', async function () {
  351. await shouldFail.reverting.withMessage(this.token.burnFrom(initialHolder, initialSupply.addn(1)),
  352. 'SafeMath: subtraction overflow'
  353. );
  354. });
  355. const describeBurnFrom = function (description, amount) {
  356. describe(description, function () {
  357. beforeEach('burning', async function () {
  358. const { logs } = await this.token.burnFrom(initialHolder, amount, { from: spender });
  359. this.logs = logs;
  360. });
  361. it('decrements totalSupply', async function () {
  362. const expectedSupply = initialSupply.sub(amount);
  363. (await this.token.totalSupply()).should.be.bignumber.equal(expectedSupply);
  364. });
  365. it('decrements initialHolder balance', async function () {
  366. const expectedBalance = initialSupply.sub(amount);
  367. (await this.token.balanceOf(initialHolder)).should.be.bignumber.equal(expectedBalance);
  368. });
  369. it('decrements spender allowance', async function () {
  370. const expectedAllowance = allowance.sub(amount);
  371. (await this.token.allowance(initialHolder, spender)).should.be.bignumber.equal(expectedAllowance);
  372. });
  373. it('emits a Transfer event', async function () {
  374. const event = expectEvent.inLogs(this.logs, 'Transfer', {
  375. from: initialHolder,
  376. to: ZERO_ADDRESS,
  377. });
  378. event.args.value.should.be.bignumber.equal(amount);
  379. });
  380. it('emits an Approval event', async function () {
  381. expectEvent.inLogs(this.logs, 'Approval', {
  382. owner: initialHolder,
  383. spender: spender,
  384. value: await this.token.allowance(initialHolder, spender),
  385. });
  386. });
  387. });
  388. };
  389. describeBurnFrom('for entire allowance', allowance);
  390. describeBurnFrom('for less amount than allowance', allowance.subn(1));
  391. });
  392. });
  393. describe('approve', function () {
  394. testApprove(initialHolder, recipient, initialSupply, function (owner, spender, amount) {
  395. return this.token.approve(spender, amount, { from: owner });
  396. });
  397. });
  398. describe('_approve', function () {
  399. testApprove(initialHolder, recipient, initialSupply, function (owner, spender, amount) {
  400. return this.token.approveInternal(owner, spender, amount);
  401. });
  402. describe('when the owner is the zero address', function () {
  403. it('reverts', async function () {
  404. await shouldFail.reverting.withMessage(this.token.approveInternal(ZERO_ADDRESS, recipient, initialSupply),
  405. 'ERC20: approve from the zero address'
  406. );
  407. });
  408. });
  409. });
  410. function testApprove (owner, spender, supply, approve) {
  411. describe('when the spender is not the zero address', function () {
  412. describe('when the sender has enough balance', function () {
  413. const amount = supply;
  414. it('emits an approval event', async function () {
  415. const { logs } = await approve.call(this, owner, spender, amount);
  416. expectEvent.inLogs(logs, 'Approval', {
  417. owner: owner,
  418. spender: spender,
  419. value: amount,
  420. });
  421. });
  422. describe('when there was no approved amount before', function () {
  423. it('approves the requested amount', async function () {
  424. await approve.call(this, owner, spender, amount);
  425. (await this.token.allowance(owner, spender)).should.be.bignumber.equal(amount);
  426. });
  427. });
  428. describe('when the spender had an approved amount', function () {
  429. beforeEach(async function () {
  430. await approve.call(this, owner, spender, new BN(1));
  431. });
  432. it('approves the requested amount and replaces the previous one', async function () {
  433. await approve.call(this, owner, spender, amount);
  434. (await this.token.allowance(owner, spender)).should.be.bignumber.equal(amount);
  435. });
  436. });
  437. });
  438. describe('when the sender does not have enough balance', function () {
  439. const amount = supply.addn(1);
  440. it('emits an approval event', async function () {
  441. const { logs } = await approve.call(this, owner, spender, amount);
  442. expectEvent.inLogs(logs, 'Approval', {
  443. owner: owner,
  444. spender: spender,
  445. value: amount,
  446. });
  447. });
  448. describe('when there was no approved amount before', function () {
  449. it('approves the requested amount', async function () {
  450. await approve.call(this, owner, spender, amount);
  451. (await this.token.allowance(owner, spender)).should.be.bignumber.equal(amount);
  452. });
  453. });
  454. describe('when the spender had an approved amount', function () {
  455. beforeEach(async function () {
  456. await approve.call(this, owner, spender, new BN(1));
  457. });
  458. it('approves the requested amount and replaces the previous one', async function () {
  459. await approve.call(this, owner, spender, amount);
  460. (await this.token.allowance(owner, spender)).should.be.bignumber.equal(amount);
  461. });
  462. });
  463. });
  464. });
  465. describe('when the spender is the zero address', function () {
  466. it('reverts', async function () {
  467. await shouldFail.reverting.withMessage(approve.call(this, owner, ZERO_ADDRESS, supply),
  468. 'ERC20: approve to the zero address'
  469. );
  470. });
  471. });
  472. }
  473. });