diff --git a/packages/manifold/contracts/burnredeem/BurnRedeemCore.sol b/packages/manifold/contracts/burnredeem/BurnRedeemCore.sol index 69cd1593..f6c6b735 100644 --- a/packages/manifold/contracts/burnredeem/BurnRedeemCore.sol +++ b/packages/manifold/contracts/burnredeem/BurnRedeemCore.sol @@ -441,7 +441,7 @@ abstract contract BurnRedeemCore is ERC165, AdminControl, ReentrancyGuard, IBurn for (uint256 i; i < burnTokens.length;) { BurnToken memory burnToken = burnTokens[i]; BurnItem memory burnItem = burnRedeemInstance.burnSet[burnToken.groupIndex].items[burnToken.itemIndex]; - if (burnToken.id != tokenIds[i]) { + if (burnToken.contractAddress != msg.sender || burnToken.id != tokenIds[i]) { revert InvalidToken(tokenIds[i]); } if (burnItem.amount * burnRedeemCount != values[i]) { diff --git a/packages/manifold/test/burnredeem/ERC1155BurnRedeem.t.sol b/packages/manifold/test/burnredeem/ERC1155BurnRedeem.t.sol new file mode 100644 index 00000000..620ecfc7 --- /dev/null +++ b/packages/manifold/test/burnredeem/ERC1155BurnRedeem.t.sol @@ -0,0 +1,644 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "../../contracts/burnredeem/ERC1155BurnRedeem.sol"; +import "@manifoldxyz/creator-core-solidity/contracts/ERC721Creator.sol"; +import "@manifoldxyz/creator-core-solidity/contracts/ERC1155Creator.sol"; + +import "../mocks/Mock.sol"; + +contract ManifoldERC1155BurnRedeemTest is Test { + ERC1155BurnRedeem public burnRedeem; + ERC1155Creator public creator; + MockManifoldMembership public manifoldMembership; + ERC721Creator public burnable721; + ERC721Creator public burnable721_2; + ERC1155Creator public burnable1155; + ERC1155Creator public burnable1155_2; + MockERC721 public oz721; + MockERC1155 public oz1155; + MockERC721Burnable public oz721Burnable; + MockERC1155Burnable public oz1155Burnable; + MockERC1155Fallback public fallback1155; + MockERC1155FallbackBurnable public fallback1155Burnable; + + address public owner = 0x6140F00e4Ff3936702E68744f2b5978885464cbB; + address public burnRedeemOwner = 0xc78Dc443c126af6E4f6Ed540c1e740C1b5be09cd; + address public anyone1 = 0x80AAC46bbd3C2FcE33681541a52CacBEd14bF425; + address public anyone2 = 0x5174cD462b60c536eb51D4ceC1D561D3Ea31004F; + + address public zeroAddress = address(0); + address public deadAddress = 0x000000000000000000000000000000000000dEaD; + + function setUp() public { + vm.startPrank(owner); + creator = new ERC1155Creator("Test", "TEST"); + burnRedeem = new ERC1155BurnRedeem(burnRedeemOwner); + manifoldMembership = new MockManifoldMembership(); + burnable721 = new ERC721Creator("Test", "TEST"); + burnable721_2 = new ERC721Creator("Test", "TEST"); + burnable1155 = new ERC1155Creator("Test", "TEST"); + burnable1155_2 = new ERC1155Creator("Test", "TEST"); + oz721 = new MockERC721("Test", "TEST"); + oz1155 = new MockERC1155("test.com"); + oz721Burnable = new MockERC721Burnable("Test", "TEST"); + oz1155Burnable = new MockERC1155Burnable("test.com"); + fallback1155 = new MockERC1155Fallback("test.com"); + fallback1155Burnable = new MockERC1155FallbackBurnable("test.com"); + creator.registerExtension(address(burnRedeem), ""); + vm.stopPrank(); + + vm.prank(burnRedeemOwner); + burnRedeem.setMembershipAddress(address(manifoldMembership)); + vm.warp(100000); + } + + function testAccess() public { + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](0); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 1, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "", + burnSet: group + }); + + // Must be admin + vm.prank(anyone1); + vm.expectRevert(abi.encodePacked(IBurnRedeemCore.NotAdmin.selector, uint256(uint160(address(creator))))); + burnRedeem.initializeBurnRedeem(address(creator), 1, params); + + // Succeeds because admin + vm.prank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params); + + // Fails because not admin + vm.prank(anyone1); + vm.expectRevert(abi.encodePacked(IBurnRedeemCore.NotAdmin.selector, uint256(uint160(address(creator))))); + burnRedeem.updateBurnRedeem(address(creator), 1, params); + } + + function testInitializeSanitation() public { + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](0); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "", + burnSet: group + }); + + vm.startPrank(owner); + + params.endDate = uint48(block.timestamp - 60); + // Fails due to endDate <= startDate + vm.expectRevert(BurnRedeemLib.InvalidDates.selector); + burnRedeem.initializeBurnRedeem(address(creator), 1, params); + + + // Fails due to non-mod-0 redeemAmount + params.endDate = uint48(block.timestamp + 1000); + params.redeemAmount = 3; + vm.expectRevert(IBurnRedeemCore.InvalidRedeemAmount.selector); + burnRedeem.initializeBurnRedeem(address(creator), 1, params); + + // Cannot update non-existant burn redeem + vm.expectRevert(abi.encodePacked(IBurnRedeemCore.BurnRedeemDoesNotExist.selector, uint256(1))); + burnRedeem.updateBurnRedeem(address(creator), 1, params); + + // Cannot have amount == 0 on ERC1155 burn item + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable1155), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + params.redeemAmount = 1; + group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + params.burnSet = group; + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnRedeem.initializeBurnRedeem(address(creator), 1, params); + + // Cannot have ValidationType == INVALID on burn item + items[0].amount = 1; + items[0].validationType = IBurnRedeemCore.ValidationType.INVALID; + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnRedeem.initializeBurnRedeem(address(creator), 1, params); + + // Cannot have TokenSpec == INVALID on burn item + items[0].validationType = IBurnRedeemCore.ValidationType.CONTRACT; + items[0].tokenSpec = IBurnRedeemCore.TokenSpec.INVALID; + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnRedeem.initializeBurnRedeem(address(creator), 1, params); + + // Cannot have requiredCount == 0 on burn group + items[0].tokenSpec = IBurnRedeemCore.TokenSpec.ERC1155; + group[0].requiredCount = 0; + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnRedeem.initializeBurnRedeem(address(creator), 1, params); + + // Cannot have requiredCount > items.length on burn group + group[0].requiredCount = 2; + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnRedeem.initializeBurnRedeem(address(creator), 1, params); + + vm.stopPrank(); + } + + function testUpdateSanitation() public { + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](0); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "", + burnSet: group + }); + + vm.deal(owner, 1 ether); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params); + + params.endDate = uint48(block.timestamp - 60); + // Fails due to endDate <= startDate + vm.expectRevert(BurnRedeemLib.InvalidDates.selector); + burnRedeem.updateBurnRedeem(address(creator), 1, params); + + // Fails due to non-mod-0 redeemAmount + params.endDate = uint48(block.timestamp + 1000); + params.redeemAmount = 3; + vm.expectRevert(IBurnRedeemCore.InvalidRedeemAmount.selector); + burnRedeem.updateBurnRedeem(address(creator), 1, params); + + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](0); + uint256 burnFee = burnRedeem.BURN_FEE(); + burnRedeem.burnRedeem{value: burnFee*2}(address(creator), 1, 2, tokens); + + // Fails due to non-mod-0 redeemAmount after redemptions + params.redeemAmount = 3; + params.totalSupply = 9; + vm.expectRevert(IBurnRedeemCore.InvalidRedeemAmount.selector); + burnRedeem.updateBurnRedeem(address(creator), 1, params); + + // totalSupply = redeemedCount if updated below redeemedCount + params.redeemAmount = 1; + params.totalSupply = 1; + burnRedeem.updateBurnRedeem(address(creator), 1, params); + IBurnRedeemCore.BurnRedeem memory burnInstance = burnRedeem.getBurnRedeem(address(creator), 1); + assertEq(burnInstance.totalSupply, 2); + + // totalSupply = 0 if updated to 0 and redeemedCount > 0 + params.totalSupply = 0; + burnRedeem.updateBurnRedeem(address(creator), 1, params); + burnInstance = burnRedeem.getBurnRedeem(address(creator), 1); + assertEq(burnInstance.totalSupply, 0); + + vm.stopPrank(); + } + + function testTokenURI() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params); + burnable721.mintBase(anyone1); + vm.stopPrank(); + vm.deal(anyone1, 1 ether); + vm.startPrank(anyone1); + burnable721.setApprovalForAll(address(burnRedeem), true); + bytes32[] memory merkleProof; + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable721), + id: 1, + merkleProof: merkleProof + }); + uint256 burnFee = burnRedeem.BURN_FEE(); + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + + string memory uri = creator.uri(1); + assertEq(uri, "XXX"); + + vm.stopPrank(); + } + + function testBurnAnything() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](6); + // tokenSpec: ERC-721, burnSpec: NONE + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.ANY, + contractAddress: address(0), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.NONE, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + // tokenSpec: ERC-721, burnSpec: MANIFOLD + items[1] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.ANY, + contractAddress: address(0), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + // tokenSpec: ERC-721, burnSpec: OPENZEPPELIN + items[2] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.ANY, + contractAddress: address(0), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.OPENZEPPELIN, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + // tokenSpec: ERC-1155, burnSpec: NONE + items[3] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.ANY, + contractAddress: address(0), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.NONE, + amount: 1, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + // tokenSpec: ERC-1155, burnSpec: MANIFOLD + items[4] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.ANY, + contractAddress: address(0), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 1, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + // tokenSpec: ERC-1155, burnSpec: OPENZEPPELIN + items[5] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.ANY, + contractAddress: address(0), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.OPENZEPPELIN, + amount: 1, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params); + + // Mint tokens to anyone1 + oz721.mint(anyone1, 1); + burnable721.mintBase(anyone1); + oz721Burnable.mint(anyone1, 1); + oz1155.mint(anyone1, 1, 1); + address[] memory recipients = new address[](1); + recipients[0] = anyone1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1; + string[] memory uris = new string[](1); + uris[0] = ""; + burnable1155.mintBaseNew(recipients, amounts, uris); + oz1155Burnable.mint(anyone1, 1, 1); + + vm.stopPrank(); + + vm.deal(anyone1, 1 ether); + bytes32[] memory merkleProof; + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(0), + id: 1, + merkleProof: merkleProof + }); + uint256 burnFee = burnRedeem.BURN_FEE(); + + vm.startPrank(anyone1); + oz721.setApprovalForAll(address(burnRedeem), true); + tokens[0].contractAddress = address(oz721); + tokens[0].itemIndex = 0; + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + assertEq(0, oz721.balanceOf(anyone1)); + assertEq(1, oz721.balanceOf(address(0x000000000000000000000000000000000000dEaD))); + + burnable721.setApprovalForAll(address(burnRedeem), true); + tokens[0].contractAddress = address(burnable721); + tokens[0].itemIndex = 1; + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + vm.expectRevert("ERC721: invalid token ID"); + burnable721.ownerOf(1); + + oz721Burnable.setApprovalForAll(address(burnRedeem), true); + tokens[0].contractAddress = address(oz721Burnable); + tokens[0].itemIndex = 2; + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + vm.expectRevert("ERC721: invalid token ID"); + oz721Burnable.ownerOf(1); + + oz1155.setApprovalForAll(address(burnRedeem), true); + tokens[0].contractAddress = address(oz1155); + tokens[0].itemIndex = 3; + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + assertEq(0, oz1155.balanceOf(anyone1, 1)); + assertEq(1, oz1155.balanceOf(address(0x000000000000000000000000000000000000dEaD), 1)); + + burnable1155.setApprovalForAll(address(burnRedeem), true); + tokens[0].contractAddress = address(burnable1155); + tokens[0].itemIndex = 4; + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + assertEq(0, burnable1155.balanceOf(anyone1, 1)); + assertEq(0, burnable1155.totalSupply(1)); + + oz1155Burnable.setApprovalForAll(address(burnRedeem), true); + tokens[0].contractAddress = address(oz1155Burnable); + tokens[0].itemIndex = 5; + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + assertEq(0, oz1155Burnable.balanceOf(anyone1, 1)); + assertEq(0, oz1155Burnable.balanceOf(address(0x000000000000000000000000000000000000dEaD), 1)); + + vm.stopPrank(); + } + + function testOnERC1155ReceivedMultiple() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable1155), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 2, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 3, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params); + address[] memory recipients = new address[](1); + recipients[0] = anyone1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + uris[0] = ""; + burnable1155.mintBaseNew(recipients, amounts, uris); + burnable1155_2.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + vm.prank(owner); + manifoldMembership.setMember(anyone1, true); + + bytes32[] memory merkleProof; + bytes memory data = abi.encode(address(creator), uint256(1), uint32(2), uint256(0), merkleProof); + vm.prank(anyone1); + burnable1155.safeTransferFrom(anyone1, address(burnRedeem), 1, 4, data); + + // Ensure tokens are burned/minted + assertEq(6, burnable1155.balanceOf(anyone1, 1)); + assertEq(2 , creator.balanceOf(anyone1, 1)); + vm.stopPrank(); + } + + function testWithdraw() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params); + for (uint256 i = 0; i < 10; i++) { + burnable721.mintBase(anyone1); + } + vm.stopPrank(); + + vm.deal(anyone1, 1 ether); + vm.startPrank(anyone1); + burnable721.setApprovalForAll(address(burnRedeem), true); + address[] memory addresses = new address[](10); + uint256[] memory indexes = new uint256[](10); + uint32[] memory burnCounts = new uint32[](10); + bytes32[] memory merkleProof; + IBurnRedeemCore.BurnToken[][] memory allTokens = new IBurnRedeemCore.BurnToken[][](10); + for (uint256 i = 0; i < 10; i++) { + addresses[i] = address(creator); + indexes[i] = 1; + burnCounts[i] = 1; + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + IBurnRedeemCore.BurnToken memory token = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable721), + id: i+1, + merkleProof: merkleProof + }); + tokens[0] = token; + allTokens[i] = tokens; + } + uint256 burnFee = burnRedeem.BURN_FEE(); + burnRedeem.burnRedeem{value: burnFee*10}(addresses, indexes, burnCounts, allTokens); + vm.stopPrank(); + + vm.prank(anyone2); + vm.expectRevert("AdminControl: Must be owner or admin"); + burnRedeem.withdraw(payable(anyone2), burnFee*10); + + vm.prank(burnRedeemOwner); + vm.expectRevert(); + burnRedeem.withdraw(payable(owner), burnFee*11); + + vm.prank(burnRedeemOwner); + burnRedeem.withdraw(payable(owner), burnFee*10); + assertEq(burnFee*10, owner.balance); + } + + function testAirdrop() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 2, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params); + vm.stopPrank(); + + address[] memory recipients = new address[](2); + recipients[0] = anyone1; + recipients[1] = anyone2; + uint32[] memory amounts = new uint32[](2); + amounts[0] = 1; + amounts[1] = 1; + + vm.prank(anyone1); + vm.expectRevert(abi.encodePacked(IBurnRedeemCore.NotAdmin.selector, uint256(uint160(address(creator))))); + burnRedeem.airdrop(address(creator), 1, recipients, amounts); + + vm.prank(owner); + burnRedeem.airdrop(address(creator), 1, recipients, amounts); + + // Check balances + assertEq(2, creator.balanceOf(anyone1, 1)); + assertEq(2, creator.balanceOf(anyone2, 1)); + + // redeemedCount updated + IBurnRedeemCore.BurnRedeem memory burnInstance = burnRedeem.getBurnRedeem(address(creator), 1); + assertEq(burnInstance.redeemedCount, 4); + assertEq(burnInstance.totalSupply, 10); + + // Second airdrop + amounts[0] = 9; + amounts[1] = 9; + vm.prank(owner); + burnRedeem.airdrop(address(creator), 1, recipients, amounts); + + // check amounts + burnInstance = burnRedeem.getBurnRedeem(address(creator), 1); + assertEq(burnInstance.redeemedCount, 40); + assertEq(burnInstance.totalSupply, 40); + + // Check balances + assertEq(20, creator.balanceOf(anyone1, 1)); + assertEq(20, creator.balanceOf(anyone2, 1)); + + // Reverts when redeemedCount would exceed max uint32 + recipients = new address[](1); + recipients[0] = anyone1; + amounts = new uint32[](1); + amounts[0] = 2147483647; + vm.prank(owner); + vm.expectRevert(IBurnRedeemCore.InvalidRedeemAmount.selector); + burnRedeem.airdrop(address(creator), 1, recipients, amounts); + } + +} diff --git a/packages/manifold/test/burnredeem/ERC721BurnRedeem.t.sol b/packages/manifold/test/burnredeem/ERC721BurnRedeem.t.sol new file mode 100644 index 00000000..fd713430 --- /dev/null +++ b/packages/manifold/test/burnredeem/ERC721BurnRedeem.t.sol @@ -0,0 +1,2403 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "../../contracts/burnredeem/ERC721BurnRedeem.sol"; +import "@manifoldxyz/creator-core-solidity/contracts/ERC721Creator.sol"; +import "@manifoldxyz/creator-core-solidity/contracts/ERC1155Creator.sol"; + +import "../mocks/Mock.sol"; + +contract ManifoldERC721BurnRedeemTest is Test { + ERC721BurnRedeem public burnRedeem; + ERC721Creator public creator; + MockManifoldMembership public manifoldMembership; + ERC721Creator public burnable721; + ERC721Creator public burnable721_2; + ERC1155Creator public burnable1155; + ERC1155Creator public burnable1155_2; + MockERC721 public oz721; + MockERC1155 public oz1155; + MockERC721Burnable public oz721Burnable; + MockERC1155Burnable public oz1155Burnable; + MockERC1155Fallback public fallback1155; + MockERC1155FallbackBurnable public fallback1155Burnable; + + address public owner = 0x6140F00e4Ff3936702E68744f2b5978885464cbB; + address public burnRedeemOwner = 0xc78Dc443c126af6E4f6Ed540c1e740C1b5be09cd; + address public anyone1 = 0x80AAC46bbd3C2FcE33681541a52CacBEd14bF425; + address public anyone2 = 0x5174cD462b60c536eb51D4ceC1D561D3Ea31004F; + + address public zeroAddress = address(0); + address public deadAddress = 0x000000000000000000000000000000000000dEaD; + + function setUp() public { + vm.startPrank(owner); + creator = new ERC721Creator("Test", "TEST"); + burnRedeem = new ERC721BurnRedeem(burnRedeemOwner); + manifoldMembership = new MockManifoldMembership(); + burnable721 = new ERC721Creator("Test", "TEST"); + burnable721_2 = new ERC721Creator("Test", "TEST"); + burnable1155 = new ERC1155Creator("Test", "TEST"); + burnable1155_2 = new ERC1155Creator("Test", "TEST"); + oz721 = new MockERC721("Test", "TEST"); + oz1155 = new MockERC1155("test.com"); + oz721Burnable = new MockERC721Burnable("Test", "TEST"); + oz1155Burnable = new MockERC1155Burnable("test.com"); + fallback1155 = new MockERC1155Fallback("test.com"); + fallback1155Burnable = new MockERC1155FallbackBurnable("test.com"); + creator.registerExtension(address(burnRedeem), ""); + vm.stopPrank(); + + vm.prank(burnRedeemOwner); + burnRedeem.setMembershipAddress(address(manifoldMembership)); + vm.warp(100000); + } + + function testAccess() public { + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](0); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 1, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "", + burnSet: group + }); + + // Must be admin + vm.prank(anyone1); + vm.expectRevert(abi.encodePacked(IBurnRedeemCore.NotAdmin.selector, uint256(uint160(address(creator))))); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, true); + + // Succeeds because admin + vm.prank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, true); + + // Fails because not admin + vm.prank(anyone1); + vm.expectRevert(abi.encodePacked(IBurnRedeemCore.NotAdmin.selector, uint256(uint160(address(creator))))); + burnRedeem.updateBurnRedeem(address(creator), 1, params, true); + } + + function testInitializeSanitation() public { + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](0); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "", + burnSet: group + }); + + vm.startPrank(owner); + + params.endDate = uint48(block.timestamp - 60); + // Fails due to endDate <= startDate + vm.expectRevert(BurnRedeemLib.InvalidDates.selector); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, true); + + + // Fails due to non-mod-0 redeemAmount + params.endDate = uint48(block.timestamp + 1000); + params.redeemAmount = 3; + vm.expectRevert(IBurnRedeemCore.InvalidRedeemAmount.selector); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, true); + + // Cannot update non-existant burn redeem + vm.expectRevert(abi.encodePacked(IBurnRedeemCore.BurnRedeemDoesNotExist.selector, uint256(1))); + burnRedeem.updateBurnRedeem(address(creator), 1, params, true); + + // Cannot have amount == 0 on ERC1155 burn item + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable1155), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + params.redeemAmount = 1; + group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + params.burnSet = group; + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, true); + + // Cannot have ValidationType == INVALID on burn item + items[0].amount = 1; + items[0].validationType = IBurnRedeemCore.ValidationType.INVALID; + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, true); + + // Cannot have TokenSpec == INVALID on burn item + items[0].validationType = IBurnRedeemCore.ValidationType.CONTRACT; + items[0].tokenSpec = IBurnRedeemCore.TokenSpec.INVALID; + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, true); + + // Cannot have requiredCount == 0 on burn group + items[0].tokenSpec = IBurnRedeemCore.TokenSpec.ERC1155; + group[0].requiredCount = 0; + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, true); + + // Cannot have requiredCount > items.length on burn group + group[0].requiredCount = 2; + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, true); + + vm.stopPrank(); + } + + function testUpdateSanitation() public { + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](0); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "", + burnSet: group + }); + + vm.deal(owner, 1 ether); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, true); + + params.endDate = uint48(block.timestamp - 60); + // Fails due to endDate <= startDate + vm.expectRevert(BurnRedeemLib.InvalidDates.selector); + burnRedeem.updateBurnRedeem(address(creator), 1, params, true); + + // Fails due to non-mod-0 redeemAmount + params.endDate = uint48(block.timestamp + 1000); + params.redeemAmount = 3; + vm.expectRevert(IBurnRedeemCore.InvalidRedeemAmount.selector); + burnRedeem.updateBurnRedeem(address(creator), 1, params, true); + + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](0); + uint256 burnFee = burnRedeem.BURN_FEE(); + burnRedeem.burnRedeem{value: burnFee*2}(address(creator), 1, 2, tokens); + + // Fails due to non-mod-0 redeemAmount after redemptions + params.redeemAmount = 3; + params.totalSupply = 9; + vm.expectRevert(IBurnRedeemCore.InvalidRedeemAmount.selector); + burnRedeem.updateBurnRedeem(address(creator), 1, params, true); + + // totalSupply = redeemedCount if updated below redeemedCount + params.redeemAmount = 1; + params.totalSupply = 1; + burnRedeem.updateBurnRedeem(address(creator), 1, params, true); + IBurnRedeemCore.BurnRedeem memory burnInstance = burnRedeem.getBurnRedeem(address(creator), 1); + assertEq(burnInstance.totalSupply, 2); + + // totalSupply = 0 if updated to 0 and redeemedCount > 0 + params.totalSupply = 0; + burnRedeem.updateBurnRedeem(address(creator), 1, params, true); + burnInstance = burnRedeem.getBurnRedeem(address(creator), 1); + assertEq(burnInstance.totalSupply, 0); + + vm.stopPrank(); + } + + function testTokenURI() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, true); + burnable721.mintBase(anyone1); + vm.stopPrank(); + vm.deal(anyone1, 1 ether); + vm.startPrank(anyone1); + burnable721.setApprovalForAll(address(burnRedeem), true); + bytes32[] memory merkleProof; + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable721), + id: 1, + merkleProof: merkleProof + }); + uint256 burnFee = burnRedeem.BURN_FEE(); + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + + string memory uri = creator.tokenURI(1); + assertEq(uri, "XXX"); + + vm.stopPrank(); + + vm.prank(owner); + burnRedeem.updateBurnRedeem(address(creator), 1, params, false); + uri = creator.tokenURI(1); + assertEq(uri, "XXX/1"); + } + + function testTokenURIWithMintInBetween() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + burnable721.mintBase(anyone1); + burnable721.mintBase(anyone1); + vm.stopPrank(); + + vm.deal(anyone1, 1 ether); + + vm.prank(anyone1); + burnable721.setApprovalForAll(address(burnRedeem), true); + + bytes32[] memory merkleProof; + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable721), + id: 1, + merkleProof: merkleProof + }); + uint256 burnFee = burnRedeem.BURN_FEE(); + + // Redeem first token + vm.prank(anyone1); + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + string memory uri = creator.tokenURI(1); + assertEq(uri, "XXX/1"); + + vm.prank(owner); + creator.mintBase(anyone1); + + // Redeem another token + tokens[0].id = 2; + vm.prank(anyone1); + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + uri = creator.tokenURI(3); + assertEq(uri, "XXX/2"); + } + + function testBurnAnything() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](6); + // tokenSpec: ERC-721, burnSpec: NONE + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.ANY, + contractAddress: address(0), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.NONE, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + // tokenSpec: ERC-721, burnSpec: MANIFOLD + items[1] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.ANY, + contractAddress: address(0), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + // tokenSpec: ERC-721, burnSpec: OPENZEPPELIN + items[2] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.ANY, + contractAddress: address(0), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.OPENZEPPELIN, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + // tokenSpec: ERC-1155, burnSpec: NONE + items[3] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.ANY, + contractAddress: address(0), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.NONE, + amount: 1, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + // tokenSpec: ERC-1155, burnSpec: MANIFOLD + items[4] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.ANY, + contractAddress: address(0), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 1, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + // tokenSpec: ERC-1155, burnSpec: OPENZEPPELIN + items[5] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.ANY, + contractAddress: address(0), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.OPENZEPPELIN, + amount: 1, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + + // Mint tokens to anyone1 + oz721.mint(anyone1, 1); + burnable721.mintBase(anyone1); + oz721Burnable.mint(anyone1, 1); + oz1155.mint(anyone1, 1, 1); + address[] memory recipients = new address[](1); + recipients[0] = anyone1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1; + string[] memory uris = new string[](1); + uris[0] = ""; + burnable1155.mintBaseNew(recipients, amounts, uris); + oz1155Burnable.mint(anyone1, 1, 1); + + vm.stopPrank(); + + vm.deal(anyone1, 1 ether); + bytes32[] memory merkleProof; + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(0), + id: 1, + merkleProof: merkleProof + }); + uint256 burnFee = burnRedeem.BURN_FEE(); + + vm.startPrank(anyone1); + oz721.setApprovalForAll(address(burnRedeem), true); + tokens[0].contractAddress = address(oz721); + tokens[0].itemIndex = 0; + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + assertEq(0, oz721.balanceOf(anyone1)); + assertEq(1, oz721.balanceOf(address(0x000000000000000000000000000000000000dEaD))); + + burnable721.setApprovalForAll(address(burnRedeem), true); + tokens[0].contractAddress = address(burnable721); + tokens[0].itemIndex = 1; + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + vm.expectRevert("ERC721: invalid token ID"); + burnable721.ownerOf(1); + + oz721Burnable.setApprovalForAll(address(burnRedeem), true); + tokens[0].contractAddress = address(oz721Burnable); + tokens[0].itemIndex = 2; + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + vm.expectRevert("ERC721: invalid token ID"); + oz721Burnable.ownerOf(1); + + oz1155.setApprovalForAll(address(burnRedeem), true); + tokens[0].contractAddress = address(oz1155); + tokens[0].itemIndex = 3; + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + assertEq(0, oz1155.balanceOf(anyone1, 1)); + assertEq(1, oz1155.balanceOf(address(0x000000000000000000000000000000000000dEaD), 1)); + + burnable1155.setApprovalForAll(address(burnRedeem), true); + tokens[0].contractAddress = address(burnable1155); + tokens[0].itemIndex = 4; + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + assertEq(0, burnable1155.balanceOf(anyone1, 1)); + assertEq(0, burnable1155.totalSupply(1)); + + oz1155Burnable.setApprovalForAll(address(burnRedeem), true); + tokens[0].contractAddress = address(oz1155Burnable); + tokens[0].itemIndex = 5; + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + assertEq(0, oz1155Burnable.balanceOf(anyone1, 1)); + assertEq(0, oz1155Burnable.balanceOf(address(0x000000000000000000000000000000000000dEaD), 1)); + + vm.stopPrank(); + } + + function testRedeemWithCustomDataEmitted() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(oz721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.NONE, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + oz721.mint(anyone1, 1); + oz721.mint(anyone1, 2); + oz721.mint(anyone1, 3); + vm.stopPrank(); + + vm.deal(anyone1, 1 ether); + + vm.prank(anyone1); + oz721.setApprovalForAll(address(burnRedeem), true); + + bytes memory message = bytes("test"); + bytes32[] memory merkleProof; + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(oz721), + id: 1, + merkleProof: merkleProof + }); + uint256 burnFee = burnRedeem.BURN_FEE(); + + // Redeem first token + vm.prank(anyone1); + vm.expectEmit(); + emit BurnRedeemLib.BurnRedeemMint(address(creator), 1, 1, 1, message); + burnRedeem.burnRedeemWithData{value: burnFee}(address(creator), 1, 1, tokens, message); + assertEq(2, oz721.balanceOf(anyone1)); + assertEq(1, creator.balanceOf(anyone1)); + } + + function testContractsWithFallbackCannotBypass() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(fallback1155), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.NONE, + amount: 1, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + fallback1155.mint(anyone1, 1, 1); + vm.stopPrank(); + + vm.deal(anyone1, 1 ether); + + vm.prank(anyone1); + fallback1155.setApprovalForAll(address(burnRedeem), true); + bytes32[] memory merkleProof; + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(fallback1155), + id: 1, + merkleProof: merkleProof + }); + uint256 burnFee = burnRedeem.BURN_FEE(); + + vm.prank(anyone1); + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + assertEq(1, fallback1155.balanceOf(address(0x000000000000000000000000000000000000dEaD), 1)); + } + + function testBurn721() public { + /** + * const merkleElements = []; + * merkleElements.push(ethers.utils.solidityPack(["uint256"], [3])); + * merkleElements.push(ethers.utils.solidityPack(["uint256"], [10])); + * merkleElements.push(ethers.utils.solidityPack(["uint256"], [15])); + * merkleElements.push(ethers.utils.solidityPack(["uint256"], [20])); + * merkleTreeWithValues = new MerkleTree(merkleElements, keccak256, { hashLeaves: true, sortPairs: true }); + * merkleRoot = merkleTreeWithValues.getHexRoot() + */ + IBurnRedeemCore.BurnItem[] memory items1 = new IBurnRedeemCore.BurnItem[](1); + items1[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnItem[] memory items2 = new IBurnRedeemCore.BurnItem[](2); + items2[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.RANGE, + contractAddress: address(burnable721_2), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 1, + maxTokenId: 2, + merkleRoot: bytes32(0) + }); + items2[1] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.MERKLE_TREE, + contractAddress: address(burnable721_2), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0x9c1cfb2dc26ce204a49fc7b1a9b4e3a57e9344e39ee46ac2d6208d1c058e4bf0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](2); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items1 + }); + group[1] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items2 + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + for (uint256 i = 0; i < 4; i++) { + burnable721.mintBase(anyone1); + burnable721_2.mintBase(anyone1); + } + vm.stopPrank(); + + vm.deal(anyone1, 1 ether); + + vm.startPrank(anyone1); + burnable721.setApprovalForAll(address(burnRedeem), true); + burnable721_2.setApprovalForAll(address(burnRedeem), true); + + bytes32[] memory merkleProof; + bytes32[] memory merkleProofToken3 = new bytes32[](2); + merkleProofToken3[0] = bytes32(0xc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8); + merkleProofToken3[1] = bytes32(0xe465c7176713655952e4cd0925e3c01b046d9399e14c8fcbea1a9839720e1928); + + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable721), + id: 1, + merkleProof: merkleProof + }); + uint256 burnFee = burnRedeem.MULTI_BURN_FEE(); + + // Reverts due to unmet requirements + vm.expectRevert(IBurnRedeemCore.InvalidBurnAmount.selector); + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + + // Reverts due to too many tokens + tokens = new IBurnRedeemCore.BurnToken[](3); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable721), + id: 1, + merkleProof: merkleProof + }); + tokens[1] = IBurnRedeemCore.BurnToken({ + groupIndex: 1, + itemIndex: 0, + contractAddress: address(burnable721_2), + id: 1, + merkleProof: merkleProof + }); + tokens[2] = IBurnRedeemCore.BurnToken({ + groupIndex: 1, + itemIndex: 1, + contractAddress: address(burnable721_2), + id: 3, + merkleProof: merkleProofToken3 + }); + vm.expectRevert(IBurnRedeemCore.InvalidBurnAmount.selector); + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + + // Reverts when token ID out of range + tokens = new IBurnRedeemCore.BurnToken[](2); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable721), + id: 1, + merkleProof: merkleProof + }); + tokens[1] = IBurnRedeemCore.BurnToken({ + groupIndex: 1, + itemIndex: 0, + contractAddress: address(burnable721_2), + id: 3, + merkleProof: merkleProof + }); + vm.expectRevert(abi.encodePacked(IBurnRedeemCore.InvalidToken.selector, uint256(3))); + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + + // Reverts with invalid merkle proof + tokens = new IBurnRedeemCore.BurnToken[](2); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable721), + id: 1, + merkleProof: merkleProof + }); + tokens[1] = IBurnRedeemCore.BurnToken({ + groupIndex: 1, + itemIndex: 1, + contractAddress: address(burnable721_2), + id: 2, + merkleProof: merkleProof + }); + vm.expectRevert(BurnRedeemLib.InvalidMerkleProof.selector); + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + + // Reverts due to no fee + tokens = new IBurnRedeemCore.BurnToken[](2); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable721), + id: 1, + merkleProof: merkleProof + }); + tokens[1] = IBurnRedeemCore.BurnToken({ + groupIndex: 1, + itemIndex: 0, + contractAddress: address(burnable721_2), + id: 1, + merkleProof: merkleProof + }); + vm.expectRevert(IBurnRedeemCore.InvalidPaymentAmount.selector); + burnRedeem.burnRedeem(address(creator), 1, 1, tokens); + + // Reverts when msg.sender is not token owner, but tokens are approved + vm.stopPrank(); + vm.deal(anyone2, 1 ether); + vm.prank(anyone2); + vm.expectRevert(IBurnRedeemCore.TransferFailure.selector); + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + + // Reverts with burnCount > 1 + vm.startPrank(anyone1); + vm.expectRevert(IBurnRedeemCore.InvalidBurnAmount.selector); + burnRedeem.burnRedeem{value: burnFee*2}(address(creator), 1, 2, tokens); + + // Passes with met requirements - range + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + + (uint256 burnInstanceId, IBurnRedeemCore.BurnRedeem memory burnInstance) = burnRedeem.getBurnRedeemForToken(address(creator), 1); + assertEq(burnInstanceId, 1); + assertEq(burnInstance.contractVersion, 3); + + // Passes with met requirements - merkle tree + tokens[0].id = 2; + tokens[1].itemIndex = 1; + tokens[1].id = 3; + tokens[1].merkleProof = merkleProofToken3; + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + + // Ensure tokens are burned/minted + assertEq(2, burnable721.balanceOf(anyone1)); + assertEq(2, burnable721_2.balanceOf(anyone1)); + assertEq(2, creator.balanceOf(anyone1)); + vm.stopPrank(); + } + + function testBurn721BurnSpecNone() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(oz721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.NONE, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + for (uint256 i = 0; i < 4; i++) { + oz721.mint(anyone1, i); + } + vm.stopPrank(); + + vm.deal(anyone1, 1 ether); + + vm.startPrank(anyone1); + oz721.setApprovalForAll(address(burnRedeem), true); + + // Reverts due to unmet requirements + bytes32[] memory merkleProof; + + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(oz721), + id: 1, + merkleProof: merkleProof + }); + uint256 burnFee = burnRedeem.BURN_FEE(); + + // Passes with met requirements - range + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + // Ensure tokens are burned/minted + assertEq(3, oz721.balanceOf(anyone1)); + assertEq(1, oz721.balanceOf(address(0xdead))); + assertEq(1, creator.balanceOf(anyone1)); + vm.stopPrank(); + } + + function testBurn721BurnSpecOpenzeppelin() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(oz721Burnable), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.OPENZEPPELIN, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + for (uint256 i = 0; i < 4; i++) { + oz721Burnable.mint(anyone1, i); + } + vm.stopPrank(); + + vm.deal(anyone1, 1 ether); + + vm.startPrank(anyone1); + oz721Burnable.setApprovalForAll(address(burnRedeem), true); + + // Reverts due to unmet requirements + bytes32[] memory merkleProof; + + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(oz721Burnable), + id: 1, + merkleProof: merkleProof + }); + uint256 burnFee = burnRedeem.BURN_FEE(); + + // Passes with met requirement + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + // Ensure tokens are burned/minted + assertEq(3, oz721Burnable.balanceOf(anyone1)); + assertEq(0, oz721Burnable.balanceOf(address(0xdead))); + assertEq(1, creator.balanceOf(anyone1)); + vm.stopPrank(); + } + + function testBurn1155() public { + /** + * const merkleElements = []; + * merkleElements.push(ethers.utils.solidityPack(["uint256"], [2])); + * merkleElements.push(ethers.utils.solidityPack(["uint256"], [10])); + * merkleElements.push(ethers.utils.solidityPack(["uint256"], [15])); + * merkleElements.push(ethers.utils.solidityPack(["uint256"], [20])); + * merkleTreeWithValues = new MerkleTree(merkleElements, keccak256, { hashLeaves: true, sortPairs: true }); + * merkleRoot = merkleTreeWithValues.getHexRoot() + */ + + IBurnRedeemCore.BurnItem[] memory items1 = new IBurnRedeemCore.BurnItem[](1); + items1[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable1155_2), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 2, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnItem[] memory items2 = new IBurnRedeemCore.BurnItem[](1); + items2[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.RANGE, + contractAddress: address(burnable1155), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 1, + minTokenId: 1, + maxTokenId: 2, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnItem[] memory items3 = new IBurnRedeemCore.BurnItem[](2); + items3[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.MERKLE_TREE, + contractAddress: address(burnable1155), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 1, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0xa0e5e9d42a6adc6538b879161a5503e44aacae809116f3c8f0507a06728a04fc) + }); + items3[1] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](3); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items1 + }); + group[1] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items2 + }); + group[2] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items3 + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + address[] memory recipients = new address[](1); + recipients[0] = anyone1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + uris[0] = ""; + burnable1155.mintBaseNew(recipients, amounts, uris); + burnable1155.mintBaseNew(recipients, amounts, uris); + burnable1155_2.mintBaseNew(recipients, amounts, uris); + burnable721.mintBase(anyone1); + vm.stopPrank(); + + vm.deal(anyone1, 1 ether); + + vm.startPrank(anyone1); + burnable1155.setApprovalForAll(address(burnRedeem), true); + burnable1155_2.setApprovalForAll(address(burnRedeem), true); + burnable721.setApprovalForAll(address(burnRedeem), true); + + // Reverts due to unmet requirements + bytes32[] memory merkleProof; + bytes32[] memory merkleProofToken2 = new bytes32[](2); + merkleProofToken2[0] = bytes32(0xc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8); + merkleProofToken2[1] = bytes32(0xe465c7176713655952e4cd0925e3c01b046d9399e14c8fcbea1a9839720e1928); + + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](3); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable1155_2), + id: 1, + merkleProof: merkleProof + }); + tokens[1] = IBurnRedeemCore.BurnToken({ + groupIndex: 1, + itemIndex: 0, + contractAddress: address(burnable1155), + id: 1, + merkleProof: merkleProof + }); + tokens[2] = IBurnRedeemCore.BurnToken({ + groupIndex: 2, + itemIndex: 1, + contractAddress: address(burnable721), + id: 1, + merkleProof: merkleProof + }); + uint256 burnFee = burnRedeem.MULTI_BURN_FEE(); + + // Reverts with burnCount > 1 if 721 is in burnTokens + vm.expectRevert(IBurnRedeemCore.InvalidBurnAmount.selector); + burnRedeem.burnRedeem{value: burnFee*2}(address(creator), 1, 2, tokens); + + // Passes with burnCount > 1 + tokens[2].itemIndex = 0; + tokens[2].contractAddress = address(burnable1155); + tokens[2].id = 2; + tokens[2].merkleProof = merkleProofToken2; + burnRedeem.burnRedeem{value: burnFee*3}(address(creator), 1, 3, tokens); + + + // Ensure tokens are burned/minted + assertEq(7, burnable1155.balanceOf(anyone1, 1)); + assertEq(7, burnable1155.balanceOf(anyone1, 2)); + assertEq(4, burnable1155_2.balanceOf(anyone1, 1)); + assertEq(3, creator.balanceOf(anyone1)); + + vm.stopPrank(); + } + + function testBurn1155BurnSpecNone() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(oz1155), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.NONE, + amount: 1, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + oz1155.mint(anyone1, 1, 3); + vm.stopPrank(); + + vm.deal(anyone1, 1 ether); + + vm.startPrank(anyone1); + oz1155.setApprovalForAll(address(burnRedeem), true); + + bytes32[] memory merkleProof; + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(oz1155), + id: 1, + merkleProof: merkleProof + }); + uint256 burnFee = burnRedeem.BURN_FEE(); + + // Passes with met requirements + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + // Ensure tokens are burned/minted + assertEq(2, oz1155.balanceOf(anyone1, 1)); + assertEq(1, oz1155.balanceOf(address(0xdead), 1)); + assertEq(1, creator.balanceOf(anyone1)); + vm.stopPrank(); + } + + function testBurn1155BurnSpecOpenzeppelin() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(oz1155Burnable), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.OPENZEPPELIN, + amount: 1, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + oz1155Burnable.mint(anyone1, 1, 3); + vm.stopPrank(); + + vm.deal(anyone1, 1 ether); + + vm.startPrank(anyone1); + oz1155Burnable.setApprovalForAll(address(burnRedeem), true); + + bytes32[] memory merkleProof; + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(oz1155Burnable), + id: 1, + merkleProof: merkleProof + }); + uint256 burnFee = burnRedeem.BURN_FEE(); + + // Passes with met requirements + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + // Ensure tokens are burned/minted + assertEq(2, oz1155Burnable.balanceOf(anyone1, 1)); + assertEq(0, oz1155Burnable.balanceOf(address(0xdead), 1)); + assertEq(1, creator.balanceOf(anyone1)); + vm.stopPrank(); + } + + function testRedeemAmountMoreThanOne() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.NONE, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 2, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + burnable721.mintBase(anyone1); + vm.stopPrank(); + + vm.deal(anyone1, 1 ether); + + vm.startPrank(anyone1); + burnable721.setApprovalForAll(address(burnRedeem), true); + + bytes32[] memory merkleProof; + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable721), + id: 1, + merkleProof: merkleProof + }); + uint256 burnFee = burnRedeem.BURN_FEE(); + + // Passes with met requirements + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + // Ensure tokens are burned/minted + assertEq(0, burnable721.balanceOf(anyone1)); + assertEq(2, creator.balanceOf(anyone1)); + vm.stopPrank(); + } + + function testMaliciousSenderReverts() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.NONE, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + // Burn #1 + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + // Burn #2 + items[0].contractAddress = address(burnable1155); + items[0].tokenSpec = IBurnRedeemCore.TokenSpec.ERC1155; + items[0].amount = 1; + burnRedeem.initializeBurnRedeem(address(creator), 2, params, false); + // Burn #3 + items[0].burnSpec = IBurnRedeemCore.BurnSpec.MANIFOLD; + burnRedeem.initializeBurnRedeem(address(creator), 3, params, false); + + burnable721.mintBase(anyone1); + address[] memory recipients = new address[](1); + recipients[0] = anyone1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 2; + string[] memory uris = new string[](1); + uris[0] = ""; + burnable1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + vm.deal(anyone1, 1 ether); + vm.deal(anyone2, 1 ether); + + vm.startPrank(anyone1); + burnable721.setApprovalForAll(address(burnRedeem), true); + burnable1155.setApprovalForAll(address(burnRedeem), true); + vm.stopPrank(); + + bytes32[] memory merkleProof; + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable721), + id: 1, + merkleProof: merkleProof + }); + uint256 burnFee = burnRedeem.BURN_FEE(); + + // Reverts when msg.sender is not token owner, but tokens are approved + // 721 with no burn + vm.prank(anyone2); + vm.expectRevert("ERC721: transfer from incorrect owner"); + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + // 1155 with no burn + tokens[0].contractAddress = address(burnable1155); + vm.expectRevert("ERC1155: caller is not token owner or approved"); + burnRedeem.burnRedeem{value: burnFee}(address(creator), 2, 1, tokens); + // 1155 with burn + vm.expectRevert("Caller is not owner or approved"); + burnRedeem.burnRedeem{value: burnFee}(address(creator), 3, 1, tokens); + } + + function testBurnRedeemWithCost() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](2); + uint160 cost = 1000000000000000000; + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + items[1] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable1155), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 1, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: cost, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + + burnable721.mintBase(anyone1); + address[] memory recipients = new address[](1); + recipients[0] = anyone1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + uris[0] = ""; + burnable1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + vm.deal(anyone1, 10 ether); + + vm.startPrank(anyone1); + burnable721.setApprovalForAll(address(burnRedeem), true); + burnable1155.setApprovalForAll(address(burnRedeem), true); + + bytes32[] memory merkleProof; + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable721), + id: 1, + merkleProof: merkleProof + }); + uint256 burnFee = burnRedeem.BURN_FEE(); + + // Reverts due to invalid value + vm.expectRevert(IBurnRedeemCore.InvalidPaymentAmount.selector); + burnRedeem.burnRedeem{value: burnFee}(address(creator), 1, 1, tokens); + + // Passes with proper value + burnRedeem.burnRedeem{value: burnFee+cost}(address(creator), 1, 1, tokens); + + // Passes with burnCount > 1 + tokens[0].itemIndex = 1; + tokens[0].contractAddress = address(burnable1155); + burnRedeem.burnRedeem{value: (burnFee+cost)*5}(address(creator), 1, 5, tokens); + vm.stopPrank(); + + // Check that cost was sent to creator + assertEq(cost*6, owner.balance); + } + + function testRedeemWithMembership() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.NONE, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + manifoldMembership.setMember(anyone1, true); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + burnable721.mintBase(anyone1); + vm.stopPrank(); + + vm.startPrank(anyone1); + burnable721.setApprovalForAll(address(burnRedeem), true); + + bytes32[] memory merkleProof; + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable721), + id: 1, + merkleProof: merkleProof + }); + + // Passes without burn fee + burnRedeem.burnRedeem(address(creator), 1, 1, tokens); + // Ensure tokens are burned/minted + assertEq(0, burnable721.balanceOf(anyone1)); + assertEq(1, creator.balanceOf(anyone1)); + vm.stopPrank(); + } + + function testMultipleRedemptions() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 2, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + // Burn #1 + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + // Burn #2 + items[0].contractAddress = address(burnable1155); + items[0].tokenSpec = IBurnRedeemCore.TokenSpec.ERC1155; + items[0].amount = 1; + burnRedeem.initializeBurnRedeem(address(creator), 2, params, false); + + burnable721.mintBase(anyone1); + address[] memory recipients = new address[](1); + recipients[0] = anyone1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 3; + string[] memory uris = new string[](1); + uris[0] = ""; + burnable1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + vm.deal(anyone1, 1 ether); + vm.startPrank(anyone1); + burnable721.setApprovalForAll(address(burnRedeem), true); + burnable1155.setApprovalForAll(address(burnRedeem), true); + + address[] memory addresses = new address[](2); + addresses[0] = address(creator); + addresses[1] = address(creator); + + uint256[] memory indexes = new uint256[](2); + indexes[0] = 1; + indexes[1] = 2; + + uint32[] memory burnCounts = new uint32[](2); + burnCounts[0] = 1; + burnCounts[1] = 3; + + bytes32[] memory merkleProof; + IBurnRedeemCore.BurnToken[][] memory allTokens = new IBurnRedeemCore.BurnToken[][](2); + IBurnRedeemCore.BurnToken[] memory tokens1 = new IBurnRedeemCore.BurnToken[](1); + IBurnRedeemCore.BurnToken[] memory tokens2 = new IBurnRedeemCore.BurnToken[](1); + + tokens1[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable721), + id: 1, + merkleProof: merkleProof + }); + tokens2[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable1155), + id: 1, + merkleProof: merkleProof + }); + allTokens[0] = tokens1; + allTokens[1] = tokens2; + uint256 burnFee = burnRedeem.BURN_FEE(); + + vm.expectRevert(IBurnRedeemCore.InvalidPaymentAmount.selector); + burnRedeem.burnRedeem{value: burnFee}(addresses, indexes, burnCounts, allTokens); + + // Reverts with mismatching lengths + address[] memory singleAddresses = new address[](1); + addresses[0] = address(creator); + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnRedeem.burnRedeem{value: burnFee*2}(singleAddresses, indexes, burnCounts, allTokens); + + uint256[] memory singleIndexes = new uint256[](1); + indexes[0] = 1; + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnRedeem.burnRedeem{value: burnFee*2}(addresses, singleIndexes, burnCounts, allTokens); + + uint32[] memory singleBurnCounts = new uint32[](1); + burnCounts[0] = 1; + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnRedeem.burnRedeem{value: burnFee*2}(addresses, indexes, singleBurnCounts, allTokens); + + IBurnRedeemCore.BurnToken[][] memory singleTokens = new IBurnRedeemCore.BurnToken[][](1); + singleTokens[0] = tokens1; + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnRedeem.burnRedeem{value: burnFee*2}(addresses, indexes, burnCounts, singleTokens); + + // Reverts with burnCount > 1 for 721 + burnCounts[0] = 2; + vm.expectRevert(IBurnRedeemCore.InvalidBurnAmount.selector); + burnRedeem.burnRedeem{value: burnFee*2}(addresses, indexes, burnCounts, allTokens); + + // Passes with multiple redemptions, burnCount > 1 for 1155 + burnCounts[0] = 1; + burnRedeem.burnRedeem{value: burnFee*4}(addresses, indexes, burnCounts, allTokens); + + // However, only 2 redeemed for 1155 because total supply is 2 + // Excess fee refunded + assertEq(1 ether - burnFee*3, anyone1.balance); + + // Check balances + assertEq(0, burnable721.balanceOf(anyone1)); + assertEq(1, burnable1155.balanceOf(anyone1, 1)); + assertEq(3, creator.balanceOf(anyone1)); + } + + function testOnERC721Received() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 2, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + + burnable721.mintBase(anyone1); + burnable721_2.mintBase(anyone1); + vm.stopPrank(); + + // Reverts without membership + bytes32[] memory merkleProof; + bytes memory data = abi.encode(address(creator), uint256(1), uint256(0), merkleProof); + vm.prank(anyone1); + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnable721.safeTransferFrom(anyone1, address(burnRedeem), 1, data); + + vm.prank(owner); + manifoldMembership.setMember(anyone1, true); + + // Reverts due to the wrong contract + vm.prank(anyone1); + vm.expectRevert(BurnRedeemLib.InvalidBurnToken.selector); + burnable721_2.safeTransferFrom(anyone1, address(burnRedeem), 1, data); + + // Passes with right token id + vm.prank(anyone1); + burnable721.safeTransferFrom(anyone1, address(burnRedeem), 1, data); + + // Ensure tokens are burned/minted + assertEq(0, burnable721.balanceOf(anyone1)); + assertEq(1, creator.balanceOf(anyone1)); + vm.stopPrank(); + } + + function testOnERC1155Received() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable1155), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 2, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + address[] memory recipients = new address[](1); + recipients[0] = anyone1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 3; + string[] memory uris = new string[](1); + uris[0] = ""; + burnable1155.mintBaseNew(recipients, amounts, uris); + burnable1155_2.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + // Reverts without membership + bytes32[] memory merkleProof; + bytes memory data = abi.encode(address(creator), uint256(1), uint32(1), uint256(0), merkleProof); + vm.prank(anyone1); + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + burnable1155.safeTransferFrom(anyone1, address(burnRedeem), 1, 2, data); + + vm.prank(owner); + manifoldMembership.setMember(anyone1, true); + + // Reverts due to the wrong contract + vm.prank(anyone1); + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + burnable1155_2.safeTransferFrom(anyone1, address(burnRedeem), 1, 2, data); + + // Reverts with invalid amount + vm.prank(anyone1); + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + burnable1155.safeTransferFrom(anyone1, address(burnRedeem), 1, 3, data); + + // Passes with right token id + vm.prank(anyone1); + burnable1155.safeTransferFrom(anyone1, address(burnRedeem), 1, 2, data); + + // Ensure tokens are burned/minted + assertEq(1, burnable1155.balanceOf(anyone1, 1)); + assertEq(1, creator.balanceOf(anyone1)); + vm.stopPrank(); + } + + function testOnERC1155ReceivedMultiple() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable1155), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 2, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 3, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + address[] memory recipients = new address[](1); + recipients[0] = anyone1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + uris[0] = ""; + burnable1155.mintBaseNew(recipients, amounts, uris); + burnable1155_2.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + vm.prank(owner); + manifoldMembership.setMember(anyone1, true); + + bytes32[] memory merkleProof; + bytes memory data = abi.encode(address(creator), uint256(1), uint32(2), uint256(0), merkleProof); + vm.prank(anyone1); + burnable1155.safeTransferFrom(anyone1, address(burnRedeem), 1, 4, data); + + // Ensure tokens are burned/minted + assertEq(6, burnable1155.balanceOf(anyone1, 1)); + assertEq(2 , creator.balanceOf(anyone1)); + vm.stopPrank(); + } + + function testOnERC1155BatchReceived() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](2); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.RANGE, + contractAddress: address(burnable1155), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 2, + minTokenId: 1, + maxTokenId: 1, + merkleRoot: bytes32(0) + }); + items[1] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.RANGE, + contractAddress: address(burnable1155), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 2, + minTokenId: 2, + maxTokenId: 2, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 2, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + address[] memory recipients = new address[](1); + recipients[0] = anyone1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + uris[0] = ""; + burnable1155.mintBaseNew(recipients, amounts, uris); + burnable1155.mintBaseNew(recipients, amounts, uris); + burnable1155.mintBaseNew(recipients, amounts, uris); + burnable1155_2.mintBaseNew(recipients, amounts, uris); + burnable1155_2.mintBaseNew(recipients, amounts, uris); + burnable1155_2.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + bytes32[] memory merkleProof; + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](2); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable1155), + id: 1, + merkleProof: merkleProof + }); + tokens[1] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 1, + contractAddress: address(burnable1155), + id: 2, + merkleProof: merkleProof + }); + bytes memory data = abi.encode(address(creator), uint256(1), uint32(1), tokens); + // Reverts without membership + vm.prank(anyone1); + uint256[] memory tokenIds = new uint256[](2); + tokenIds[0] = 1; + tokenIds[1] = 2; + uint256[] memory values = new uint256[](2); + values[0] = 2; + values[1] = 2; + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + burnable1155.safeBatchTransferFrom(anyone1, address(burnRedeem), tokenIds, values, data); + + vm.prank(owner); + manifoldMembership.setMember(anyone1, true); + + // Reverts due to the wrong contract + vm.prank(anyone1); + vm.expectRevert(abi.encodePacked("ERC1155: transfer to non-ERC1155Receiver implementer")); + burnable1155_2.safeBatchTransferFrom(anyone1, address(burnRedeem), tokenIds, values, data); + + // Reverts with mismatching token ids amount + vm.prank(anyone1); + tokenIds[1] = 3; + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + burnable1155.safeBatchTransferFrom(anyone1, address(burnRedeem), tokenIds, values, data); + + // Reverts with mismatching values amount + vm.prank(anyone1); + tokenIds[1] = 2; + values[0] = 1; + values[1] = 1; + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + burnable1155.safeBatchTransferFrom(anyone1, address(burnRedeem), tokenIds, values, data); + + // Reverts with extra tokens + vm.prank(anyone1); + uint256[] memory extraTokenIds = new uint256[](3); + extraTokenIds[0] = 1; + extraTokenIds[1] = 2; + extraTokenIds[2] = 3; + uint256[] memory extraValues = new uint256[](3); + values[0] = 2; + values[1] = 2; + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + burnable1155.safeBatchTransferFrom(anyone1, address(burnRedeem), extraTokenIds, extraValues, data); + + // Passes with right token id + values[0] = 2; + values[1] = 2; + vm.prank(anyone1); + burnable1155.safeBatchTransferFrom(anyone1, address(burnRedeem), tokenIds, values, data); + + // burnCount > 1 + vm.prank(anyone1); + data = abi.encode(address(creator), uint256(1), uint32(4), tokens); + // Reverts with insufficient values + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + burnable1155.safeBatchTransferFrom(anyone1, address(burnRedeem), tokenIds, values, data); + + // Passes with right values + vm.prank(anyone1); + values[0] = 8; + values[1] = 8; + burnable1155.safeBatchTransferFrom(anyone1, address(burnRedeem), tokenIds, values, data); + + // Ensure tokens are burned/minted + assertEq(0, burnable1155.balanceOf(anyone1, 1)); + assertEq(0, burnable1155.balanceOf(anyone1, 2)); + assertEq(5, creator.balanceOf(anyone1)); + vm.stopPrank(); + } + + + function testOnERC1155BatchReceivedDirectCall() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.RANGE, + contractAddress: address(burnable721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 1, + maxTokenId: 1, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + burnable721.mintBase(anyone1); + vm.stopPrank(); + + bytes32[] memory merkleProof; + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable721), + id: 1, + merkleProof: merkleProof + }); + bytes memory data = abi.encode(address(creator), uint256(1), uint32(1), tokens); + + vm.prank(anyone1); + burnable721.setApprovalForAll(address(burnRedeem), true); + + vm.prank(owner); + manifoldMembership.setMember(anyone1, true); + + uint256[] memory tokenIds = new uint256[](1); + tokenIds[0] = 1; + uint256[] memory values = new uint256[](1); + values[0] = 0; + vm.prank(anyone2); + vm.expectRevert(abi.encodePacked(IBurnRedeemCore.InvalidToken.selector, uint256(1))); + burnRedeem.onERC1155BatchReceived(anyone2, anyone1, tokenIds, values, data); + } + + function testReceiverInvalidInput() public { + uint160 cost = 1000000000000000000; + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 2, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: cost, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + // Burn #1 - burn with cost + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + // Burn #2 - zero cost multi set requirement + group = new IBurnRedeemCore.BurnGroup[](2); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + group[1] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + params.cost = 0; + params.burnSet = group; + burnRedeem.initializeBurnRedeem(address(creator), 2, params, false); + // Burn #3 - zero cost multi item set + items = new IBurnRedeemCore.BurnItem[](2); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.RANGE, + contractAddress: address(burnable1155), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 1, + minTokenId: 1, + maxTokenId: 1, + merkleRoot: bytes32(0) + }); + items[1] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.RANGE, + contractAddress: address(burnable1155), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 1, + minTokenId: 2, + maxTokenId: 2, + merkleRoot: bytes32(0) + }); + group = new IBurnRedeemCore.BurnGroup[](1); + group[0].items = items; + group[0].requiredCount = 2; + params.cost = 0; + params.burnSet = group; + burnRedeem.initializeBurnRedeem(address(creator), 3, params, false); + + + burnable721.mintBase(anyone1); + address[] memory recipients = new address[](1); + recipients[0] = anyone1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1; + string[] memory uris = new string[](1); + uris[0] = ""; + burnable1155.mintBaseNew(recipients, amounts, uris); + burnable1155.mintBaseNew(recipients, amounts, uris); + manifoldMembership.setMember(anyone1, true); + vm.stopPrank(); + + vm.deal(anyone1, 1 ether); + vm.startPrank(anyone1); + + // Receivers revert on burns with cost + // onERC721Received + bytes32[] memory merkleProof; + bytes memory data = abi.encode(address(creator), uint256(1), uint256(0), merkleProof); + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnable721.safeTransferFrom(anyone1, address(burnRedeem), 1, data); + + // onERC1155Received + data = abi.encode(address(creator), uint256(1), uint32(1), uint256(0), merkleProof); + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + burnable1155.safeTransferFrom(anyone1, address(burnRedeem), 1, 1, data); + + // onERC1155BatchReceived + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + tokens[0] = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable1155), + id: 1, + merkleProof: merkleProof + }); + data = abi.encode(address(creator), uint256(1), uint32(1), tokens); + uint256[] memory tokenIds = new uint256[](1); + tokenIds[0] = 1; + uint256[] memory values = new uint256[](1); + values[0] = 1; + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + burnable1155.safeBatchTransferFrom(anyone1, address(burnRedeem), tokenIds, values, data); + + // Single receivers revert when burnSets.length > 1 (burn #2) + data = abi.encode(address(creator), uint256(2), uint256(0), merkleProof); + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnable721.safeTransferFrom(anyone1, address(burnRedeem), 1, data); + data = abi.encode(address(creator), uint256(2), uint32(1), uint256(0), merkleProof); + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + burnable1155.safeTransferFrom(anyone1, address(burnRedeem), 1, 1, data); + + // Single receivers revert when requiredCount > 1 (burn #3) + data = abi.encode(address(creator), uint256(3), uint256(0), merkleProof); + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnable721.safeTransferFrom(anyone1, address(burnRedeem), 1, data); + data = abi.encode(address(creator), uint256(3), uint32(1), uint256(0), merkleProof); + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + burnable1155.safeTransferFrom(anyone1, address(burnRedeem), 1, 1, data); + } + + function testMisconfigurationOnERC721Received() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC1155, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 1, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + manifoldMembership.setMember(anyone1, true); + burnable721.mintBase(anyone1); + vm.stopPrank(); + bytes32[] memory merkleProof; + bytes memory data = abi.encode(address(creator), uint256(1), uint256(0), merkleProof); + + vm.prank(anyone1); + vm.expectRevert(IBurnRedeemCore.InvalidInput.selector); + burnable721.safeTransferFrom(anyone1, address(burnRedeem), 1, data); + } + + function testMisconfigurationOnERC1155Received() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable1155), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + manifoldMembership.setMember(anyone1, true); + address[] memory recipients = new address[](1); + recipients[0] = anyone1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1; + string[] memory uris = new string[](1); + uris[0] = ""; + burnable1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + bytes32[] memory merkleProof; + bytes memory data = abi.encode(address(creator), uint256(1), uint256(0), merkleProof); + + vm.prank(anyone1); + data = abi.encode(address(creator), uint256(1), uint32(1), uint256(0), merkleProof); + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + burnable1155.safeTransferFrom(anyone1, address(burnRedeem), 1, 1, data); + } + + function testWithdraw() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 1, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + for (uint256 i = 0; i < 10; i++) { + burnable721.mintBase(anyone1); + } + vm.stopPrank(); + + vm.deal(anyone1, 1 ether); + vm.startPrank(anyone1); + burnable721.setApprovalForAll(address(burnRedeem), true); + address[] memory addresses = new address[](10); + uint256[] memory indexes = new uint256[](10); + uint32[] memory burnCounts = new uint32[](10); + bytes32[] memory merkleProof; + IBurnRedeemCore.BurnToken[][] memory allTokens = new IBurnRedeemCore.BurnToken[][](10); + for (uint256 i = 0; i < 10; i++) { + addresses[i] = address(creator); + indexes[i] = 1; + burnCounts[i] = 1; + IBurnRedeemCore.BurnToken[] memory tokens = new IBurnRedeemCore.BurnToken[](1); + IBurnRedeemCore.BurnToken memory token = IBurnRedeemCore.BurnToken({ + groupIndex: 0, + itemIndex: 0, + contractAddress: address(burnable721), + id: i+1, + merkleProof: merkleProof + }); + tokens[0] = token; + allTokens[i] = tokens; + } + uint256 burnFee = burnRedeem.BURN_FEE(); + burnRedeem.burnRedeem{value: burnFee*10}(addresses, indexes, burnCounts, allTokens); + vm.stopPrank(); + + vm.prank(anyone2); + vm.expectRevert("AdminControl: Must be owner or admin"); + burnRedeem.withdraw(payable(anyone2), burnFee*10); + + vm.prank(burnRedeemOwner); + vm.expectRevert(); + burnRedeem.withdraw(payable(owner), burnFee*11); + + vm.prank(burnRedeemOwner); + burnRedeem.withdraw(payable(owner), burnFee*10); + assertEq(burnFee*10, owner.balance); + } + + function testAirdrop() public { + IBurnRedeemCore.BurnItem[] memory items = new IBurnRedeemCore.BurnItem[](1); + items[0] = IBurnRedeemCore.BurnItem({ + validationType: IBurnRedeemCore.ValidationType.CONTRACT, + contractAddress: address(burnable721), + tokenSpec: IBurnRedeemCore.TokenSpec.ERC721, + burnSpec: IBurnRedeemCore.BurnSpec.MANIFOLD, + amount: 0, + minTokenId: 0, + maxTokenId: 0, + merkleRoot: bytes32(0) + }); + IBurnRedeemCore.BurnGroup[] memory group = new IBurnRedeemCore.BurnGroup[](1); + group[0] = IBurnRedeemCore.BurnGroup({ + requiredCount: 1, + items: items + }); + IBurnRedeemCore.BurnRedeemParameters memory params = IBurnRedeemCore.BurnRedeemParameters({ + paymentReceiver: payable(owner), + storageProtocol: IBurnRedeemCore.StorageProtocol.NONE, + redeemAmount: 2, + totalSupply: 10, + startDate: uint48(block.timestamp - 30), + endDate: uint48(block.timestamp + 1000), + cost: 0, + location: "XXX", + burnSet: group + }); + vm.startPrank(owner); + burnRedeem.initializeBurnRedeem(address(creator), 1, params, false); + vm.stopPrank(); + + address[] memory recipients = new address[](2); + recipients[0] = anyone1; + recipients[1] = anyone2; + uint32[] memory amounts = new uint32[](2); + amounts[0] = 1; + amounts[1] = 1; + + vm.prank(anyone1); + vm.expectRevert(abi.encodePacked(IBurnRedeemCore.NotAdmin.selector, uint256(uint160(address(creator))))); + burnRedeem.airdrop(address(creator), 1, recipients, amounts); + + vm.prank(owner); + burnRedeem.airdrop(address(creator), 1, recipients, amounts); + + // Check the token uri + assertEq(creator.tokenURI(1), "XXX/1"); + assertEq(creator.tokenURI(2), "XXX/2"); + + // Check balances + assertEq(2, creator.balanceOf(anyone1)); + assertEq(2, creator.balanceOf(anyone2)); + + // redeemedCount updated + IBurnRedeemCore.BurnRedeem memory burnInstance = burnRedeem.getBurnRedeem(address(creator), 1); + assertEq(burnInstance.redeemedCount, 4); + assertEq(burnInstance.totalSupply, 10); + + // Second airdrop + amounts[0] = 9; + amounts[1] = 9; + vm.prank(owner); + burnRedeem.airdrop(address(creator), 1, recipients, amounts); + + // check amounts + burnInstance = burnRedeem.getBurnRedeem(address(creator), 1); + assertEq(burnInstance.redeemedCount, 40); + assertEq(burnInstance.totalSupply, 40); + + // Check balances + assertEq(20, creator.balanceOf(anyone1)); + assertEq(20, creator.balanceOf(anyone2)); + + // Reverts when redeemedCount would exceed max uint32 + recipients = new address[](1); + recipients[0] = anyone1; + amounts = new uint32[](1); + amounts[0] = 2147483647; + vm.prank(owner); + vm.expectRevert(IBurnRedeemCore.InvalidRedeemAmount.selector); + burnRedeem.airdrop(address(creator), 1, recipients, amounts); + } + +} diff --git a/packages/manifold/test/burnredeem1155.js b/packages/manifold/test/burnredeem1155.js deleted file mode 100644 index 6d72d367..00000000 --- a/packages/manifold/test/burnredeem1155.js +++ /dev/null @@ -1,375 +0,0 @@ -const truffleAssert = require("truffle-assertions"); -const ERC1155BurnRedeem = artifacts.require("ERC1155BurnRedeem"); -const ERC721Creator = artifacts.require("@manifoldxyz/creator-core-extensions-solidity/ERC721Creator"); -const ERC1155Creator = artifacts.require("@manifoldxyz/creator-core-extensions-solidity/ERC1155Creator"); -const ethers = require("ethers"); -const MockManifoldMembership = artifacts.require("MockManifoldMembership"); - -const BURN_FEE = ethers.BigNumber.from("690000000000000"); - -contract("ERC1155BurnRedeem", function ([...accounts]) { - const [owner, burnRedeemOwner, anyone1] = accounts; - describe("BurnRedeem", function () { - let creator, burnRedeem, burnable1155; - beforeEach(async function () { - creator = await ERC1155Creator.new("Test", "TEST", { from: owner }); - burnRedeem = await ERC1155BurnRedeem.new(burnRedeemOwner, { from: owner }); - manifoldMembership = await MockManifoldMembership.new({ from: owner }); - await burnRedeem.setMembershipAddress(manifoldMembership.address, { from: burnRedeemOwner }); - burnable1155 = await ERC1155Creator.new("Test", "TEST", { from: owner }); - burnable1155_2 = await ERC1155Creator.new("Test", "TEST", { from: owner }); - burnable721 = await ERC721Creator.new("Test", "TEST", { from: owner }); - burnable721_2 = await ERC721Creator.new("Test", "TEST", { from: owner }); - - // Must register with empty prefix in order to set per-token uri's - await creator.registerExtension(burnRedeem.address, { from: owner }); - }); - - it("access test", async function () { - let now = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let later = now + 1000; - - // Must be admin - await truffleAssert.reverts( - burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: anyone1, - burnSet: [], - }, - { from: anyone1 } - ) - ); - - // Succeeds because admin - await truffleAssert.passes( - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [], - }, - { from: owner } - ) - ); - - // Fails because not admin - await truffleAssert.reverts( - burnRedeem.updateBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: anyone1, - burnSet: [], - }, - { from: anyone1 } - ) - ); - - // Fails because not admin - await truffleAssert.reverts(burnRedeem.updateURI(creator.address, 1, 1, "", { from: anyone1 })); - }); - - it("initializeBurnRedeem input sanitization test", async function () { - let now = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let later = now + 1000; - - // Fails due to endDate <= startDate - await truffleAssert.reverts( - burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: now, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [], - }, - { from: owner } - ) - ); - - // Cannot update non-existant burn redeem - await truffleAssert.reverts( - burnRedeem.updateBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [], - }, - { from: owner } - ) - ); - }); - - it("updateBurnRedeem input sanitization test", async function () { - let now = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let later = now + 1000; - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [], - }, - { from: owner } - ); - - // Fails due to non multiple of totalSupply - await truffleAssert.reverts( - burnRedeem.updateBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 3, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [], - }, - { from: owner } - ) - ); - - // Fails due to endDate <= startDate - await truffleAssert.reverts( - burnRedeem.updateBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: now, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [], - }, - { from: owner } - ) - ); - }); - - it("tokenURI test", async function () { - let now = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let later = now + 1000; - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - totalSupply: 10, - redeemAmount: 1, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - { from: owner } - ); - - // Mint burnable tokens - await burnable1155.mintBaseNew([anyone1], [2], [""], { from: owner }); - await burnable1155.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - - await burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable1155.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: BURN_FEE } - ); - - assert.equal("XXX", await creator.uri(1)); - }); - - it("onERC1155Received test - multiple redemptions", async function () { - // Test initializing a new burn redeem - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - let burnRedeemData = web3.eth.abi.encodeParameters( - ["address", "uint256", "uint32", "uint256", "bytes32[]"], - [creator.address, 1, 2, 0, []] - ); - - // Mint burnable tokens - await burnable1155.mintBaseNew([anyone1], [10], [""], { from: owner }); - - // Ensure that the creator contract state is what we expect before mints - let balance = await creator.balanceOf(anyone1, 1); - assert.equal(0, balance); - balance = await burnable1155.balanceOf(anyone1, 1); - assert.equal(10, balance); - - await truffleAssert.reverts(burnRedeem.getBurnRedeemForToken(creator.address, 1)); - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 2, - totalSupply: 6, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - { from: owner } - ); - - // Get burn redeem for token, should succeed - const burnRedeemInfo = await burnRedeem.getBurnRedeemForToken(creator.address, 1); - assert.equal(burnRedeemInfo[0], 1); - const burnRedeemForToken = burnRedeemInfo[1]; - assert.equal(burnRedeemForToken.contractVersion, 0); - - // Receiver functions require membership - await manifoldMembership.setMember(anyone1, true, { from: owner }); - - // Passes with value == 2 * burnItem.amount (2 redemptions) - await truffleAssert.passes( - burnable1155.methods["safeTransferFrom(address,address,uint256,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - 2, - burnRedeemData, - { from: anyone1 } - ) - ); - - // Ensure tokens are burned/minted - balance = await burnable1155.balanceOf(anyone1, 1); - assert.equal(8, balance); - balance = await creator.balanceOf(anyone1, 1); - assert.equal(4, balance); - - // Passes with value == 2 * burnItem.amount (2 redemptions), but only 1 redemption left - await truffleAssert.passes( - burnable1155.methods["safeTransferFrom(address,address,uint256,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - 2, - burnRedeemData, - { from: anyone1 } - ) - ); - - // Ensure tokens are burned/returned/minted - balance = await burnable1155.balanceOf(anyone1, 1); - // Only 1 redemption left, so 1 token is returned - assert.equal(7, balance); - balance = await creator.balanceOf(anyone1, 1); - assert.equal(6, balance); - - // Reverts with no redemptions left - await truffleAssert.reverts( - burnable1155.methods["safeTransferFrom(address,address,uint256,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - 1, - burnRedeemData, - { from: anyone1 } - ) - ); - }); - }); -}); diff --git a/packages/manifold/test/burnredeem721.js b/packages/manifold/test/burnredeem721.js deleted file mode 100644 index 612338d9..00000000 --- a/packages/manifold/test/burnredeem721.js +++ /dev/null @@ -1,3689 +0,0 @@ -const truffleAssert = require("truffle-assertions"); -const ERC721BurnRedeem = artifacts.require("ERC721BurnRedeem"); -const ERC721Creator = artifacts.require("@manifoldxyz/creator-core-extensions-solidity/ERC721Creator"); -const ERC1155Creator = artifacts.require("@manifoldxyz/creator-core-extensions-solidity/ERC1155Creator"); -const { MerkleTree } = require("merkletreejs"); -const keccak256 = require("keccak256"); -const ethers = require("ethers"); -const MockManifoldMembership = artifacts.require("MockManifoldMembership"); -const MockERC1155Fallback = artifacts.require("MockERC1155Fallback"); -const MockERC1155FallbackBurnable = artifacts.require("MockERC1155FallbackBurnable"); -const ERC721 = artifacts.require("MockERC721"); -const ERC1155 = artifacts.require("MockERC1155"); -const ERC721Burnable = artifacts.require("MockERC721Burnable"); -const ERC1155Burnable = artifacts.require("MockERC1155Burnable"); - -const BURN_FEE = ethers.BigNumber.from("690000000000000"); -const MULTI_BURN_FEE = ethers.BigNumber.from("990000000000000"); - -contract("ERC721BurnRedeem", function ([...accounts]) { - const [owner, burnRedeemOwner, anyone1, anyone2] = accounts; - describe("BurnRedeem", function () { - let creator, burnRedeem, burnable721, burnable721_2, burnable1155, burnable1155_2; - beforeEach(async function () { - creator = await ERC721Creator.new("Test", "TEST", { from: owner }); - burnRedeem = await ERC721BurnRedeem.new(burnRedeemOwner, { from: owner }); - manifoldMembership = await MockManifoldMembership.new({ from: owner }); - await burnRedeem.setMembershipAddress(manifoldMembership.address, { from: burnRedeemOwner }); - burnable721 = await ERC721Creator.new("Test", "TEST", { from: owner }); - burnable721_2 = await ERC721Creator.new("Test", "TEST", { from: owner }); - burnable1155 = await ERC1155Creator.new("Test", "TEST", { from: owner }); - burnable1155_2 = await ERC1155Creator.new("Test", "TEST", { from: owner }); - oz721 = await ERC721.new("Test", "TEST", { from: owner }); - oz1155 = await ERC1155.new("test.com", { from: owner }); - oz721Burnable = await ERC721Burnable.new("Test", "TEST", { from: owner }); - oz1155Burnable = await ERC1155Burnable.new("test.com", { from: owner }); - fallback1155 = await MockERC1155Fallback.new("test.com", { from: owner }); - fallback1155Burnable = await MockERC1155FallbackBurnable.new("test.com", { from: owner }); - - // Must register with empty prefix in order to set per-token uri's - await creator.registerExtension(burnRedeem.address, { from: owner }); - }); - - it("access test", async function () { - let now = Math.floor(Date.now() / 1000) - 30; // seconds since unix epoch - let later = now + 1000; - - // Must be admin - await truffleAssert.reverts( - burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [], - }, - true, - { from: anyone1 } - ) - ); - - // Succeeds because admin - await truffleAssert.passes( - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [], - }, - true, - { from: owner } - ) - ); - - // Fails because not admin - await truffleAssert.reverts( - burnRedeem.updateBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [], - }, - true, - { from: anyone1 } - ) - ); - - // Fails because not admin - await truffleAssert.reverts(burnRedeem.updateTokenURI(creator.address, 1, 1, "", true, { from: anyone1 })); - }); - - it("initializeBurnRedeem input sanitization test", async function () { - let now = (await web3.eth.getBlock("latest")).timestamp; // seconds since unix epoch - let later = now + 1000; - - // Fails due to endDate <= startDate - await truffleAssert.reverts( - burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: now, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [], - }, - true, - { from: owner } - ) - ); - - // Fails due to non-mod-0 redeemAmount - await truffleAssert.reverts( - burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: 0, - endDate: now, - redeemAmount: 3, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [], - }, - true, - { from: owner } - ) - ); - - // Cannot update non-existant burn redeem - await truffleAssert.reverts( - burnRedeem.updateBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [], - }, - true, - { from: owner } - ) - ); - - // Cannot have amount == 0 on ERC1155 burn item - await truffleAssert.reverts( - burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ) - ); - - // Cannot have ValidationType == INVALID on burn item - await truffleAssert.reverts( - burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 0, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ) - ); - - // Cannot have TokenSpec == INVALID on burn item - await truffleAssert.reverts( - burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable1155.address, - tokenSpec: 0, - burnSpec: 1, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ) - ); - - // Cannot have requiredCount == 0 on burn group - await truffleAssert.reverts( - burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 0, - items: [ - { - validationType: 1, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ) - ); - - // Cannot have requiredCount > items.length on burn group - await truffleAssert.reverts( - burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 2, - items: [ - { - validationType: 1, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ) - ); - }); - - it("updateBurnRedeem input sanitization test", async function () { - let now = (await web3.eth.getBlock("latest")).timestamp; // seconds since unix epoch - let later = now + 1000; - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [], - }, - true, - { from: owner } - ); - - // Fails due to endDate <= startDate - await truffleAssert.reverts( - burnRedeem.updateBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: now, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [], - }, - true, - { from: owner } - ) - ); - - // Fails due to non-mod-0 redeemAmount - await truffleAssert.reverts( - burnRedeem.updateBurnRedeem( - creator.address, - 1, - { - startDate: 0, - endDate: now, - redeemAmount: 3, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [], - }, - true, - { from: owner } - ) - ); - - await burnRedeem.burnRedeem(creator.address, 1, 2, [], { from: owner, value: BURN_FEE.mul(2) }); - - // Fails due to non-mod-0 redeemAmount after redemptions - await truffleAssert.reverts( - burnRedeem.updateBurnRedeem( - creator.address, - 1, - { - startDate: 0, - endDate: now, - redeemAmount: 3, - totalSupply: 9, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [], - }, - true, - { from: owner } - ) - ); - - // totalSupply = redeemedCount if updated below redeemedCount - await burnRedeem.updateBurnRedeem( - creator.address, - 1, - { - startDate: 0, - endDate: now, - redeemAmount: 1, - totalSupply: 1, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [], - }, - true, - { from: owner } - ); - let burnRedeemInstance = await burnRedeem.getBurnRedeem(creator.address, 1); - assert.equal(burnRedeemInstance.totalSupply, 2); - - // totalSupply = 0 if updated to 0 and redeemedCount > 0 - await burnRedeem.updateBurnRedeem( - creator.address, - 1, - { - startDate: 0, - endDate: now, - redeemAmount: 1, - totalSupply: 0, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [], - }, - true, - { from: owner } - ); - burnRedeemInstance = await burnRedeem.getBurnRedeem(creator.address, 1); - assert.equal(burnRedeemInstance.totalSupply, 0); - }); - - it("tokenURI test", async function () { - let now = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let later = now + 1000; - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: anyone1, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable721.address, - tokenSpec: 1, - burnSpec: 1, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - true, - { from: owner } - ); - - // Mint burnable tokens - await burnable721.mintBase(anyone1, { from: owner }); - await burnable721.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - - await burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: BURN_FEE } - ); - - assert.equal("XXX", await creator.tokenURI(1)); - - await burnRedeem.updateBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable721.address, - tokenSpec: 1, - burnSpec: 1, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - assert.equal("XXX/1", await creator.tokenURI(1)); - }); - - it("tokenURI test - mint between burns", async function () { - let now = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let later = now + 1000; - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: anyone1, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable721.address, - tokenSpec: 1, - burnSpec: 1, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Mint burnable tokens - await burnable721.mintBase(anyone1, { from: owner }); - await burnable721.mintBase(anyone1, { from: owner }); - await burnable721.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - - // Redeem first token - await burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: BURN_FEE } - ); - - assert.equal("XXX/1", await creator.tokenURI(1)); - - // Mint another token on creator contract - await creator.mintBase(anyone1, { from: owner }); - - // Redeem another token - await burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 2, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: BURN_FEE } - ); - - assert.equal("XXX/2", await creator.tokenURI(3)); - }); - - it("burnRedeem test - burn anything", async function () { - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - // tokenSpec: ERC-721, burnSpec: NONE - { - validationType: 4, - contractAddress: "0x0000000000000000000000000000000000000000", - tokenSpec: 1, - burnSpec: 0, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - // tokenSpec: ERC-721, burnSpec: MANIFOLD - { - validationType: 4, - contractAddress: "0x0000000000000000000000000000000000000000", - tokenSpec: 1, - burnSpec: 1, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - // tokenSpec: ERC-721, burnSpec: OPENZEPPELIN - { - validationType: 4, - contractAddress: "0x0000000000000000000000000000000000000000", - tokenSpec: 1, - burnSpec: 2, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - // tokenSpec: ERC-1155, burnSpec: NONE - { - validationType: 4, - contractAddress: "0x0000000000000000000000000000000000000000", - tokenSpec: 2, - burnSpec: 0, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - // tokenSpec: ERC-1155, burnSpec: MANIFOLD - { - validationType: 4, - contractAddress: "0x0000000000000000000000000000000000000000", - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - // tokenSpec: ERC-1155, burnSpec: OPENZEPPELIN - { - validationType: 4, - contractAddress: "0x0000000000000000000000000000000000000000", - tokenSpec: 2, - burnSpec: 2, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Range of burnable options - const burnContracts = [ - { - contract: oz721, - tokenSpec: 1, - mintTx: await oz721.mint(anyone1, 1, { from: owner }), - }, - { - contract: burnable721, - tokenSpec: 1, - mintTx: await burnable721.mintBase(anyone1, { from: owner }), - supportsBurn: true, - }, - { - contract: oz721Burnable, - tokenSpec: 1, - mintTx: await oz721Burnable.mint(anyone1, 1, { from: owner }), - supportsBurn: true, - }, - { - contract: oz1155, - tokenSpec: 2, - mintTx: await oz1155.mint(anyone1, 1, 1, { from: owner }), - }, - { - contract: burnable1155, - tokenSpec: 2, - mintTx: await burnable1155.mintBaseNew([anyone1], [10], [""], { from: owner }), - supportsBurn: true, - }, - { - contract: oz1155Burnable, - tokenSpec: 2, - mintTx: await oz1155Burnable.mint(anyone1, 1, 1, { from: owner }), - supportsBurn: true, - }, - ]; - - const promises = burnContracts.map(async ({ contract, tokenSpec, supportsBurn }, i) => { - await contract.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - - await truffleAssert.passes( - burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: i, - contractAddress: contract.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: BURN_FEE } - ) - ); - - if (supportsBurn) { - if (tokenSpec === 1) { - await truffleAssert.reverts(contract.ownerOf(1)); - } else { - assert.equal(await contract.balanceOf(owner, 1), 0); - } - } else { - if (tokenSpec === 1) { - assert.equal(await contract.ownerOf(1), "0x000000000000000000000000000000000000dEaD"); - } else { - assert.equal(await contract.balanceOf("0x000000000000000000000000000000000000dEaD", 1), 1); - } - } - }); - - await Promise.all(promises); - }); - it("redeem with custom data to be emitted", async function () { - // Test initializing a new burn redeem - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - - // Mint burnable tokens - for (let i = 0; i < 3; i++) { - await oz721.mint(anyone1, i + 1, { from: owner }); - } - - // Ensure that the creator contract state is what we expect before mints - let balance = await creator.balanceOf(anyone1); - assert.equal(0, balance); - balance = await oz721.balanceOf(anyone1); - assert.equal(3, balance); - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: oz721.address, - tokenSpec: 1, - burnSpec: 0, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Set approvals - await oz721.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - - const message = "hello"; - // Passes - const tx = await burnRedeem.burnRedeemWithData( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: oz721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - ethers.utils.formatBytes32String(message), - { from: anyone1, value: BURN_FEE } - ); - - // Verify correct data was emitted - assert.equal(tx.receipt.logs[0].args.data, ethers.utils.formatBytes32String(message)); - - // Ensure tokens are burned/minted - balance = await oz721.balanceOf(anyone1); - assert.equal(2, balance); - balance = await creator.balanceOf(anyone1); - assert.equal(1, balance); - }); - - it("burnRedeem test - contracts with fallbacks cant bypass burn requirements", async function () { - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - - await fallback1155.mint(anyone1, 1, 1, { from: owner }); - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: fallback1155.address, - tokenSpec: 2, - burnSpec: 0, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - await fallback1155.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - - await truffleAssert.passes( - burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: fallback1155.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: BURN_FEE } - ) - ); - - // Assert balance change - assert.equal(await fallback1155.balanceOf("0x000000000000000000000000000000000000dEaD", 1), 1); - }); - - it("burnRedeem test - burn 721", async function () { - // Test initializing a new burn redeem - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - - // Mint burnable tokens - for (let i = 0; i < 4; i++) { - await burnable721.mintBase(anyone1, { from: owner }); - await burnable721_2.mintBase(anyone1, { from: owner }); - } - - // Ensure that the creator contract state is what we expect before mints - let balance = await creator.balanceOf(anyone1); - assert.equal(0, balance); - balance = await burnable721.balanceOf(anyone1); - assert.equal(4, balance); - balance = await burnable721_2.balanceOf(anyone1); - assert.equal(4, balance); - - const merkleElements = []; - merkleElements.push(ethers.utils.solidityPack(["uint256"], [3])); - merkleElements.push(ethers.utils.solidityPack(["uint256"], [10])); - merkleElements.push(ethers.utils.solidityPack(["uint256"], [15])); - merkleElements.push(ethers.utils.solidityPack(["uint256"], [20])); - merkleTreeWithValues = new MerkleTree(merkleElements, keccak256, { hashLeaves: true, sortPairs: true }); - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable721.address, - tokenSpec: 1, - burnSpec: 1, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - { - requiredCount: 1, - items: [ - { - validationType: 2, - contractAddress: burnable721_2.address, - tokenSpec: 1, - burnSpec: 1, - amount: 0, - minTokenId: 1, - maxTokenId: 2, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - { - validationType: 3, - contractAddress: burnable721_2.address, - tokenSpec: 1, - burnSpec: 1, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: merkleTreeWithValues.getHexRoot(), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Set approvals - await burnable721.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - await burnable721_2.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - - // Reverts due to unmet requirements - await truffleAssert.reverts( - burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: BURN_FEE } - ) - ); - - // Reverts due to too many tokens - await truffleAssert.reverts( - burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - { - groupIndex: 1, - itemIndex: 0, - contractAddress: burnable721_2.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - { - groupIndex: 1, - itemIndex: 0, - contractAddress: burnable721_2.address, - id: 2, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: MULTI_BURN_FEE } - ) - ); - - // Reverts when token ID out of range - await truffleAssert.reverts( - burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - { - groupIndex: 1, - itemIndex: 0, - contractAddress: burnable721_2.address, - id: 3, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: MULTI_BURN_FEE } - ) - ); - - // Reverts with invalid merkle proof - await truffleAssert.reverts( - burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - { - groupIndex: 1, - itemIndex: 1, - contractAddress: burnable721_2.address, - id: 3, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: MULTI_BURN_FEE } - ) - ); - - // Reverts due to no fee - await truffleAssert.reverts( - burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - { - groupIndex: 1, - itemIndex: 0, - contractAddress: burnable721_2.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1 } - ) - ); - - // Reverts when msg.sender is not token owner, but tokens are approved - await truffleAssert.reverts( - burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - { - groupIndex: 1, - itemIndex: 0, - contractAddress: burnable721_2.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone2, value: MULTI_BURN_FEE } - ) - ); - - // Reverts with burnCount > 1 - await truffleAssert.reverts( - burnRedeem.burnRedeem( - creator.address, - 1, - 2, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - { - groupIndex: 1, - itemIndex: 0, - contractAddress: burnable721_2.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: MULTI_BURN_FEE.mul(2) } - ) - ); - - await truffleAssert.reverts(burnRedeem.getBurnRedeemForToken(creator.address, 1)); - - // Passes with met requirements - range - await burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - { - groupIndex: 1, - itemIndex: 0, - contractAddress: burnable721_2.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: MULTI_BURN_FEE } - ); - - // Get burn redeem for token, should succeed - const burnRedeemInfo = await burnRedeem.getBurnRedeemForToken(creator.address, 1); - assert.equal(burnRedeemInfo[0], 1); - const burnRedeemForToken = burnRedeemInfo[1]; - assert.equal(burnRedeemForToken.contractVersion, 3); - - // Grab gas cost - let tx = await burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 2, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - { - groupIndex: 1, - itemIndex: 0, - contractAddress: burnable721_2.address, - id: 2, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: MULTI_BURN_FEE } - ); - - console.log("Gas cost:\tBurn 2 721s (range validation) through burnRedeem:\t" + tx.receipt.gasUsed); - - // Passes with met requirements - merkle proof - const merkleLeaf = keccak256(ethers.utils.solidityPack(["uint256"], [3])); - const merkleProof = merkleTreeWithValues.getHexProof(merkleLeaf); - tx = await burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 3, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - { - groupIndex: 1, - itemIndex: 1, - contractAddress: burnable721_2.address, - id: 3, - merkleProof: merkleProof, - }, - ], - { from: anyone1, value: MULTI_BURN_FEE } - ); - - console.log("Gas cost:\tBurn 2 721s (range and merkle validation) through burnRedeem:\t" + tx.receipt.gasUsed); - - // Ensure tokens are burned/minted - balance = await burnable721.balanceOf(anyone1); - assert.equal(1, balance); - balance = await burnable721_2.balanceOf(anyone1); - assert.equal(1, balance); - balance = await creator.balanceOf(anyone1); - assert.equal(3, balance); - }); - - it("burnRedeem test - burn 721 - burnSpec = NONE", async function () { - // Test initializing a new burn redeem - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - - // Mint burnable tokens - for (let i = 0; i < 3; i++) { - await oz721.mint(anyone1, i + 1, { from: owner }); - } - - // Ensure that the creator contract state is what we expect before mints - let balance = await creator.balanceOf(anyone1); - assert.equal(0, balance); - balance = await oz721.balanceOf(anyone1); - assert.equal(3, balance); - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: oz721.address, - tokenSpec: 1, - burnSpec: 0, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Set approvals - await oz721.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - - // Passes - await truffleAssert.passes( - burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: oz721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: BURN_FEE } - ) - ); - - // Ensure tokens are burned/minted - balance = await oz721.balanceOf(anyone1); - assert.equal(2, balance); - balance = await creator.balanceOf(anyone1); - assert.equal(1, balance); - }); - - it("burnRedeem test - burn 721 - burnSpec = OPENZEPPELIN", async function () { - // Test initializing a new burn redeem - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - - // Mint burnable tokens - for (let i = 0; i < 3; i++) { - await oz721Burnable.mint(anyone1, i + 1, { from: owner }); - } - - // Ensure that the creator contract state is what we expect before mints - let balance = await creator.balanceOf(anyone1); - assert.equal(0, balance); - balance = await oz721Burnable.balanceOf(anyone1); - assert.equal(3, balance); - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: oz721Burnable.address, - tokenSpec: 1, - burnSpec: 2, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Set approvals - await oz721Burnable.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - - // Passes - await truffleAssert.passes( - burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: oz721Burnable.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: BURN_FEE } - ) - ); - - // Ensure tokens are burned/minted - balance = await oz721Burnable.balanceOf(anyone1); - assert.equal(2, balance); - balance = await creator.balanceOf(anyone1); - assert.equal(1, balance); - }); - - it("burnRedeem test - burn 1155", async function () { - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - - // Mint burnable tokens - await burnable1155.mintBaseNew([anyone1], [10], [""], { from: owner }); - await burnable1155.mintBaseNew([anyone1], [10], [""], { from: owner }); - await burnable1155_2.mintBaseNew([anyone1], [10], [""], { from: owner }); - await burnable721.mintBase(anyone1, { from: owner }); - - const merkleElements = []; - merkleElements.push(ethers.utils.solidityPack(["uint256"], [2])); - merkleElements.push(ethers.utils.solidityPack(["uint256"], [10])); - merkleElements.push(ethers.utils.solidityPack(["uint256"], [15])); - merkleElements.push(ethers.utils.solidityPack(["uint256"], [20])); - merkleTreeWithValues = new MerkleTree(merkleElements, keccak256, { hashLeaves: true, sortPairs: true }); - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable1155_2.address, - tokenSpec: 2, - burnSpec: 1, - amount: 2, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - { - requiredCount: 1, - items: [ - { - validationType: 2, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 1, - maxTokenId: 2, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - { - requiredCount: 1, - items: [ - { - validationType: 3, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: merkleTreeWithValues.getHexRoot(), - }, - { - validationType: 1, - contractAddress: burnable721.address, - tokenSpec: 1, - burnSpec: 1, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Set approvals - await burnable1155.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - await burnable1155_2.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - - const merkleLeaf = keccak256(ethers.utils.solidityPack(["uint256"], [2])); - const merkleProof = merkleTreeWithValues.getHexProof(merkleLeaf); - - // Reverts with burnCount > 1 if 721 is in burnTokens - await truffleAssert.reverts( - burnRedeem.burnRedeem( - creator.address, - 1, - 3, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable1155_2.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - { - groupIndex: 1, - itemIndex: 0, - contractAddress: burnable1155.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - { - groupIndex: 2, - itemIndex: 1, - contractAddress: burnable721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: MULTI_BURN_FEE.mul(3) } - ) - ); - - // Passes with burnCount > 1 - let tx = await burnRedeem.burnRedeem( - creator.address, - 1, - 3, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable1155_2.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - { - groupIndex: 1, - itemIndex: 0, - contractAddress: burnable1155.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - { - groupIndex: 2, - itemIndex: 0, - contractAddress: burnable1155.address, - id: 2, - merkleProof: merkleProof, - }, - ], - { from: anyone1, value: MULTI_BURN_FEE.mul(3) } - ); - - console.log( - "Gas cost:\tBurn 3 1155s x3 (contract, range and merkle validation) through burnRedeem:\t" + tx.receipt.gasUsed - ); - - // Ensure tokens are burned/minted - balance = await burnable1155.balanceOf(anyone1, 1); - assert.equal(7, balance); - balance = await burnable1155.balanceOf(anyone1, 2); - assert.equal(7, balance); - balance = await burnable1155_2.balanceOf(anyone1, 1); - assert.equal(4, balance); - balance = await creator.balanceOf(anyone1); - assert.equal(3, balance); - }); - - it("burnRedeem test - burn 1155 - burnSpec = NONE", async function () { - // Test initializing a new burn redeem - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - - // Mint burnable tokens - await oz1155.mint(anyone1, 1, 3, { from: owner }); - - // Ensure that the creator contract state is what we expect before mints - let balance = await creator.balanceOf(anyone1); - assert.equal(0, balance); - balance = await oz1155.balanceOf(anyone1, 1); - assert.equal(3, balance); - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: oz1155.address, - tokenSpec: 2, - burnSpec: 0, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Set approvals - await oz1155.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - - // Passes - await truffleAssert.passes( - burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: oz1155.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: BURN_FEE } - ) - ); - - // Ensure tokens are burned/minted - balance = await oz1155.balanceOf(anyone1, 1); - assert.equal(2, balance); - balance = await creator.balanceOf(anyone1); - assert.equal(1, balance); - }); - - it("burnRedeem test - burn 1155 - burnSpec = OPENZEPPELIN", async function () { - // Test initializing a new burn redeem - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - - // Mint burnable tokens - await oz1155Burnable.mint(anyone1, 1, 3, { from: owner }); - - // Ensure that the creator contract state is what we expect before mints - let balance = await creator.balanceOf(anyone1); - assert.equal(0, balance); - balance = await oz1155Burnable.balanceOf(anyone1, 1); - assert.equal(3, balance); - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: oz1155Burnable.address, - tokenSpec: 2, - burnSpec: 2, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Set approvals - await oz1155Burnable.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - - // Passes - await truffleAssert.passes( - burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: oz1155Burnable.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: BURN_FEE } - ) - ); - - // Ensure tokens are burned/minted - balance = await oz1155Burnable.balanceOf(anyone1, 1); - assert.equal(2, balance); - balance = await creator.balanceOf(anyone1); - assert.equal(1, balance); - }); - - it("burnRedeem test - redeemAmount > 1", async function () { - // Test initializing a new burn redeem - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - - // Mint burnable tokens - await burnable721.mintBase(anyone1, { from: owner }); - - // Ensure that the creator contract state is what we expect before mints - let balance = await creator.balanceOf(anyone1); - assert.equal(0, balance); - balance = await burnable721.balanceOf(anyone1); - assert.equal(1, balance); - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 3, - totalSupply: 9, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable721.address, - tokenSpec: 1, - burnSpec: 1, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Set approvals - await burnable721.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - - // Passes with met requirements - range - await truffleAssert.passes( - burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: BURN_FEE } - ) - ); - - // Ensure tokens are burned/minted - balance = await burnable721.balanceOf(anyone1); - assert.equal(0, balance); - balance = await creator.balanceOf(anyone1); - assert.equal(3, balance); - }); - - it("burnRedeem test - malicious sender reverts", async function () { - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - - // Mint burnable tokens - await burnable721.mintBase(anyone1, { from: owner }); - await burnable1155.mintBaseNew([anyone1], [2], [""], { from: owner }); - - // Burn #1 - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable721.address, - tokenSpec: 1, - burnSpec: 0, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - // Burn #2 - await burnRedeem.initializeBurnRedeem( - creator.address, - 2, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 0, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - // Burn #3 - await burnRedeem.initializeBurnRedeem( - creator.address, - 3, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Set approvals - await burnable721.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - await burnable1155.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - - // Reverts when msg.sender is not token owner, but tokens are approved - // 721 with no burn - await truffleAssert.reverts( - burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone2, value: BURN_FEE } - ) - ); - // 1155 with no burn - await truffleAssert.reverts( - burnRedeem.burnRedeem( - creator.address, - 2, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable1155.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone2, value: BURN_FEE } - ) - ); - // 1155 with burn - await truffleAssert.reverts( - burnRedeem.burnRedeem( - creator.address, - 3, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable1155.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone2, value: BURN_FEE } - ) - ); - }); - - it("burnRedeem test - burnRedeem.cost", async function () { - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - const cost = ethers.BigNumber.from("1000000000000000000"); - - // Mint burnable token - await burnable721.mintBase(anyone1, { from: owner }); - await burnable1155.mintBaseNew([anyone1], [10], [""], { from: owner }); - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: cost, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable721.address, - tokenSpec: 1, - burnSpec: 1, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - { - validationType: 1, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Set approvals - await burnable721.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - await burnable1155.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - - // Reverts due to invalid value - await truffleAssert.reverts( - burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: BURN_FEE } - ) - ); - - let creatorBalanceBefore = await web3.eth.getBalance(owner); - - // Passes with propper value - await truffleAssert.passes( - burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: BURN_FEE.add(cost) } - ) - ); - - // Check that cost was sent to creator - let creatorBalanceAfter = await web3.eth.getBalance(owner); - assert.equal(ethers.BigNumber.from(creatorBalanceBefore).add(cost).toString(), creatorBalanceAfter); - - creatorBalanceBefore = await web3.eth.getBalance(owner); - - // Passes with burnCount > 1 - await truffleAssert.passes( - burnRedeem.burnRedeem( - creator.address, - 1, - 5, - [ - { - groupIndex: 0, - itemIndex: 1, - contractAddress: burnable1155.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1, value: BURN_FEE.mul(5).add(cost.mul(5)) } - ) - ); - - // Check that cost was sent to creator - creatorBalanceAfter = await web3.eth.getBalance(owner); - assert.equal(ethers.BigNumber.from(creatorBalanceBefore).add(cost.mul(5)).toString(), creatorBalanceAfter); - }); - - it("burnRedeem test - with membership", async function () { - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - - // Mint burnable token - await burnable721.mintBase(anyone1, { from: owner }); - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable721.address, - tokenSpec: 1, - burnSpec: 1, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Set approvals - await burnable721.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - - await manifoldMembership.setMember(anyone1, true, { from: owner }); - - // Passes with no fee - let tx = await burnRedeem.burnRedeem( - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - { from: anyone1 } - ); - - console.log("Gas cost:\tBurn 1 721 (contract validation) through burnRedeem:\t" + tx.receipt.gasUsed); - }); - - it("burnRedeem test - multiple redemptions", async function () { - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - - // Mint burnable tokens - await burnable721.mintBase(anyone1, { from: owner }); - await burnable1155.mintBaseNew([anyone1], [2], [""], { from: owner }); - - // Burn #1 - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable721.address, - tokenSpec: 1, - burnSpec: 1, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - // Burn #2 - await burnRedeem.initializeBurnRedeem( - creator.address, - 2, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 1, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Set approvals - await burnable721.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - await burnable1155.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - - const addresses = [creator.address, creator.address]; - const indexes = [1, 2]; - const burnCounts = [1, 2]; - const burnTokens = [ - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable1155.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - ]; - - // Reverts with insufficient fee - await truffleAssert.reverts( - burnRedeem.methods["burnRedeem(address[],uint256[],uint32[],(uint48,uint48,address,uint256,bytes32[])[][])"]( - addresses, - indexes, - burnCounts, - burnTokens, - { from: anyone1, value: BURN_FEE } - ) - ); - - // Reverts with mismatching lengths - await truffleAssert.reverts( - burnRedeem.methods["burnRedeem(address[],uint256[],uint32[],(uint48,uint48,address,uint256,bytes32[])[][])"]( - [creator.address], - indexes, - burnCounts, - burnTokens, - { from: anyone1, value: BURN_FEE } - ) - ); - await truffleAssert.reverts( - burnRedeem.methods["burnRedeem(address[],uint256[],uint32[],(uint48,uint48,address,uint256,bytes32[])[][])"]( - addresses, - [1], - burnCounts, - burnTokens, - { from: anyone1, value: BURN_FEE } - ) - ); - await truffleAssert.reverts( - burnRedeem.methods["burnRedeem(address[],uint256[],uint32[],(uint48,uint48,address,uint256,bytes32[])[][])"]( - addresses, - indexes, - [1], - burnTokens, - { from: anyone1, value: BURN_FEE } - ) - ); - await truffleAssert.reverts( - burnRedeem.methods["burnRedeem(address[],uint256[],uint32[],(uint48,uint48,address,uint256,bytes32[])[][])"]( - addresses, - indexes, - burnCounts, - [burnTokens[0]], - { from: anyone1, value: BURN_FEE } - ) - ); - - // Reverts with burnCount > 1 for 721 - await truffleAssert.reverts( - burnRedeem.methods["burnRedeem(address[],uint256[],uint32[],(uint48,uint48,address,uint256,bytes32[])[][])"]( - addresses, - indexes, - [2, 2], - burnTokens, - { from: anyone1, value: BURN_FEE.mul(4) } - ) - ); - - const userBalanceBefore = await web3.eth.getBalance(anyone1); - - // Passes with multiple redemptions, burnCount > 1 for 1155 - const tx = await burnRedeem.methods[ - "burnRedeem(address[],uint256[],uint32[],(uint48,uint48,address,uint256,bytes32[])[][])" - ](addresses, indexes, burnCounts, burnTokens, { from: anyone1, value: BURN_FEE.mul(3) }); - - console.log( - "Gas cost:\tBatch (burn 1 721), (burn 1 1155 x2) (contract validation) through burnRedeem:\t" + tx.receipt.gasUsed - ); - - const userBalanceAfter = await web3.eth.getBalance(anyone1); - - let balance = await burnable721.balanceOf(anyone1); - assert.equal(0, balance); - // 1 token burned for Burn #2 (sold out thereafter), the second should not be burned - balance = await burnable1155.balanceOf(anyone1, 1); - assert.equal(1, balance); - // Only 2 redemptions should have gone through - balance = await creator.balanceOf(anyone1); - assert.equal(2, balance); - - // User should only be charged for 2 burn redeems - const cost = BURN_FEE.mul(2); - const gasFee = tx.receipt.gasUsed * (await web3.eth.getTransaction(tx.tx)).gasPrice; - assert.equal(ethers.BigNumber.from(userBalanceBefore).sub(cost).sub(gasFee).toString(), userBalanceAfter); - }); - - it("onERC721Received test", async function () { - // Test initializing a new burn redeem - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - let burnRedeemData = web3.eth.abi.encodeParameters( - ["address", "uint256", "uint256", "bytes32[]"], - [creator.address, 1, 0, []] - ); - - // Mint burnable tokens - await burnable721.mintBase(anyone1, { from: owner }); - await burnable721_2.mintBase(anyone1, { from: owner }); - - // Ensure that the creator contract state is what we expect before mints - let balance = await creator.balanceOf(anyone1); - assert.equal(0, balance); - balance = await burnable721.balanceOf(anyone1); - assert.equal(1, balance); - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable721.address, - tokenSpec: 1, - burnSpec: 1, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Reverts without membership - await truffleAssert.reverts( - burnable721.methods["safeTransferFrom(address,address,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - burnRedeemData, - { from: anyone1 } - ) - ); - - // Receiver functions require membership - await manifoldMembership.setMember(anyone1, true, { from: owner }); - - // Reverts due to wrong contract - await truffleAssert.reverts( - burnable721_2.methods["safeTransferFrom(address,address,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - burnRedeemData, - { from: anyone1 } - ) - ); - - // Passes with right token id - let tx = await burnable721.methods["safeTransferFrom(address,address,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - burnRedeemData, - { from: anyone1 } - ); - - console.log("Gas cost:\tBurn 1 721 (contract validation) through 721 receiver:\t" + tx.receipt.gasUsed); - - // Ensure tokens are burned/minted - balance = await burnable721.balanceOf(anyone1); - assert.equal(0, balance); - balance = await creator.balanceOf(anyone1); - assert.equal(1, balance); - }); - - it("onERC1155Received test", async function () { - // Test initializing a new burn redeem - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - let burnRedeemData = web3.eth.abi.encodeParameters( - ["address", "uint256", "uint32", "uint256", "bytes32[]"], - [creator.address, 1, 1, 0, []] - ); - - // Mint burnable tokens - await burnable1155.mintBaseNew([anyone1], [3], [""], { from: owner }); - await burnable1155_2.mintBaseNew([anyone1], [3], [""], { from: owner }); - - // Ensure that the creator contract state is what we expect before mints - let balance = await creator.balanceOf(anyone1); - assert.equal(0, balance); - balance = await burnable1155.balanceOf(anyone1, 1); - assert.equal(3, balance); - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 2, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Reverts without membership - await truffleAssert.reverts( - burnable1155.methods["safeTransferFrom(address,address,uint256,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - 2, - burnRedeemData, - { from: anyone1 } - ) - ); - - // Receiver functions require membership - await manifoldMembership.setMember(anyone1, true, { from: owner }); - - // Reverts due to wrong contract - await truffleAssert.reverts( - burnable1155_2.methods["safeTransferFrom(address,address,uint256,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - 2, - burnRedeemData, - { from: anyone1 } - ) - ); - - // Reverts with invalid amount - await truffleAssert.reverts( - burnable1155.methods["safeTransferFrom(address,address,uint256,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - 3, - burnRedeemData, - { from: anyone1 } - ) - ); - - // Passes with right token id - let tx = await burnable1155.methods["safeTransferFrom(address,address,uint256,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - 2, - burnRedeemData, - { from: anyone1 } - ); - - console.log("Gas cost:\tBurn 2 1155s (contract validation) through 1155 receiver:\t" + tx.receipt.gasUsed); - - // Ensure tokens are burned/minted - balance = await burnable1155.balanceOf(anyone1, 1); - assert.equal(1, balance); - balance = await creator.balanceOf(anyone1); - assert.equal(1, balance); - }); - - it("onERC1155Received test - multiple redemptions", async function () { - // Test initializing a new burn redeem - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - let burnRedeemData = web3.eth.abi.encodeParameters( - ["address", "uint256", "uint32", "uint256", "bytes32[]"], - [creator.address, 1, 2, 0, []] - ); - - // Mint burnable tokens - await burnable1155.mintBaseNew([anyone1], [10], [""], { from: owner }); - - // Ensure that the creator contract state is what we expect before mints - let balance = await creator.balanceOf(anyone1); - assert.equal(0, balance); - balance = await burnable1155.balanceOf(anyone1, 1); - assert.equal(10, balance); - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 3, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 2, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Receiver functions require membership - await manifoldMembership.setMember(anyone1, true, { from: owner }); - - // Passes with value == 2 * burnItem.amount (2 redemptions) - let tx = await burnable1155.methods["safeTransferFrom(address,address,uint256,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - 4, - burnRedeemData, - { from: anyone1 } - ); - - console.log("Gas cost:\tBurn 2 1155s x2 (contract validation) through 1155 receiver:\t" + tx.receipt.gasUsed); - - // Ensure tokens are burned/minted - balance = await burnable1155.balanceOf(anyone1, 1); - assert.equal(6, balance); - balance = await creator.balanceOf(anyone1); - assert.equal(2, balance); - - // Passes with value == 2 * burnItem.amount (2 redemptions), but only 1 redemption left - await truffleAssert.passes( - burnable1155.methods["safeTransferFrom(address,address,uint256,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - 4, - burnRedeemData, - { from: anyone1 } - ) - ); - - // Ensure tokens are burned/returned/minted - balance = await creator.balanceOf(anyone1); - assert.equal(3, balance); - // Only 1 redemption left, so 2 tokens are returned - balance = await burnable1155.balanceOf(anyone1, 1); - assert.equal(4, balance); - - // Reverts with no redemptions left - await truffleAssert.reverts( - burnable1155.methods["safeTransferFrom(address,address,uint256,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - 4, - burnRedeemData, - { from: anyone1 } - ) - ); - }); - - it("onERC1155BatchReceived test", async function () { - // Test initializing a new burn redeem - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - let burnRedeemData = web3.eth.abi.encodeParameters( - [ - "address", - "uint256", - "uint32", - { - "BurnToken[]": { - groupIndex: "uint48", - itemIndex: "uint48", - contractAddress: "address", - id: "uint256", - merkleProof: "bytes32[]", - }, - }, - ], - [ - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable1155.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - { - groupIndex: 0, - itemIndex: 1, - contractAddress: burnable1155.address, - id: 2, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - ] - ); - - // Mint burnable tokens - await burnable1155.mintBaseNew([anyone1], [10], [""], { from: owner }); - await burnable1155.mintBaseNew([anyone1], [10], [""], { from: owner }); - await burnable1155.mintBaseNew([anyone1], [10], [""], { from: owner }); - - // Ensure that the creator contract state is what we expect before mints - let balance = await creator.balanceOf(anyone1); - assert.equal(0, balance); - balance = await burnable1155.balanceOf(anyone1, 1); - assert.equal(10, balance); - balance = await burnable1155.balanceOf(anyone1, 2); - assert.equal(10, balance); - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 2, - items: [ - { - validationType: 2, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 2, - minTokenId: 1, - maxTokenId: 1, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - { - validationType: 2, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 2, - minTokenId: 2, - maxTokenId: 2, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Reverts without membership - await truffleAssert.reverts( - burnable1155.methods["safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)"]( - anyone1, - burnRedeem.address, - [1, 2], - [2, 2], - burnRedeemData, - { from: anyone1 } - ) - ); - - // Receiver functions require membership - await manifoldMembership.setMember(anyone1, true, { from: owner }); - - // Reverts without mismatching token ids - await truffleAssert.reverts( - burnable1155.methods["safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)"]( - anyone1, - burnRedeem.address, - [1, 3], - [2, 2], - burnRedeemData, - { from: anyone1 } - ) - ); - - // Reverts without mismatching values - await truffleAssert.reverts( - burnable1155.methods["safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)"]( - anyone1, - burnRedeem.address, - [1, 2], - [1, 1], - burnRedeemData, - { from: anyone1 } - ) - ); - - // Reverts with extra tokens - await truffleAssert.reverts( - burnable1155.methods["safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)"]( - anyone1, - burnRedeem.address, - [1, 2, 3], - [2, 2, 2], - burnRedeemData, - { from: anyone1 } - ) - ); - - // Passes with right token id - let tx = await burnable1155.methods["safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)"]( - anyone1, - burnRedeem.address, - [1, 2], - [2, 2], - burnRedeemData, - { from: anyone1 } - ); - - console.log("Gas cost:\tBurn 4 1155s (contract validation) through 1155 batch receiver:\t" + tx.receipt.gasUsed); - - // burnCount > 1 - burnRedeemData = web3.eth.abi.encodeParameters( - [ - "address", - "uint256", - "uint32", - { - "BurnToken[]": { - groupIndex: "uint48", - itemIndex: "uint48", - contractAddress: "address", - id: "uint256", - merkleProof: "bytes32[]", - }, - }, - ], - [ - creator.address, - 1, - 4, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable1155.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - { - groupIndex: 0, - itemIndex: 1, - contractAddress: burnable1155.address, - id: 2, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - ] - ); - // Reverts with insufficient values - await truffleAssert.reverts( - burnable1155.methods["safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)"]( - anyone1, - burnRedeem.address, - [1, 2], - [2, 2], - burnRedeemData, - { from: anyone1 } - ) - ); - - // Passes with right values - tx = await burnable1155.methods["safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)"]( - anyone1, - burnRedeem.address, - [1, 2], - [8, 8], - burnRedeemData, - { from: anyone1 } - ); - - console.log("Gas cost:\tBurn 4 1155s x4 (contract validation) through 1155 batch receiver:\t" + tx.receipt.gasUsed); - - // Ensure tokens are burned/minted - balance = await burnable1155.balanceOf(anyone1, 1); - assert.equal(0, balance); - balance = await burnable1155.balanceOf(anyone1, 2); - assert.equal(0, balance); - balance = await creator.balanceOf(anyone1); - assert.equal(5, balance); - }); - - it("receiver invalid input test", async function () { - // Test initializing a new burn redeem - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - let cost = ethers.BigNumber.from("1000000000000000000"); - - // Mint burnable tokens - await burnable721.mintBase(anyone1, { from: owner }); - await burnable1155.mintBaseNew([anyone1], [1], [""], { from: owner }); - await burnable1155.mintBaseNew([anyone1], [1], [""], { from: owner }); - - // Burn #1 - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: cost, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable721.address, - tokenSpec: 1, - burnSpec: 1, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - // Burn #2 - await burnRedeem.initializeBurnRedeem( - creator.address, - 2, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: cost, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - // Burn #3 - await burnRedeem.initializeBurnRedeem( - creator.address, - 3, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: cost, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 2, - items: [ - { - validationType: 2, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 1, - maxTokenId: 1, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - { - validationType: 2, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 2, - maxTokenId: 2, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Receivers revert on burns with cost - // onERC721Received - let burnRedeemData = web3.eth.abi.encodeParameters( - ["address", "uint256", "uint256", "bytes32[]"], - [creator.address, 1, 0, []] - ); - await truffleAssert.reverts( - burnable721.methods["safeTransferFrom(address,address,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - burnRedeemData, - { from: anyone1 } - ) - ); - // onERC1155Received - burnRedeemData = web3.eth.abi.encodeParameters( - ["address", "uint256", "uint32", "uint256", "bytes32[]"], - [creator.address, 2, 1, 0, []] - ); - await truffleAssert.reverts( - burnable1155.methods["safeTransferFrom(address,address,uint256,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - 1, - burnRedeemData, - { from: anyone1 } - ) - ); - // onERC1155BatchReceived - burnRedeemData = web3.eth.abi.encodeParameters( - [ - "address", - "uint256", - "uint32", - { - "BurnToken[]": { - groupIndex: "uint48", - itemIndex: "uint48", - contractAddress: "address", - id: "uint256", - merkleProof: "bytes32[]", - }, - }, - ], - [ - creator.address, - 1, - 1, - [ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable1155.address, - id: 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - { - groupIndex: 0, - itemIndex: 1, - contractAddress: burnable1155.address, - id: 2, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ], - ] - ); - await truffleAssert.reverts( - burnable1155.methods["safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)"]( - anyone1, - burnRedeem.address, - [1, 2], - [1, 1], - burnRedeemData, - { from: anyone1 } - ) - ); - - // Single receivers revert on single set burns with requiredCount > 1 - // Burn #3 - await burnRedeem.updateBurnRedeem( - creator.address, - 3, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 2, - items: [ - { - validationType: 2, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 1, - maxTokenId: 1, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - { - validationType: 2, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 2, - maxTokenId: 2, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - // onERC721Received - burnRedeemData = web3.eth.abi.encodeParameters( - ["address", "uint256", "uint256", "bytes32[]"], - [creator.address, 3, 0, []] - ); - await truffleAssert.reverts( - burnable721.methods["safeTransferFrom(address,address,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - burnRedeemData, - { from: anyone1 } - ) - ); - // onERC1155Received - burnRedeemData = web3.eth.abi.encodeParameters( - ["address", "uint256", "uint32", "uint256", "bytes32[]"], - [creator.address, 3, 1, 0, []] - ); - await truffleAssert.reverts( - burnable1155.methods["safeTransferFrom(address,address,uint256,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - 1, - burnRedeemData, - { from: anyone1 } - ) - ); - - // Single receivers revert when burnSets.length > 1 - await burnRedeem.updateBurnRedeem( - creator.address, - 3, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 2, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 1, - maxTokenId: 1, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - { - requiredCount: 1, - items: [ - { - validationType: 2, - contractAddress: burnable1155.address, - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 2, - maxTokenId: 2, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - // onERC721Received - burnRedeemData = web3.eth.abi.encodeParameters( - ["address", "uint256", "uint256", "bytes32[]"], - [creator.address, 3, 0, []] - ); - await truffleAssert.reverts( - burnable721.methods["safeTransferFrom(address,address,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - burnRedeemData, - { from: anyone1 } - ) - ); - // onERC1155Received - burnRedeemData = web3.eth.abi.encodeParameters( - ["address", "uint256", "uint32", "uint256", "bytes32[]"], - [creator.address, 3, 1, 0, []] - ); - await truffleAssert.reverts( - burnable1155.methods["safeTransferFrom(address,address,uint256,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - 1, - burnRedeemData, - { from: anyone1 } - ) - ); - }); - - it("withdraw test", async function () { - // Test initializing a new burn redeem - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - - // Mint burnable tokens - for (let i = 0; i < 10; i++) { - await burnable721.mintBase(anyone1, { from: owner }); - } - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable721.address, - tokenSpec: 1, - burnSpec: 1, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - const addresses = []; - const indexes = []; - const burnTokens = []; - const burnCounts = []; - - for (let i = 0; i < 10; i++) { - addresses.push(creator.address); - indexes.push(1); - burnTokens.push([ - { - groupIndex: 0, - itemIndex: 0, - contractAddress: burnable721.address, - id: i + 1, - merkleProof: [ethers.utils.formatBytes32String("")], - }, - ]); - burnCounts.push(1); - } - - await burnable721.setApprovalForAll(burnRedeem.address, true, { from: anyone1 }); - - await burnRedeem.methods["burnRedeem(address[],uint256[],uint32[],(uint48,uint48,address,uint256,bytes32[])[][])"]( - addresses, - indexes, - burnCounts, - burnTokens, - { from: anyone1, value: BURN_FEE.mul(10) } - ); - - // Reverts when non admin tries to withdraw - await truffleAssert.reverts(burnRedeem.withdraw(anyone1, BURN_FEE.mul(10), { from: anyone1 })); - - // Reverts with too large of a withdrawal - await truffleAssert.reverts(burnRedeem.withdraw(owner, BURN_FEE.mul(200), { from: burnRedeemOwner })); - - const ownerBalanceBefore = await web3.eth.getBalance(owner); - - // Passes with valid withdrawal amount from owner - await burnRedeem.withdraw(owner, BURN_FEE.mul(10), { from: burnRedeemOwner }); - const ownerBalanceAfter = await web3.eth.getBalance(owner); - assert.equal(ethers.BigNumber.from(ownerBalanceBefore).add(BURN_FEE.mul(10)).toString(), ownerBalanceAfter); - }); - - it("misconfiguration - onERC721Received test", async function () { - // Test initializing a new burn redeem - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - let burnRedeemData = web3.eth.abi.encodeParameters( - ["address", "uint256", "uint256", "bytes32[]"], - [creator.address, 1, 0, []] - ); - - // Mint burnable tokens - await burnable721.mintBase(anyone1, { from: owner }); - - // Ensure that the creator contract state is what we expect before mints - let balance = await creator.balanceOf(anyone1); - assert.equal(0, balance); - balance = await burnable721.balanceOf(anyone1); - assert.equal(1, balance); - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable721.address, - tokenSpec: 2, - burnSpec: 1, - amount: 1, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Receiver functions require membership - await manifoldMembership.setMember(anyone1, true, { from: owner }); - - // Reverts due to misconfiguration - await truffleAssert.reverts( - burnable721.methods["safeTransferFrom(address,address,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - burnRedeemData, - { from: anyone1 } - ) - ); - }); - - it("misconfiguration - onERC1155Received test", async function () { - // Test initializing a new burn redeem - let start = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let end = start + 300; - let burnRedeemData = web3.eth.abi.encodeParameters( - ["address", "uint256", "uint32", "uint256", "bytes32[]"], - [creator.address, 1, 1, 0, []] - ); - - // Mint burnable tokens - await burnable1155.mintBaseNew([anyone1], [1], [""], { from: owner }); - - // Ensure that the creator contract state is what we expect before mints - let balance = await creator.balanceOf(anyone1); - assert.equal(0, balance); - balance = await burnable1155.balanceOf(anyone1, 1); - assert.equal(1, balance); - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: start, - endDate: end, - redeemAmount: 1, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: owner, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable1155.address, - tokenSpec: 1, - burnSpec: 1, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // Receiver functions require membership - await manifoldMembership.setMember(anyone1, true, { from: owner }); - - // Reverts due to misconfiguration - await truffleAssert.reverts( - burnable1155.methods["safeTransferFrom(address,address,uint256,uint256,bytes)"]( - anyone1, - burnRedeem.address, - 1, - 1, - burnRedeemData, - { from: anyone1 } - ) - ); - }); - - it("airdrop test", async function () { - let now = (await web3.eth.getBlock("latest")).timestamp - 30; // seconds since unix epoch - let later = now + 1000; - - await burnRedeem.initializeBurnRedeem( - creator.address, - 1, - { - startDate: now, - endDate: later, - redeemAmount: 2, - totalSupply: 10, - storageProtocol: 1, - location: "XXX", - cost: 0, - paymentReceiver: anyone1, - burnSet: [ - { - requiredCount: 1, - items: [ - { - validationType: 1, - contractAddress: burnable721.address, - tokenSpec: 1, - burnSpec: 1, - amount: 0, - minTokenId: 0, - maxTokenId: 0, - merkleRoot: ethers.utils.formatBytes32String(""), - }, - ], - }, - ], - }, - false, - { from: owner } - ); - - // First airdrop - await burnRedeem.airdrop(creator.address, 1, [anyone1, anyone2], [1, 1], { from: owner }); - - // redeemedCount updated - let burnRedeemInstance = await burnRedeem.getBurnRedeem(creator.address, 1); - assert.equal(burnRedeemInstance.redeemedCount, 4); - assert.equal(burnRedeemInstance.totalSupply, 10); - - // Check tokenURI - assert.equal(await creator.tokenURI(1), "XXX/1"); - assert.equal(await creator.tokenURI(2), "XXX/2"); - - // Tokens minted - let balance = await creator.balanceOf(anyone1); - assert.equal(balance, 2); - balance = await creator.balanceOf(anyone2); - assert.equal(balance, 2); - - // Second airdrop - await burnRedeem.airdrop(creator.address, 1, [anyone1, anyone2], [9, 9], { from: owner }); - - // Check tokenURI - assert.equal(await creator.tokenURI(3), "XXX/3"); - assert.equal(await creator.tokenURI(4), "XXX/4"); - - // Total supply updated to redeemedCount - burnRedeemInstance = await burnRedeem.getBurnRedeem(creator.address, 1); - assert.equal(burnRedeemInstance.redeemedCount, 40); - assert.equal(burnRedeemInstance.totalSupply, 40); - - // Tokens minted - balance = await creator.balanceOf(anyone1); - assert.equal(balance, 20); - balance = await creator.balanceOf(anyone2); - assert.equal(balance, 20); - - // Reverts when redeemedCount would exceed max uint32 - await truffleAssert.reverts(burnRedeem.airdrop(creator.address, 1, [anyone1], [2147483647])); - }); - }); -});