ERC777.test.js 16 KB

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