ERC20.test.js 19 KB

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