ERC20.test.js 20 KB

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