ERC777.test.js 17 KB

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