ERC777.test.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. const { BN, expectEvent, shouldFail, singletons } = require('openzeppelin-test-helpers');
  2. const {
  3. shouldBehaveLikeERC777DirectSendBurn,
  4. shouldBehaveLikeERC777OperatorSendBurn,
  5. shouldBehaveLikeERC777UnauthorizedOperatorSendBurn,
  6. shouldDirectSendTokens,
  7. shouldDirectBurnTokens,
  8. shouldBehaveLikeERC777InternalMint,
  9. shouldInternalMintTokens,
  10. shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook,
  11. shouldBehaveLikeERC777SendBurnWithSendHook,
  12. } = require('./ERC777.behavior');
  13. const ERC777 = artifacts.require('ERC777Mock');
  14. const ERC777SenderRecipientMock = artifacts.require('ERC777SenderRecipientMock');
  15. contract('ERC777', function ([
  16. _, registryFunder, holder, defaultOperatorA, defaultOperatorB, newOperator, anyone,
  17. ]) {
  18. const initialSupply = new BN('10000');
  19. const name = 'ERC777Test';
  20. const symbol = '777T';
  21. const data = web3.utils.sha3('OZ777TestData');
  22. const operatorData = web3.utils.sha3('OZ777TestOperatorData');
  23. const defaultOperators = [defaultOperatorA, defaultOperatorB];
  24. beforeEach(async function () {
  25. this.erc1820 = await singletons.ERC1820Registry(registryFunder);
  26. });
  27. it('reverts with a granularity of zero', async function () {
  28. await shouldFail.reverting(ERC777.new(holder, initialSupply, name, symbol, 0, []));
  29. });
  30. context('with a granularity of one', function () {
  31. const granularity = new BN('1');
  32. context('with default operators', function () {
  33. beforeEach(async function () {
  34. this.token = await ERC777.new(holder, initialSupply, name, symbol, granularity, defaultOperators);
  35. });
  36. it.skip('does not emit AuthorizedOperator events for default operators', async function () {
  37. expectEvent.not.inConstructor(this.token, 'AuthorizedOperator'); // This helper needs to be implemented
  38. });
  39. describe('basic information', function () {
  40. it('returns the name', async function () {
  41. (await this.token.name()).should.equal(name);
  42. });
  43. it('returns the symbol', async function () {
  44. (await this.token.symbol()).should.equal(symbol);
  45. });
  46. it('returns the granularity', async function () {
  47. (await this.token.granularity()).should.be.bignumber.equal(granularity);
  48. });
  49. it('returns the default operators', async function () {
  50. (await this.token.defaultOperators()).should.deep.equal(defaultOperators);
  51. });
  52. it('default operators are operators for all accounts', async function () {
  53. for (const operator of defaultOperators) {
  54. (await this.token.isOperatorFor(operator, anyone)).should.equal(true);
  55. }
  56. });
  57. it('returns thte total supply', async function () {
  58. (await this.token.totalSupply()).should.be.bignumber.equal(initialSupply);
  59. });
  60. it('is registered in the registry', async function () {
  61. (await this.erc1820.getInterfaceImplementer(this.token.address, web3.utils.soliditySha3('ERC777Token')))
  62. .should.equal(this.token.address);
  63. });
  64. });
  65. describe('balanceOf', function () {
  66. context('for an account with no tokens', function () {
  67. it('returns zero', async function () {
  68. (await this.token.balanceOf(anyone)).should.be.bignumber.equal('0');
  69. });
  70. });
  71. context('for an account with tokens', function () {
  72. it('returns their balance', async function () {
  73. (await this.token.balanceOf(holder)).should.be.bignumber.equal(initialSupply);
  74. });
  75. });
  76. });
  77. context('with no ERC777TokensSender and no ERC777TokensRecipient implementers', function () {
  78. describe('send/burn', function () {
  79. shouldBehaveLikeERC777DirectSendBurn(holder, anyone, data);
  80. context('with self operator', function () {
  81. shouldBehaveLikeERC777OperatorSendBurn(holder, anyone, holder, data, operatorData);
  82. });
  83. context('with first default operator', function () {
  84. shouldBehaveLikeERC777OperatorSendBurn(holder, anyone, defaultOperatorA, data, operatorData);
  85. });
  86. context('with second default operator', function () {
  87. shouldBehaveLikeERC777OperatorSendBurn(holder, anyone, defaultOperatorB, data, operatorData);
  88. });
  89. context('before authorizing a new operator', function () {
  90. shouldBehaveLikeERC777UnauthorizedOperatorSendBurn(holder, anyone, newOperator, data, operatorData);
  91. });
  92. context('with new authorized operator', function () {
  93. beforeEach(async function () {
  94. await this.token.authorizeOperator(newOperator, { from: holder });
  95. });
  96. shouldBehaveLikeERC777OperatorSendBurn(holder, anyone, newOperator, data, operatorData);
  97. context('with revoked operator', function () {
  98. beforeEach(async function () {
  99. await this.token.revokeOperator(newOperator, { from: holder });
  100. });
  101. shouldBehaveLikeERC777UnauthorizedOperatorSendBurn(holder, anyone, newOperator, data, operatorData);
  102. });
  103. });
  104. });
  105. describe('mint (internal)', function () {
  106. const to = anyone;
  107. const amount = new BN('5');
  108. context('with default operator', function () {
  109. const operator = defaultOperatorA;
  110. shouldBehaveLikeERC777InternalMint(to, operator, amount, data, operatorData);
  111. });
  112. context('with non operator', function () {
  113. const operator = newOperator;
  114. shouldBehaveLikeERC777InternalMint(to, operator, amount, data, operatorData);
  115. });
  116. });
  117. });
  118. describe('operator management', function () {
  119. it('accounts are their own operator', async function () {
  120. (await this.token.isOperatorFor(holder, holder)).should.equal(true);
  121. });
  122. it('reverts when self-authorizing', async function () {
  123. await shouldFail.reverting(this.token.authorizeOperator(holder, { from: holder }));
  124. });
  125. it('reverts when self-revoking', async function () {
  126. await shouldFail.reverting(this.token.revokeOperator(holder, { from: holder }));
  127. });
  128. it('non-operators can be revoked', async function () {
  129. (await this.token.isOperatorFor(newOperator, holder)).should.equal(false);
  130. const { logs } = await this.token.revokeOperator(newOperator, { from: holder });
  131. expectEvent.inLogs(logs, 'RevokedOperator', { operator: newOperator, tokenHolder: holder });
  132. (await this.token.isOperatorFor(newOperator, holder)).should.equal(false);
  133. });
  134. it('non-operators can be authorized', async function () {
  135. (await this.token.isOperatorFor(newOperator, holder)).should.equal(false);
  136. const { logs } = await this.token.authorizeOperator(newOperator, { from: holder });
  137. expectEvent.inLogs(logs, 'AuthorizedOperator', { operator: newOperator, tokenHolder: holder });
  138. (await this.token.isOperatorFor(newOperator, holder)).should.equal(true);
  139. });
  140. describe('new operators', function () {
  141. beforeEach(async function () {
  142. await this.token.authorizeOperator(newOperator, { from: holder });
  143. });
  144. it('are not added to the default operators list', async function () {
  145. (await this.token.defaultOperators()).should.deep.equal(defaultOperators);
  146. });
  147. it('can be re-authorized', async function () {
  148. const { logs } = await this.token.authorizeOperator(newOperator, { from: holder });
  149. expectEvent.inLogs(logs, 'AuthorizedOperator', { operator: newOperator, tokenHolder: holder });
  150. (await this.token.isOperatorFor(newOperator, holder)).should.equal(true);
  151. });
  152. it('can be revoked', async function () {
  153. const { logs } = await this.token.revokeOperator(newOperator, { from: holder });
  154. expectEvent.inLogs(logs, 'RevokedOperator', { operator: newOperator, tokenHolder: holder });
  155. (await this.token.isOperatorFor(newOperator, holder)).should.equal(false);
  156. });
  157. });
  158. describe('default operators', function () {
  159. it('can be re-authorized', async function () {
  160. const { logs } = await this.token.authorizeOperator(defaultOperatorA, { from: holder });
  161. expectEvent.inLogs(logs, 'AuthorizedOperator', { operator: defaultOperatorA, tokenHolder: holder });
  162. (await this.token.isOperatorFor(defaultOperatorA, holder)).should.equal(true);
  163. });
  164. it('can be revoked', async function () {
  165. const { logs } = await this.token.revokeOperator(defaultOperatorA, { from: holder });
  166. expectEvent.inLogs(logs, 'RevokedOperator', { operator: defaultOperatorA, tokenHolder: holder });
  167. (await this.token.isOperatorFor(defaultOperatorA, holder)).should.equal(false);
  168. });
  169. it('cannot be revoked for themselves', async function () {
  170. await shouldFail.reverting(this.token.revokeOperator(defaultOperatorA, { from: defaultOperatorA }));
  171. });
  172. context('with revoked default operator', function () {
  173. beforeEach(async function () {
  174. await this.token.revokeOperator(defaultOperatorA, { from: holder });
  175. });
  176. it('default operator is not revoked for other holders', async function () {
  177. (await this.token.isOperatorFor(defaultOperatorA, anyone)).should.equal(true);
  178. });
  179. it('other default operators are not revoked', async function () {
  180. (await this.token.isOperatorFor(defaultOperatorB, holder)).should.equal(true);
  181. });
  182. it('default operators list is not modified', async function () {
  183. (await this.token.defaultOperators()).should.deep.equal(defaultOperators);
  184. });
  185. it('revoked default operator can be re-authorized', async function () {
  186. const { logs } = await this.token.authorizeOperator(defaultOperatorA, { from: holder });
  187. expectEvent.inLogs(logs, 'AuthorizedOperator', { operator: defaultOperatorA, tokenHolder: holder });
  188. (await this.token.isOperatorFor(defaultOperatorA, holder)).should.equal(true);
  189. });
  190. });
  191. });
  192. });
  193. describe('send and receive hooks', function () {
  194. const amount = new BN('1');
  195. const operator = defaultOperatorA;
  196. // sender and recipient are stored inside 'this', since in some tests their addresses are determined dynamically
  197. describe('tokensReceived', function () {
  198. beforeEach(function () {
  199. this.sender = holder;
  200. });
  201. context('with no ERC777TokensRecipient implementer', function () {
  202. context('with contract recipient', function () {
  203. beforeEach(async function () {
  204. this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new();
  205. this.recipient = this.tokensRecipientImplementer.address;
  206. // Note that tokensRecipientImplementer doesn't implement the recipient interface for the recipient
  207. });
  208. it('send reverts', async function () {
  209. await shouldFail.reverting(this.token.send(this.recipient, amount, data));
  210. });
  211. it('operatorSend reverts', async function () {
  212. await shouldFail.reverting(
  213. this.token.operatorSend(this.sender, this.recipient, amount, data, operatorData, { from: operator })
  214. );
  215. });
  216. it('mint (internal) reverts', async function () {
  217. await shouldFail.reverting(
  218. this.token.mintInternal(operator, this.recipient, amount, data, operatorData)
  219. );
  220. });
  221. });
  222. });
  223. context('with ERC777TokensRecipient implementer', function () {
  224. context('with contract as implementer for an externally owned account', function () {
  225. beforeEach(async function () {
  226. this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new();
  227. this.recipient = anyone;
  228. await this.tokensRecipientImplementer.recipientFor(this.recipient);
  229. await this.erc1820.setInterfaceImplementer(
  230. this.recipient,
  231. web3.utils.soliditySha3('ERC777TokensRecipient'), this.tokensRecipientImplementer.address,
  232. { from: this.recipient },
  233. );
  234. });
  235. shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook(operator, amount, data, operatorData);
  236. });
  237. context('with contract as implementer for another contract', function () {
  238. beforeEach(async function () {
  239. this.recipientContract = await ERC777SenderRecipientMock.new();
  240. this.recipient = this.recipientContract.address;
  241. this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new();
  242. await this.tokensRecipientImplementer.recipientFor(this.recipient);
  243. await this.recipientContract.registerRecipient(this.tokensRecipientImplementer.address);
  244. });
  245. shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook(operator, amount, data, operatorData);
  246. });
  247. context('with contract as implementer for itself', function () {
  248. beforeEach(async function () {
  249. this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new();
  250. this.recipient = this.tokensRecipientImplementer.address;
  251. await this.tokensRecipientImplementer.recipientFor(this.recipient);
  252. });
  253. shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook(operator, amount, data, operatorData);
  254. });
  255. });
  256. });
  257. describe('tokensToSend', function () {
  258. beforeEach(function () {
  259. this.recipient = anyone;
  260. });
  261. context('with a contract as implementer for an externally owned account', function () {
  262. beforeEach(async function () {
  263. this.tokensSenderImplementer = await ERC777SenderRecipientMock.new();
  264. this.sender = holder;
  265. await this.tokensSenderImplementer.senderFor(this.sender);
  266. await this.erc1820.setInterfaceImplementer(
  267. this.sender,
  268. web3.utils.soliditySha3('ERC777TokensSender'), this.tokensSenderImplementer.address,
  269. { from: this.sender },
  270. );
  271. });
  272. shouldBehaveLikeERC777SendBurnWithSendHook(operator, amount, data, operatorData);
  273. });
  274. context('with contract as implementer for another contract', function () {
  275. beforeEach(async function () {
  276. this.senderContract = await ERC777SenderRecipientMock.new();
  277. this.sender = this.senderContract.address;
  278. this.tokensSenderImplementer = await ERC777SenderRecipientMock.new();
  279. await this.tokensSenderImplementer.senderFor(this.sender);
  280. await this.senderContract.registerSender(this.tokensSenderImplementer.address);
  281. // For the contract to be able to receive tokens (that it can later send), it must also implement the
  282. // recipient interface.
  283. await this.senderContract.recipientFor(this.sender);
  284. await this.token.send(this.sender, amount, data, { from: holder });
  285. });
  286. shouldBehaveLikeERC777SendBurnWithSendHook(operator, amount, data, operatorData);
  287. });
  288. context('with a contract as implementer for itself', function () {
  289. beforeEach(async function () {
  290. this.tokensSenderImplementer = await ERC777SenderRecipientMock.new();
  291. this.sender = this.tokensSenderImplementer.address;
  292. await this.tokensSenderImplementer.senderFor(this.sender);
  293. // For the contract to be able to receive tokens (that it can later send), it must also implement the
  294. // recipient interface.
  295. await this.tokensSenderImplementer.recipientFor(this.sender);
  296. await this.token.send(this.sender, amount, data, { from: holder });
  297. });
  298. shouldBehaveLikeERC777SendBurnWithSendHook(operator, amount, data, operatorData);
  299. });
  300. });
  301. });
  302. });
  303. context('with no default operators', function () {
  304. beforeEach(async function () {
  305. this.token = await ERC777.new(holder, initialSupply, name, symbol, granularity, []);
  306. });
  307. it('default operators list is empty', async function () {
  308. (await this.token.defaultOperators()).should.deep.equal([]);
  309. });
  310. });
  311. });
  312. context('with granularity larger than 1', function () {
  313. const granularity = new BN('4');
  314. beforeEach(async function () {
  315. initialSupply.mod(granularity).should.be.bignumber.equal('0');
  316. this.token = await ERC777.new(holder, initialSupply, name, symbol, granularity, defaultOperators);
  317. });
  318. it('returns the granularity', async function () {
  319. (await this.token.granularity()).should.be.bignumber.equal(granularity);
  320. });
  321. context('when the sender has tokens', function () {
  322. const from = holder;
  323. shouldDirectSendTokens(from, anyone, new BN('0'), data);
  324. shouldDirectSendTokens(from, anyone, granularity, data);
  325. shouldDirectSendTokens(from, anyone, granularity.muln(2), data);
  326. it('reverts when sending an amount non-multiple of the granularity', async function () {
  327. await shouldFail.reverting(this.token.send(anyone, granularity.subn(1), data, { from }));
  328. });
  329. shouldDirectBurnTokens(from, new BN('0'), data);
  330. shouldDirectBurnTokens(from, granularity, data);
  331. shouldDirectBurnTokens(from, granularity.muln(2), data);
  332. it('reverts when burning an amount non-multiple of the granularity', async function () {
  333. await shouldFail.reverting(this.token.burn(granularity.subn(1), data, { from }));
  334. });
  335. });
  336. shouldInternalMintTokens(anyone, defaultOperatorA, new BN('0'), data, operatorData);
  337. shouldInternalMintTokens(anyone, defaultOperatorA, granularity, data, operatorData);
  338. shouldInternalMintTokens(anyone, defaultOperatorA, granularity.muln(2), data, operatorData);
  339. it('reverts when minting an amount non-multiple of the granularity', async function () {
  340. await shouldFail.reverting(
  341. this.token.mintInternal(defaultOperatorA, anyone, granularity.subn(1), data, operatorData)
  342. );
  343. });
  344. });
  345. });