ERC721Wrapper.test.js 10 KB

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