ERC20.test.js 20 KB

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