diff --git a/contracts/extension/BatchMintMetadata.sol b/contracts/extension/BatchMintMetadata.sol index 53883a65b..605b2435b 100644 --- a/contracts/extension/BatchMintMetadata.sol +++ b/contracts/extension/BatchMintMetadata.sol @@ -11,7 +11,7 @@ pragma solidity ^0.8.0; */ contract BatchMintMetadata { - /// @dev Largest tokenId of each batch of tokens with the same baseURI. + /// @dev Largest tokenId of each batch of tokens with the same baseURI + 1 {ex: batchId 100 at position 0 includes tokens 0-99} uint256[] private batchIds; /// @dev Mapping from id of a batch of tokens => to base URI for the respective batch of tokens. diff --git a/src/test/drop/drop-erc721/_beforeClaim/_beforeClaim.t.sol b/src/test/drop/drop-erc721/_beforeClaim/_beforeClaim.t.sol new file mode 100644 index 000000000..6495e8dc2 --- /dev/null +++ b/src/test/drop/drop-erc721/_beforeClaim/_beforeClaim.t.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { DropERC721 } from "contracts/prebuilts/drop/DropERC721.sol"; + +// Test imports +import "../../../utils/BaseTest.sol"; + +contract DropERC721Test_beforeClaim is BaseTest { + event TokenURIRevealed(uint256 indexed index, string revealedURI); + + DropERC721 public drop; + + bytes private beforeClaim_data; + string private beforeClaim_baseURI; + uint256 private beforeClaim_amount; + address private receiver = address(0x92Bb439374a091c7507bE100183d8D1Ed2c9dAD3); + + DropERC721.AllowlistProof private alp; + + function setUp() public override { + super.setUp(); + drop = DropERC721(getContract("DropERC721")); + + string[] memory inputs = new string[](5); + + inputs[0] = "node"; + inputs[1] = "src/test/scripts/generateRoot.ts"; + inputs[2] = "300"; + inputs[3] = "0"; + inputs[4] = Strings.toHexString(uint160(address(erc20))); + + bytes memory result = vm.ffi(inputs); + // revert(); + bytes32 root = abi.decode(result, (bytes32)); + + inputs[1] = "src/test/scripts/getProof.ts"; + result = vm.ffi(inputs); + bytes32[] memory proofs = abi.decode(result, (bytes32[])); + + alp.proof = proofs; + alp.quantityLimitPerWallet = 300; + alp.pricePerToken = 0; + alp.currency = address(erc20); + + vm.warp(1); + + DropERC721.ClaimCondition[] memory conditions = new DropERC721.ClaimCondition[](1); + conditions[0].maxClaimableSupply = 500; + conditions[0].quantityLimitPerWallet = 10; + conditions[0].merkleRoot = root; + conditions[0].pricePerToken = 10; + conditions[0].currency = address(erc20); + + vm.prank(deployer); + drop.setClaimConditions(conditions, false); + } + + /*/////////////////////////////////////////////////////////////// + Branch Testing + //////////////////////////////////////////////////////////////*/ + + modifier lazyMintUnEncrypted() { + beforeClaim_amount = 10; + beforeClaim_baseURI = "ipfs://"; + vm.prank(deployer); + drop.lazyMint(beforeClaim_amount, beforeClaim_baseURI, beforeClaim_data); + _; + } + + modifier setMaxSupply() { + vm.prank(deployer); + drop.setMaxTotalSupply(5); + _; + } + + function test_revert_greaterThanNextTokenIdToLazyMint() public lazyMintUnEncrypted { + vm.prank(receiver, receiver); + vm.expectRevert("!Tokens"); + drop.claim(receiver, 11, address(erc20), 0, alp, ""); + } + + function test_revert_greaterThanMaxTotalSupply() public lazyMintUnEncrypted setMaxSupply { + vm.prank(receiver, receiver); + vm.expectRevert("!Supply"); + drop.claim(receiver, 6, address(erc20), 0, alp, ""); + } +} diff --git a/src/test/drop/drop-erc721/_beforeClaim/_beforeClaim.tree b/src/test/drop/drop-erc721/_beforeClaim/_beforeClaim.tree new file mode 100644 index 000000000..a225a8be4 --- /dev/null +++ b/src/test/drop/drop-erc721/_beforeClaim/_beforeClaim.tree @@ -0,0 +1,12 @@ +function _beforeClaim( + address, + uint256 _quantity, + address, + uint256, + AllowlistProof calldata, + bytes memory +) +├── when _current index + _quantity are greater than nextTokenIdToLazyMint +│ └── it should revert ✅ +└── when maxTotalSupply does not equal zero and _currentIndex + _quantity is greater than maxTotalSupply + └── it should revert ✅ diff --git a/src/test/drop/drop-erc721/_canSetFunctions/_canSetFunctions.t.sol b/src/test/drop/drop-erc721/_canSetFunctions/_canSetFunctions.t.sol new file mode 100644 index 000000000..4dbdf57b1 --- /dev/null +++ b/src/test/drop/drop-erc721/_canSetFunctions/_canSetFunctions.t.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { DropERC721 } from "contracts/prebuilts/drop/DropERC721.sol"; + +// Test imports +import "../../../utils/BaseTest.sol"; + +contract DropERC721Test_canSetFunctions is BaseTest { + DropERC721 public drop; + + bytes private canset_data; + string private canset_baseURI; + uint256 private canset_amount; + bytes private canset_encryptedURI; + bytes32 private canset_provenanceHash; + address private unauthorized = address(0x123); + + function setUp() public override { + super.setUp(); + drop = DropERC721(getContract("DropERC721")); + } + + /*/////////////////////////////////////////////////////////////// + Branch Testing + //////////////////////////////////////////////////////////////*/ + + modifier callerNotAdmin() { + vm.startPrank(unauthorized); + _; + } + + modifier callerAdmin() { + vm.startPrank(deployer); + _; + } + + modifier callerNotMinter() { + vm.startPrank(unauthorized); + _; + } + + modifier callerMinter() { + vm.startPrank(deployer); + _; + } + + function test__canSetPlatformFeeInfo_revert_callerNotAdmin() public callerNotAdmin { + vm.expectRevert("Not authorized"); + drop.setPlatformFeeInfo(address(0x1), 1); + } + + function test__canSetPlatformFeeInfo_callerAdmin() public callerAdmin { + drop.setPlatformFeeInfo(address(0x1), 1); + (address recipient, uint16 bps) = drop.getPlatformFeeInfo(); + assertEq(recipient, address(0x1)); + assertEq(bps, 1); + } + + function test__canSetPrimarySaleRecipient_revert_callerNotAdmin() public callerNotAdmin { + vm.expectRevert("Not authorized"); + drop.setPrimarySaleRecipient(address(0x1)); + } + + function test__canSetPrimarySaleRecipient_callerAdmin() public callerAdmin { + drop.setPrimarySaleRecipient(address(0x1)); + assertEq(drop.primarySaleRecipient(), address(0x1)); + } + + function test__canSetOwner_revert_callerNotAdmin() public callerNotAdmin { + vm.expectRevert("Not authorized"); + drop.setOwner(address(0x1)); + } + + function test__canSetOwner_callerAdmin() public callerAdmin { + drop.setOwner(address(0x1)); + assertEq(drop.owner(), address(0x1)); + } + + function test__canSetRoyaltyInfo_revert_callerNotAdmin() public callerNotAdmin { + vm.expectRevert("Not authorized"); + drop.setDefaultRoyaltyInfo(address(0x1), 1); + } + + function test__canSetRoyaltyInfo_callerAdmin() public callerAdmin { + drop.setDefaultRoyaltyInfo(address(0x1), 1); + (address recipient, uint16 bps) = drop.getDefaultRoyaltyInfo(); + assertEq(recipient, address(0x1)); + assertEq(bps, 1); + } + + function test__canSetContractURI_revert_callerNotAdmin() public callerNotAdmin { + vm.expectRevert("Not authorized"); + drop.setContractURI("ipfs://"); + } + + function test__canSetContractURI_callerAdmin() public callerAdmin { + drop.setContractURI("ipfs://"); + assertEq(drop.contractURI(), "ipfs://"); + } + + function test__canSetClaimConditions_revert_callerNotAdmin() public callerNotAdmin { + DropERC721.ClaimCondition[] memory conditions = new DropERC721.ClaimCondition[](1); + conditions[0].maxClaimableSupply = 500; + conditions[0].quantityLimitPerWallet = 10; + conditions[0].merkleRoot = bytes32(0); + conditions[0].pricePerToken = 10; + conditions[0].currency = address(0x111); + vm.expectRevert("Not authorized"); + drop.setClaimConditions(conditions, true); + } + + function test__canSetClaimConditions_callerAdmin() public callerAdmin { + DropERC721.ClaimCondition[] memory conditions = new DropERC721.ClaimCondition[](1); + conditions[0].maxClaimableSupply = 500; + conditions[0].quantityLimitPerWallet = 10; + conditions[0].merkleRoot = bytes32(0); + conditions[0].pricePerToken = 10; + conditions[0].currency = address(0x111); + drop.setClaimConditions(conditions, true); + } + + function test__canLazyMint_revert_callerNotMinter() public callerNotMinter { + canset_amount = 10; + canset_baseURI = "ipfs://"; + canset_data = abi.encode(canset_encryptedURI, canset_provenanceHash); + vm.expectRevert("Not authorized"); + drop.lazyMint(canset_amount, canset_baseURI, canset_data); + } + + function test__canLazyMint_callerMinter() public callerMinter { + canset_amount = 10; + canset_baseURI = "ipfs://"; + canset_data = abi.encode(canset_encryptedURI, canset_provenanceHash); + drop.lazyMint(canset_amount, canset_baseURI, canset_data); + } +} diff --git a/src/test/drop/drop-erc721/_canSetFunctions/_canSetFunctions.tree b/src/test/drop/drop-erc721/_canSetFunctions/_canSetFunctions.tree new file mode 100644 index 000000000..72bfbe39a --- /dev/null +++ b/src/test/drop/drop-erc721/_canSetFunctions/_canSetFunctions.tree @@ -0,0 +1,41 @@ +function _canSetPlatformFeeInfo() +├── when caller has DEFAULT_ADMIN_ROLE +│ └── it should return true ✅ +└── when caller does not have DEFAULT_ADMIN_ROLE + └── it should return false ✅ + +function _canSetPrimarySaleRecipient() +├── when caller has DEFAULT_ADMIN_ROLE +│ └── it should return true ✅ +└── when caller does not have DEFAULT_ADMIN_ROLE + └── it should return false ✅ + +function _canSetOwner() +├── when caller has DEFAULT_ADMIN_ROLE +│ └── it should return true ✅ +└── when caller does not have DEFAULT_ADMIN_ROLE + └── it should return false ✅ + +function _canSetRoyaltyInfo() +├── when caller has DEFAULT_ADMIN_ROLE +│ └── it should return true ✅ +└── when caller does not have DEFAULT_ADMIN_ROLE + └── it should return false ✅ + +function _canSetContractURI() +├── when caller has DEFAULT_ADMIN_ROLE +│ └── it should return true ✅ +└── when caller does not have DEFAULT_ADMIN_ROLE + └── it should return false ✅ + +function _canSetClaimConditions() +├── when caller has DEFAULT_ADMIN_ROLE +│ └── it should return true ✅ +└── when caller does not have DEFAULT_ADMIN_ROLE + └── it should return false ✅ + +function _canLazyMint() +├── when caller has minterRole +│ └── it should return true ✅ +└── when caller does not have minterRole + └── it should return false ✅ \ No newline at end of file diff --git a/src/test/drop/drop-erc721/_collectPriceOnClaim/_collectPriceOnClaim.t.sol b/src/test/drop/drop-erc721/_collectPriceOnClaim/_collectPriceOnClaim.t.sol new file mode 100644 index 000000000..cee3d2798 --- /dev/null +++ b/src/test/drop/drop-erc721/_collectPriceOnClaim/_collectPriceOnClaim.t.sol @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { DropERC721 } from "contracts/prebuilts/drop/DropERC721.sol"; +import { TWProxy } from "contracts/infra/TWProxy.sol"; + +// Test imports +import "../../../utils/BaseTest.sol"; + +contract HarnessDropERC721 is DropERC721 { + function collectionPriceOnClaim( + address _primarySaleRecipient, + uint256 _quantityToClaim, + address _currency, + uint256 _pricePerToken + ) public payable { + _collectPriceOnClaim(_primarySaleRecipient, _quantityToClaim, _currency, _pricePerToken); + } +} + +contract DropERC721Test_collectPrice is BaseTest { + address public dropImp; + HarnessDropERC721 public proxy; + + address private collectPrice_saleRecipient = address(0x010); + uint256 private collectPrice_quantityToClaim = 1; + uint256 private collectPrice_pricePerToken; + address private collectPrice_currency; + uint256 private collectPrice_msgValue; + + function setUp() public override { + super.setUp(); + + bytes memory initializeData = abi.encodeCall( + DropERC721.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + royaltyRecipient, + royaltyBps, + platformFeeBps, + platformFeeRecipient + ) + ); + + dropImp = address(new HarnessDropERC721()); + proxy = HarnessDropERC721(address(new TWProxy(dropImp, initializeData))); + } + + modifier pricePerTokenZero() { + collectPrice_pricePerToken = 0; + _; + } + + modifier pricePerTokenNotZero() { + collectPrice_pricePerToken = 1 ether; + _; + } + + modifier msgValueNotZero() { + collectPrice_msgValue = 1 ether; + _; + } + + modifier nativeCurrency() { + collectPrice_currency = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + _; + } + + modifier erc20Currency() { + collectPrice_currency = address(erc20); + erc20.mint(address(this), 1_000 ether); + _; + } + + /*/////////////////////////////////////////////////////////////// + Branch Testing + //////////////////////////////////////////////////////////////*/ + + function test_revert_msgValueNotZero() public nativeCurrency msgValueNotZero pricePerTokenZero { + vm.expectRevert(); + proxy.collectionPriceOnClaim{ value: collectPrice_msgValue }( + collectPrice_saleRecipient, + collectPrice_quantityToClaim, + collectPrice_currency, + collectPrice_pricePerToken + ); + } + + function test_revert_priceValueMismatchNativeCurrency() public nativeCurrency pricePerTokenNotZero { + vm.expectRevert(); + proxy.collectionPriceOnClaim{ value: collectPrice_msgValue }( + collectPrice_saleRecipient, + collectPrice_quantityToClaim, + collectPrice_currency, + collectPrice_pricePerToken + ); + } + + function test_transferNativeCurrency() public nativeCurrency pricePerTokenNotZero msgValueNotZero { + uint256 balanceSaleRecipientBefore = address(saleRecipient).balance; + uint256 platformFeeRecipientBefore = address(platformFeeRecipient).balance; + proxy.collectionPriceOnClaim{ value: collectPrice_msgValue }( + saleRecipient, + collectPrice_quantityToClaim, + collectPrice_currency, + collectPrice_pricePerToken + ); + + uint256 balanceSaleRecipientAfter = address(saleRecipient).balance; + uint256 platformFeeRecipientAfter = address(platformFeeRecipient).balance; + uint256 expectedPlatformFee = (collectPrice_pricePerToken * platformFeeBps) / MAX_BPS; + uint256 expectedSaleRecipientProceed = collectPrice_msgValue - expectedPlatformFee; + + assertEq(balanceSaleRecipientAfter - balanceSaleRecipientBefore, expectedSaleRecipientProceed); + assertEq(platformFeeRecipientAfter - platformFeeRecipientBefore, expectedPlatformFee); + } + + function test_transferERC20() public erc20Currency pricePerTokenNotZero { + uint256 balanceSaleRecipientBefore = erc20.balanceOf(saleRecipient); + uint256 platformFeeRecipientBefore = erc20.balanceOf(platformFeeRecipient); + erc20.approve(address(proxy), collectPrice_pricePerToken); + proxy.collectionPriceOnClaim( + saleRecipient, + collectPrice_quantityToClaim, + collectPrice_currency, + collectPrice_pricePerToken + ); + + uint256 balanceSaleRecipientAfter = erc20.balanceOf(saleRecipient); + uint256 platformFeeRecipientAfter = erc20.balanceOf(platformFeeRecipient); + uint256 expectedPlatformFee = (collectPrice_pricePerToken * platformFeeBps) / MAX_BPS; + uint256 expectedSaleRecipientProceed = collectPrice_pricePerToken - expectedPlatformFee; + + assertEq(balanceSaleRecipientAfter - balanceSaleRecipientBefore, expectedSaleRecipientProceed); + assertEq(platformFeeRecipientAfter - platformFeeRecipientBefore, expectedPlatformFee); + } +} diff --git a/src/test/drop/drop-erc721/_collectPriceOnClaim/_collectPriceOnClaim.tree b/src/test/drop/drop-erc721/_collectPriceOnClaim/_collectPriceOnClaim.tree new file mode 100644 index 000000000..b962e7d4e --- /dev/null +++ b/src/test/drop/drop-erc721/_collectPriceOnClaim/_collectPriceOnClaim.tree @@ -0,0 +1,20 @@ +function _collectPriceOnClaim( + address _primarySaleRecipient, + uint256 _quantityToClaim, + address _currency, + uint256 _pricePerToken +) +├── when _pricePerToken is equal to zero +│ └── when msg.value does not equal to zero +│ └── it should revert ✅ +└── when _pricePerToken is not equal to zero + └── when _primarySaleRecipient is equal to address(0) + ├── when _currency is native token + │ ├── when msg.value does not equal to totalPrice + │ │ └── it should revert ✅ + │ └── when msg.value does equal to totalPrice + │ ├── it should transfer platformFees to platformFeeRecipient in native token ✅ + │ └── it should transfer totalPrice - platformFees to saleRecipient in native token ✅ + └── when _currency is not native token + ├── it should transfer platformFees to platformFeeRecipient in _currency token ✅ + └── it should transfer totalPrice - platformFees to saleRecipient in _currency token ✅ \ No newline at end of file diff --git a/src/test/drop/drop-erc721/_transferTokensOnClaim/_tranferTokensOnClaim.tree b/src/test/drop/drop-erc721/_transferTokensOnClaim/_tranferTokensOnClaim.tree new file mode 100644 index 000000000..6bf501586 --- /dev/null +++ b/src/test/drop/drop-erc721/_transferTokensOnClaim/_tranferTokensOnClaim.tree @@ -0,0 +1,2 @@ +function _transferTokensOnClaim(address _to, uint256 _quantityBeingClaimed) +└── it should mint `_quantityBeingClaimed` number of tokens to `to` ✅ \ No newline at end of file diff --git a/src/test/drop/drop-erc721/_transferTokensOnClaim/_transferTokensOnClaim.t.sol b/src/test/drop/drop-erc721/_transferTokensOnClaim/_transferTokensOnClaim.t.sol new file mode 100644 index 000000000..87e98d606 --- /dev/null +++ b/src/test/drop/drop-erc721/_transferTokensOnClaim/_transferTokensOnClaim.t.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { DropERC721 } from "contracts/prebuilts/drop/DropERC721.sol"; +import { TWProxy } from "contracts/infra/TWProxy.sol"; + +// Test imports +import "erc721a-upgradeable/contracts/IERC721AUpgradeable.sol"; +import "../../../utils/BaseTest.sol"; + +contract HarnessDropERC721 is DropERC721 { + function transferTokensOnClaim(address _to, uint256 _quantityToClaim) public payable { + _transferTokensOnClaim(_to, _quantityToClaim); + } +} + +contract DropERC721Test_transferTokensOnClaim is BaseTest { + address public dropImp; + HarnessDropERC721 public proxy; + + address private transferTokens_receiver; + + ERC20 private nonReceiver; + + function setUp() public override { + super.setUp(); + + bytes memory initializeData = abi.encodeCall( + DropERC721.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + royaltyRecipient, + royaltyBps, + platformFeeBps, + platformFeeRecipient + ) + ); + + dropImp = address(new HarnessDropERC721()); + proxy = HarnessDropERC721(address(new TWProxy(dropImp, initializeData))); + + nonReceiver = new ERC20("", ""); + } + + modifier transferToEOA() { + transferTokens_receiver = address(0x111); + _; + } + + modifier transferToNonReceiver() { + transferTokens_receiver = address(nonReceiver); + _; + } + + /*/////////////////////////////////////////////////////////////// + Branch Testing + //////////////////////////////////////////////////////////////*/ + + function test_revert_transferToNonReceiver() public transferToNonReceiver { + vm.expectRevert(IERC721AUpgradeable.TransferToNonERC721ReceiverImplementer.selector); + proxy.transferTokensOnClaim(transferTokens_receiver, 1); + } + + function test_transferToEOA() public transferToEOA { + uint256 eoaBalanceBefore = proxy.balanceOf(transferTokens_receiver); + uint256 supplyBefore = proxy.totalSupply(); + proxy.transferTokensOnClaim(transferTokens_receiver, 1); + assertEq(proxy.totalSupply(), supplyBefore + 1); + assertEq(proxy.balanceOf(transferTokens_receiver), eoaBalanceBefore + 1); + } +} diff --git a/src/test/drop/drop-erc721/freezeBatchBaseURI/freezeBatchBaseURI.t.sol b/src/test/drop/drop-erc721/freezeBatchBaseURI/freezeBatchBaseURI.t.sol new file mode 100644 index 000000000..7c527acad --- /dev/null +++ b/src/test/drop/drop-erc721/freezeBatchBaseURI/freezeBatchBaseURI.t.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { DropERC721 } from "contracts/prebuilts/drop/DropERC721.sol"; + +// Test imports +import "../../../utils/BaseTest.sol"; + +contract DropERC721Test_freezeBatchBaseURI is BaseTest { + event MetadataFrozen(); + + DropERC721 public drop; + + bytes private freeze_data; + string private freeze_baseURI; + uint256 private freeze_amount; + bytes private freeze_encryptedURI; + bytes32 private freeze_provenanceHash; + string private freeze_revealedURI; + bytes private freeze_key; + address private unauthorized = address(0x123); + + function setUp() public override { + super.setUp(); + drop = DropERC721(getContract("DropERC721")); + } + + /*/////////////////////////////////////////////////////////////// + Branch Testing + //////////////////////////////////////////////////////////////*/ + + modifier callerWithoutMetadataRole() { + vm.startPrank(unauthorized); + _; + } + + modifier callerWithMetadataRole() { + vm.startPrank(deployer); + _; + } + + modifier lazyMintEncrypted() { + freeze_amount = 10; + freeze_baseURI = "ipfs://"; + freeze_revealedURI = "ipfs://revealed"; + freeze_key = "key"; + freeze_encryptedURI = drop.encryptDecrypt(bytes(freeze_revealedURI), freeze_key); + freeze_provenanceHash = keccak256(abi.encodePacked(freeze_revealedURI, freeze_key, block.chainid)); + freeze_data = abi.encode(freeze_encryptedURI, freeze_provenanceHash); + vm.prank(deployer); + drop.lazyMint(freeze_amount, freeze_baseURI, freeze_data); + _; + } + + modifier lazyMintUnEncryptedEmptyBaseURI() { + freeze_amount = 10; + freeze_baseURI = ""; + vm.prank(deployer); + drop.lazyMint(freeze_amount, freeze_baseURI, freeze_data); + _; + } + + modifier lazyMintUnEncryptedRegularBaseURI() { + freeze_amount = 10; + freeze_baseURI = "ipfs://"; + vm.prank(deployer); + drop.lazyMint(freeze_amount, freeze_baseURI, freeze_data); + _; + } + + function test_revert_NoMetadataRole() public callerWithoutMetadataRole { + bytes32 role = keccak256("METADATA_ROLE"); + vm.expectRevert( + abi.encodePacked( + "Permissions: account ", + TWStrings.toHexString(uint160(unauthorized), 20), + " is missing role ", + TWStrings.toHexString(uint256(role), 32) + ) + ); + drop.freezeBatchBaseURI(0); + } + + function test_revert_EncryptedBatch() public lazyMintEncrypted callerWithMetadataRole { + vm.expectRevert("Encrypted batch"); + drop.freezeBatchBaseURI(0); + } + + function test_revert_EmptyBaseURI() public lazyMintUnEncryptedEmptyBaseURI callerWithMetadataRole { + vm.expectRevert("Invalid batch"); + drop.freezeBatchBaseURI(0); + } + + function test_state() public lazyMintUnEncryptedRegularBaseURI callerWithMetadataRole { + uint256 batchId = drop.getBatchIdAtIndex(0); + drop.freezeBatchBaseURI(0); + assertEq(drop.batchFrozen(batchId), true); + } + + function test_event() public lazyMintUnEncryptedRegularBaseURI callerWithMetadataRole { + vm.expectEmit(false, false, false, false); + emit MetadataFrozen(); + drop.freezeBatchBaseURI(0); + } +} diff --git a/src/test/drop/drop-erc721/freezeBatchBaseURI/freezeBatchBaseURI.tree b/src/test/drop/drop-erc721/freezeBatchBaseURI/freezeBatchBaseURI.tree new file mode 100644 index 000000000..9c29a9a83 --- /dev/null +++ b/src/test/drop/drop-erc721/freezeBatchBaseURI/freezeBatchBaseURI.tree @@ -0,0 +1,12 @@ +function freezeBatchBaseURI(uint256 _index) +├── when called by a user without the METADATA_ROLE +│ └── it should revert ✅ +└── when called by a user with the METADATA_ROLE + ├── when the batchId for the provided _index is an encrypted batch + │ └── it should revert ✅ + └── when the batchId for the provided _index is not an encrypted batch + ├── when the baseURI for the batchId is empty + │ └── it should revert ✅ + └── when the baseURI for the batchId is not empty + ├── it should set batchFrozen[batchId] as true ✅ + └── it should emit MetadataFrozen ✅ \ No newline at end of file diff --git a/src/test/drop/drop-erc721/initalizer/initializer.t.sol b/src/test/drop/drop-erc721/initalizer/initializer.t.sol new file mode 100644 index 000000000..4c035da97 --- /dev/null +++ b/src/test/drop/drop-erc721/initalizer/initializer.t.sol @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { DropERC721 } from "contracts/prebuilts/drop/DropERC721.sol"; + +// Test imports +import "../../../utils/BaseTest.sol"; + +contract DropERC721Test_initializer is BaseTest { + DropERC721 public newDropContract; + + event ContractURIUpdated(string prevURI, string newURI); + event OwnerUpdated(address indexed prevOwner, address indexed newOwner); + event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); + event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); + event PlatformFeeInfoUpdated(address indexed platformFeeRecipient, uint256 platformFeeBps); + event DefaultRoyalty(address indexed newRoyaltyRecipient, uint256 newRoyaltyBps); + event PrimarySaleRecipientUpdated(address indexed recipient); + + function setUp() public override { + super.setUp(); + } + + modifier royaltyBPSTooHigh() { + uint128 royaltyBps = 10001; + _; + } + + modifier platformFeeBPSTooHigh() { + uint128 platformFeeBps = 10001; + _; + } + + function test_state() public { + deployContractProxy( + "DropERC721", + abi.encodeCall( + DropERC721.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + royaltyRecipient, + royaltyBps, + platformFeeBps, + platformFeeRecipient + ) + ) + ); + + newDropContract = DropERC721(getContract("DropERC721")); + (address _platformFeeRecipient, uint128 _platformFeeBps) = newDropContract.getPlatformFeeInfo(); + (address _royaltyRecipient, uint128 _royaltyBps) = newDropContract.getDefaultRoyaltyInfo(); + address _saleRecipient = newDropContract.primarySaleRecipient(); + + for (uint256 i = 0; i < forwarders().length; i++) { + assertEq(newDropContract.isTrustedForwarder(forwarders()[i]), true); + } + + assertEq(newDropContract.name(), NAME); + assertEq(newDropContract.symbol(), SYMBOL); + assertEq(newDropContract.contractURI(), CONTRACT_URI); + assertEq(newDropContract.owner(), deployer); + assertEq(_platformFeeRecipient, platformFeeRecipient); + assertEq(_platformFeeBps, platformFeeBps); + assertEq(_royaltyRecipient, royaltyRecipient); + assertEq(_royaltyBps, royaltyBps); + assertEq(_saleRecipient, saleRecipient); + } + + function test_revert_RoyaltyBPSTooHigh() public royaltyBPSTooHigh { + vm.expectRevert("Exceeds max bps"); + deployContractProxy( + "DropERC721", + abi.encodeCall( + DropERC721.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + royaltyRecipient, + 10001, + platformFeeBps, + platformFeeRecipient + ) + ) + ); + } + + function test_revert_PlatformFeeBPSTooHigh() public platformFeeBPSTooHigh { + vm.expectRevert("Exceeds max bps"); + deployContractProxy( + "DropERC721", + abi.encodeCall( + DropERC721.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + royaltyRecipient, + royaltyBps, + 10001, + platformFeeRecipient + ) + ) + ); + } + + function test_event_ContractURIUpdated() public { + vm.expectEmit(false, false, false, true); + emit ContractURIUpdated("", CONTRACT_URI); + deployContractProxy( + "DropERC721", + abi.encodeCall( + DropERC721.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + royaltyRecipient, + royaltyBps, + platformFeeBps, + platformFeeRecipient + ) + ) + ); + } + + function test_event_OwnerUpdated() public { + vm.expectEmit(true, true, false, false); + emit OwnerUpdated(address(0), deployer); + deployContractProxy( + "DropERC721", + abi.encodeCall( + DropERC721.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + royaltyRecipient, + royaltyBps, + platformFeeBps, + platformFeeRecipient + ) + ) + ); + } + + function test_event_RoleGrantedDefaultAdminRole() public { + bytes32 role = bytes32(0x00); + vm.expectEmit(true, true, true, false); + emit RoleGranted(role, deployer, 0xf29D12e4c9d593D2887EEDDe076bBe39EDf3cD0F); + deployContractProxy( + "DropERC721", + abi.encodeCall( + DropERC721.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + royaltyRecipient, + royaltyBps, + platformFeeBps, + platformFeeRecipient + ) + ) + ); + } + + function test_event_RoleGrantedMinterRole() public { + bytes32 role = keccak256("MINTER_ROLE"); + vm.expectEmit(true, true, true, false); + emit RoleGranted(role, deployer, 0xf29D12e4c9d593D2887EEDDe076bBe39EDf3cD0F); + deployContractProxy( + "DropERC721", + abi.encodeCall( + DropERC721.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + royaltyRecipient, + royaltyBps, + platformFeeBps, + platformFeeRecipient + ) + ) + ); + } + + function test_event_RoleGrantedTransferRole() public { + bytes32 role = keccak256("TRANSFER_ROLE"); + vm.expectEmit(true, true, true, false); + emit RoleGranted(role, deployer, 0xf29D12e4c9d593D2887EEDDe076bBe39EDf3cD0F); + deployContractProxy( + "DropERC721", + abi.encodeCall( + DropERC721.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + royaltyRecipient, + royaltyBps, + platformFeeBps, + platformFeeRecipient + ) + ) + ); + } + + function test_event_RoleGrantedTransferRoleZeroAddress() public { + bytes32 role = keccak256("TRANSFER_ROLE"); + vm.expectEmit(true, true, true, false); + emit RoleGranted(role, address(0), 0xf29D12e4c9d593D2887EEDDe076bBe39EDf3cD0F); + deployContractProxy( + "DropERC721", + abi.encodeCall( + DropERC721.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + royaltyRecipient, + royaltyBps, + platformFeeBps, + platformFeeRecipient + ) + ) + ); + } + + function test_event_RoleGrantedMetadataRole() public { + bytes32 role = keccak256("METADATA_ROLE"); + vm.expectEmit(true, true, true, false); + emit RoleGranted(role, deployer, 0xf29D12e4c9d593D2887EEDDe076bBe39EDf3cD0F); + deployContractProxy( + "DropERC721", + abi.encodeCall( + DropERC721.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + royaltyRecipient, + royaltyBps, + platformFeeBps, + platformFeeRecipient + ) + ) + ); + } + + function test_event_RoleAdminChangedMetadataRole() public { + bytes32 role = keccak256("METADATA_ROLE"); + vm.expectEmit(true, true, true, false); + emit RoleAdminChanged(role, bytes32(0x00), role); + deployContractProxy( + "DropERC721", + abi.encodeCall( + DropERC721.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + royaltyRecipient, + royaltyBps, + platformFeeBps, + platformFeeRecipient + ) + ) + ); + } + + function test_event_PlatformFeeInfoUpdated() public { + vm.expectEmit(true, false, false, true); + emit PlatformFeeInfoUpdated(platformFeeRecipient, platformFeeBps); + deployContractProxy( + "DropERC721", + abi.encodeCall( + DropERC721.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + royaltyRecipient, + royaltyBps, + platformFeeBps, + platformFeeRecipient + ) + ) + ); + } + + function test_event_DefaultRoyalty() public { + vm.expectEmit(true, false, false, true); + emit DefaultRoyalty(royaltyRecipient, royaltyBps); + deployContractProxy( + "DropERC721", + abi.encodeCall( + DropERC721.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + royaltyRecipient, + royaltyBps, + platformFeeBps, + platformFeeRecipient + ) + ) + ); + } + + function test_event_PrimarySaleRecipientUpdated() public { + vm.expectEmit(true, false, false, false); + emit PrimarySaleRecipientUpdated(saleRecipient); + deployContractProxy( + "DropERC721", + abi.encodeCall( + DropERC721.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + royaltyRecipient, + royaltyBps, + platformFeeBps, + platformFeeRecipient + ) + ) + ); + } + + function test_roleCheck() public { + deployContractProxy( + "DropERC721", + abi.encodeCall( + DropERC721.initialize, + ( + deployer, + NAME, + SYMBOL, + CONTRACT_URI, + forwarders(), + saleRecipient, + royaltyRecipient, + royaltyBps, + platformFeeBps, + platformFeeRecipient + ) + ) + ); + + newDropContract = DropERC721(getContract("DropERC721")); + + assertEq(newDropContract.hasRole(bytes32(0x00), deployer), true); + assertEq(newDropContract.hasRole(keccak256("MINTER_ROLE"), deployer), true); + assertEq(newDropContract.hasRole(keccak256("TRANSFER_ROLE"), deployer), true); + assertEq(newDropContract.hasRole(keccak256("TRANSFER_ROLE"), address(0)), true); + assertEq(newDropContract.hasRole(keccak256("METADATA_ROLE"), deployer), true); + + assertEq(newDropContract.getRoleAdmin(keccak256("METADATA_ROLE")), keccak256("METADATA_ROLE")); + } +} diff --git a/src/test/drop/drop-erc721/initalizer/initializer.tree b/src/test/drop/drop-erc721/initalizer/initializer.tree new file mode 100644 index 000000000..cec2fc97d --- /dev/null +++ b/src/test/drop/drop-erc721/initalizer/initializer.tree @@ -0,0 +1,53 @@ +function initialize( + address _defaultAdmin, + string memory _name, + string memory _symbol, + string memory _contractURI, + address[] memory _trustedForwarders, + address _saleRecipient, + address _royaltyRecipient, + uint128 _royaltyBps, + uint128 _platformFeeBps, + address _platformFeeRecipient +) +├── when _trustedForwarders.length > 0 +│ └── it should set _trustedForwarder[_trustedForwarders[i]] as true for each address in _trustedForwarders ✅ +├── it should set _name as the value provided in _name ✅ +├── it should set _symbol as the value provided in _symbol ✅ +├── it should set _currentIndex as 0 +├── it should set contractURI as _contractURI ✅ +├── it should emit ContractURIUpdated with the parameters: prevURI, _uri ✅ +├── it should set _defaultAdmin as the owner of the contract ✅ +├── it should emit OwnerUpdated with the parameters: _prevOwner, _defaultAdmin ✅ +├── it should assign the role DEFAULT_ADMIN_ROLE to _defaultAdmin ✅ +├── it should emit RoleGranted with the parameters: DEFAULT_ADMIN_ROLE, _defaultAdmin, msg.sender ✅ +├── it should assign the role _minterRole to _defaultAdmin ✅ +├── it should emit RoleGranted with the parameters: _minterRole, _defaultAdmin, msg.sender ✅ +├── it should assign the role _transferRole to _defaultAdmin ✅ +├── it should emit RoleGranted with the parameters: _transferRole, _defaultAdmin, msg.sender ✅ +├── it should assign the role _transferRole to address(0) ✅ +├── it should emit RoleGranted with the parameters: _transferRole, address(0), msg.sender ✅ +├── it should assign the role _metadataRole to _defaultAdmin ✅ +├── it should emit RoleGranted with the parameters: _metadataRole, _defaultAdmin, msg.sender ✅ +├── it should set _getAdminRole[_metadataRole] to equal _metadataRole ✅ +├── it should emit RoleAdminChanged with the parameters _metadataRole, previousAdminRole, _metadataRole ✅ +├── when _platformFeeBps is greater than 10_000 +│ └── it should revert ✅ +├── when _platformFeeBps is less than or equal to 10_000 +│ ├── it should set platformFeeBps to uint16(_platformFeeBps) ✅ +│ ├── it should set platformFeeRecipient to _platformFeeRecipient ✅ +│ └── it should emit PlatformFeeInfoUpdated with the following parameters: _platformFeeRecipient, _platformFeeBps ✅ +├── when _royaltyBps is greater than 10_000 +│ └── it should revert ✅ +├── when _royaltyBps is less than or equal to 10_000 +│ ├── it should set royaltyRecipient as _royaltyRecipient ✅ +│ ├── it should set royaltyBps as uint16(_royaltyBps) ✅ +│ └── it should emit DefaultRoyalty with the parameters _royaltyRecipient, _royaltyBps ✅ +├── it should set recipient as _primarySaleRecipient ✅ +├── it should emit PrimarySaleRecipientUpdated with the parameters _primarySaleRecipient ✅ +├── it should set transferRole as keccak256("TRANSFER_ROLE") +├── it should set minterRole as keccak256("MINTER_ROLE") +└── it should set metadataRole as keccak256("METADATA_ROLE") + + + diff --git a/src/test/drop/drop-erc721/lazyMint/lazyMint.t.sol b/src/test/drop/drop-erc721/lazyMint/lazyMint.t.sol new file mode 100644 index 000000000..baff5a29b --- /dev/null +++ b/src/test/drop/drop-erc721/lazyMint/lazyMint.t.sol @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { DropERC721 } from "contracts/prebuilts/drop/DropERC721.sol"; + +// Test imports +import "../../../utils/BaseTest.sol"; + +contract DropERC721Test_lazyMint is BaseTest { + event TokensLazyMinted(uint256 indexed startTokenId, uint256 endTokenId, string baseURI, bytes encryptedBaseURI); + + DropERC721 public drop; + + bytes private lazymint_data; + uint256 private lazyMint_amount; + bytes private lazyMint_encryptedURI; + bytes32 private lazyMint_provenanceHash; + string private lazyMint_revealedURI = "test"; + + function setUp() public override { + super.setUp(); + drop = DropERC721(getContract("DropERC721")); + } + + /*/////////////////////////////////////////////////////////////// + Branch Testing + //////////////////////////////////////////////////////////////*/ + + modifier callerWithoutMinterRole() { + vm.startPrank(address(0x123)); + _; + } + + modifier callerWithMinterRole() { + vm.startPrank(deployer); + _; + } + + modifier amountEqualZero() { + lazyMint_amount = 0; + _; + } + + modifier amountNotEqualZero() { + lazyMint_amount = 1; + _; + } + + modifier dataLengthZero() { + lazymint_data = abi.encode(""); + _; + } + + modifier dataInvalidFormat() { + lazyMint_provenanceHash = bytes32("provenanceHash"); + lazymint_data = abi.encode(lazyMint_provenanceHash); + console.log(lazymint_data.length); + _; + } + + modifier dataValidFormat() { + lazyMint_provenanceHash = bytes32("provenanceHash"); + lazyMint_encryptedURI = "encryptedURI"; + lazymint_data = abi.encode(lazyMint_encryptedURI, lazyMint_provenanceHash); + console.log(lazymint_data.length); + _; + } + + modifier dataValidFormatNoURI() { + lazyMint_provenanceHash = bytes32("provenanceHash"); + lazyMint_encryptedURI = ""; + lazymint_data = abi.encode(lazyMint_encryptedURI, lazyMint_provenanceHash); + console.log(lazymint_data.length); + _; + } + + modifier dataValidFormatNoHash() { + lazyMint_provenanceHash = bytes32(""); + lazyMint_encryptedURI = "encryptedURI"; + lazymint_data = abi.encode(lazyMint_encryptedURI, lazyMint_provenanceHash); + console.log(lazymint_data.length); + _; + } + + function test_revert_NoMinterRole() public callerWithoutMinterRole dataLengthZero { + vm.expectRevert("Not authorized"); + drop.lazyMint(lazyMint_amount, lazyMint_revealedURI, lazymint_data); + } + + function test_revert_AmountEqualZero() public callerWithMinterRole dataLengthZero amountEqualZero { + vm.expectRevert("0 amt"); + drop.lazyMint(lazyMint_amount, lazyMint_revealedURI, lazymint_data); + } + + function test_revert_DataInvalidFormat() public callerWithMinterRole amountNotEqualZero dataInvalidFormat { + vm.expectRevert(); + drop.lazyMint(lazyMint_amount, lazyMint_revealedURI, lazymint_data); + } + + function test_state_dataLengthZero() public callerWithMinterRole amountNotEqualZero dataLengthZero { + uint256 nextTokenIdToLazyMintBefore = drop.nextTokenIdToMint(); + uint256 expectedBatchId = nextTokenIdToLazyMintBefore + lazyMint_amount; + + uint256 batchIdReturn = drop.lazyMint(lazyMint_amount, lazyMint_revealedURI, lazymint_data); + + uint256 batchIdState = drop.getBatchIdAtIndex(0); + string memory baseURIState = drop.tokenURI(0); + + assertEq(nextTokenIdToLazyMintBefore + lazyMint_amount, drop.nextTokenIdToMint()); + assertEq(expectedBatchId, batchIdReturn); + assertEq(expectedBatchId, batchIdState); + assertEq(string(abi.encodePacked(lazyMint_revealedURI, "0")), baseURIState); + } + + function test_event_dataLengthZero() public callerWithMinterRole amountNotEqualZero dataLengthZero { + uint256 nextTokenIdToLazyMintBefore = drop.nextTokenIdToMint(); + + vm.expectEmit(true, false, false, true); + emit TokensLazyMinted( + nextTokenIdToLazyMintBefore, + nextTokenIdToLazyMintBefore + lazyMint_amount - 1, + lazyMint_revealedURI, + lazymint_data + ); + drop.lazyMint(lazyMint_amount, lazyMint_revealedURI, lazymint_data); + } + + function test_state_noEncryptedURI() public callerWithMinterRole amountNotEqualZero dataValidFormatNoURI { + uint256 nextTokenIdToLazyMintBefore = drop.nextTokenIdToMint(); + uint256 expectedBatchId = nextTokenIdToLazyMintBefore + lazyMint_amount; + bytes memory expectedEncryptedData; + + uint256 batchIdReturn = drop.lazyMint(lazyMint_amount, lazyMint_revealedURI, lazymint_data); + + uint256 batchIdState = drop.getBatchIdAtIndex(0); + string memory baseURIState = drop.tokenURI(0); + bytes memory encryptedDataState = drop.encryptedData(0); + + assertEq(nextTokenIdToLazyMintBefore + lazyMint_amount, drop.nextTokenIdToMint()); + assertEq(expectedBatchId, batchIdReturn); + assertEq(expectedBatchId, batchIdState); + assertEq(string(abi.encodePacked(lazyMint_revealedURI, "0")), baseURIState); + assertEq(expectedEncryptedData, encryptedDataState); + } + + function test_event_noEncryptedURI() public callerWithMinterRole amountNotEqualZero dataValidFormatNoURI { + uint256 nextTokenIdToLazyMintBefore = drop.nextTokenIdToMint(); + + vm.expectEmit(true, false, false, true); + emit TokensLazyMinted( + nextTokenIdToLazyMintBefore, + nextTokenIdToLazyMintBefore + lazyMint_amount - 1, + lazyMint_revealedURI, + lazymint_data + ); + drop.lazyMint(lazyMint_amount, lazyMint_revealedURI, lazymint_data); + } + + function test_state_noProvenanceHash() public callerWithMinterRole amountNotEqualZero dataValidFormatNoHash { + uint256 nextTokenIdToLazyMintBefore = drop.nextTokenIdToMint(); + uint256 expectedBatchId = nextTokenIdToLazyMintBefore + lazyMint_amount; + bytes memory expectedEncryptedData; + + uint256 batchIdReturn = drop.lazyMint(lazyMint_amount, lazyMint_revealedURI, lazymint_data); + + uint256 batchIdState = drop.getBatchIdAtIndex(0); + string memory baseURIState = drop.tokenURI(0); + bytes memory encryptedDataState = drop.encryptedData(0); + + assertEq(nextTokenIdToLazyMintBefore + lazyMint_amount, drop.nextTokenIdToMint()); + assertEq(expectedBatchId, batchIdReturn); + assertEq(expectedBatchId, batchIdState); + assertEq(string(abi.encodePacked(lazyMint_revealedURI, "0")), baseURIState); + assertEq(expectedEncryptedData, encryptedDataState); + } + + function test_event_noProvenanceHash() public callerWithMinterRole amountNotEqualZero dataValidFormatNoHash { + uint256 nextTokenIdToLazyMintBefore = drop.nextTokenIdToMint(); + + vm.expectEmit(true, false, false, true); + emit TokensLazyMinted( + nextTokenIdToLazyMintBefore, + nextTokenIdToLazyMintBefore + lazyMint_amount - 1, + lazyMint_revealedURI, + lazymint_data + ); + drop.lazyMint(lazyMint_amount, lazyMint_revealedURI, lazymint_data); + } + + function test_state_encryptedURIAndHash() public callerWithMinterRole amountNotEqualZero dataValidFormat { + uint256 nextTokenIdToLazyMintBefore = drop.nextTokenIdToMint(); + uint256 expectedBatchId = nextTokenIdToLazyMintBefore + lazyMint_amount; + + uint256 batchIdReturn = drop.lazyMint(lazyMint_amount, lazyMint_revealedURI, lazymint_data); + + uint256 batchIdState = drop.getBatchIdAtIndex(0); + string memory baseURIState = drop.tokenURI(0); + bytes memory encryptedDataState = drop.encryptedData(batchIdReturn); + + assertEq(nextTokenIdToLazyMintBefore + lazyMint_amount, drop.nextTokenIdToMint()); + assertEq(expectedBatchId, batchIdReturn); + assertEq(expectedBatchId, batchIdState); + assertEq(string(abi.encodePacked(lazyMint_revealedURI, "0")), baseURIState); + assertEq(lazymint_data, encryptedDataState); + } + + function test_event_encryptedURIAndHash() public callerWithMinterRole amountNotEqualZero dataValidFormat { + uint256 nextTokenIdToLazyMintBefore = drop.nextTokenIdToMint(); + + vm.expectEmit(true, false, false, true); + emit TokensLazyMinted( + nextTokenIdToLazyMintBefore, + nextTokenIdToLazyMintBefore + lazyMint_amount - 1, + lazyMint_revealedURI, + lazymint_data + ); + drop.lazyMint(lazyMint_amount, lazyMint_revealedURI, lazymint_data); + } +} diff --git a/src/test/drop/drop-erc721/lazyMint/lazyMint.tree b/src/test/drop/drop-erc721/lazyMint/lazyMint.tree new file mode 100644 index 000000000..84738d926 --- /dev/null +++ b/src/test/drop/drop-erc721/lazyMint/lazyMint.tree @@ -0,0 +1,33 @@ +function lazyMint( + uint256 _amount, + string calldata _baseURIForTokens, + bytes calldata _data +) +├── when called by a user without MINTER_ROLE +│ └── it should revert ✅ +└── when called by a user with MINTER_ROLE + ├── when _data.length == 0 + │ ├── when _amount is equal to 0 + │ │ └── it should revert ✅ + │ └── when _amount is greater than 0 + │ ├── it should push batchId (_startId + _amountToMint) to the batchIds array ✅ + │ ├── it should set baseURI[batchId] as _baseURIForTokens ✅ + │ └── it should emit TokensLazyMinted with the parameters: startId, startId + amount - 1, _baseURIForTokens, _data ✅ + └── when _data.length > 0 + ├── when _data invalid format + │ └── it should revert ✅ + └── when _data valid format + ├── it should decode _data into bytes memory encryptedURI and bytes32 provenanceHash ✅ + ├── when encryptedURI.length = 0 + │ ├── it should push batchId (_startId + _amountToMint) to the batchIds array ✅ + │ ├── it should set baseURI[batchId] as _baseURIForTokens ✅ + │ └── it should emit TokensLazyMinted with the parameters: startId, startId + amount - 1, _baseURIForTokens, _data ✅ + ├── when provenanceHash = "" + │ ├── it should push batchId (_startId + _amountToMint) to the batchIds array ✅ + │ ├── it should set baseURI[batchId] as _baseURIForTokens ✅ + │ └── it should emit TokensLazyMinted with the parameters: startId, startId + amount - 1, _baseURIForTokens, _data ✅ + └── when encryptedURI.length > 0 and provenanceHash does not equal "" + ├── it should set the encryptedData[batchId] equal to _data ✅ + ├── it should push batchId (_startId + _amountToMint) to the batchIds array ✅ + ├── it should set baseURI[batchId] as _baseURIForTokens ✅ + └── it should emit TokensLazyMinted with the parameters: startId, startId + amount - 1, _baseURIForTokens, _data ✅ \ No newline at end of file diff --git a/src/test/drop/drop-erc721/miscellaneous/miscellaneous.t.sol b/src/test/drop/drop-erc721/miscellaneous/miscellaneous.t.sol new file mode 100644 index 000000000..bb125a688 --- /dev/null +++ b/src/test/drop/drop-erc721/miscellaneous/miscellaneous.t.sol @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { DropERC721 } from "contracts/prebuilts/drop/DropERC721.sol"; + +// Test imports +import "erc721a-upgradeable/contracts/IERC721AUpgradeable.sol"; +import "../../../utils/BaseTest.sol"; +import "../../../../../lib/openzeppelin-contracts-upgradeable/contracts/interfaces/IERC2981Upgradeable.sol"; + +contract DropERC721Test_misc is BaseTest { + DropERC721 public drop; + + bytes private misc_data; + string private misc_baseURI; + uint256 private misc_amount; + bytes private misc_encryptedURI; + bytes32 private misc_provenanceHash; + string private misc_revealedURI; + uint256 private misc_index; + bytes private misc_key; + address private unauthorized = address(0x123); + + function setUp() public override { + super.setUp(); + drop = DropERC721(getContract("DropERC721")); + } + + /*/////////////////////////////////////////////////////////////// + Branch Testing + //////////////////////////////////////////////////////////////*/ + + modifier callerNotApproved() { + vm.startPrank(unauthorized); + _; + } + + modifier callerOwner() { + address receiver = address(0x92Bb439374a091c7507bE100183d8D1Ed2c9dAD3); + vm.startPrank(receiver); + _; + } + + modifier callerApproved() { + address receiver = address(0x92Bb439374a091c7507bE100183d8D1Ed2c9dAD3); + vm.prank(receiver); + drop.setApprovalForAll(deployer, true); + vm.startPrank(deployer); + _; + } + + modifier validIndex() { + misc_index = 0; + _; + } + + modifier invalidKey() { + misc_key = "invalidKey"; + _; + } + + modifier lazyMintEncrypted() { + misc_amount = 10; + misc_baseURI = "ipfs://"; + misc_revealedURI = "ipfs://revealed"; + misc_key = "key"; + misc_encryptedURI = drop.encryptDecrypt(bytes(misc_revealedURI), misc_key); + misc_provenanceHash = keccak256(abi.encodePacked(misc_revealedURI, misc_key, block.chainid)); + misc_data = abi.encode(misc_encryptedURI, misc_provenanceHash); + vm.prank(deployer); + drop.lazyMint(misc_amount, misc_baseURI, misc_data); + _; + } + + modifier tokenClaimed() { + string[] memory inputs = new string[](5); + + inputs[0] = "node"; + inputs[1] = "src/test/scripts/generateRoot.ts"; + inputs[2] = "300"; + inputs[3] = "0"; + inputs[4] = Strings.toHexString(uint160(address(erc20))); // address of erc20 + + bytes memory result = vm.ffi(inputs); + // revert(); + bytes32 root = abi.decode(result, (bytes32)); + + inputs[1] = "src/test/scripts/getProof.ts"; + result = vm.ffi(inputs); + bytes32[] memory proofs = abi.decode(result, (bytes32[])); + + DropERC721.AllowlistProof memory alp; + alp.proof = proofs; + alp.quantityLimitPerWallet = 300; + alp.pricePerToken = 0; + alp.currency = address(erc20); + + vm.warp(1); + + address receiver = address(0x92Bb439374a091c7507bE100183d8D1Ed2c9dAD3); // in allowlist + + DropERC721.ClaimCondition[] memory conditions = new DropERC721.ClaimCondition[](1); + conditions[0].maxClaimableSupply = 500; + conditions[0].quantityLimitPerWallet = 10; + conditions[0].merkleRoot = root; + conditions[0].pricePerToken = 10; + conditions[0].currency = address(erc20); + + vm.prank(deployer); + drop.setClaimConditions(conditions, false); + + // vm.prank(getActor(5), getActor(5)); + vm.prank(receiver, receiver); + drop.claim(receiver, 10, address(erc20), 0, alp, ""); // claims for free, because allowlist price is 0 + _; + } + + function test_totalMinted_TenLazyMintedZeroClaim() public lazyMintEncrypted { + uint256 totalMinted = drop.totalMinted(); + assertEq(totalMinted, 0); + } + + function test_totalMinted_TenLazyMintedTenClaim() public lazyMintEncrypted tokenClaimed { + uint256 totalMinted = drop.totalMinted(); + assertEq(totalMinted, 10); + } + + function test_nextTokenIdToMint_ZeroLazyMinted() public { + uint256 nextTokenIdToMint = drop.nextTokenIdToMint(); + assertEq(nextTokenIdToMint, 0); + } + + function test_nextTokenIdToMint_TenLazyMinted() public lazyMintEncrypted { + uint256 nextTokenIdToMint = drop.nextTokenIdToMint(); + assertEq(nextTokenIdToMint, 10); + } + + function test_nextTokenIdToClaim_ZeroClaimed() public { + uint256 nextTokenIdToClaim = drop.nextTokenIdToClaim(); + assertEq(nextTokenIdToClaim, 0); + } + + function test_nextTokenIdToClaim_TenClaimed() public lazyMintEncrypted tokenClaimed { + uint256 nextTokenIdToClaim = drop.nextTokenIdToClaim(); + assertEq(nextTokenIdToClaim, 10); + } + + function test_burn_revert_callerNotApproved() public lazyMintEncrypted tokenClaimed callerNotApproved { + vm.expectRevert(IERC721AUpgradeable.TransferCallerNotOwnerNorApproved.selector); + drop.burn(0); + } + + function test_burn_CallerApproved() public lazyMintEncrypted tokenClaimed callerApproved { + drop.burn(0); + uint256 totalSupply = drop.totalSupply(); + assertEq(totalSupply, 9); + vm.expectRevert(IERC721AUpgradeable.OwnerQueryForNonexistentToken.selector); + drop.ownerOf(0); + } + + function test_burn_revert_callerOwnerOfToken() public lazyMintEncrypted tokenClaimed callerOwner { + drop.burn(0); + uint256 totalSupply = drop.totalSupply(); + assertEq(totalSupply, 9); + vm.expectRevert(IERC721AUpgradeable.OwnerQueryForNonexistentToken.selector); + drop.ownerOf(0); + } + + function test_contractType() public { + assertEq(drop.contractType(), bytes32("DropERC721")); + } + + function test_contractVersion() public { + assertEq(drop.contractVersion(), uint8(4)); + } + + function test_supportsInterface() public { + assertEq(drop.supportsInterface(type(IERC2981Upgradeable).interfaceId), true); + assertEq(drop.supportsInterface(type(IERC721Upgradeable).interfaceId), true); + assertEq(drop.supportsInterface(type(IERC721MetadataUpgradeable).interfaceId), true); + } + + function test__msgData() public { + HarnessDropERC721MsgData msgDataDrop = new HarnessDropERC721MsgData(); + bytes memory msgData = msgDataDrop.msgData(); + bytes4 expectedData = msgDataDrop.msgData.selector; + assertEq(bytes4(msgData), expectedData); + } +} + +contract HarnessDropERC721MsgData is DropERC721 { + function msgData() public view returns (bytes memory) { + return _msgData(); + } +} diff --git a/src/test/drop/drop-erc721/miscellaneous/miscellaneous.tree b/src/test/drop/drop-erc721/miscellaneous/miscellaneous.tree new file mode 100644 index 000000000..0e15c80ad --- /dev/null +++ b/src/test/drop/drop-erc721/miscellaneous/miscellaneous.tree @@ -0,0 +1,23 @@ +function totalMinted() +└── it should return total number of minted tokens ✅ + +function nextTokenIdToMint() +└── it should return the next tokenId that is to be lazy minted ✅ + +function nextTokenIdToClaim() +└── it should return the next tokenId to be minted ✅ + +function burn(uint256 tokenId) +├── when caller is not the owner of tokenId +│ ├── when caller is not an approved operator of the owner of tokenId +│ │ └── it should revert ✅ +│ └── when caller is an approved operator of the owner of tokenId +│ └── it should burn the token ✅ +└── when caller is the owner of tokenId + └── it should burn the token ✅ + +function contractType() +└── it should return "DropERC721" in bytes32 format ✅ + +function contractVersion() +└── it should return 4 in uint8 format ✅ \ No newline at end of file diff --git a/src/test/drop/drop-erc721/reveal/reveal.t.sol b/src/test/drop/drop-erc721/reveal/reveal.t.sol new file mode 100644 index 000000000..64d4347f5 --- /dev/null +++ b/src/test/drop/drop-erc721/reveal/reveal.t.sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { DropERC721 } from "contracts/prebuilts/drop/DropERC721.sol"; + +// Test imports +import "contracts/lib/TWStrings.sol"; +import "../../../utils/BaseTest.sol"; +import "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol"; + +contract DropERC721Test_reveal is BaseTest { + using StringsUpgradeable for uint256; + + event TokenURIRevealed(uint256 indexed index, string revealedURI); + + DropERC721 public drop; + + bytes private reveal_data; + string private reveal_baseURI; + uint256 private reveal_amount; + bytes private reveal_encryptedURI; + bytes32 private reveal_provenanceHash; + string private reveal_revealedURI; + uint256 private reveal_index; + bytes private reveal_key; + address private unauthorized = address(0x123); + + function setUp() public override { + super.setUp(); + drop = DropERC721(getContract("DropERC721")); + } + + /*/////////////////////////////////////////////////////////////// + Branch Testing + //////////////////////////////////////////////////////////////*/ + + modifier callerWithoutMetadataRole() { + vm.startPrank(unauthorized); + _; + } + + modifier callerWithMetadataRole() { + vm.startPrank(deployer); + _; + } + + modifier validIndex() { + reveal_index = 0; + _; + } + + modifier invalidKey() { + reveal_key = "invalidKey"; + _; + } + + modifier invalidIndex() { + reveal_index = 1; + _; + } + + modifier lazyMintEncrypted() { + reveal_amount = 10; + reveal_baseURI = "ipfs://"; + reveal_revealedURI = "ipfs://revealed"; + reveal_key = "key"; + reveal_encryptedURI = drop.encryptDecrypt(bytes(reveal_revealedURI), reveal_key); + reveal_provenanceHash = keccak256(abi.encodePacked(reveal_revealedURI, reveal_key, block.chainid)); + reveal_data = abi.encode(reveal_encryptedURI, reveal_provenanceHash); + vm.prank(deployer); + drop.lazyMint(reveal_amount, reveal_baseURI, reveal_data); + _; + } + + modifier lazyMintUnEncrypted() { + reveal_amount = 10; + reveal_baseURI = "ipfs://"; + vm.prank(deployer); + drop.lazyMint(reveal_amount, reveal_baseURI, reveal_data); + _; + } + + function test_revert_NoMetadataRole() public callerWithoutMetadataRole { + bytes32 role = keccak256("METADATA_ROLE"); + vm.expectRevert( + abi.encodePacked( + "Permissions: account ", + TWStrings.toHexString(uint160(unauthorized), 20), + " is missing role ", + TWStrings.toHexString(uint256(role), 32) + ) + ); + drop.reveal(reveal_index, reveal_key); + } + + function test_state() public validIndex lazyMintEncrypted callerWithMetadataRole { + for (uint256 i = 0; i < reveal_amount; i += 1) { + string memory uri = drop.tokenURI(i); + assertEq(uri, string(abi.encodePacked(reveal_baseURI, "0"))); + } + + string memory revealedURI = drop.reveal(reveal_index, reveal_key); + assertEq(revealedURI, string(reveal_revealedURI)); + + for (uint256 i = 0; i < reveal_amount; i += 1) { + string memory uri = drop.tokenURI(i); + assertEq(uri, string(abi.encodePacked(reveal_revealedURI, i.toString()))); + } + + assertEq(drop.encryptedData(reveal_index), ""); + } + + function test_event() public validIndex lazyMintEncrypted callerWithMetadataRole { + vm.expectEmit(); + emit TokenURIRevealed(reveal_index, reveal_revealedURI); + drop.reveal(reveal_index, reveal_key); + } + + function test_revert_InvalidIndex() public invalidIndex lazyMintEncrypted callerWithMetadataRole { + vm.expectRevert("Invalid index"); + drop.reveal(reveal_index, reveal_key); + } + + function test_revert_InvalidKey() public validIndex lazyMintEncrypted invalidKey callerWithMetadataRole { + vm.expectRevert("Incorrect key"); + drop.reveal(reveal_index, reveal_key); + } + + function test_revert_NoEncryptedData() public validIndex lazyMintUnEncrypted callerWithMetadataRole { + vm.expectRevert("Nothing to reveal"); + drop.reveal(reveal_index, reveal_key); + } +} diff --git a/src/test/drop/drop-erc721/reveal/reveal.tree b/src/test/drop/drop-erc721/reveal/reveal.tree new file mode 100644 index 000000000..5e1e6717d --- /dev/null +++ b/src/test/drop/drop-erc721/reveal/reveal.tree @@ -0,0 +1,16 @@ +function reveal(uint256 _index, bytes calldata _key) +├── when called by a user without the METADATA_ROLE +│ └── it should revert ✅ +└── when called by a user with the METADATA_ROLE + ├── when called with an invalid index + │ └── it should revert ✅ + └── when called with a valid index + ├── when called on a batch with no encryptedData + │ └── it should revert ✅ + └── when called a batch with encryptedData + ├── when called with an invalid key + │ └── it should revert ✅ + └── when called with a valid key + ├── it should set encryptedData[(batchId of _index)] as a blank string ("") ✅ + ├── it should set the baseURI[(batchId of _index)] as the revealed uri ✅ + └── it should emit TokenURIRevealed with the following parameters: _index, revealed uri ✅ \ No newline at end of file diff --git a/src/test/drop/drop-erc721/setMaxTotalSupply/setMaxTotalSupply.t.sol b/src/test/drop/drop-erc721/setMaxTotalSupply/setMaxTotalSupply.t.sol new file mode 100644 index 000000000..b87a8db20 --- /dev/null +++ b/src/test/drop/drop-erc721/setMaxTotalSupply/setMaxTotalSupply.t.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { DropERC721 } from "contracts/prebuilts/drop/DropERC721.sol"; + +// Test imports +import "contracts/lib/TWStrings.sol"; +import "../../../utils/BaseTest.sol"; + +contract DropERC721Test_setMaxTotalSupply is BaseTest { + event MaxTotalSupplyUpdated(uint256 maxTotalSupply); + + DropERC721 public drop; + + address private unauthorized = address(0x123); + + function setUp() public override { + super.setUp(); + drop = DropERC721(getContract("DropERC721")); + } + + /*/////////////////////////////////////////////////////////////// + Branch Testing + //////////////////////////////////////////////////////////////*/ + + modifier callerNotAdmin() { + vm.startPrank(unauthorized); + _; + } + + modifier callerAdmin() { + vm.startPrank(deployer); + _; + } + + function test_revert_CallerNotAdmin() public callerNotAdmin { + bytes32 role = bytes32(0x00); + vm.expectRevert( + abi.encodePacked( + "Permissions: account ", + TWStrings.toHexString(uint160(unauthorized), 20), + " is missing role ", + TWStrings.toHexString(uint256(role), 32) + ) + ); + drop.setMaxTotalSupply(0); + } + + function test_state() public callerAdmin { + drop.setMaxTotalSupply(0); + assertEq(drop.maxTotalSupply(), 0); + } + + function test_event() public callerAdmin { + vm.expectEmit(false, false, false, false); + emit MaxTotalSupplyUpdated(0); + drop.setMaxTotalSupply(0); + } +} diff --git a/src/test/drop/drop-erc721/setMaxTotalSupply/setMaxTotalSupply.tree b/src/test/drop/drop-erc721/setMaxTotalSupply/setMaxTotalSupply.tree new file mode 100644 index 000000000..19631c92f --- /dev/null +++ b/src/test/drop/drop-erc721/setMaxTotalSupply/setMaxTotalSupply.tree @@ -0,0 +1,6 @@ +function setMaxTotalSupply(uint256 _maxTotalSupply) +├── when called by a user without the DEFAULT_ADMIN_ROLE +│ └── it should revert ✅ +└── when called by a user with the DEFAULT_ADMIN_ROLE + ├── it should set maxTotalSupply to _maxTotalSupply ✅ + └── it should emit MaxTotalSupplyUpdated with the following parameters: _maxTotalSupply ✅ \ No newline at end of file diff --git a/src/test/drop/drop-erc721/updateBatchBaseURI/updateBatchBaseURI.t.sol b/src/test/drop/drop-erc721/updateBatchBaseURI/updateBatchBaseURI.t.sol new file mode 100644 index 000000000..a0cbb249d --- /dev/null +++ b/src/test/drop/drop-erc721/updateBatchBaseURI/updateBatchBaseURI.t.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { DropERC721 } from "contracts/prebuilts/drop/DropERC721.sol"; + +// Test imports +import "contracts/lib/TWStrings.sol"; +import "../../../utils/BaseTest.sol"; +import "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol"; + +contract DropERC721Test_updateBatchBaseURI is BaseTest { + using StringsUpgradeable for uint256; + + event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); + + DropERC721 public drop; + + bytes private updateBatch_data; + string private updateBatch_baseURI; + string private updateBatch_newBaseURI; + uint256 private updateBatch_amount; + bytes private updateBatch_encryptedURI; + bytes32 private updateBatch_provenanceHash; + string private updateBatch_revealedURI; + bytes private updateBatch_key; + address private unauthorized = address(0x123); + + function setUp() public override { + super.setUp(); + drop = DropERC721(getContract("DropERC721")); + } + + /*/////////////////////////////////////////////////////////////// + Branch Testing + //////////////////////////////////////////////////////////////*/ + + modifier callerWithoutMetadataRole() { + vm.startPrank(unauthorized); + _; + } + + modifier callerWithMetadataRole() { + vm.startPrank(deployer); + _; + } + + modifier lazyMintEncrypted() { + updateBatch_amount = 10; + updateBatch_baseURI = "ipfs://"; + updateBatch_revealedURI = "ipfs://revealed"; + updateBatch_key = "key"; + updateBatch_encryptedURI = drop.encryptDecrypt(bytes(updateBatch_revealedURI), updateBatch_key); + updateBatch_provenanceHash = keccak256( + abi.encodePacked(updateBatch_revealedURI, updateBatch_key, block.chainid) + ); + updateBatch_data = abi.encode(updateBatch_encryptedURI, updateBatch_provenanceHash); + vm.prank(deployer); + drop.lazyMint(updateBatch_amount, updateBatch_baseURI, updateBatch_data); + _; + } + + modifier lazyMintUnEncryptedEmptyBaseURI() { + updateBatch_amount = 10; + updateBatch_baseURI = ""; + vm.prank(deployer); + drop.lazyMint(updateBatch_amount, updateBatch_baseURI, updateBatch_data); + _; + } + + modifier lazyMintUnEncryptedRegularBaseURI() { + updateBatch_amount = 10; + updateBatch_baseURI = "ipfs://"; + vm.prank(deployer); + drop.lazyMint(updateBatch_amount, updateBatch_baseURI, updateBatch_data); + _; + } + + modifier batchFrozen() { + drop.freezeBatchBaseURI(0); + _; + } + + function test_revert_NoMetadataRole() public callerWithoutMetadataRole { + bytes32 role = keccak256("METADATA_ROLE"); + vm.expectRevert( + abi.encodePacked( + "Permissions: account ", + TWStrings.toHexString(uint160(unauthorized), 20), + " is missing role ", + TWStrings.toHexString(uint256(role), 32) + ) + ); + drop.updateBatchBaseURI(0, updateBatch_newBaseURI); + } + + function test_revert_EncryptedBatch() public lazyMintEncrypted callerWithMetadataRole { + vm.expectRevert("Encrypted batch"); + drop.updateBatchBaseURI(0, updateBatch_newBaseURI); + } + + function test_revert_FrozenBatch() public lazyMintUnEncryptedRegularBaseURI callerWithMetadataRole batchFrozen { + vm.expectRevert("Batch frozen"); + drop.updateBatchBaseURI(0, updateBatch_newBaseURI); + } + + function test_state() public lazyMintUnEncryptedRegularBaseURI callerWithMetadataRole { + drop.updateBatchBaseURI(0, updateBatch_newBaseURI); + for (uint256 i = 0; i < updateBatch_amount; i += 1) { + string memory uri = drop.tokenURI(i); + assertEq(uri, string(abi.encodePacked(updateBatch_newBaseURI, i.toString()))); + } + } + + function test_event() public lazyMintUnEncryptedRegularBaseURI callerWithMetadataRole { + vm.expectEmit(false, false, false, false); + emit BatchMetadataUpdate(0, 10); + drop.updateBatchBaseURI(0, updateBatch_newBaseURI); + } +} diff --git a/src/test/drop/drop-erc721/updateBatchBaseURI/updateBatchBaseURI.tree b/src/test/drop/drop-erc721/updateBatchBaseURI/updateBatchBaseURI.tree new file mode 100644 index 000000000..b03e1198f --- /dev/null +++ b/src/test/drop/drop-erc721/updateBatchBaseURI/updateBatchBaseURI.tree @@ -0,0 +1,12 @@ +function updateBatchBaseURI(uint256 _index, string calldata _uri) +├── when called by a user without the METADATA_ROLE +│ └── it should revert ✅ +└── when called by a user with the METADATA_ROLE + ├── when the batchId for the provided _index is an encrypted batch + │ └── it should revert ✅ + └── when the batchId for the provided _index is not an encrypted batch + ├── when the batchId for the provided _index is frozen + │ └── it should revert ✅ + └── when the batchId for the provided _index is not frozen + ├── it should set the baseURI for the batchId as _uri ✅ + └── it should emit BatchMetadataUpdate with the following parameters: starting tokenId of batch, ending tokenId of batch ✅ \ No newline at end of file