ERC20.behavior.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. const { BN, constants, expectEvent, shouldFail } = require('openzeppelin-test-helpers');
  2. const { ZERO_ADDRESS } = constants;
  3. function shouldBehaveLikeERC20 (errorPrefix, initialSupply, initialHolder, recipient, anotherAccount) {
  4. describe('total supply', function () {
  5. it('returns the total amount of tokens', async function () {
  6. (await this.token.totalSupply()).should.be.bignumber.equal(initialSupply);
  7. });
  8. });
  9. describe('balanceOf', function () {
  10. describe('when the requested account has no tokens', function () {
  11. it('returns zero', async function () {
  12. (await this.token.balanceOf(anotherAccount)).should.be.bignumber.equal('0');
  13. });
  14. });
  15. describe('when the requested account has some tokens', function () {
  16. it('returns the total amount of tokens', async function () {
  17. (await this.token.balanceOf(initialHolder)).should.be.bignumber.equal(initialSupply);
  18. });
  19. });
  20. });
  21. describe('transfer', function () {
  22. describe('when the recipient is not the zero address', function () {
  23. const to = recipient;
  24. describe('when the sender does not have enough balance', function () {
  25. const amount = initialSupply.addn(1);
  26. it('reverts', async function () {
  27. await shouldFail.reverting.withMessage(this.token.transfer(to, amount, { from: initialHolder }),
  28. 'SafeMath: subtraction overflow'
  29. );
  30. });
  31. });
  32. describe('when the sender has enough balance', function () {
  33. const amount = initialSupply;
  34. it('transfers the requested amount', async function () {
  35. await this.token.transfer(to, amount, { from: initialHolder });
  36. (await this.token.balanceOf(initialHolder)).should.be.bignumber.equal('0');
  37. (await this.token.balanceOf(to)).should.be.bignumber.equal(amount);
  38. });
  39. it('emits a transfer event', async function () {
  40. const { logs } = await this.token.transfer(to, amount, { from: initialHolder });
  41. expectEvent.inLogs(logs, 'Transfer', {
  42. from: initialHolder,
  43. to: to,
  44. value: amount,
  45. });
  46. });
  47. });
  48. });
  49. describe('when the recipient is the zero address', function () {
  50. const to = ZERO_ADDRESS;
  51. it('reverts', async function () {
  52. await shouldFail.reverting.withMessage(this.token.transfer(to, initialSupply, { from: initialHolder }),
  53. `${errorPrefix}: transfer to the zero address`
  54. );
  55. });
  56. });
  57. });
  58. describe('transfer from', function () {
  59. const spender = recipient;
  60. describe('when the recipient is not the zero address', function () {
  61. const to = anotherAccount;
  62. describe('when the spender has enough approved balance', function () {
  63. beforeEach(async function () {
  64. await this.token.approve(spender, initialSupply, { from: initialHolder });
  65. });
  66. describe('when the initial holder has enough balance', function () {
  67. const amount = initialSupply;
  68. it('transfers the requested amount', async function () {
  69. await this.token.transferFrom(initialHolder, to, amount, { from: spender });
  70. (await this.token.balanceOf(initialHolder)).should.be.bignumber.equal('0');
  71. (await this.token.balanceOf(to)).should.be.bignumber.equal(amount);
  72. });
  73. it('decreases the spender allowance', async function () {
  74. await this.token.transferFrom(initialHolder, to, amount, { from: spender });
  75. (await this.token.allowance(initialHolder, spender)).should.be.bignumber.equal('0');
  76. });
  77. it('emits a transfer event', async function () {
  78. const { logs } = await this.token.transferFrom(initialHolder, to, amount, { from: spender });
  79. expectEvent.inLogs(logs, 'Transfer', {
  80. from: initialHolder,
  81. to: to,
  82. value: amount,
  83. });
  84. });
  85. it('emits an approval event', async function () {
  86. const { logs } = await this.token.transferFrom(initialHolder, to, amount, { from: spender });
  87. expectEvent.inLogs(logs, 'Approval', {
  88. owner: initialHolder,
  89. spender: spender,
  90. value: await this.token.allowance(initialHolder, spender),
  91. });
  92. });
  93. });
  94. describe('when the initial holder does not have enough balance', function () {
  95. const amount = initialSupply.addn(1);
  96. it('reverts', async function () {
  97. await shouldFail.reverting.withMessage(this.token.transferFrom(
  98. initialHolder, to, amount, { from: spender }), 'SafeMath: subtraction overflow'
  99. );
  100. });
  101. });
  102. });
  103. describe('when the spender does not have enough approved balance', function () {
  104. beforeEach(async function () {
  105. await this.token.approve(spender, initialSupply.subn(1), { from: initialHolder });
  106. });
  107. describe('when the initial holder has enough balance', function () {
  108. const amount = initialSupply;
  109. it('reverts', async function () {
  110. await shouldFail.reverting.withMessage(this.token.transferFrom(
  111. initialHolder, to, amount, { from: spender }), 'SafeMath: subtraction overflow'
  112. );
  113. });
  114. });
  115. describe('when the initial holder does not have enough balance', function () {
  116. const amount = initialSupply.addn(1);
  117. it('reverts', async function () {
  118. await shouldFail.reverting.withMessage(this.token.transferFrom(
  119. initialHolder, to, amount, { from: spender }), 'SafeMath: subtraction overflow'
  120. );
  121. });
  122. });
  123. });
  124. });
  125. describe('when the recipient is the zero address', function () {
  126. const amount = initialSupply;
  127. const to = ZERO_ADDRESS;
  128. beforeEach(async function () {
  129. await this.token.approve(spender, amount, { from: initialHolder });
  130. });
  131. it('reverts', async function () {
  132. await shouldFail.reverting.withMessage(this.token.transferFrom(
  133. initialHolder, to, amount, { from: spender }), `${errorPrefix}: transfer to the zero address`
  134. );
  135. });
  136. });
  137. });
  138. describe('approve', function () {
  139. shouldBehaveLikeERC20Approve(errorPrefix, initialHolder, recipient, initialSupply,
  140. function (owner, spender, amount) {
  141. return this.token.approve(spender, amount, { from: owner });
  142. }
  143. );
  144. });
  145. }
  146. function shouldBehaveLikeERC20Approve (errorPrefix, owner, spender, supply, approve) {
  147. describe('when the spender is not the zero address', function () {
  148. describe('when the sender has enough balance', function () {
  149. const amount = supply;
  150. it('emits an approval event', async function () {
  151. const { logs } = await approve.call(this, owner, spender, amount);
  152. expectEvent.inLogs(logs, 'Approval', {
  153. owner: owner,
  154. spender: spender,
  155. value: amount,
  156. });
  157. });
  158. describe('when there was no approved amount before', function () {
  159. it('approves the requested amount', async function () {
  160. await approve.call(this, owner, spender, amount);
  161. (await this.token.allowance(owner, spender)).should.be.bignumber.equal(amount);
  162. });
  163. });
  164. describe('when the spender had an approved amount', function () {
  165. beforeEach(async function () {
  166. await approve.call(this, owner, spender, new BN(1));
  167. });
  168. it('approves the requested amount and replaces the previous one', async function () {
  169. await approve.call(this, owner, spender, amount);
  170. (await this.token.allowance(owner, spender)).should.be.bignumber.equal(amount);
  171. });
  172. });
  173. });
  174. describe('when the sender does not have enough balance', function () {
  175. const amount = supply.addn(1);
  176. it('emits an approval event', async function () {
  177. const { logs } = await approve.call(this, owner, spender, amount);
  178. expectEvent.inLogs(logs, 'Approval', {
  179. owner: owner,
  180. spender: spender,
  181. value: amount,
  182. });
  183. });
  184. describe('when there was no approved amount before', function () {
  185. it('approves the requested amount', async function () {
  186. await approve.call(this, owner, spender, amount);
  187. (await this.token.allowance(owner, spender)).should.be.bignumber.equal(amount);
  188. });
  189. });
  190. describe('when the spender had an approved amount', function () {
  191. beforeEach(async function () {
  192. await approve.call(this, owner, spender, new BN(1));
  193. });
  194. it('approves the requested amount and replaces the previous one', async function () {
  195. await approve.call(this, owner, spender, amount);
  196. (await this.token.allowance(owner, spender)).should.be.bignumber.equal(amount);
  197. });
  198. });
  199. });
  200. });
  201. describe('when the spender is the zero address', function () {
  202. it('reverts', async function () {
  203. await shouldFail.reverting.withMessage(approve.call(this, owner, ZERO_ADDRESS, supply),
  204. `${errorPrefix}: approve to the zero address`
  205. );
  206. });
  207. });
  208. }
  209. module.exports = {
  210. shouldBehaveLikeERC20,
  211. shouldBehaveLikeERC20Approve,
  212. };