ERC777.test.js 18 KB

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