ERC1155.behavior.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs');
  4. const {
  5. bigint: { RevertType },
  6. } = require('../../helpers/enums');
  7. const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior');
  8. function shouldBehaveLikeERC1155() {
  9. const firstTokenId = 1n;
  10. const secondTokenId = 2n;
  11. const unknownTokenId = 3n;
  12. const firstTokenValue = 1000n;
  13. const secondTokenValue = 2000n;
  14. const RECEIVER_SINGLE_MAGIC_VALUE = '0xf23a6e61';
  15. const RECEIVER_BATCH_MAGIC_VALUE = '0xbc197c81';
  16. beforeEach(async function () {
  17. [this.recipient, this.proxy, this.alice, this.bruce] = this.otherAccounts;
  18. });
  19. describe('like an ERC1155', function () {
  20. describe('balanceOf', function () {
  21. it('should return 0 when queried about the zero address', async function () {
  22. expect(await this.token.balanceOf(ethers.ZeroAddress, firstTokenId)).to.equal(0n);
  23. });
  24. describe("when accounts don't own tokens", function () {
  25. it('returns zero for given addresses', async function () {
  26. expect(await this.token.balanceOf(this.alice, firstTokenId)).to.equal(0n);
  27. expect(await this.token.balanceOf(this.bruce, secondTokenId)).to.equal(0n);
  28. expect(await this.token.balanceOf(this.alice, unknownTokenId)).to.equal(0n);
  29. });
  30. });
  31. describe('when accounts own some tokens', function () {
  32. beforeEach(async function () {
  33. await this.token.$_mint(this.alice, firstTokenId, firstTokenValue, '0x');
  34. await this.token.$_mint(this.bruce, secondTokenId, secondTokenValue, '0x');
  35. });
  36. it('returns the amount of tokens owned by the given addresses', async function () {
  37. expect(await this.token.balanceOf(this.alice, firstTokenId)).to.equal(firstTokenValue);
  38. expect(await this.token.balanceOf(this.bruce, secondTokenId)).to.equal(secondTokenValue);
  39. expect(await this.token.balanceOf(this.alice, unknownTokenId)).to.equal(0n);
  40. });
  41. });
  42. });
  43. describe('balanceOfBatch', function () {
  44. it("reverts when input arrays don't match up", async function () {
  45. const accounts1 = [this.alice, this.bruce, this.alice, this.bruce];
  46. const ids1 = [firstTokenId, secondTokenId, unknownTokenId];
  47. await expect(this.token.balanceOfBatch(accounts1, ids1))
  48. .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength')
  49. .withArgs(ids1.length, accounts1.length);
  50. const accounts2 = [this.alice, this.bruce];
  51. const ids2 = [firstTokenId, secondTokenId, unknownTokenId];
  52. await expect(this.token.balanceOfBatch(accounts2, ids2))
  53. .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength')
  54. .withArgs(ids2.length, accounts2.length);
  55. });
  56. it('should return 0 as the balance when one of the addresses is the zero address', async function () {
  57. const result = await this.token.balanceOfBatch(
  58. [this.alice, this.bruce, ethers.ZeroAddress],
  59. [firstTokenId, secondTokenId, unknownTokenId],
  60. );
  61. expect(result).to.deep.equal([0n, 0n, 0n]);
  62. });
  63. describe("when accounts don't own tokens", function () {
  64. it('returns zeros for each account', async function () {
  65. const result = await this.token.balanceOfBatch(
  66. [this.alice, this.bruce, this.alice],
  67. [firstTokenId, secondTokenId, unknownTokenId],
  68. );
  69. expect(result).to.deep.equal([0n, 0n, 0n]);
  70. });
  71. });
  72. describe('when accounts own some tokens', function () {
  73. beforeEach(async function () {
  74. await this.token.$_mint(this.alice, firstTokenId, firstTokenValue, '0x');
  75. await this.token.$_mint(this.bruce, secondTokenId, secondTokenValue, '0x');
  76. });
  77. it('returns amounts owned by each account in order passed', async function () {
  78. const result = await this.token.balanceOfBatch(
  79. [this.bruce, this.alice, this.alice],
  80. [secondTokenId, firstTokenId, unknownTokenId],
  81. );
  82. expect(result).to.deep.equal([secondTokenValue, firstTokenValue, 0n]);
  83. });
  84. it('returns multiple times the balance of the same address when asked', async function () {
  85. const result = await this.token.balanceOfBatch(
  86. [this.alice, this.bruce, this.alice],
  87. [firstTokenId, secondTokenId, firstTokenId],
  88. );
  89. expect(result).to.deep.equal([firstTokenValue, secondTokenValue, firstTokenValue]);
  90. });
  91. });
  92. });
  93. describe('setApprovalForAll', function () {
  94. beforeEach(async function () {
  95. this.tx = await this.token.connect(this.holder).setApprovalForAll(this.proxy, true);
  96. });
  97. it('sets approval status which can be queried via isApprovedForAll', async function () {
  98. expect(await this.token.isApprovedForAll(this.holder, this.proxy)).to.be.true;
  99. });
  100. it('emits an ApprovalForAll log', async function () {
  101. await expect(this.tx)
  102. .to.emit(this.token, 'ApprovalForAll')
  103. .withArgs(this.holder.address, this.proxy.address, true);
  104. });
  105. it('can unset approval for an operator', async function () {
  106. await this.token.connect(this.holder).setApprovalForAll(this.proxy, false);
  107. expect(await this.token.isApprovedForAll(this.holder, this.proxy)).to.be.false;
  108. });
  109. it('reverts if attempting to approve zero address as an operator', async function () {
  110. await expect(this.token.connect(this.holder).setApprovalForAll(ethers.ZeroAddress, true))
  111. .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidOperator')
  112. .withArgs(ethers.ZeroAddress);
  113. });
  114. });
  115. describe('safeTransferFrom', function () {
  116. beforeEach(async function () {
  117. await this.token.$_mint(this.holder, firstTokenId, firstTokenValue, '0x');
  118. await this.token.$_mint(this.holder, secondTokenId, secondTokenValue, '0x');
  119. });
  120. it('reverts when transferring more than balance', async function () {
  121. await expect(
  122. this.token
  123. .connect(this.holder)
  124. .safeTransferFrom(this.holder, this.recipient, firstTokenId, firstTokenValue + 1n, '0x'),
  125. )
  126. .to.be.revertedWithCustomError(this.token, 'ERC1155InsufficientBalance')
  127. .withArgs(this.holder.address, firstTokenValue, firstTokenValue + 1n, firstTokenId);
  128. });
  129. it('reverts when transferring to zero address', async function () {
  130. await expect(
  131. this.token
  132. .connect(this.holder)
  133. .safeTransferFrom(this.holder, ethers.ZeroAddress, firstTokenId, firstTokenValue, '0x'),
  134. )
  135. .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver')
  136. .withArgs(ethers.ZeroAddress);
  137. });
  138. function transferWasSuccessful() {
  139. it('debits transferred balance from sender', async function () {
  140. expect(await this.token.balanceOf(this.args.from, this.args.id)).to.equal(0n);
  141. });
  142. it('credits transferred balance to receiver', async function () {
  143. expect(await this.token.balanceOf(this.args.to, this.args.id)).to.equal(this.args.value);
  144. });
  145. it('emits a TransferSingle log', async function () {
  146. await expect(this.tx)
  147. .to.emit(this.token, 'TransferSingle')
  148. .withArgs(
  149. this.args.operator.address ?? this.args.operator.target ?? this.args.operator,
  150. this.args.from.address ?? this.args.from.target ?? this.args.from,
  151. this.args.to.address ?? this.args.to.target ?? this.args.to,
  152. this.args.id,
  153. this.args.value,
  154. );
  155. });
  156. }
  157. describe('when called by the holder', async function () {
  158. beforeEach(async function () {
  159. this.args = {
  160. operator: this.holder,
  161. from: this.holder,
  162. to: this.recipient,
  163. id: firstTokenId,
  164. value: firstTokenValue,
  165. data: '0x',
  166. };
  167. this.tx = await this.token
  168. .connect(this.args.operator)
  169. .safeTransferFrom(this.args.from, this.args.to, this.args.id, this.args.value, this.args.data);
  170. });
  171. transferWasSuccessful();
  172. it('preserves existing balances which are not transferred by holder', async function () {
  173. expect(await this.token.balanceOf(this.holder, secondTokenId)).to.equal(secondTokenValue);
  174. expect(await this.token.balanceOf(this.recipient, secondTokenId)).to.equal(0n);
  175. });
  176. });
  177. describe('when called by an operator on behalf of the holder', function () {
  178. describe('when operator is not approved by holder', function () {
  179. beforeEach(async function () {
  180. await this.token.connect(this.holder).setApprovalForAll(this.proxy, false);
  181. });
  182. it('reverts', async function () {
  183. await expect(
  184. this.token
  185. .connect(this.proxy)
  186. .safeTransferFrom(this.holder, this.recipient, firstTokenId, firstTokenValue, '0x'),
  187. )
  188. .to.be.revertedWithCustomError(this.token, 'ERC1155MissingApprovalForAll')
  189. .withArgs(this.proxy.address, this.holder.address);
  190. });
  191. });
  192. describe('when operator is approved by holder', function () {
  193. beforeEach(async function () {
  194. await this.token.connect(this.holder).setApprovalForAll(this.proxy, true);
  195. this.args = {
  196. operator: this.proxy,
  197. from: this.holder,
  198. to: this.recipient,
  199. id: firstTokenId,
  200. value: firstTokenValue,
  201. data: '0x',
  202. };
  203. this.tx = await this.token
  204. .connect(this.args.operator)
  205. .safeTransferFrom(this.args.from, this.args.to, this.args.id, this.args.value, this.args.data);
  206. });
  207. transferWasSuccessful();
  208. it("preserves operator's balances not involved in the transfer", async function () {
  209. expect(await this.token.balanceOf(this.proxy, firstTokenId)).to.equal(0n);
  210. expect(await this.token.balanceOf(this.proxy, secondTokenId)).to.equal(0n);
  211. });
  212. });
  213. });
  214. describe('when sending to a valid receiver', function () {
  215. beforeEach(async function () {
  216. this.receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
  217. RECEIVER_SINGLE_MAGIC_VALUE,
  218. RECEIVER_BATCH_MAGIC_VALUE,
  219. RevertType.None,
  220. ]);
  221. });
  222. describe('without data', function () {
  223. beforeEach(async function () {
  224. this.args = {
  225. operator: this.holder,
  226. from: this.holder,
  227. to: this.receiver,
  228. id: firstTokenId,
  229. value: firstTokenValue,
  230. data: '0x',
  231. };
  232. this.tx = await this.token
  233. .connect(this.args.operator)
  234. .safeTransferFrom(this.args.from, this.args.to, this.args.id, this.args.value, this.args.data);
  235. });
  236. transferWasSuccessful();
  237. it('calls onERC1155Received', async function () {
  238. await expect(this.tx)
  239. .to.emit(this.receiver, 'Received')
  240. .withArgs(
  241. this.args.operator.address,
  242. this.args.from.address,
  243. this.args.id,
  244. this.args.value,
  245. this.args.data,
  246. anyValue,
  247. );
  248. });
  249. });
  250. describe('with data', function () {
  251. beforeEach(async function () {
  252. this.args = {
  253. operator: this.holder,
  254. from: this.holder,
  255. to: this.receiver,
  256. id: firstTokenId,
  257. value: firstTokenValue,
  258. data: '0xf00dd00d',
  259. };
  260. this.tx = await this.token
  261. .connect(this.args.operator)
  262. .safeTransferFrom(this.args.from, this.args.to, this.args.id, this.args.value, this.args.data);
  263. });
  264. transferWasSuccessful();
  265. it('calls onERC1155Received', async function () {
  266. await expect(this.tx)
  267. .to.emit(this.receiver, 'Received')
  268. .withArgs(
  269. this.args.operator.address,
  270. this.args.from.address,
  271. this.args.id,
  272. this.args.value,
  273. this.args.data,
  274. anyValue,
  275. );
  276. });
  277. });
  278. });
  279. describe('to a receiver contract returning unexpected value', function () {
  280. it('reverts', async function () {
  281. const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
  282. '0x00c0ffee',
  283. RECEIVER_BATCH_MAGIC_VALUE,
  284. RevertType.None,
  285. ]);
  286. await expect(
  287. this.token
  288. .connect(this.holder)
  289. .safeTransferFrom(this.holder, receiver, firstTokenId, firstTokenValue, '0x'),
  290. )
  291. .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver')
  292. .withArgs(receiver.target);
  293. });
  294. });
  295. describe('to a receiver contract that reverts', function () {
  296. describe('with a revert string', function () {
  297. it('reverts', async function () {
  298. const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
  299. RECEIVER_SINGLE_MAGIC_VALUE,
  300. RECEIVER_BATCH_MAGIC_VALUE,
  301. RevertType.RevertWithMessage,
  302. ]);
  303. await expect(
  304. this.token
  305. .connect(this.holder)
  306. .safeTransferFrom(this.holder, receiver, firstTokenId, firstTokenValue, '0x'),
  307. ).to.be.revertedWith('ERC1155ReceiverMock: reverting on receive');
  308. });
  309. });
  310. describe('without a revert string', function () {
  311. it('reverts', async function () {
  312. const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
  313. RECEIVER_SINGLE_MAGIC_VALUE,
  314. RECEIVER_BATCH_MAGIC_VALUE,
  315. RevertType.RevertWithoutMessage,
  316. ]);
  317. await expect(
  318. this.token
  319. .connect(this.holder)
  320. .safeTransferFrom(this.holder, receiver, firstTokenId, firstTokenValue, '0x'),
  321. )
  322. .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver')
  323. .withArgs(receiver.target);
  324. });
  325. });
  326. describe('with a custom error', function () {
  327. it('reverts', async function () {
  328. const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
  329. RECEIVER_SINGLE_MAGIC_VALUE,
  330. RECEIVER_BATCH_MAGIC_VALUE,
  331. RevertType.RevertWithCustomError,
  332. ]);
  333. await expect(
  334. this.token
  335. .connect(this.holder)
  336. .safeTransferFrom(this.holder, receiver, firstTokenId, firstTokenValue, '0x'),
  337. )
  338. .to.be.revertedWithCustomError(receiver, 'CustomError')
  339. .withArgs(RECEIVER_SINGLE_MAGIC_VALUE);
  340. });
  341. });
  342. describe('with a panic', function () {
  343. it('reverts', async function () {
  344. const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
  345. RECEIVER_SINGLE_MAGIC_VALUE,
  346. RECEIVER_BATCH_MAGIC_VALUE,
  347. RevertType.Panic,
  348. ]);
  349. await expect(
  350. this.token
  351. .connect(this.holder)
  352. .safeTransferFrom(this.holder, receiver, firstTokenId, firstTokenValue, '0x'),
  353. ).to.be.revertedWithPanic();
  354. });
  355. });
  356. });
  357. describe('to a contract that does not implement the required function', function () {
  358. it('reverts', async function () {
  359. const invalidReceiver = this.token.target;
  360. await expect(
  361. this.token
  362. .connect(this.holder)
  363. .safeTransferFrom(this.holder, invalidReceiver, firstTokenId, firstTokenValue, '0x'),
  364. )
  365. .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver')
  366. .withArgs(invalidReceiver);
  367. });
  368. });
  369. });
  370. describe('safeBatchTransferFrom', function () {
  371. beforeEach(async function () {
  372. await this.token.$_mint(this.holder, firstTokenId, firstTokenValue, '0x');
  373. await this.token.$_mint(this.holder, secondTokenId, secondTokenValue, '0x');
  374. });
  375. it('reverts when transferring value more than any of balances', async function () {
  376. await expect(
  377. this.token
  378. .connect(this.holder)
  379. .safeBatchTransferFrom(
  380. this.holder,
  381. this.recipient,
  382. [firstTokenId, secondTokenId],
  383. [firstTokenValue, secondTokenValue + 1n],
  384. '0x',
  385. ),
  386. )
  387. .to.be.revertedWithCustomError(this.token, 'ERC1155InsufficientBalance')
  388. .withArgs(this.holder.address, secondTokenValue, secondTokenValue + 1n, secondTokenId);
  389. });
  390. it("reverts when ids array length doesn't match values array length", async function () {
  391. const ids1 = [firstTokenId];
  392. const tokenValues1 = [firstTokenValue, secondTokenValue];
  393. await expect(
  394. this.token.connect(this.holder).safeBatchTransferFrom(this.holder, this.recipient, ids1, tokenValues1, '0x'),
  395. )
  396. .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength')
  397. .withArgs(ids1.length, tokenValues1.length);
  398. const ids2 = [firstTokenId, secondTokenId];
  399. const tokenValues2 = [firstTokenValue];
  400. await expect(
  401. this.token.connect(this.holder).safeBatchTransferFrom(this.holder, this.recipient, ids2, tokenValues2, '0x'),
  402. )
  403. .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength')
  404. .withArgs(ids2.length, tokenValues2.length);
  405. });
  406. it('reverts when transferring to zero address', async function () {
  407. await expect(
  408. this.token
  409. .connect(this.holder)
  410. .safeBatchTransferFrom(
  411. this.holder,
  412. ethers.ZeroAddress,
  413. [firstTokenId, secondTokenId],
  414. [firstTokenValue, secondTokenValue],
  415. '0x',
  416. ),
  417. )
  418. .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver')
  419. .withArgs(ethers.ZeroAddress);
  420. });
  421. it('reverts when transferring from zero address', async function () {
  422. await expect(
  423. this.token.$_safeBatchTransferFrom(ethers.ZeroAddress, this.holder, [firstTokenId], [firstTokenValue], '0x'),
  424. )
  425. .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidSender')
  426. .withArgs(ethers.ZeroAddress);
  427. });
  428. function batchTransferWasSuccessful() {
  429. it('debits transferred balances from sender', async function () {
  430. const newBalances = await this.token.balanceOfBatch(
  431. this.args.ids.map(() => this.args.from),
  432. this.args.ids,
  433. );
  434. expect(newBalances).to.deep.equal(this.args.ids.map(() => 0n));
  435. });
  436. it('credits transferred balances to receiver', async function () {
  437. const newBalances = await this.token.balanceOfBatch(
  438. this.args.ids.map(() => this.args.to),
  439. this.args.ids,
  440. );
  441. expect(newBalances).to.deep.equal(this.args.values);
  442. });
  443. it('emits a TransferBatch log', async function () {
  444. await expect(this.tx)
  445. .to.emit(this.token, 'TransferBatch')
  446. .withArgs(
  447. this.args.operator.address ?? this.args.operator.target ?? this.args.operator,
  448. this.args.from.address ?? this.args.from.target ?? this.args.from,
  449. this.args.to.address ?? this.args.to.target ?? this.args.to,
  450. this.args.ids,
  451. this.args.values,
  452. );
  453. });
  454. }
  455. describe('when called by the holder', async function () {
  456. beforeEach(async function () {
  457. this.args = {
  458. operator: this.holder,
  459. from: this.holder,
  460. to: this.recipient,
  461. ids: [firstTokenId, secondTokenId],
  462. values: [firstTokenValue, secondTokenValue],
  463. data: '0x',
  464. };
  465. this.tx = await this.token
  466. .connect(this.args.operator)
  467. .safeBatchTransferFrom(this.args.from, this.args.to, this.args.ids, this.args.values, this.args.data);
  468. });
  469. batchTransferWasSuccessful();
  470. });
  471. describe('when called by an operator on behalf of the holder', function () {
  472. describe('when operator is not approved by holder', function () {
  473. beforeEach(async function () {
  474. await this.token.connect(this.holder).setApprovalForAll(this.proxy, false);
  475. });
  476. it('reverts', async function () {
  477. await expect(
  478. this.token
  479. .connect(this.proxy)
  480. .safeBatchTransferFrom(
  481. this.holder,
  482. this.recipient,
  483. [firstTokenId, secondTokenId],
  484. [firstTokenValue, secondTokenValue],
  485. '0x',
  486. ),
  487. )
  488. .to.be.revertedWithCustomError(this.token, 'ERC1155MissingApprovalForAll')
  489. .withArgs(this.proxy.address, this.holder.address);
  490. });
  491. });
  492. describe('when operator is approved by holder', function () {
  493. beforeEach(async function () {
  494. await this.token.connect(this.holder).setApprovalForAll(this.proxy, true);
  495. this.args = {
  496. operator: this.proxy,
  497. from: this.holder,
  498. to: this.recipient,
  499. ids: [firstTokenId, secondTokenId],
  500. values: [firstTokenValue, secondTokenValue],
  501. data: '0x',
  502. };
  503. this.tx = await this.token
  504. .connect(this.args.operator)
  505. .safeBatchTransferFrom(this.args.from, this.args.to, this.args.ids, this.args.values, this.args.data);
  506. });
  507. batchTransferWasSuccessful();
  508. it("preserves operator's balances not involved in the transfer", async function () {
  509. expect(await this.token.balanceOf(this.proxy, firstTokenId)).to.equal(0n);
  510. expect(await this.token.balanceOf(this.proxy, secondTokenId)).to.equal(0n);
  511. });
  512. });
  513. });
  514. describe('when sending to a valid receiver', function () {
  515. beforeEach(async function () {
  516. this.receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
  517. RECEIVER_SINGLE_MAGIC_VALUE,
  518. RECEIVER_BATCH_MAGIC_VALUE,
  519. RevertType.None,
  520. ]);
  521. });
  522. describe('without data', function () {
  523. beforeEach(async function () {
  524. this.args = {
  525. operator: this.holder,
  526. from: this.holder,
  527. to: this.receiver,
  528. ids: [firstTokenId, secondTokenId],
  529. values: [firstTokenValue, secondTokenValue],
  530. data: '0x',
  531. };
  532. this.tx = await this.token
  533. .connect(this.args.operator)
  534. .safeBatchTransferFrom(this.args.from, this.args.to, this.args.ids, this.args.values, this.args.data);
  535. });
  536. batchTransferWasSuccessful();
  537. it('calls onERC1155BatchReceived', async function () {
  538. await expect(this.tx)
  539. .to.emit(this.receiver, 'BatchReceived')
  540. .withArgs(
  541. this.holder.address,
  542. this.holder.address,
  543. this.args.ids,
  544. this.args.values,
  545. this.args.data,
  546. anyValue,
  547. );
  548. });
  549. });
  550. describe('with data', function () {
  551. beforeEach(async function () {
  552. this.args = {
  553. operator: this.holder,
  554. from: this.holder,
  555. to: this.receiver,
  556. ids: [firstTokenId, secondTokenId],
  557. values: [firstTokenValue, secondTokenValue],
  558. data: '0xf00dd00d',
  559. };
  560. this.tx = await this.token
  561. .connect(this.args.operator)
  562. .safeBatchTransferFrom(this.args.from, this.args.to, this.args.ids, this.args.values, this.args.data);
  563. });
  564. batchTransferWasSuccessful();
  565. it('calls onERC1155Received', async function () {
  566. await expect(this.tx)
  567. .to.emit(this.receiver, 'BatchReceived')
  568. .withArgs(
  569. this.holder.address,
  570. this.holder.address,
  571. this.args.ids,
  572. this.args.values,
  573. this.args.data,
  574. anyValue,
  575. );
  576. });
  577. });
  578. });
  579. describe('to a receiver contract returning unexpected value', function () {
  580. it('reverts', async function () {
  581. const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
  582. RECEIVER_SINGLE_MAGIC_VALUE,
  583. RECEIVER_SINGLE_MAGIC_VALUE,
  584. RevertType.None,
  585. ]);
  586. await expect(
  587. this.token
  588. .connect(this.holder)
  589. .safeBatchTransferFrom(
  590. this.holder,
  591. receiver,
  592. [firstTokenId, secondTokenId],
  593. [firstTokenValue, secondTokenValue],
  594. '0x',
  595. ),
  596. )
  597. .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver')
  598. .withArgs(receiver.target);
  599. });
  600. });
  601. describe('to a receiver contract that reverts', function () {
  602. describe('with a revert string', function () {
  603. it('reverts', async function () {
  604. const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
  605. RECEIVER_SINGLE_MAGIC_VALUE,
  606. RECEIVER_BATCH_MAGIC_VALUE,
  607. RevertType.RevertWithMessage,
  608. ]);
  609. await expect(
  610. this.token
  611. .connect(this.holder)
  612. .safeBatchTransferFrom(
  613. this.holder,
  614. receiver,
  615. [firstTokenId, secondTokenId],
  616. [firstTokenValue, secondTokenValue],
  617. '0x',
  618. ),
  619. ).to.be.revertedWith('ERC1155ReceiverMock: reverting on batch receive');
  620. });
  621. });
  622. describe('without a revert string', function () {
  623. it('reverts', async function () {
  624. const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
  625. RECEIVER_SINGLE_MAGIC_VALUE,
  626. RECEIVER_BATCH_MAGIC_VALUE,
  627. RevertType.RevertWithoutMessage,
  628. ]);
  629. await expect(
  630. this.token
  631. .connect(this.holder)
  632. .safeBatchTransferFrom(
  633. this.holder,
  634. receiver,
  635. [firstTokenId, secondTokenId],
  636. [firstTokenValue, secondTokenValue],
  637. '0x',
  638. ),
  639. )
  640. .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver')
  641. .withArgs(receiver.target);
  642. });
  643. });
  644. describe('with a custom error', function () {
  645. it('reverts', async function () {
  646. const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
  647. RECEIVER_SINGLE_MAGIC_VALUE,
  648. RECEIVER_BATCH_MAGIC_VALUE,
  649. RevertType.RevertWithCustomError,
  650. ]);
  651. await expect(
  652. this.token
  653. .connect(this.holder)
  654. .safeBatchTransferFrom(
  655. this.holder,
  656. receiver,
  657. [firstTokenId, secondTokenId],
  658. [firstTokenValue, secondTokenValue],
  659. '0x',
  660. ),
  661. )
  662. .to.be.revertedWithCustomError(receiver, 'CustomError')
  663. .withArgs(RECEIVER_SINGLE_MAGIC_VALUE);
  664. });
  665. });
  666. describe('with a panic', function () {
  667. it('reverts', async function () {
  668. const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [
  669. RECEIVER_SINGLE_MAGIC_VALUE,
  670. RECEIVER_BATCH_MAGIC_VALUE,
  671. RevertType.Panic,
  672. ]);
  673. await expect(
  674. this.token
  675. .connect(this.holder)
  676. .safeBatchTransferFrom(
  677. this.holder,
  678. receiver,
  679. [firstTokenId, secondTokenId],
  680. [firstTokenValue, secondTokenValue],
  681. '0x',
  682. ),
  683. ).to.be.revertedWithPanic();
  684. });
  685. });
  686. });
  687. describe('to a contract that does not implement the required function', function () {
  688. it('reverts', async function () {
  689. const invalidReceiver = this.token.target;
  690. await expect(
  691. this.token
  692. .connect(this.holder)
  693. .safeBatchTransferFrom(
  694. this.holder,
  695. invalidReceiver,
  696. [firstTokenId, secondTokenId],
  697. [firstTokenValue, secondTokenValue],
  698. '0x',
  699. ),
  700. )
  701. .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver')
  702. .withArgs(invalidReceiver);
  703. });
  704. });
  705. });
  706. shouldSupportInterfaces(['ERC165', 'ERC1155', 'ERC1155MetadataURI']);
  707. });
  708. }
  709. module.exports = {
  710. shouldBehaveLikeERC1155,
  711. };