ERC777.test.js 18 KB

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