ERC721.behavior.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945
  1. const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
  2. const { expect } = require('chai');
  3. const { ZERO_ADDRESS } = constants;
  4. const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior');
  5. const ERC721ReceiverMock = artifacts.require('ERC721ReceiverMock');
  6. const Error = [ 'None', 'RevertWithMessage', 'RevertWithoutMessage', 'Panic' ]
  7. .reduce((acc, entry, idx) => Object.assign({ [entry]: idx }, acc), {});
  8. const firstTokenId = new BN('5042');
  9. const secondTokenId = new BN('79217');
  10. const nonExistentTokenId = new BN('13');
  11. const fourthTokenId = new BN(4);
  12. const baseURI = 'https://api.example.com/v1/';
  13. const RECEIVER_MAGIC_VALUE = '0x150b7a02';
  14. function shouldBehaveLikeERC721 (errorPrefix, owner, newOwner, approved, anotherApproved, operator, other) {
  15. shouldSupportInterfaces([
  16. 'ERC165',
  17. 'ERC721',
  18. ]);
  19. context('with minted tokens', function () {
  20. beforeEach(async function () {
  21. await this.token.mint(owner, firstTokenId);
  22. await this.token.mint(owner, secondTokenId);
  23. this.toWhom = other; // default to other for toWhom in context-dependent tests
  24. });
  25. describe('balanceOf', function () {
  26. context('when the given address owns some tokens', function () {
  27. it('returns the amount of tokens owned by the given address', async function () {
  28. expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('2');
  29. });
  30. });
  31. context('when the given address does not own any tokens', function () {
  32. it('returns 0', async function () {
  33. expect(await this.token.balanceOf(other)).to.be.bignumber.equal('0');
  34. });
  35. });
  36. context('when querying the zero address', function () {
  37. it('throws', async function () {
  38. await expectRevert(
  39. this.token.balanceOf(ZERO_ADDRESS), 'ERC721: address zero is not a valid owner',
  40. );
  41. });
  42. });
  43. });
  44. describe('ownerOf', function () {
  45. context('when the given token ID was tracked by this token', function () {
  46. const tokenId = firstTokenId;
  47. it('returns the owner of the given token ID', async function () {
  48. expect(await this.token.ownerOf(tokenId)).to.be.equal(owner);
  49. });
  50. });
  51. context('when the given token ID was not tracked by this token', function () {
  52. const tokenId = nonExistentTokenId;
  53. it('reverts', async function () {
  54. await expectRevert(
  55. this.token.ownerOf(tokenId), 'ERC721: owner query for nonexistent token',
  56. );
  57. });
  58. });
  59. });
  60. describe('transfers', function () {
  61. const tokenId = firstTokenId;
  62. const data = '0x42';
  63. let receipt = null;
  64. beforeEach(async function () {
  65. await this.token.approve(approved, tokenId, { from: owner });
  66. await this.token.setApprovalForAll(operator, true, { from: owner });
  67. });
  68. const transferWasSuccessful = function ({ owner, tokenId, approved }) {
  69. it('transfers the ownership of the given token ID to the given address', async function () {
  70. expect(await this.token.ownerOf(tokenId)).to.be.equal(this.toWhom);
  71. });
  72. it('emits a Transfer event', async function () {
  73. expectEvent(receipt, 'Transfer', { from: owner, to: this.toWhom, tokenId: tokenId });
  74. });
  75. it('clears the approval for the token ID', async function () {
  76. expect(await this.token.getApproved(tokenId)).to.be.equal(ZERO_ADDRESS);
  77. });
  78. it('emits an Approval event', async function () {
  79. expectEvent(receipt, 'Approval', { owner, approved: ZERO_ADDRESS, tokenId: tokenId });
  80. });
  81. it('adjusts owners balances', async function () {
  82. expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('1');
  83. });
  84. it('adjusts owners tokens by index', async function () {
  85. if (!this.token.tokenOfOwnerByIndex) return;
  86. expect(await this.token.tokenOfOwnerByIndex(this.toWhom, 0)).to.be.bignumber.equal(tokenId);
  87. expect(await this.token.tokenOfOwnerByIndex(owner, 0)).to.be.bignumber.not.equal(tokenId);
  88. });
  89. };
  90. const shouldTransferTokensByUsers = function (transferFunction) {
  91. context('when called by the owner', function () {
  92. beforeEach(async function () {
  93. (receipt = await transferFunction.call(this, owner, this.toWhom, tokenId, { from: owner }));
  94. });
  95. transferWasSuccessful({ owner, tokenId, approved });
  96. });
  97. context('when called by the approved individual', function () {
  98. beforeEach(async function () {
  99. (receipt = await transferFunction.call(this, owner, this.toWhom, tokenId, { from: approved }));
  100. });
  101. transferWasSuccessful({ owner, tokenId, approved });
  102. });
  103. context('when called by the operator', function () {
  104. beforeEach(async function () {
  105. (receipt = await transferFunction.call(this, owner, this.toWhom, tokenId, { from: operator }));
  106. });
  107. transferWasSuccessful({ owner, tokenId, approved });
  108. });
  109. context('when called by the owner without an approved user', function () {
  110. beforeEach(async function () {
  111. await this.token.approve(ZERO_ADDRESS, tokenId, { from: owner });
  112. (receipt = await transferFunction.call(this, owner, this.toWhom, tokenId, { from: operator }));
  113. });
  114. transferWasSuccessful({ owner, tokenId, approved: null });
  115. });
  116. context('when sent to the owner', function () {
  117. beforeEach(async function () {
  118. (receipt = await transferFunction.call(this, owner, owner, tokenId, { from: owner }));
  119. });
  120. it('keeps ownership of the token', async function () {
  121. expect(await this.token.ownerOf(tokenId)).to.be.equal(owner);
  122. });
  123. it('clears the approval for the token ID', async function () {
  124. expect(await this.token.getApproved(tokenId)).to.be.equal(ZERO_ADDRESS);
  125. });
  126. it('emits only a transfer event', async function () {
  127. expectEvent(receipt, 'Transfer', {
  128. from: owner,
  129. to: owner,
  130. tokenId: tokenId,
  131. });
  132. });
  133. it('keeps the owner balance', async function () {
  134. expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('2');
  135. });
  136. it('keeps same tokens by index', async function () {
  137. if (!this.token.tokenOfOwnerByIndex) return;
  138. const tokensListed = await Promise.all(
  139. [0, 1].map(i => this.token.tokenOfOwnerByIndex(owner, i)),
  140. );
  141. expect(tokensListed.map(t => t.toNumber())).to.have.members(
  142. [firstTokenId.toNumber(), secondTokenId.toNumber()],
  143. );
  144. });
  145. });
  146. context('when the address of the previous owner is incorrect', function () {
  147. it('reverts', async function () {
  148. await expectRevert(
  149. transferFunction.call(this, other, other, tokenId, { from: owner }),
  150. 'ERC721: transfer from incorrect owner',
  151. );
  152. });
  153. });
  154. context('when the sender is not authorized for the token id', function () {
  155. it('reverts', async function () {
  156. await expectRevert(
  157. transferFunction.call(this, owner, other, tokenId, { from: other }),
  158. 'ERC721: transfer caller is not owner nor approved',
  159. );
  160. });
  161. });
  162. context('when the given token ID does not exist', function () {
  163. it('reverts', async function () {
  164. await expectRevert(
  165. transferFunction.call(this, owner, other, nonExistentTokenId, { from: owner }),
  166. 'ERC721: operator query for nonexistent token',
  167. );
  168. });
  169. });
  170. context('when the address to transfer the token to is the zero address', function () {
  171. it('reverts', async function () {
  172. await expectRevert(
  173. transferFunction.call(this, owner, ZERO_ADDRESS, tokenId, { from: owner }),
  174. 'ERC721: transfer to the zero address',
  175. );
  176. });
  177. });
  178. };
  179. describe('via transferFrom', function () {
  180. shouldTransferTokensByUsers(function (from, to, tokenId, opts) {
  181. return this.token.transferFrom(from, to, tokenId, opts);
  182. });
  183. });
  184. describe('via safeTransferFrom', function () {
  185. const safeTransferFromWithData = function (from, to, tokenId, opts) {
  186. return this.token.methods['safeTransferFrom(address,address,uint256,bytes)'](from, to, tokenId, data, opts);
  187. };
  188. const safeTransferFromWithoutData = function (from, to, tokenId, opts) {
  189. return this.token.methods['safeTransferFrom(address,address,uint256)'](from, to, tokenId, opts);
  190. };
  191. const shouldTransferSafely = function (transferFun, data) {
  192. describe('to a user account', function () {
  193. shouldTransferTokensByUsers(transferFun);
  194. });
  195. describe('to a valid receiver contract', function () {
  196. beforeEach(async function () {
  197. this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.None);
  198. this.toWhom = this.receiver.address;
  199. });
  200. shouldTransferTokensByUsers(transferFun);
  201. it('calls onERC721Received', async function () {
  202. const receipt = await transferFun.call(this, owner, this.receiver.address, tokenId, { from: owner });
  203. await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', {
  204. operator: owner,
  205. from: owner,
  206. tokenId: tokenId,
  207. data: data,
  208. });
  209. });
  210. it('calls onERC721Received from approved', async function () {
  211. const receipt = await transferFun.call(this, owner, this.receiver.address, tokenId, { from: approved });
  212. await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', {
  213. operator: approved,
  214. from: owner,
  215. tokenId: tokenId,
  216. data: data,
  217. });
  218. });
  219. describe('with an invalid token id', function () {
  220. it('reverts', async function () {
  221. await expectRevert(
  222. transferFun.call(
  223. this,
  224. owner,
  225. this.receiver.address,
  226. nonExistentTokenId,
  227. { from: owner },
  228. ),
  229. 'ERC721: operator query for nonexistent token',
  230. );
  231. });
  232. });
  233. });
  234. };
  235. describe('with data', function () {
  236. shouldTransferSafely(safeTransferFromWithData, data);
  237. });
  238. describe('without data', function () {
  239. shouldTransferSafely(safeTransferFromWithoutData, null);
  240. });
  241. describe('to a receiver contract returning unexpected value', function () {
  242. it('reverts', async function () {
  243. const invalidReceiver = await ERC721ReceiverMock.new('0x42', Error.None);
  244. await expectRevert(
  245. this.token.safeTransferFrom(owner, invalidReceiver.address, tokenId, { from: owner }),
  246. 'ERC721: transfer to non ERC721Receiver implementer',
  247. );
  248. });
  249. });
  250. describe('to a receiver contract that reverts with message', function () {
  251. it('reverts', async function () {
  252. const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.RevertWithMessage);
  253. await expectRevert(
  254. this.token.safeTransferFrom(owner, revertingReceiver.address, tokenId, { from: owner }),
  255. 'ERC721ReceiverMock: reverting',
  256. );
  257. });
  258. });
  259. describe('to a receiver contract that reverts without message', function () {
  260. it('reverts', async function () {
  261. const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.RevertWithoutMessage);
  262. await expectRevert(
  263. this.token.safeTransferFrom(owner, revertingReceiver.address, tokenId, { from: owner }),
  264. 'ERC721: transfer to non ERC721Receiver implementer',
  265. );
  266. });
  267. });
  268. describe('to a receiver contract that panics', function () {
  269. it('reverts', async function () {
  270. const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.Panic);
  271. await expectRevert.unspecified(
  272. this.token.safeTransferFrom(owner, revertingReceiver.address, tokenId, { from: owner }),
  273. );
  274. });
  275. });
  276. describe('to a contract that does not implement the required function', function () {
  277. it('reverts', async function () {
  278. const nonReceiver = this.token;
  279. await expectRevert(
  280. this.token.safeTransferFrom(owner, nonReceiver.address, tokenId, { from: owner }),
  281. 'ERC721: transfer to non ERC721Receiver implementer',
  282. );
  283. });
  284. });
  285. });
  286. });
  287. describe('safe mint', function () {
  288. const tokenId = fourthTokenId;
  289. const data = '0x42';
  290. describe('via safeMint', function () { // regular minting is tested in ERC721Mintable.test.js and others
  291. it('calls onERC721Received — with data', async function () {
  292. this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.None);
  293. const receipt = await this.token.safeMint(this.receiver.address, tokenId, data);
  294. await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', {
  295. from: ZERO_ADDRESS,
  296. tokenId: tokenId,
  297. data: data,
  298. });
  299. });
  300. it('calls onERC721Received — without data', async function () {
  301. this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.None);
  302. const receipt = await this.token.safeMint(this.receiver.address, tokenId);
  303. await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', {
  304. from: ZERO_ADDRESS,
  305. tokenId: tokenId,
  306. });
  307. });
  308. context('to a receiver contract returning unexpected value', function () {
  309. it('reverts', async function () {
  310. const invalidReceiver = await ERC721ReceiverMock.new('0x42', Error.None);
  311. await expectRevert(
  312. this.token.safeMint(invalidReceiver.address, tokenId),
  313. 'ERC721: transfer to non ERC721Receiver implementer',
  314. );
  315. });
  316. });
  317. context('to a receiver contract that reverts with message', function () {
  318. it('reverts', async function () {
  319. const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.RevertWithMessage);
  320. await expectRevert(
  321. this.token.safeMint(revertingReceiver.address, tokenId),
  322. 'ERC721ReceiverMock: reverting',
  323. );
  324. });
  325. });
  326. context('to a receiver contract that reverts without message', function () {
  327. it('reverts', async function () {
  328. const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.RevertWithoutMessage);
  329. await expectRevert(
  330. this.token.safeMint(revertingReceiver.address, tokenId),
  331. 'ERC721: transfer to non ERC721Receiver implementer',
  332. );
  333. });
  334. });
  335. context('to a receiver contract that panics', function () {
  336. it('reverts', async function () {
  337. const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.Panic);
  338. await expectRevert.unspecified(
  339. this.token.safeMint(revertingReceiver.address, tokenId),
  340. );
  341. });
  342. });
  343. context('to a contract that does not implement the required function', function () {
  344. it('reverts', async function () {
  345. const nonReceiver = this.token;
  346. await expectRevert(
  347. this.token.safeMint(nonReceiver.address, tokenId),
  348. 'ERC721: transfer to non ERC721Receiver implementer',
  349. );
  350. });
  351. });
  352. });
  353. });
  354. describe('approve', function () {
  355. const tokenId = firstTokenId;
  356. let receipt = null;
  357. const itClearsApproval = function () {
  358. it('clears approval for the token', async function () {
  359. expect(await this.token.getApproved(tokenId)).to.be.equal(ZERO_ADDRESS);
  360. });
  361. };
  362. const itApproves = function (address) {
  363. it('sets the approval for the target address', async function () {
  364. expect(await this.token.getApproved(tokenId)).to.be.equal(address);
  365. });
  366. };
  367. const itEmitsApprovalEvent = function (address) {
  368. it('emits an approval event', async function () {
  369. expectEvent(receipt, 'Approval', {
  370. owner: owner,
  371. approved: address,
  372. tokenId: tokenId,
  373. });
  374. });
  375. };
  376. context('when clearing approval', function () {
  377. context('when there was no prior approval', function () {
  378. beforeEach(async function () {
  379. (receipt = await this.token.approve(ZERO_ADDRESS, tokenId, { from: owner }));
  380. });
  381. itClearsApproval();
  382. itEmitsApprovalEvent(ZERO_ADDRESS);
  383. });
  384. context('when there was a prior approval', function () {
  385. beforeEach(async function () {
  386. await this.token.approve(approved, tokenId, { from: owner });
  387. (receipt = await this.token.approve(ZERO_ADDRESS, tokenId, { from: owner }));
  388. });
  389. itClearsApproval();
  390. itEmitsApprovalEvent(ZERO_ADDRESS);
  391. });
  392. });
  393. context('when approving a non-zero address', function () {
  394. context('when there was no prior approval', function () {
  395. beforeEach(async function () {
  396. (receipt = await this.token.approve(approved, tokenId, { from: owner }));
  397. });
  398. itApproves(approved);
  399. itEmitsApprovalEvent(approved);
  400. });
  401. context('when there was a prior approval to the same address', function () {
  402. beforeEach(async function () {
  403. await this.token.approve(approved, tokenId, { from: owner });
  404. (receipt = await this.token.approve(approved, tokenId, { from: owner }));
  405. });
  406. itApproves(approved);
  407. itEmitsApprovalEvent(approved);
  408. });
  409. context('when there was a prior approval to a different address', function () {
  410. beforeEach(async function () {
  411. await this.token.approve(anotherApproved, tokenId, { from: owner });
  412. (receipt = await this.token.approve(anotherApproved, tokenId, { from: owner }));
  413. });
  414. itApproves(anotherApproved);
  415. itEmitsApprovalEvent(anotherApproved);
  416. });
  417. });
  418. context('when the address that receives the approval is the owner', function () {
  419. it('reverts', async function () {
  420. await expectRevert(
  421. this.token.approve(owner, tokenId, { from: owner }), 'ERC721: approval to current owner',
  422. );
  423. });
  424. });
  425. context('when the sender does not own the given token ID', function () {
  426. it('reverts', async function () {
  427. await expectRevert(this.token.approve(approved, tokenId, { from: other }),
  428. 'ERC721: approve caller is not owner nor approved');
  429. });
  430. });
  431. context('when the sender is approved for the given token ID', function () {
  432. it('reverts', async function () {
  433. await this.token.approve(approved, tokenId, { from: owner });
  434. await expectRevert(this.token.approve(anotherApproved, tokenId, { from: approved }),
  435. 'ERC721: approve caller is not owner nor approved for all');
  436. });
  437. });
  438. context('when the sender is an operator', function () {
  439. beforeEach(async function () {
  440. await this.token.setApprovalForAll(operator, true, { from: owner });
  441. (receipt = await this.token.approve(approved, tokenId, { from: operator }));
  442. });
  443. itApproves(approved);
  444. itEmitsApprovalEvent(approved);
  445. });
  446. context('when the given token ID does not exist', function () {
  447. it('reverts', async function () {
  448. await expectRevert(this.token.approve(approved, nonExistentTokenId, { from: operator }),
  449. 'ERC721: owner query for nonexistent token');
  450. });
  451. });
  452. });
  453. describe('setApprovalForAll', function () {
  454. context('when the operator willing to approve is not the owner', function () {
  455. context('when there is no operator approval set by the sender', function () {
  456. it('approves the operator', async function () {
  457. await this.token.setApprovalForAll(operator, true, { from: owner });
  458. expect(await this.token.isApprovedForAll(owner, operator)).to.equal(true);
  459. });
  460. it('emits an approval event', async function () {
  461. const receipt = await this.token.setApprovalForAll(operator, true, { from: owner });
  462. expectEvent(receipt, 'ApprovalForAll', {
  463. owner: owner,
  464. operator: operator,
  465. approved: true,
  466. });
  467. });
  468. });
  469. context('when the operator was set as not approved', function () {
  470. beforeEach(async function () {
  471. await this.token.setApprovalForAll(operator, false, { from: owner });
  472. });
  473. it('approves the operator', async function () {
  474. await this.token.setApprovalForAll(operator, true, { from: owner });
  475. expect(await this.token.isApprovedForAll(owner, operator)).to.equal(true);
  476. });
  477. it('emits an approval event', async function () {
  478. const receipt = await this.token.setApprovalForAll(operator, true, { from: owner });
  479. expectEvent(receipt, 'ApprovalForAll', {
  480. owner: owner,
  481. operator: operator,
  482. approved: true,
  483. });
  484. });
  485. it('can unset the operator approval', async function () {
  486. await this.token.setApprovalForAll(operator, false, { from: owner });
  487. expect(await this.token.isApprovedForAll(owner, operator)).to.equal(false);
  488. });
  489. });
  490. context('when the operator was already approved', function () {
  491. beforeEach(async function () {
  492. await this.token.setApprovalForAll(operator, true, { from: owner });
  493. });
  494. it('keeps the approval to the given address', async function () {
  495. await this.token.setApprovalForAll(operator, true, { from: owner });
  496. expect(await this.token.isApprovedForAll(owner, operator)).to.equal(true);
  497. });
  498. it('emits an approval event', async function () {
  499. const receipt = await this.token.setApprovalForAll(operator, true, { from: owner });
  500. expectEvent(receipt, 'ApprovalForAll', {
  501. owner: owner,
  502. operator: operator,
  503. approved: true,
  504. });
  505. });
  506. });
  507. });
  508. context('when the operator is the owner', function () {
  509. it('reverts', async function () {
  510. await expectRevert(this.token.setApprovalForAll(owner, true, { from: owner }),
  511. 'ERC721: approve to caller');
  512. });
  513. });
  514. });
  515. describe('getApproved', async function () {
  516. context('when token is not minted', async function () {
  517. it('reverts', async function () {
  518. await expectRevert(
  519. this.token.getApproved(nonExistentTokenId),
  520. 'ERC721: approved query for nonexistent token',
  521. );
  522. });
  523. });
  524. context('when token has been minted ', async function () {
  525. it('should return the zero address', async function () {
  526. expect(await this.token.getApproved(firstTokenId)).to.be.equal(
  527. ZERO_ADDRESS,
  528. );
  529. });
  530. context('when account has been approved', async function () {
  531. beforeEach(async function () {
  532. await this.token.approve(approved, firstTokenId, { from: owner });
  533. });
  534. it('returns approved account', async function () {
  535. expect(await this.token.getApproved(firstTokenId)).to.be.equal(approved);
  536. });
  537. });
  538. });
  539. });
  540. });
  541. describe('_mint(address, uint256)', function () {
  542. it('reverts with a null destination address', async function () {
  543. await expectRevert(
  544. this.token.mint(ZERO_ADDRESS, firstTokenId), 'ERC721: mint to the zero address',
  545. );
  546. });
  547. context('with minted token', async function () {
  548. beforeEach(async function () {
  549. (this.receipt = await this.token.mint(owner, firstTokenId));
  550. });
  551. it('emits a Transfer event', function () {
  552. expectEvent(this.receipt, 'Transfer', { from: ZERO_ADDRESS, to: owner, tokenId: firstTokenId });
  553. });
  554. it('creates the token', async function () {
  555. expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('1');
  556. expect(await this.token.ownerOf(firstTokenId)).to.equal(owner);
  557. });
  558. it('reverts when adding a token id that already exists', async function () {
  559. await expectRevert(this.token.mint(owner, firstTokenId), 'ERC721: token already minted');
  560. });
  561. });
  562. });
  563. describe('_burn', function () {
  564. it('reverts when burning a non-existent token id', async function () {
  565. await expectRevert(
  566. this.token.burn(nonExistentTokenId), 'ERC721: owner query for nonexistent token',
  567. );
  568. });
  569. context('with minted tokens', function () {
  570. beforeEach(async function () {
  571. await this.token.mint(owner, firstTokenId);
  572. await this.token.mint(owner, secondTokenId);
  573. });
  574. context('with burnt token', function () {
  575. beforeEach(async function () {
  576. (this.receipt = await this.token.burn(firstTokenId));
  577. });
  578. it('emits a Transfer event', function () {
  579. expectEvent(this.receipt, 'Transfer', { from: owner, to: ZERO_ADDRESS, tokenId: firstTokenId });
  580. });
  581. it('emits an Approval event', function () {
  582. expectEvent(this.receipt, 'Approval', { owner, approved: ZERO_ADDRESS, tokenId: firstTokenId });
  583. });
  584. it('deletes the token', async function () {
  585. expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('1');
  586. await expectRevert(
  587. this.token.ownerOf(firstTokenId), 'ERC721: owner query for nonexistent token',
  588. );
  589. });
  590. it('reverts when burning a token id that has been deleted', async function () {
  591. await expectRevert(
  592. this.token.burn(firstTokenId), 'ERC721: owner query for nonexistent token',
  593. );
  594. });
  595. });
  596. });
  597. });
  598. }
  599. function shouldBehaveLikeERC721Enumerable (errorPrefix, owner, newOwner, approved, anotherApproved, operator, other) {
  600. shouldSupportInterfaces([
  601. 'ERC721Enumerable',
  602. ]);
  603. context('with minted tokens', function () {
  604. beforeEach(async function () {
  605. await this.token.mint(owner, firstTokenId);
  606. await this.token.mint(owner, secondTokenId);
  607. this.toWhom = other; // default to other for toWhom in context-dependent tests
  608. });
  609. describe('totalSupply', function () {
  610. it('returns total token supply', async function () {
  611. expect(await this.token.totalSupply()).to.be.bignumber.equal('2');
  612. });
  613. });
  614. describe('tokenOfOwnerByIndex', function () {
  615. describe('when the given index is lower than the amount of tokens owned by the given address', function () {
  616. it('returns the token ID placed at the given index', async function () {
  617. expect(await this.token.tokenOfOwnerByIndex(owner, 0)).to.be.bignumber.equal(firstTokenId);
  618. });
  619. });
  620. describe('when the index is greater than or equal to the total tokens owned by the given address', function () {
  621. it('reverts', async function () {
  622. await expectRevert(
  623. this.token.tokenOfOwnerByIndex(owner, 2), 'ERC721Enumerable: owner index out of bounds',
  624. );
  625. });
  626. });
  627. describe('when the given address does not own any token', function () {
  628. it('reverts', async function () {
  629. await expectRevert(
  630. this.token.tokenOfOwnerByIndex(other, 0), 'ERC721Enumerable: owner index out of bounds',
  631. );
  632. });
  633. });
  634. describe('after transferring all tokens to another user', function () {
  635. beforeEach(async function () {
  636. await this.token.transferFrom(owner, other, firstTokenId, { from: owner });
  637. await this.token.transferFrom(owner, other, secondTokenId, { from: owner });
  638. });
  639. it('returns correct token IDs for target', async function () {
  640. expect(await this.token.balanceOf(other)).to.be.bignumber.equal('2');
  641. const tokensListed = await Promise.all(
  642. [0, 1].map(i => this.token.tokenOfOwnerByIndex(other, i)),
  643. );
  644. expect(tokensListed.map(t => t.toNumber())).to.have.members([firstTokenId.toNumber(),
  645. secondTokenId.toNumber()]);
  646. });
  647. it('returns empty collection for original owner', async function () {
  648. expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('0');
  649. await expectRevert(
  650. this.token.tokenOfOwnerByIndex(owner, 0), 'ERC721Enumerable: owner index out of bounds',
  651. );
  652. });
  653. });
  654. });
  655. describe('tokenByIndex', function () {
  656. it('returns all tokens', async function () {
  657. const tokensListed = await Promise.all(
  658. [0, 1].map(i => this.token.tokenByIndex(i)),
  659. );
  660. expect(tokensListed.map(t => t.toNumber())).to.have.members([firstTokenId.toNumber(),
  661. secondTokenId.toNumber()]);
  662. });
  663. it('reverts if index is greater than supply', async function () {
  664. await expectRevert(
  665. this.token.tokenByIndex(2), 'ERC721Enumerable: global index out of bounds',
  666. );
  667. });
  668. [firstTokenId, secondTokenId].forEach(function (tokenId) {
  669. it(`returns all tokens after burning token ${tokenId} and minting new tokens`, async function () {
  670. const newTokenId = new BN(300);
  671. const anotherNewTokenId = new BN(400);
  672. await this.token.burn(tokenId);
  673. await this.token.mint(newOwner, newTokenId);
  674. await this.token.mint(newOwner, anotherNewTokenId);
  675. expect(await this.token.totalSupply()).to.be.bignumber.equal('3');
  676. const tokensListed = await Promise.all(
  677. [0, 1, 2].map(i => this.token.tokenByIndex(i)),
  678. );
  679. const expectedTokens = [firstTokenId, secondTokenId, newTokenId, anotherNewTokenId].filter(
  680. x => (x !== tokenId),
  681. );
  682. expect(tokensListed.map(t => t.toNumber())).to.have.members(expectedTokens.map(t => t.toNumber()));
  683. });
  684. });
  685. });
  686. });
  687. describe('_mint(address, uint256)', function () {
  688. it('reverts with a null destination address', async function () {
  689. await expectRevert(
  690. this.token.mint(ZERO_ADDRESS, firstTokenId), 'ERC721: mint to the zero address',
  691. );
  692. });
  693. context('with minted token', async function () {
  694. beforeEach(async function () {
  695. (this.receipt = await this.token.mint(owner, firstTokenId));
  696. });
  697. it('adjusts owner tokens by index', async function () {
  698. expect(await this.token.tokenOfOwnerByIndex(owner, 0)).to.be.bignumber.equal(firstTokenId);
  699. });
  700. it('adjusts all tokens list', async function () {
  701. expect(await this.token.tokenByIndex(0)).to.be.bignumber.equal(firstTokenId);
  702. });
  703. });
  704. });
  705. describe('_burn', function () {
  706. it('reverts when burning a non-existent token id', async function () {
  707. await expectRevert(
  708. this.token.burn(firstTokenId), 'ERC721: owner query for nonexistent token',
  709. );
  710. });
  711. context('with minted tokens', function () {
  712. beforeEach(async function () {
  713. await this.token.mint(owner, firstTokenId);
  714. await this.token.mint(owner, secondTokenId);
  715. });
  716. context('with burnt token', function () {
  717. beforeEach(async function () {
  718. (this.receipt = await this.token.burn(firstTokenId));
  719. });
  720. it('removes that token from the token list of the owner', async function () {
  721. expect(await this.token.tokenOfOwnerByIndex(owner, 0)).to.be.bignumber.equal(secondTokenId);
  722. });
  723. it('adjusts all tokens list', async function () {
  724. expect(await this.token.tokenByIndex(0)).to.be.bignumber.equal(secondTokenId);
  725. });
  726. it('burns all tokens', async function () {
  727. await this.token.burn(secondTokenId, { from: owner });
  728. expect(await this.token.totalSupply()).to.be.bignumber.equal('0');
  729. await expectRevert(
  730. this.token.tokenByIndex(0), 'ERC721Enumerable: global index out of bounds',
  731. );
  732. });
  733. });
  734. });
  735. });
  736. }
  737. function shouldBehaveLikeERC721Metadata (errorPrefix, name, symbol, owner) {
  738. shouldSupportInterfaces([
  739. 'ERC721Metadata',
  740. ]);
  741. describe('metadata', function () {
  742. it('has a name', async function () {
  743. expect(await this.token.name()).to.be.equal(name);
  744. });
  745. it('has a symbol', async function () {
  746. expect(await this.token.symbol()).to.be.equal(symbol);
  747. });
  748. describe('token URI', function () {
  749. beforeEach(async function () {
  750. await this.token.mint(owner, firstTokenId);
  751. });
  752. it('return empty string by default', async function () {
  753. expect(await this.token.tokenURI(firstTokenId)).to.be.equal('');
  754. });
  755. it('reverts when queried for non existent token id', async function () {
  756. await expectRevert(
  757. this.token.tokenURI(nonExistentTokenId), 'ERC721Metadata: URI query for nonexistent token',
  758. );
  759. });
  760. describe('base URI', function () {
  761. beforeEach(function () {
  762. if (this.token.setBaseURI === undefined) {
  763. this.skip();
  764. }
  765. });
  766. it('base URI can be set', async function () {
  767. await this.token.setBaseURI(baseURI);
  768. expect(await this.token.baseURI()).to.equal(baseURI);
  769. });
  770. it('base URI is added as a prefix to the token URI', async function () {
  771. await this.token.setBaseURI(baseURI);
  772. expect(await this.token.tokenURI(firstTokenId)).to.be.equal(baseURI + firstTokenId.toString());
  773. });
  774. it('token URI can be changed by changing the base URI', async function () {
  775. await this.token.setBaseURI(baseURI);
  776. const newBaseURI = 'https://api.example.com/v2/';
  777. await this.token.setBaseURI(newBaseURI);
  778. expect(await this.token.tokenURI(firstTokenId)).to.be.equal(newBaseURI + firstTokenId.toString());
  779. });
  780. });
  781. });
  782. });
  783. }
  784. module.exports = {
  785. shouldBehaveLikeERC721,
  786. shouldBehaveLikeERC721Enumerable,
  787. shouldBehaveLikeERC721Metadata,
  788. };