diff --git a/src/lib/ERC7498NFTRedeemables.sol b/src/lib/ERC7498NFTRedeemables.sol index 63c62b4..117103d 100644 --- a/src/lib/ERC7498NFTRedeemables.sol +++ b/src/lib/ERC7498NFTRedeemables.sol @@ -182,34 +182,32 @@ contract ERC7498NFTRedeemables is IERC7498, RedeemablesErrors { } function _transferConsiderationItem(uint256 id, ConsiderationItem memory c) internal { + // WITH_CRITERIA with identifier 0 is wildcard: any id is valid. + // Criteria is not yet implemented, for that functionality use the contract offerer. + if ( + id != c.identifierOrCriteria && c.identifierOrCriteria != 0 + && (c.itemType != ItemType.ERC721_WITH_CRITERIA || c.itemType != ItemType.ERC1155_WITH_CRITERIA) + ) { + revert InvalidConsiderationTokenIdSupplied(c.token, id, c.identifierOrCriteria); + } + // If consideration item is this contract, recipient is burn address, and _useInternalBurn() fn returns true, // call the internal burn function and return. if (c.token == address(this) && c.recipient == payable(_BURN_ADDRESS) && _useInternalBurn()) { _internalBurn(id, c.startAmount); - return; - } - - // Transfer the token to the consideration recipient. - if (c.itemType == ItemType.ERC721 || c.itemType == ItemType.ERC721_WITH_CRITERIA) { - // ERC721_WITH_CRITERIA with identifier 0 is wildcard: any id is valid. - // Criteria is not yet implemented, for that functionality use the contract offerer. - if (c.itemType == ItemType.ERC721 && id != c.identifierOrCriteria) { - revert InvalidConsiderationTokenIdSupplied(c.token, id, c.identifierOrCriteria); - } - IERC721(c.token).safeTransferFrom(msg.sender, c.recipient, id); - } else if ((c.itemType == ItemType.ERC1155 || c.itemType == ItemType.ERC1155_WITH_CRITERIA)) { - // ERC1155_WITH_CRITERIA with identifier 0 is wildcard: any id is valid. - // Criteria is not yet implemented, for that functionality use the contract offerer. - if (c.itemType == ItemType.ERC1155 && id != c.identifierOrCriteria) { - revert InvalidConsiderationTokenIdSupplied(c.token, id, c.identifierOrCriteria); - } - IERC1155(c.token).safeTransferFrom(msg.sender, c.recipient, id, c.startAmount, ""); - } else if (c.itemType == ItemType.ERC20) { - IERC20(c.token).transferFrom(msg.sender, c.recipient, c.startAmount); } else { - // ItemType.NATIVE - (bool success,) = c.recipient.call{value: msg.value}(""); - if (!success) revert EtherTransferFailed(); + // Transfer the token to the consideration recipient. + if (c.itemType == ItemType.ERC721 || c.itemType == ItemType.ERC721_WITH_CRITERIA) { + IERC721(c.token).safeTransferFrom(msg.sender, c.recipient, id); + } else if ((c.itemType == ItemType.ERC1155 || c.itemType == ItemType.ERC1155_WITH_CRITERIA)) { + IERC1155(c.token).safeTransferFrom(msg.sender, c.recipient, id, c.startAmount, ""); + } else if (c.itemType == ItemType.ERC20) { + IERC20(c.token).transferFrom(msg.sender, c.recipient, c.startAmount); + } else { + // ItemType.NATIVE + (bool success,) = c.recipient.call{value: msg.value}(""); + if (!success) revert EtherTransferFailed(); + } } } @@ -272,7 +270,7 @@ contract ERC7498NFTRedeemables is IERC7498, RedeemablesErrors { } // Ensure the balance is sufficient. - if (balance < c.startAmount) { + if (c.itemType != ItemType.NATIVE && balance < c.startAmount) { revert ConsiderationItemInsufficientBalance(c.token, balance, c.startAmount); } diff --git a/test/ERC721ShipyardRedeemable.t.sol b/test/ERC721ShipyardRedeemable.t.sol index fb92865..d9028f4 100644 --- a/test/ERC721ShipyardRedeemable.t.sol +++ b/test/ERC721ShipyardRedeemable.t.sol @@ -1,18 +1,28 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {Test} from "forge-std/Test.sol"; +import {BaseRedeemablesTest} from "./utils/BaseRedeemablesTest.sol"; import {Solarray} from "solarray/Solarray.sol"; import {ERC721} from "solady/src/tokens/ERC721.sol"; +import {TestERC20} from "./utils/mocks/TestERC20.sol"; import {TestERC721} from "./utils/mocks/TestERC721.sol"; +import {TestERC1155} from "./utils/mocks/TestERC1155.sol"; import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {ItemType, OrderType, Side} from "seaport-sol/src/SeaportEnums.sol"; +import {OfferItemLib} from "seaport-sol/src/lib/OfferItemLib.sol"; +import {ConsiderationItemLib} from "seaport-sol/src/lib/ConsiderationItemLib.sol"; import {CampaignParams, CampaignRequirements, TraitRedemption} from "../src/lib/RedeemablesStructs.sol"; -import {RedeemablesErrors} from "../src/lib/RedeemablesErrors.sol"; import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; import {ERC721ShipyardRedeemableOwnerMintable} from "../src/test/ERC721ShipyardRedeemableOwnerMintable.sol"; -contract TestERC721ShipyardRedeemable is RedeemablesErrors, Test { +contract TestERC721ShipyardRedeemable is BaseRedeemablesTest { + using OfferItemLib for OfferItem; + using OfferItemLib for OfferItem[]; + using ConsiderationItemLib for ConsiderationItem; + using ConsiderationItemLib for ConsiderationItem[]; + + uint256 tokenId = 2; + event Redemption( uint256 indexed campaignId, uint256 requirementsIndex, @@ -22,36 +32,105 @@ contract TestERC721ShipyardRedeemable is RedeemablesErrors, Test { address redeemedBy ); - ERC721ShipyardRedeemableOwnerMintable redeemToken; - ERC721RedemptionMintable receiveToken; - address alice; + function setUp() public virtual override { + super.setUp(); + } + + function testBurnInternalToken() public { + redeemToken.mint(address(this), tokenId); + + CampaignRequirements[] memory requirements = new CampaignRequirements[]( + 1 + ); + + requirements[0] = CampaignRequirements({ + offer: defaultCampaignOffer, + consideration: defaultCampaignConsideration, + traitRedemptions: defaultTraitRedemptions + }); + + CampaignParams memory params = CampaignParams({ + requirements: requirements, + signer: address(0), + startTime: uint32(block.timestamp), + endTime: uint32(block.timestamp + 1000), + maxCampaignRedemptions: 5, + manager: address(this) + }); + + redeemToken.createCampaign(params, ""); + + // campaignId: 1 + // requirementsIndex: 0 + // redemptionHash: bytes32(0) + bytes memory extraData = abi.encode(1, 0, bytes32(0)); - address constant _BURN_ADDRESS = 0x000000000000000000000000000000000000dEaD; + uint256[] memory considerationTokenIds = Solarray.uint256s(tokenId); - function setUp() public { - redeemToken = new ERC721ShipyardRedeemableOwnerMintable(); - receiveToken = new ERC721RedemptionMintable(address(redeemToken)); - alice = makeAddr("alice"); + vm.expectEmit(true, true, true, true); + emit Redemption(1, 0, bytes32(0), considerationTokenIds, defaultTraitRedemptionTokenIds, address(this)); + redeemToken.redeem(considerationTokenIds, address(this), extraData); - vm.label(address(redeemToken), "redeemToken"); - vm.label(address(receiveToken), "receiveToken"); - vm.label(alice, "alice"); + vm.expectRevert(ERC721.TokenDoesNotExist.selector); + redeemToken.ownerOf(tokenId); + + assertEq(receiveToken.ownerOf(1), address(this)); } - function testBurnInternalToken() public { - uint256 tokenId = 2; + function testRevert721ConsiderationItemInsufficientBalance() public { redeemToken.mint(address(this), tokenId); - OfferItem[] memory offer = new OfferItem[](1); - offer[0] = OfferItem({ - itemType: ItemType.ERC721_WITH_CRITERIA, - token: address(receiveToken), - identifierOrCriteria: 0, - startAmount: 1, - endAmount: 1 + uint256 invalidTokenId = tokenId + 1; + redeemToken.mint(dillon.addr, invalidTokenId); + + CampaignRequirements[] memory requirements = new CampaignRequirements[]( + 1 + ); + + requirements[0] = CampaignRequirements({ + offer: defaultCampaignOffer, + consideration: defaultCampaignConsideration, + traitRedemptions: defaultTraitRedemptions }); - ConsiderationItem[] memory consideration = new ConsiderationItem[](1); + CampaignParams memory params = CampaignParams({ + requirements: requirements, + signer: address(0), + startTime: uint32(block.timestamp), + endTime: uint32(block.timestamp + 1000), + maxCampaignRedemptions: 5, + manager: address(this) + }); + + redeemToken.createCampaign(params, ""); + // campaignId: 1 + // requirementsIndex: 0 + // redemptionHash: bytes32(0) + bytes memory extraData = abi.encode(1, 0, bytes32(0)); + uint256[] memory tokenIds = Solarray.uint256s(invalidTokenId); + + vm.expectRevert( + abi.encodeWithSelector( + ConsiderationItemInsufficientBalance.selector, + requirements[0].consideration[0].token, + 0, + requirements[0].consideration[0].startAmount + ) + ); + redeemToken.redeem(tokenIds, address(this), extraData); + + assertEq(redeemToken.ownerOf(tokenId), address(this)); + + vm.expectRevert(ERC721.TokenDoesNotExist.selector); + receiveToken.ownerOf(1); + } + + function testRevertConsiderationLengthNotMet() public { + redeemToken.mint(address(this), tokenId); + + ERC721ShipyardRedeemableOwnerMintable secondRedeemToken = new ERC721ShipyardRedeemableOwnerMintable(); + + ConsiderationItem[] memory consideration = new ConsiderationItem[](2); consideration[0] = ConsiderationItem({ itemType: ItemType.ERC721_WITH_CRITERIA, token: address(redeemToken), @@ -60,72 +139,57 @@ contract TestERC721ShipyardRedeemable is RedeemablesErrors, Test { endAmount: 1, recipient: payable(_BURN_ADDRESS) }); + consideration[1] = ConsiderationItem({ + itemType: ItemType.ERC721_WITH_CRITERIA, + token: address(secondRedeemToken), + identifierOrCriteria: 0, + startAmount: 1, + endAmount: 1, + recipient: payable(_BURN_ADDRESS) + }); CampaignRequirements[] memory requirements = new CampaignRequirements[]( 1 ); - requirements[0].offer = offer; + requirements[0].offer = defaultCampaignOffer; requirements[0].consideration = consideration; - { - CampaignParams memory params = CampaignParams({ - requirements: requirements, - signer: address(0), - startTime: uint32(block.timestamp), - endTime: uint32(block.timestamp + 1000), - maxCampaignRedemptions: 5, - manager: address(this) - }); + CampaignParams memory params = CampaignParams({ + requirements: requirements, + signer: address(0), + startTime: uint32(block.timestamp), + endTime: uint32(block.timestamp + 1000), + maxCampaignRedemptions: 5, + manager: address(this) + }); - redeemToken.createCampaign(params, ""); - } + redeemToken.createCampaign(params, ""); - { - OfferItem[] memory offerFromEvent = new OfferItem[](1); - offerFromEvent[0] = OfferItem({ - itemType: ItemType.ERC721, - token: address(receiveToken), - identifierOrCriteria: tokenId, - startAmount: 1, - endAmount: 1 - }); - ConsiderationItem[] memory considerationFromEvent = new ConsiderationItem[](1); - considerationFromEvent[0] = ConsiderationItem({ - itemType: ItemType.ERC721, - token: address(redeemToken), - identifierOrCriteria: tokenId, - startAmount: 1, - endAmount: 1, - recipient: payable(_BURN_ADDRESS) - }); + // campaignId: 1 + // requirementsIndex: 0 + // redemptionHash: bytes32(0) + bytes memory extraData = abi.encode(1, 0, bytes32(0)); + consideration[0].identifierOrCriteria = tokenId; - assertGt(uint256(consideration[0].itemType), uint256(considerationFromEvent[0].itemType)); + uint256[] memory tokenIds = Solarray.uint256s(tokenId); - // campaignId: 1 - // requirementsIndex: 0 - // redemptionHash: bytes32(0) - bytes memory extraData = abi.encode(1, 0, bytes32(0)); - consideration[0].identifierOrCriteria = tokenId; + vm.expectRevert(abi.encodeWithSelector(TokenIdsDontMatchConsiderationLength.selector, 2, 1)); - uint256[] memory considerationTokenIds = Solarray.uint256s(tokenId); - uint256[] memory traitRedemptionTokenIds; - - vm.expectEmit(true, true, true, true); - emit Redemption(1, 0, bytes32(0), considerationTokenIds, traitRedemptionTokenIds, address(this)); - redeemToken.redeem(considerationTokenIds, address(this), extraData); + redeemToken.redeem(tokenIds, address(this), extraData); - vm.expectRevert(ERC721.TokenDoesNotExist.selector); - redeemToken.ownerOf(tokenId); + assertEq(redeemToken.ownerOf(tokenId), address(this)); - assertEq(receiveToken.ownerOf(1), address(this)); - } + vm.expectRevert(ERC721.TokenDoesNotExist.selector); + receiveToken.ownerOf(1); } - function testRevert721ConsiderationItemInsufficientBalance() public { - uint256 tokenId = 2; - uint256 invalidTokenId = tokenId + 1; + function testBurnWithSecondConsiderationItem() public { + ERC721ShipyardRedeemableOwnerMintable secondRedeemToken = new ERC721ShipyardRedeemableOwnerMintable(); + vm.label(address(secondRedeemToken), "secondRedeemToken"); + secondRedeemToken.setApprovalForAll(address(redeemToken), true); + redeemToken.mint(address(this), tokenId); - redeemToken.mint(alice, invalidTokenId); + secondRedeemToken.mint(address(this), tokenId); OfferItem[] memory offer = new OfferItem[](1); offer[0] = OfferItem({ @@ -136,7 +200,7 @@ contract TestERC721ShipyardRedeemable is RedeemablesErrors, Test { endAmount: 1 }); - ConsiderationItem[] memory consideration = new ConsiderationItem[](1); + ConsiderationItem[] memory consideration = new ConsiderationItem[](2); consideration[0] = ConsiderationItem({ itemType: ItemType.ERC721_WITH_CRITERIA, token: address(redeemToken), @@ -145,6 +209,14 @@ contract TestERC721ShipyardRedeemable is RedeemablesErrors, Test { endAmount: 1, recipient: payable(_BURN_ADDRESS) }); + consideration[1] = ConsiderationItem({ + itemType: ItemType.ERC721_WITH_CRITERIA, + token: address(secondRedeemToken), + identifierOrCriteria: 0, + startAmount: 1, + endAmount: 1, + recipient: payable(_BURN_ADDRESS) + }); CampaignRequirements[] memory requirements = new CampaignRequirements[]( 1 @@ -192,30 +264,26 @@ contract TestERC721ShipyardRedeemable is RedeemablesErrors, Test { bytes memory extraData = abi.encode(1, 0, bytes32(0)); consideration[0].identifierOrCriteria = tokenId; - uint256[] memory tokenIds = Solarray.uint256s(invalidTokenId); + uint256[] memory tokenIds = Solarray.uint256s(tokenId, tokenId); - vm.expectRevert( - abi.encodeWithSelector( - ConsiderationItemInsufficientBalance.selector, - requirements[0].consideration[0].token, - 0, - requirements[0].consideration[0].startAmount - ) - ); redeemToken.redeem(tokenIds, address(this), extraData); - assertEq(redeemToken.ownerOf(tokenId), address(this)); - vm.expectRevert(ERC721.TokenDoesNotExist.selector); - receiveToken.ownerOf(1); + redeemToken.ownerOf(tokenId); + + assertEq(secondRedeemToken.ownerOf(tokenId), _BURN_ADDRESS); + + assertEq(receiveToken.ownerOf(1), address(this)); } } - function testRevertConsiderationLengthNotMet() public { + function testBurnWithSecondRequirementsIndex() public { ERC721ShipyardRedeemableOwnerMintable secondRedeemToken = new ERC721ShipyardRedeemableOwnerMintable(); + vm.label(address(secondRedeemToken), "secondRedeemToken"); + secondRedeemToken.setApprovalForAll(address(redeemToken), true); - uint256 tokenId = 2; redeemToken.mint(address(this), tokenId); + secondRedeemToken.mint(address(this), tokenId); OfferItem[] memory offer = new OfferItem[](1); offer[0] = OfferItem({ @@ -226,7 +294,7 @@ contract TestERC721ShipyardRedeemable is RedeemablesErrors, Test { endAmount: 1 }); - ConsiderationItem[] memory consideration = new ConsiderationItem[](2); + ConsiderationItem[] memory consideration = new ConsiderationItem[](1); consideration[0] = ConsiderationItem({ itemType: ItemType.ERC721_WITH_CRITERIA, token: address(redeemToken), @@ -235,7 +303,9 @@ contract TestERC721ShipyardRedeemable is RedeemablesErrors, Test { endAmount: 1, recipient: payable(_BURN_ADDRESS) }); - consideration[1] = ConsiderationItem({ + + ConsiderationItem[] memory secondRequirementConsideration = new ConsiderationItem[](1); + secondRequirementConsideration[0] = ConsiderationItem({ itemType: ItemType.ERC721_WITH_CRITERIA, token: address(secondRedeemToken), identifierOrCriteria: 0, @@ -245,11 +315,14 @@ contract TestERC721ShipyardRedeemable is RedeemablesErrors, Test { }); CampaignRequirements[] memory requirements = new CampaignRequirements[]( - 1 + 2 ); requirements[0].offer = offer; requirements[0].consideration = consideration; + requirements[1].offer = offer; + requirements[1].consideration = secondRequirementConsideration; + { CampaignParams memory params = CampaignParams({ requirements: requirements, @@ -287,30 +360,23 @@ contract TestERC721ShipyardRedeemable is RedeemablesErrors, Test { // campaignId: 1 // requirementsIndex: 0 // redemptionHash: bytes32(0) - bytes memory extraData = abi.encode(1, 0, bytes32(0)); + bytes memory extraData = abi.encode(1, 1, bytes32(0)); consideration[0].identifierOrCriteria = tokenId; uint256[] memory tokenIds = Solarray.uint256s(tokenId); - vm.expectRevert(abi.encodeWithSelector(TokenIdsDontMatchConsiderationLength.selector, 2, 1)); - redeemToken.redeem(tokenIds, address(this), extraData); assertEq(redeemToken.ownerOf(tokenId), address(this)); - vm.expectRevert(ERC721.TokenDoesNotExist.selector); - receiveToken.ownerOf(1); + assertEq(secondRedeemToken.ownerOf(tokenId), _BURN_ADDRESS); + + assertEq(receiveToken.ownerOf(1), address(this)); } } - function testBurnWithSecondConsiderationItem() public { - ERC721ShipyardRedeemableOwnerMintable secondRedeemToken = new ERC721ShipyardRedeemableOwnerMintable(); - vm.label(address(secondRedeemToken), "secondRedeemToken"); - secondRedeemToken.setApprovalForAll(address(redeemToken), true); - - uint256 tokenId = 2; + function testRevertInvalidTxValue() public { redeemToken.mint(address(this), tokenId); - secondRedeemToken.mint(address(this), tokenId); OfferItem[] memory offer = new OfferItem[](1); offer[0] = OfferItem({ @@ -331,12 +397,12 @@ contract TestERC721ShipyardRedeemable is RedeemablesErrors, Test { recipient: payable(_BURN_ADDRESS) }); consideration[1] = ConsiderationItem({ - itemType: ItemType.ERC721_WITH_CRITERIA, - token: address(secondRedeemToken), + itemType: ItemType.NATIVE, + token: address(0), identifierOrCriteria: 0, - startAmount: 1, - endAmount: 1, - recipient: payable(_BURN_ADDRESS) + startAmount: 0.1 ether, + endAmount: 0.1 ether, + recipient: payable(dillon.addr) }); CampaignRequirements[] memory requirements = new CampaignRequirements[]( @@ -385,27 +451,25 @@ contract TestERC721ShipyardRedeemable is RedeemablesErrors, Test { bytes memory extraData = abi.encode(1, 0, bytes32(0)); consideration[0].identifierOrCriteria = tokenId; - uint256[] memory tokenIds = Solarray.uint256s(tokenId, tokenId); + uint256[] memory considerationTokenIds = Solarray.uint256s(tokenId, 0); + uint256[] memory traitRedemptionTokenIds; - redeemToken.redeem(tokenIds, address(this), extraData); + vm.expectRevert(abi.encodeWithSelector(InvalidTxValue.selector, 0.05 ether, 0.1 ether)); + redeemToken.redeem{value: 0.05 ether}(considerationTokenIds, address(this), extraData); vm.expectRevert(ERC721.TokenDoesNotExist.selector); - redeemToken.ownerOf(tokenId); - - assertEq(secondRedeemToken.ownerOf(tokenId), _BURN_ADDRESS); + receiveToken.ownerOf(1); - assertEq(receiveToken.ownerOf(1), address(this)); + assertEq(redeemToken.ownerOf(tokenId), address(this)); } } - function testBurnWithSecondRequirementsIndex() public { - ERC721ShipyardRedeemableOwnerMintable secondRedeemToken = new ERC721ShipyardRedeemableOwnerMintable(); - vm.label(address(secondRedeemToken), "secondRedeemToken"); - secondRedeemToken.setApprovalForAll(address(redeemToken), true); - - uint256 tokenId = 2; + function testRevertErc20ConsiderationItemInsufficientBalance() public { redeemToken.mint(address(this), tokenId); - secondRedeemToken.mint(address(this), tokenId); + + TestERC20 redeemErc20 = new TestERC20(); + redeemErc20.mint(address(this), 0.05 ether); + redeemErc20.approve(address(redeemToken), 1 ether); OfferItem[] memory offer = new OfferItem[](1); offer[0] = OfferItem({ @@ -416,7 +480,7 @@ contract TestERC721ShipyardRedeemable is RedeemablesErrors, Test { endAmount: 1 }); - ConsiderationItem[] memory consideration = new ConsiderationItem[](1); + ConsiderationItem[] memory consideration = new ConsiderationItem[](2); consideration[0] = ConsiderationItem({ itemType: ItemType.ERC721_WITH_CRITERIA, token: address(redeemToken), @@ -425,26 +489,21 @@ contract TestERC721ShipyardRedeemable is RedeemablesErrors, Test { endAmount: 1, recipient: payable(_BURN_ADDRESS) }); - - ConsiderationItem[] memory secondRequirementConsideration = new ConsiderationItem[](1); - secondRequirementConsideration[0] = ConsiderationItem({ - itemType: ItemType.ERC721_WITH_CRITERIA, - token: address(secondRedeemToken), + consideration[1] = ConsiderationItem({ + itemType: ItemType.ERC20, + token: address(redeemErc20), identifierOrCriteria: 0, - startAmount: 1, - endAmount: 1, - recipient: payable(_BURN_ADDRESS) + startAmount: 0.1 ether, + endAmount: 0.1 ether, + recipient: payable(dillon.addr) }); CampaignRequirements[] memory requirements = new CampaignRequirements[]( - 2 + 1 ); requirements[0].offer = offer; requirements[0].consideration = consideration; - requirements[1].offer = offer; - requirements[1].consideration = secondRequirementConsideration; - { CampaignParams memory params = CampaignParams({ requirements: requirements, @@ -482,18 +541,139 @@ contract TestERC721ShipyardRedeemable is RedeemablesErrors, Test { // campaignId: 1 // requirementsIndex: 0 // redemptionHash: bytes32(0) - bytes memory extraData = abi.encode(1, 1, bytes32(0)); + bytes memory extraData = abi.encode(1, 0, bytes32(0)); consideration[0].identifierOrCriteria = tokenId; - uint256[] memory tokenIds = Solarray.uint256s(tokenId); + uint256[] memory considerationTokenIds = Solarray.uint256s(tokenId, 0); + uint256[] memory traitRedemptionTokenIds; - redeemToken.redeem(tokenIds, address(this), extraData); + vm.expectRevert( + abi.encodeWithSelector( + ConsiderationItemInsufficientBalance.selector, address(redeemErc20), 0.05 ether, 0.1 ether + ) + ); + redeemToken.redeem(considerationTokenIds, address(this), extraData); + + vm.expectRevert(ERC721.TokenDoesNotExist.selector); + receiveToken.ownerOf(1); assertEq(redeemToken.ownerOf(tokenId), address(this)); + } + } - assertEq(secondRedeemToken.ownerOf(tokenId), _BURN_ADDRESS); + function testRevertErc721InvalidConsiderationTokenIdSupplied() public { + uint256 considerationTokenId = 1; + redeemToken.mint(address(this), tokenId); + redeemToken.mint(address(this), considerationTokenId); - assertEq(receiveToken.ownerOf(1), address(this)); - } + CampaignRequirements[] memory requirements = new CampaignRequirements[]( + 1 + ); + + ConsiderationItem[] memory consideration = new ConsiderationItem[](1); + consideration[0] = defaultCampaignConsideration[0].withIdentifierOrCriteria(considerationTokenId); + + requirements[0] = CampaignRequirements({ + offer: defaultCampaignOffer, + consideration: consideration, + traitRedemptions: defaultTraitRedemptions + }); + + CampaignParams memory params = CampaignParams({ + requirements: requirements, + signer: address(0), + startTime: uint32(block.timestamp), + endTime: uint32(block.timestamp + 1000), + maxCampaignRedemptions: 5, + manager: address(this) + }); + + redeemToken.createCampaign(params, ""); + + // campaignId: 1 + // requirementsIndex: 0 + // redemptionHash: bytes32(0) + bytes memory extraData = abi.encode(1, 0, bytes32(0)); + + uint256[] memory considerationTokenIds = Solarray.uint256s(tokenId); + + vm.expectRevert( + abi.encodeWithSelector( + InvalidConsiderationTokenIdSupplied.selector, address(redeemToken), tokenId, considerationTokenId + ) + ); + redeemToken.redeem(considerationTokenIds, address(this), extraData); + + vm.expectRevert(ERC721.TokenDoesNotExist.selector); + receiveToken.ownerOf(1); + + assertEq(redeemToken.ownerOf(tokenId), address(this)); + assertEq(redeemToken.ownerOf(considerationTokenId), address(this)); } + + function testRevertErc1155InvalidConsiderationTokenIdSupplied() public { + TestERC1155 redeemErc1155 = new TestERC1155(); + uint256 considerationTokenId = 1; + redeemErc1155.mint(address(this), tokenId, 1 ether); + redeemErc1155.mint(address(this), considerationTokenId, 1 ether); + + CampaignRequirements[] memory requirements = new CampaignRequirements[]( + 1 + ); + + ConsiderationItem[] memory consideration = new ConsiderationItem[](1); + consideration[0] = defaultCampaignConsideration[0].withToken(address(redeemErc1155)).withItemType( + ItemType.ERC1155 + ).withIdentifierOrCriteria(considerationTokenId); + + requirements[0] = CampaignRequirements({ + offer: defaultCampaignOffer, + consideration: consideration, + traitRedemptions: defaultTraitRedemptions + }); + + CampaignParams memory params = CampaignParams({ + requirements: requirements, + signer: address(0), + startTime: uint32(block.timestamp), + endTime: uint32(block.timestamp + 1000), + maxCampaignRedemptions: 5, + manager: address(this) + }); + + redeemToken.createCampaign(params, ""); + + // campaignId: 1 + // requirementsIndex: 0 + // redemptionHash: bytes32(0) + bytes memory extraData = abi.encode(1, 0, bytes32(0)); + + uint256[] memory considerationTokenIds = Solarray.uint256s(tokenId); + uint256[] memory traitRedemptionTokenIds; + + vm.expectRevert( + abi.encodeWithSelector( + InvalidConsiderationTokenIdSupplied.selector, address(redeemErc1155), tokenId, considerationTokenId + ) + ); + redeemToken.redeem(considerationTokenIds, address(this), extraData); + + vm.expectRevert(ERC721.TokenDoesNotExist.selector); + receiveToken.ownerOf(1); + + assertEq(redeemErc1155.balanceOf(address(this), tokenId), 1 ether); + assertEq(redeemErc1155.balanceOf(address(this), considerationTokenId), 1 ether); + } + + // ERC721: specific id ONLY + // ERC721_WITH_CRITERIA id=0: wildcard + // ERC721_WITH_CRITERIA id=!0: revert + // ^ add this validation to create/update campaign + // ERC1155: specific ID ONLY + // ERC1155_WITH_CRITERIA id=0: wildcard + // ERC1155_WITH_CRITERIA id=!0: revert + // ERC20 + // NATIVE + + // RedeemablesTestHelper.sol, with CampaignParams in storage } diff --git a/test/utils/BaseRedeemablesTest.sol b/test/utils/BaseRedeemablesTest.sol new file mode 100644 index 0000000..1836e64 --- /dev/null +++ b/test/utils/BaseRedeemablesTest.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {BaseOrderTest} from "./BaseOrderTest.sol"; +import {OfferItemLib, ConsiderationItemLib} from "seaport-sol/src/SeaportSol.sol"; +import {OfferItem, ConsiderationItem} from "seaport-sol/src/SeaportStructs.sol"; +import {ERC721RedemptionMintable} from "../../src/extensions/ERC721RedemptionMintable.sol"; +import {ERC721ShipyardRedeemableOwnerMintable} from "../../src/test/ERC721ShipyardRedeemableOwnerMintable.sol"; +import {RedeemablesErrors} from "../../src/lib/RedeemablesErrors.sol"; +import {CampaignParams, CampaignRequirements, TraitRedemption} from "../../src/lib/RedeemablesStructs.sol"; + +contract BaseRedeemablesTest is RedeemablesErrors, BaseOrderTest { + using OfferItemLib for OfferItem; + using OfferItemLib for OfferItem[]; + using ConsiderationItemLib for ConsiderationItem; + using ConsiderationItemLib for ConsiderationItem[]; + + bytes32 private constant CAMPAIGN_PARAMS_MAP_POSITION = keccak256("CampaignParamsDefault"); + + ERC721ShipyardRedeemableOwnerMintable redeemToken; + ERC721RedemptionMintable receiveToken; + + OfferItem[] defaultCampaignOffer; + ConsiderationItem[] defaultCampaignConsideration; + TraitRedemption[] defaultTraitRedemptions; + uint256[] defaultTraitRedemptionTokenIds = new uint256[](0); + + CampaignRequirements[] defaultCampaignRequirements; + // CampaignParams defaultCampaignParams; + + address constant _BURN_ADDRESS = 0x000000000000000000000000000000000000dEaD; + + string constant DEFAULT_ERC721_CAMPAIGN_OFFER = "default erc721 campaign offer"; + string constant DEFAULT_ERC721_CAMPAIGN_CONSIDERATION = "default erc721 campaign consideration"; + + function setUp() public virtual override { + super.setUp(); + + redeemToken = new ERC721ShipyardRedeemableOwnerMintable(); + receiveToken = new ERC721RedemptionMintable(address(redeemToken)); + + vm.label(address(redeemToken), "redeemToken"); + vm.label(address(receiveToken), "receiveToken"); + + // Save the default campaign offer and consideration + OfferItemLib.fromDefault(SINGLE_ERC721).withToken(address(receiveToken)).saveDefault( + DEFAULT_ERC721_CAMPAIGN_OFFER + ); + + ConsiderationItemLib.fromDefault(SINGLE_ERC721).withToken(address(redeemToken)).withRecipient(_BURN_ADDRESS) + .saveDefault(DEFAULT_ERC721_CAMPAIGN_CONSIDERATION); + + defaultCampaignOffer.push(OfferItemLib.fromDefault(DEFAULT_ERC721_CAMPAIGN_OFFER)); + + defaultCampaignConsideration.push(ConsiderationItemLib.fromDefault(DEFAULT_ERC721_CAMPAIGN_CONSIDERATION)); + } + + function _campaignParamsMap() private pure returns (mapping(string => CampaignParams) storage campaignParamsMap) { + bytes32 position = CAMPAIGN_PARAMS_MAP_POSITION; + assembly { + campaignParamsMap.slot := position + } + } +}