ERC721Consecutive.t.sol 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. // SPDX-License-Identifier: MIT
  2. pragma solidity ^0.8.20;
  3. // solhint-disable func-name-mixedcase
  4. import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
  5. import {ERC721Consecutive} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Consecutive.sol";
  6. import {Test, StdUtils} from "forge-std/Test.sol";
  7. function toSingleton(address account) pure returns (address[] memory) {
  8. address[] memory accounts = new address[](1);
  9. accounts[0] = account;
  10. return accounts;
  11. }
  12. contract ERC721ConsecutiveTarget is StdUtils, ERC721Consecutive {
  13. uint96 private immutable _offset;
  14. uint256 private _totalMinted = 0;
  15. constructor(address[] memory receivers, uint256[] memory batches, uint256 startingId) ERC721("", "") {
  16. _offset = uint96(startingId);
  17. for (uint256 i = 0; i < batches.length; i++) {
  18. address receiver = receivers[i % receivers.length];
  19. uint96 batchSize = uint96(bound(batches[i], 0, _maxBatchSize()));
  20. _mintConsecutive(receiver, batchSize);
  21. _totalMinted += batchSize;
  22. }
  23. }
  24. function totalMinted() public view returns (uint256) {
  25. return _totalMinted;
  26. }
  27. function burn(uint256 tokenId) public {
  28. _burn(tokenId);
  29. }
  30. function _firstConsecutiveId() internal view virtual override returns (uint96) {
  31. return _offset;
  32. }
  33. }
  34. contract ERC721ConsecutiveTest is Test {
  35. function test_balance(address receiver, uint256[] calldata batches, uint96 startingId) public {
  36. vm.assume(receiver != address(0));
  37. uint256 startingTokenId = bound(startingId, 0, 5000);
  38. ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(toSingleton(receiver), batches, startingTokenId);
  39. assertEq(token.balanceOf(receiver), token.totalMinted());
  40. }
  41. function test_ownership(
  42. address receiver,
  43. uint256[] calldata batches,
  44. uint256[2] calldata unboundedTokenId,
  45. uint96 startingId
  46. ) public {
  47. vm.assume(receiver != address(0));
  48. uint256 startingTokenId = bound(startingId, 0, 5000);
  49. ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(toSingleton(receiver), batches, startingTokenId);
  50. if (token.totalMinted() > 0) {
  51. uint256 validTokenId = bound(
  52. unboundedTokenId[0],
  53. startingTokenId,
  54. startingTokenId + token.totalMinted() - 1
  55. );
  56. assertEq(token.ownerOf(validTokenId), receiver);
  57. }
  58. uint256 invalidTokenId = bound(
  59. unboundedTokenId[1],
  60. startingTokenId + token.totalMinted(),
  61. startingTokenId + token.totalMinted() + 1
  62. );
  63. vm.expectRevert();
  64. token.ownerOf(invalidTokenId);
  65. }
  66. function test_burn(
  67. address receiver,
  68. uint256[] calldata batches,
  69. uint256 unboundedTokenId,
  70. uint96 startingId
  71. ) public {
  72. vm.assume(receiver != address(0));
  73. uint256 startingTokenId = bound(startingId, 0, 5000);
  74. ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(toSingleton(receiver), batches, startingTokenId);
  75. // only test if we minted at least one token
  76. uint256 supply = token.totalMinted();
  77. vm.assume(supply > 0);
  78. // burn a token in [0; supply[
  79. uint256 tokenId = bound(unboundedTokenId, startingTokenId, startingTokenId + supply - 1);
  80. token.burn(tokenId);
  81. // balance should have decreased
  82. assertEq(token.balanceOf(receiver), supply - 1);
  83. // token should be burnt
  84. vm.expectRevert();
  85. token.ownerOf(tokenId);
  86. }
  87. function test_transfer(
  88. address[2] calldata accounts,
  89. uint256[2] calldata unboundedBatches,
  90. uint256[2] calldata unboundedTokenId,
  91. uint96 startingId
  92. ) public {
  93. vm.assume(accounts[0] != address(0));
  94. vm.assume(accounts[1] != address(0));
  95. vm.assume(accounts[0] != accounts[1]);
  96. uint256 startingTokenId = bound(startingId, 1, 5000);
  97. address[] memory receivers = new address[](2);
  98. receivers[0] = accounts[0];
  99. receivers[1] = accounts[1];
  100. // We assume _maxBatchSize is 5000 (the default). This test will break otherwise.
  101. uint256[] memory batches = new uint256[](2);
  102. batches[0] = bound(unboundedBatches[0], startingTokenId, 5000);
  103. batches[1] = bound(unboundedBatches[1], startingTokenId, 5000);
  104. ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(receivers, batches, startingTokenId);
  105. uint256 tokenId0 = bound(unboundedTokenId[0], startingTokenId, batches[0]);
  106. uint256 tokenId1 = bound(unboundedTokenId[1], startingTokenId, batches[1]) + batches[0];
  107. assertEq(token.ownerOf(tokenId0), accounts[0]);
  108. assertEq(token.ownerOf(tokenId1), accounts[1]);
  109. assertEq(token.balanceOf(accounts[0]), batches[0]);
  110. assertEq(token.balanceOf(accounts[1]), batches[1]);
  111. vm.prank(accounts[0]);
  112. // forge-lint: disable-next-line(erc20-unchecked-transfer)
  113. token.transferFrom(accounts[0], accounts[1], tokenId0);
  114. assertEq(token.ownerOf(tokenId0), accounts[1]);
  115. assertEq(token.ownerOf(tokenId1), accounts[1]);
  116. assertEq(token.balanceOf(accounts[0]), batches[0] - 1);
  117. assertEq(token.balanceOf(accounts[1]), batches[1] + 1);
  118. vm.prank(accounts[1]);
  119. // forge-lint: disable-next-line(erc20-unchecked-transfer)
  120. token.transferFrom(accounts[1], accounts[0], tokenId1);
  121. assertEq(token.ownerOf(tokenId0), accounts[1]);
  122. assertEq(token.ownerOf(tokenId1), accounts[0]);
  123. assertEq(token.balanceOf(accounts[0]), batches[0]);
  124. assertEq(token.balanceOf(accounts[1]), batches[1]);
  125. }
  126. function test_start_consecutive_id(
  127. address receiver,
  128. uint256[2] calldata unboundedBatches,
  129. uint256[2] calldata unboundedTokenId,
  130. uint96 startingId
  131. ) public {
  132. vm.assume(receiver != address(0));
  133. uint256 startingTokenId = bound(startingId, 1, 5000);
  134. // We assume _maxBatchSize is 5000 (the default). This test will break otherwise.
  135. uint256[] memory batches = new uint256[](2);
  136. batches[0] = bound(unboundedBatches[0], startingTokenId, 5000);
  137. batches[1] = bound(unboundedBatches[1], startingTokenId, 5000);
  138. ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(toSingleton(receiver), batches, startingTokenId);
  139. uint256 tokenId0 = bound(unboundedTokenId[0], startingTokenId, batches[0]);
  140. uint256 tokenId1 = bound(unboundedTokenId[1], startingTokenId, batches[1]);
  141. assertEq(token.ownerOf(tokenId0), receiver);
  142. assertEq(token.ownerOf(tokenId1), receiver);
  143. assertEq(token.balanceOf(receiver), batches[0] + batches[1]);
  144. }
  145. }