ERC721Wrapper.test.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. const { BN, expectEvent, constants } = require('@openzeppelin/test-helpers');
  2. const { expect } = require('chai');
  3. const { shouldBehaveLikeERC721 } = require('../ERC721.behavior');
  4. const { expectRevertCustomError } = require('../../../helpers/customError');
  5. const ERC721 = artifacts.require('$ERC721');
  6. const ERC721Wrapper = artifacts.require('$ERC721Wrapper');
  7. contract('ERC721Wrapper', function (accounts) {
  8. const [initialHolder, anotherAccount, approvedAccount] = accounts;
  9. const name = 'My Token';
  10. const symbol = 'MTKN';
  11. const firstTokenId = new BN(1);
  12. const secondTokenId = new BN(2);
  13. beforeEach(async function () {
  14. this.underlying = await ERC721.new(name, symbol);
  15. this.token = await ERC721Wrapper.new(`Wrapped ${name}`, `W${symbol}`, this.underlying.address);
  16. await this.underlying.$_safeMint(initialHolder, firstTokenId);
  17. await this.underlying.$_safeMint(initialHolder, secondTokenId);
  18. });
  19. it('has a name', async function () {
  20. expect(await this.token.name()).to.equal(`Wrapped ${name}`);
  21. });
  22. it('has a symbol', async function () {
  23. expect(await this.token.symbol()).to.equal(`W${symbol}`);
  24. });
  25. it('has underlying', async function () {
  26. expect(await this.token.underlying()).to.be.bignumber.equal(this.underlying.address);
  27. });
  28. describe('depositFor', function () {
  29. it('works with token approval', async function () {
  30. await this.underlying.approve(this.token.address, firstTokenId, { from: initialHolder });
  31. const { tx } = await this.token.depositFor(initialHolder, [firstTokenId], { from: initialHolder });
  32. await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
  33. from: initialHolder,
  34. to: this.token.address,
  35. tokenId: firstTokenId,
  36. });
  37. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  38. from: constants.ZERO_ADDRESS,
  39. to: initialHolder,
  40. tokenId: firstTokenId,
  41. });
  42. });
  43. it('works with approval for all', async function () {
  44. await this.underlying.setApprovalForAll(this.token.address, true, { from: initialHolder });
  45. const { tx } = await this.token.depositFor(initialHolder, [firstTokenId], { from: initialHolder });
  46. await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
  47. from: initialHolder,
  48. to: this.token.address,
  49. tokenId: firstTokenId,
  50. });
  51. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  52. from: constants.ZERO_ADDRESS,
  53. to: initialHolder,
  54. tokenId: firstTokenId,
  55. });
  56. });
  57. it('works sending to another account', async function () {
  58. await this.underlying.approve(this.token.address, firstTokenId, { from: initialHolder });
  59. const { tx } = await this.token.depositFor(anotherAccount, [firstTokenId], { from: initialHolder });
  60. await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
  61. from: initialHolder,
  62. to: this.token.address,
  63. tokenId: firstTokenId,
  64. });
  65. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  66. from: constants.ZERO_ADDRESS,
  67. to: anotherAccount,
  68. tokenId: firstTokenId,
  69. });
  70. });
  71. it('works with multiple tokens', async function () {
  72. await this.underlying.approve(this.token.address, firstTokenId, { from: initialHolder });
  73. await this.underlying.approve(this.token.address, secondTokenId, { from: initialHolder });
  74. const { tx } = await this.token.depositFor(initialHolder, [firstTokenId, secondTokenId], { from: initialHolder });
  75. await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
  76. from: initialHolder,
  77. to: this.token.address,
  78. tokenId: firstTokenId,
  79. });
  80. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  81. from: constants.ZERO_ADDRESS,
  82. to: initialHolder,
  83. tokenId: firstTokenId,
  84. });
  85. await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
  86. from: initialHolder,
  87. to: this.token.address,
  88. tokenId: secondTokenId,
  89. });
  90. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  91. from: constants.ZERO_ADDRESS,
  92. to: initialHolder,
  93. tokenId: secondTokenId,
  94. });
  95. });
  96. it('reverts with missing approval', async function () {
  97. await expectRevertCustomError(
  98. this.token.depositFor(initialHolder, [firstTokenId], { from: initialHolder }),
  99. 'ERC721InsufficientApproval',
  100. [this.token.address, firstTokenId],
  101. );
  102. });
  103. });
  104. describe('withdrawTo', function () {
  105. beforeEach(async function () {
  106. await this.underlying.approve(this.token.address, firstTokenId, { from: initialHolder });
  107. await this.token.depositFor(initialHolder, [firstTokenId], { from: initialHolder });
  108. });
  109. it('works for an owner', async function () {
  110. const { tx } = await this.token.withdrawTo(initialHolder, [firstTokenId], { from: initialHolder });
  111. await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
  112. from: this.token.address,
  113. to: initialHolder,
  114. tokenId: firstTokenId,
  115. });
  116. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  117. from: initialHolder,
  118. to: constants.ZERO_ADDRESS,
  119. tokenId: firstTokenId,
  120. });
  121. });
  122. it('works for an approved', async function () {
  123. await this.token.approve(approvedAccount, firstTokenId, { from: initialHolder });
  124. const { tx } = await this.token.withdrawTo(initialHolder, [firstTokenId], { from: approvedAccount });
  125. await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
  126. from: this.token.address,
  127. to: initialHolder,
  128. tokenId: firstTokenId,
  129. });
  130. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  131. from: initialHolder,
  132. to: constants.ZERO_ADDRESS,
  133. tokenId: firstTokenId,
  134. });
  135. });
  136. it('works for an approved for all', async function () {
  137. await this.token.setApprovalForAll(approvedAccount, true, { from: initialHolder });
  138. const { tx } = await this.token.withdrawTo(initialHolder, [firstTokenId], { from: approvedAccount });
  139. await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
  140. from: this.token.address,
  141. to: initialHolder,
  142. tokenId: firstTokenId,
  143. });
  144. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  145. from: initialHolder,
  146. to: constants.ZERO_ADDRESS,
  147. tokenId: firstTokenId,
  148. });
  149. });
  150. it("doesn't work for a non-owner nor approved", async function () {
  151. await expectRevertCustomError(
  152. this.token.withdrawTo(initialHolder, [firstTokenId], { from: anotherAccount }),
  153. 'ERC721InsufficientApproval',
  154. [anotherAccount, firstTokenId],
  155. );
  156. });
  157. it('works with multiple tokens', async function () {
  158. await this.underlying.approve(this.token.address, secondTokenId, { from: initialHolder });
  159. await this.token.depositFor(initialHolder, [secondTokenId], { from: initialHolder });
  160. const { tx } = await this.token.withdrawTo(initialHolder, [firstTokenId, secondTokenId], { from: initialHolder });
  161. await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
  162. from: this.token.address,
  163. to: initialHolder,
  164. tokenId: firstTokenId,
  165. });
  166. await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
  167. from: this.token.address,
  168. to: initialHolder,
  169. tokenId: secondTokenId,
  170. });
  171. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  172. from: initialHolder,
  173. to: constants.ZERO_ADDRESS,
  174. tokenId: firstTokenId,
  175. });
  176. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  177. from: initialHolder,
  178. to: constants.ZERO_ADDRESS,
  179. tokenId: secondTokenId,
  180. });
  181. });
  182. it('works to another account', async function () {
  183. const { tx } = await this.token.withdrawTo(anotherAccount, [firstTokenId], { from: initialHolder });
  184. await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
  185. from: this.token.address,
  186. to: anotherAccount,
  187. tokenId: firstTokenId,
  188. });
  189. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  190. from: initialHolder,
  191. to: constants.ZERO_ADDRESS,
  192. tokenId: firstTokenId,
  193. });
  194. });
  195. });
  196. describe('onERC721Received', function () {
  197. it('only allows calls from underlying', async function () {
  198. await expectRevertCustomError(
  199. this.token.onERC721Received(
  200. initialHolder,
  201. this.token.address,
  202. firstTokenId,
  203. anotherAccount, // Correct data
  204. { from: anotherAccount },
  205. ),
  206. 'ERC721UnsupportedToken',
  207. [anotherAccount],
  208. );
  209. });
  210. it('mints a token to from', async function () {
  211. const { tx } = await this.underlying.safeTransferFrom(initialHolder, this.token.address, firstTokenId, {
  212. from: initialHolder,
  213. });
  214. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  215. from: constants.ZERO_ADDRESS,
  216. to: initialHolder,
  217. tokenId: firstTokenId,
  218. });
  219. });
  220. });
  221. describe('_recover', function () {
  222. it('works if there is something to recover', async function () {
  223. // Should use `transferFrom` to avoid `onERC721Received` minting
  224. await this.underlying.transferFrom(initialHolder, this.token.address, firstTokenId, { from: initialHolder });
  225. const { tx } = await this.token.$_recover(anotherAccount, firstTokenId);
  226. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  227. from: constants.ZERO_ADDRESS,
  228. to: anotherAccount,
  229. tokenId: firstTokenId,
  230. });
  231. });
  232. it('reverts if there is nothing to recover', async function () {
  233. const owner = await this.underlying.ownerOf(firstTokenId);
  234. await expectRevertCustomError(this.token.$_recover(initialHolder, firstTokenId), 'ERC721IncorrectOwner', [
  235. this.token.address,
  236. firstTokenId,
  237. owner,
  238. ]);
  239. });
  240. });
  241. describe('ERC712 behavior', function () {
  242. shouldBehaveLikeERC721(...accounts);
  243. });
  244. });