ERC777.test.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  1. const { BN, constants, expectEvent, expectRevert, singletons } = require('@openzeppelin/test-helpers');
  2. const { ZERO_ADDRESS } = constants;
  3. const { expect } = require('chai');
  4. const {
  5. shouldBehaveLikeERC777DirectSendBurn,
  6. shouldBehaveLikeERC777OperatorSendBurn,
  7. shouldBehaveLikeERC777UnauthorizedOperatorSendBurn,
  8. shouldBehaveLikeERC777InternalMint,
  9. shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook,
  10. shouldBehaveLikeERC777SendBurnWithSendHook,
  11. } = require('./ERC777.behavior');
  12. const {
  13. shouldBehaveLikeERC20,
  14. shouldBehaveLikeERC20Approve,
  15. } = require('../ERC20/ERC20.behavior');
  16. const ERC777 = artifacts.require('ERC777Mock');
  17. const ERC777SenderRecipientMock = artifacts.require('ERC777SenderRecipientMock');
  18. contract('ERC777', function (accounts) {
  19. const [ registryFunder, holder, defaultOperatorA, defaultOperatorB, newOperator, anyone ] = accounts;
  20. const initialSupply = new BN('10000');
  21. const name = 'ERC777Test';
  22. const symbol = '777T';
  23. const data = web3.utils.sha3('OZ777TestData');
  24. const operatorData = web3.utils.sha3('OZ777TestOperatorData');
  25. const defaultOperators = [defaultOperatorA, defaultOperatorB];
  26. beforeEach(async function () {
  27. this.erc1820 = await singletons.ERC1820Registry(registryFunder);
  28. });
  29. context('with default operators', function () {
  30. beforeEach(async function () {
  31. this.token = await ERC777.new(holder, initialSupply, name, symbol, defaultOperators);
  32. });
  33. describe('as an ERC20 token', function () {
  34. shouldBehaveLikeERC20('ERC777', initialSupply, holder, anyone, defaultOperatorA);
  35. describe('_approve', function () {
  36. shouldBehaveLikeERC20Approve('ERC777', holder, anyone, initialSupply, function (owner, spender, amount) {
  37. return this.token.approveInternal(owner, spender, amount);
  38. });
  39. describe('when the owner is the zero address', function () {
  40. it('reverts', async function () {
  41. await expectRevert(this.token.approveInternal(ZERO_ADDRESS, anyone, initialSupply),
  42. 'ERC777: approve from the zero address',
  43. );
  44. });
  45. });
  46. });
  47. });
  48. it('does not emit AuthorizedOperator events for default operators', async function () {
  49. await expectEvent.notEmitted.inConstruction(this.token, 'AuthorizedOperator');
  50. });
  51. describe('basic information', function () {
  52. it('returns the name', async function () {
  53. expect(await this.token.name()).to.equal(name);
  54. });
  55. it('returns the symbol', async function () {
  56. expect(await this.token.symbol()).to.equal(symbol);
  57. });
  58. it('returns a granularity of 1', async function () {
  59. expect(await this.token.granularity()).to.be.bignumber.equal('1');
  60. });
  61. it('returns the default operators', async function () {
  62. expect(await this.token.defaultOperators()).to.deep.equal(defaultOperators);
  63. });
  64. it('default operators are operators for all accounts', async function () {
  65. for (const operator of defaultOperators) {
  66. expect(await this.token.isOperatorFor(operator, anyone)).to.equal(true);
  67. }
  68. });
  69. it('returns the total supply', async function () {
  70. expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply);
  71. });
  72. it('returns 18 when decimals is called', async function () {
  73. expect(await this.token.decimals()).to.be.bignumber.equal('18');
  74. });
  75. it('the ERC777Token interface is registered in the registry', async function () {
  76. expect(await this.erc1820.getInterfaceImplementer(this.token.address, web3.utils.soliditySha3('ERC777Token')))
  77. .to.equal(this.token.address);
  78. });
  79. it('the ERC20Token interface is registered in the registry', async function () {
  80. expect(await this.erc1820.getInterfaceImplementer(this.token.address, web3.utils.soliditySha3('ERC20Token')))
  81. .to.equal(this.token.address);
  82. });
  83. });
  84. describe('balanceOf', function () {
  85. context('for an account with no tokens', function () {
  86. it('returns zero', async function () {
  87. expect(await this.token.balanceOf(anyone)).to.be.bignumber.equal('0');
  88. });
  89. });
  90. context('for an account with tokens', function () {
  91. it('returns their balance', async function () {
  92. expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply);
  93. });
  94. });
  95. });
  96. context('with no ERC777TokensSender and no ERC777TokensRecipient implementers', function () {
  97. describe('send/burn', function () {
  98. shouldBehaveLikeERC777DirectSendBurn(holder, anyone, data);
  99. context('with self operator', function () {
  100. shouldBehaveLikeERC777OperatorSendBurn(holder, anyone, holder, data, operatorData);
  101. });
  102. context('with first default operator', function () {
  103. shouldBehaveLikeERC777OperatorSendBurn(holder, anyone, defaultOperatorA, data, operatorData);
  104. });
  105. context('with second default operator', function () {
  106. shouldBehaveLikeERC777OperatorSendBurn(holder, anyone, defaultOperatorB, data, operatorData);
  107. });
  108. context('before authorizing a new operator', function () {
  109. shouldBehaveLikeERC777UnauthorizedOperatorSendBurn(holder, anyone, newOperator, data, operatorData);
  110. });
  111. context('with new authorized operator', function () {
  112. beforeEach(async function () {
  113. await this.token.authorizeOperator(newOperator, { from: holder });
  114. });
  115. shouldBehaveLikeERC777OperatorSendBurn(holder, anyone, newOperator, data, operatorData);
  116. context('with revoked operator', function () {
  117. beforeEach(async function () {
  118. await this.token.revokeOperator(newOperator, { from: holder });
  119. });
  120. shouldBehaveLikeERC777UnauthorizedOperatorSendBurn(holder, anyone, newOperator, data, operatorData);
  121. });
  122. });
  123. });
  124. describe('mint (internal)', function () {
  125. const to = anyone;
  126. const amount = new BN('5');
  127. context('with default operator', function () {
  128. const operator = defaultOperatorA;
  129. shouldBehaveLikeERC777InternalMint(to, operator, amount, data, operatorData);
  130. });
  131. context('with non operator', function () {
  132. const operator = newOperator;
  133. shouldBehaveLikeERC777InternalMint(to, operator, amount, data, operatorData);
  134. });
  135. });
  136. describe('mint (internal extended)', function () {
  137. const amount = new BN('5');
  138. context('to anyone', function () {
  139. beforeEach(async function () {
  140. this.recipient = anyone;
  141. });
  142. context('with default operator', function () {
  143. const operator = defaultOperatorA;
  144. it('without requireReceptionAck', async function () {
  145. await this.token.mintInternalExtended(
  146. this.recipient,
  147. amount,
  148. data,
  149. operatorData,
  150. false,
  151. { from: operator },
  152. );
  153. });
  154. it('with requireReceptionAck', async function () {
  155. await this.token.mintInternalExtended(
  156. this.recipient,
  157. amount,
  158. data,
  159. operatorData,
  160. true,
  161. { from: operator },
  162. );
  163. });
  164. });
  165. context('with non operator', function () {
  166. const operator = newOperator;
  167. it('without requireReceptionAck', async function () {
  168. await this.token.mintInternalExtended(
  169. this.recipient,
  170. amount,
  171. data,
  172. operatorData,
  173. false,
  174. { from: operator },
  175. );
  176. });
  177. it('with requireReceptionAck', async function () {
  178. await this.token.mintInternalExtended(
  179. this.recipient,
  180. amount,
  181. data,
  182. operatorData,
  183. true,
  184. { from: operator },
  185. );
  186. });
  187. });
  188. });
  189. context('to non ERC777TokensRecipient implementer', function () {
  190. beforeEach(async function () {
  191. this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new();
  192. this.recipient = this.tokensRecipientImplementer.address;
  193. });
  194. context('with default operator', function () {
  195. const operator = defaultOperatorA;
  196. it('without requireReceptionAck', async function () {
  197. await this.token.mintInternalExtended(
  198. this.recipient,
  199. amount,
  200. data,
  201. operatorData,
  202. false,
  203. { from: operator },
  204. );
  205. });
  206. it('with requireReceptionAck', async function () {
  207. await expectRevert(
  208. this.token.mintInternalExtended(
  209. this.recipient,
  210. amount,
  211. data,
  212. operatorData,
  213. true,
  214. { from: operator },
  215. ),
  216. 'ERC777: token recipient contract has no implementer for ERC777TokensRecipient',
  217. );
  218. });
  219. });
  220. context('with non operator', function () {
  221. const operator = newOperator;
  222. it('without requireReceptionAck', async function () {
  223. await this.token.mintInternalExtended(
  224. this.recipient,
  225. amount,
  226. data,
  227. operatorData,
  228. false,
  229. { from: operator },
  230. );
  231. });
  232. it('with requireReceptionAck', async function () {
  233. await expectRevert(
  234. this.token.mintInternalExtended(
  235. this.recipient,
  236. amount,
  237. data,
  238. operatorData,
  239. true,
  240. { from: operator },
  241. ),
  242. 'ERC777: token recipient contract has no implementer for ERC777TokensRecipient',
  243. );
  244. });
  245. });
  246. });
  247. });
  248. });
  249. describe('operator management', function () {
  250. it('accounts are their own operator', async function () {
  251. expect(await this.token.isOperatorFor(holder, holder)).to.equal(true);
  252. });
  253. it('reverts when self-authorizing', async function () {
  254. await expectRevert(
  255. this.token.authorizeOperator(holder, { from: holder }), 'ERC777: authorizing self as operator',
  256. );
  257. });
  258. it('reverts when self-revoking', async function () {
  259. await expectRevert(
  260. this.token.revokeOperator(holder, { from: holder }), 'ERC777: revoking self as operator',
  261. );
  262. });
  263. it('non-operators can be revoked', async function () {
  264. expect(await this.token.isOperatorFor(newOperator, holder)).to.equal(false);
  265. const receipt = await this.token.revokeOperator(newOperator, { from: holder });
  266. expectEvent(receipt, 'RevokedOperator', { operator: newOperator, tokenHolder: holder });
  267. expect(await this.token.isOperatorFor(newOperator, holder)).to.equal(false);
  268. });
  269. it('non-operators can be authorized', async function () {
  270. expect(await this.token.isOperatorFor(newOperator, holder)).to.equal(false);
  271. const receipt = await this.token.authorizeOperator(newOperator, { from: holder });
  272. expectEvent(receipt, 'AuthorizedOperator', { operator: newOperator, tokenHolder: holder });
  273. expect(await this.token.isOperatorFor(newOperator, holder)).to.equal(true);
  274. });
  275. describe('new operators', function () {
  276. beforeEach(async function () {
  277. await this.token.authorizeOperator(newOperator, { from: holder });
  278. });
  279. it('are not added to the default operators list', async function () {
  280. expect(await this.token.defaultOperators()).to.deep.equal(defaultOperators);
  281. });
  282. it('can be re-authorized', async function () {
  283. const receipt = await this.token.authorizeOperator(newOperator, { from: holder });
  284. expectEvent(receipt, 'AuthorizedOperator', { operator: newOperator, tokenHolder: holder });
  285. expect(await this.token.isOperatorFor(newOperator, holder)).to.equal(true);
  286. });
  287. it('can be revoked', async function () {
  288. const receipt = await this.token.revokeOperator(newOperator, { from: holder });
  289. expectEvent(receipt, 'RevokedOperator', { operator: newOperator, tokenHolder: holder });
  290. expect(await this.token.isOperatorFor(newOperator, holder)).to.equal(false);
  291. });
  292. });
  293. describe('default operators', function () {
  294. it('can be re-authorized', async function () {
  295. const receipt = await this.token.authorizeOperator(defaultOperatorA, { from: holder });
  296. expectEvent(receipt, 'AuthorizedOperator', { operator: defaultOperatorA, tokenHolder: holder });
  297. expect(await this.token.isOperatorFor(defaultOperatorA, holder)).to.equal(true);
  298. });
  299. it('can be revoked', async function () {
  300. const receipt = await this.token.revokeOperator(defaultOperatorA, { from: holder });
  301. expectEvent(receipt, 'RevokedOperator', { operator: defaultOperatorA, tokenHolder: holder });
  302. expect(await this.token.isOperatorFor(defaultOperatorA, holder)).to.equal(false);
  303. });
  304. it('cannot be revoked for themselves', async function () {
  305. await expectRevert(
  306. this.token.revokeOperator(defaultOperatorA, { from: defaultOperatorA }),
  307. 'ERC777: revoking self as operator',
  308. );
  309. });
  310. context('with revoked default operator', function () {
  311. beforeEach(async function () {
  312. await this.token.revokeOperator(defaultOperatorA, { from: holder });
  313. });
  314. it('default operator is not revoked for other holders', async function () {
  315. expect(await this.token.isOperatorFor(defaultOperatorA, anyone)).to.equal(true);
  316. });
  317. it('other default operators are not revoked', async function () {
  318. expect(await this.token.isOperatorFor(defaultOperatorB, holder)).to.equal(true);
  319. });
  320. it('default operators list is not modified', async function () {
  321. expect(await this.token.defaultOperators()).to.deep.equal(defaultOperators);
  322. });
  323. it('revoked default operator can be re-authorized', async function () {
  324. const receipt = await this.token.authorizeOperator(defaultOperatorA, { from: holder });
  325. expectEvent(receipt, 'AuthorizedOperator', { operator: defaultOperatorA, tokenHolder: holder });
  326. expect(await this.token.isOperatorFor(defaultOperatorA, holder)).to.equal(true);
  327. });
  328. });
  329. });
  330. });
  331. describe('send and receive hooks', function () {
  332. const amount = new BN('1');
  333. const operator = defaultOperatorA;
  334. // sender and recipient are stored inside 'this', since in some tests their addresses are determined dynamically
  335. describe('tokensReceived', function () {
  336. beforeEach(function () {
  337. this.sender = holder;
  338. });
  339. context('with no ERC777TokensRecipient implementer', function () {
  340. context('with contract recipient', function () {
  341. beforeEach(async function () {
  342. this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new();
  343. this.recipient = this.tokensRecipientImplementer.address;
  344. // Note that tokensRecipientImplementer doesn't implement the recipient interface for the recipient
  345. });
  346. it('send reverts', async function () {
  347. await expectRevert(
  348. this.token.send(this.recipient, amount, data, { from: holder }),
  349. 'ERC777: token recipient contract has no implementer for ERC777TokensRecipient',
  350. );
  351. });
  352. it('operatorSend reverts', async function () {
  353. await expectRevert(
  354. this.token.operatorSend(this.sender, this.recipient, amount, data, operatorData, { from: operator }),
  355. 'ERC777: token recipient contract has no implementer for ERC777TokensRecipient',
  356. );
  357. });
  358. it('mint (internal) reverts', async function () {
  359. await expectRevert(
  360. this.token.mintInternal(this.recipient, amount, data, operatorData, { from: operator }),
  361. 'ERC777: token recipient contract has no implementer for ERC777TokensRecipient',
  362. );
  363. });
  364. it('(ERC20) transfer succeeds', async function () {
  365. await this.token.transfer(this.recipient, amount, { from: holder });
  366. });
  367. it('(ERC20) transferFrom succeeds', async function () {
  368. const approved = anyone;
  369. await this.token.approve(approved, amount, { from: this.sender });
  370. await this.token.transferFrom(this.sender, this.recipient, amount, { from: approved });
  371. });
  372. });
  373. });
  374. context('with ERC777TokensRecipient implementer', function () {
  375. context('with contract as implementer for an externally owned account', function () {
  376. beforeEach(async function () {
  377. this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new();
  378. this.recipient = anyone;
  379. await this.tokensRecipientImplementer.recipientFor(this.recipient);
  380. await this.erc1820.setInterfaceImplementer(
  381. this.recipient,
  382. web3.utils.soliditySha3('ERC777TokensRecipient'), this.tokensRecipientImplementer.address,
  383. { from: this.recipient },
  384. );
  385. });
  386. shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook(operator, amount, data, operatorData);
  387. });
  388. context('with contract as implementer for another contract', function () {
  389. beforeEach(async function () {
  390. this.recipientContract = await ERC777SenderRecipientMock.new();
  391. this.recipient = this.recipientContract.address;
  392. this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new();
  393. await this.tokensRecipientImplementer.recipientFor(this.recipient);
  394. await this.recipientContract.registerRecipient(this.tokensRecipientImplementer.address);
  395. });
  396. shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook(operator, amount, data, operatorData);
  397. });
  398. context('with contract as implementer for itself', function () {
  399. beforeEach(async function () {
  400. this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new();
  401. this.recipient = this.tokensRecipientImplementer.address;
  402. await this.tokensRecipientImplementer.recipientFor(this.recipient);
  403. });
  404. shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook(operator, amount, data, operatorData);
  405. });
  406. });
  407. });
  408. describe('tokensToSend', function () {
  409. beforeEach(function () {
  410. this.recipient = anyone;
  411. });
  412. context('with a contract as implementer for an externally owned account', function () {
  413. beforeEach(async function () {
  414. this.tokensSenderImplementer = await ERC777SenderRecipientMock.new();
  415. this.sender = holder;
  416. await this.tokensSenderImplementer.senderFor(this.sender);
  417. await this.erc1820.setInterfaceImplementer(
  418. this.sender,
  419. web3.utils.soliditySha3('ERC777TokensSender'), this.tokensSenderImplementer.address,
  420. { from: this.sender },
  421. );
  422. });
  423. shouldBehaveLikeERC777SendBurnWithSendHook(operator, amount, data, operatorData);
  424. });
  425. context('with contract as implementer for another contract', function () {
  426. beforeEach(async function () {
  427. this.senderContract = await ERC777SenderRecipientMock.new();
  428. this.sender = this.senderContract.address;
  429. this.tokensSenderImplementer = await ERC777SenderRecipientMock.new();
  430. await this.tokensSenderImplementer.senderFor(this.sender);
  431. await this.senderContract.registerSender(this.tokensSenderImplementer.address);
  432. // For the contract to be able to receive tokens (that it can later send), it must also implement the
  433. // recipient interface.
  434. await this.senderContract.recipientFor(this.sender);
  435. await this.token.send(this.sender, amount, data, { from: holder });
  436. });
  437. shouldBehaveLikeERC777SendBurnWithSendHook(operator, amount, data, operatorData);
  438. });
  439. context('with a contract as implementer for itself', function () {
  440. beforeEach(async function () {
  441. this.tokensSenderImplementer = await ERC777SenderRecipientMock.new();
  442. this.sender = this.tokensSenderImplementer.address;
  443. await this.tokensSenderImplementer.senderFor(this.sender);
  444. // For the contract to be able to receive tokens (that it can later send), it must also implement the
  445. // recipient interface.
  446. await this.tokensSenderImplementer.recipientFor(this.sender);
  447. await this.token.send(this.sender, amount, data, { from: holder });
  448. });
  449. shouldBehaveLikeERC777SendBurnWithSendHook(operator, amount, data, operatorData);
  450. });
  451. });
  452. });
  453. });
  454. context('with no default operators', function () {
  455. beforeEach(async function () {
  456. this.token = await ERC777.new(holder, initialSupply, name, symbol, []);
  457. });
  458. it('default operators list is empty', async function () {
  459. expect(await this.token.defaultOperators()).to.deep.equal([]);
  460. });
  461. });
  462. describe('relative order of hooks', function () {
  463. beforeEach(async function () {
  464. await singletons.ERC1820Registry(registryFunder);
  465. this.sender = await ERC777SenderRecipientMock.new();
  466. await this.sender.registerRecipient(this.sender.address);
  467. await this.sender.registerSender(this.sender.address);
  468. this.token = await ERC777.new(holder, initialSupply, name, symbol, []);
  469. await this.token.send(this.sender.address, 1, '0x', { from: holder });
  470. });
  471. it('send', async function () {
  472. const { receipt } = await this.sender.send(this.token.address, anyone, 1, '0x');
  473. const internalBeforeHook = receipt.logs.findIndex(l => l.event === 'BeforeTokenTransfer');
  474. expect(internalBeforeHook).to.be.gte(0);
  475. const externalSendHook = receipt.logs.findIndex(l => l.event === 'TokensToSendCalled');
  476. expect(externalSendHook).to.be.gte(0);
  477. expect(externalSendHook).to.be.lt(internalBeforeHook);
  478. });
  479. it('burn', async function () {
  480. const { receipt } = await this.sender.burn(this.token.address, 1, '0x');
  481. const internalBeforeHook = receipt.logs.findIndex(l => l.event === 'BeforeTokenTransfer');
  482. expect(internalBeforeHook).to.be.gte(0);
  483. const externalSendHook = receipt.logs.findIndex(l => l.event === 'TokensToSendCalled');
  484. expect(externalSendHook).to.be.gte(0);
  485. expect(externalSendHook).to.be.lt(internalBeforeHook);
  486. });
  487. });
  488. });