From 8ea02beea55859c0337164475e7e7ba2d09b49b4 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Wed, 8 Nov 2023 12:54:50 -0800 Subject: [PATCH 1/6] fix scripts' abi encoding --- ...oyAndRedeemTokens-CampaignOnReceiveToken.s.sol | 14 +++++++++----- script/DeployAndRedeemTokens.s.sol | 15 +++++++++------ script/RedeemTokens.s.sol | 15 +++++++++------ 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol b/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol index 848a441..8ad813e 100644 --- a/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol +++ b/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol @@ -60,11 +60,15 @@ contract DeployAndRedeemTokens_CampaignOnReceiveToken is Script, Test { redeemToken.mint(msg.sender, 1); // Let's redeem them! - uint256 requirementsIndex = 0; - bytes32 redemptionHash; - uint256 salt; - bytes memory signature; - bytes memory data = abi.encode(campaignId, requirementsIndex, redemptionHash, salt, signature); + uint256[] memory traitRedemptionTokenIds; + bytes memory data = abi.encode( + campaignId, + 0, // requirementsIndex + bytes32(0), // redemptionHash + traitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); uint256[] memory tokenIds = new uint256[](1); tokenIds[0] = 1; diff --git a/script/DeployAndRedeemTokens.s.sol b/script/DeployAndRedeemTokens.s.sol index 925418f..ace0719 100644 --- a/script/DeployAndRedeemTokens.s.sol +++ b/script/DeployAndRedeemTokens.s.sol @@ -66,12 +66,15 @@ contract DeployAndRedeemTokens is Script, Test { redeemToken.mint(msg.sender, 1); // Let's redeem them! - uint256 campaignId = 1; - uint256 requirementsIndex = 0; - bytes32 redemptionHash; - uint256 salt; - bytes memory signature; - bytes memory data = abi.encode(campaignId, requirementsIndex, redemptionHash, salt, signature); + uint256[] memory traitRedemptionTokenIds; + bytes memory data = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + traitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); uint256[] memory tokenIds = new uint256[](1); tokenIds[0] = 1; diff --git a/script/RedeemTokens.s.sol b/script/RedeemTokens.s.sol index 66c6704..8b4e364 100644 --- a/script/RedeemTokens.s.sol +++ b/script/RedeemTokens.s.sol @@ -19,12 +19,15 @@ contract RedeemTokens is Script, Test { ERC1155ShipyardRedeemableMintable(0x3D0fa2a8D07dfe357905a4cB4ed51b0Aea8385B9); // Let's redeem them! - uint256 campaignId = 1; - uint256 requirementsIndex = 0; - bytes32 redemptionHash; - uint256 salt; - bytes memory signature; - bytes memory data = abi.encode(campaignId, requirementsIndex, redemptionHash, salt, signature); + uint256[] memory traitRedemptionTokenIds; + bytes memory data = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + traitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); uint256[] memory redeemTokenIds = new uint256[](1); redeemTokenIds[0] = 1; From 7f5a6361b25910de6dc09a5ede71bf335f101a0d Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Tue, 14 Nov 2023 18:49:52 -0800 Subject: [PATCH 2/6] progress --- .gitignore | 2 + script/DeployAndConfigure1155Receive.s.sol | 16 +- ...dRedeemTokens-CampaignOnReceiveToken.s.sol | 13 +- script/DeployAndRedeemTokens.s.sol | 10 +- script/DeployAndRedeemTrait.s.sol | 10 +- ...kenWithPredeployedSeadropRedeemToken.s.sol | 10 +- src/ERC1155SeaDropRedeemable.sol | 6 +- src/ERC1155ShipyardRedeemable.sol | 6 +- src/ERC721SeaDropRedeemable.sol | 6 +- src/ERC721ShipyardRedeemable.sol | 6 +- src/RedeemableContractOfferer.sol.txt | 6 +- src/interfaces/IERC7498.sol | 10 +- src/interfaces/IRedeemableContractOfferer.sol | 8 +- src/lib/ERC7498NFTRedeemables.sol | 118 ++++----- src/lib/RedeemablesStructs.sol | 6 +- test/ERC7498-DynamicTraits.t.sol | 69 ++---- test/ERC7498-GetAndUpdateCampaign.t.sol | 64 +++++ test/ERC7498-MultiRedeem.t.sol | 95 +++----- test/ERC7498-RedemptionMintable.t.sol | 4 - test/ERC7498-Revert.t.sol | 225 ++++++++---------- test/ERC7498-SimpleRedeem.t.sol | 168 ++++++------- test/ERC7498.t.sol | 5 - test/RedeemableContractOfferer-1155.t.sol.txt | 36 +-- test/RedeemableContractOfferer-721.t.sol.txt | 58 ++--- ...RedeemableContractOfferer-Revert.t.sol.txt | 22 +- test/utils/BaseRedeemablesTest.sol | 33 ++- 26 files changed, 490 insertions(+), 522 deletions(-) create mode 100644 test/ERC7498-GetAndUpdateCampaign.t.sol diff --git a/.gitignore b/.gitignore index bf61470..6170378 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # Compiler files cache/ out/ +coverage/ +lcov.info # Ignores broadcast logs /broadcast diff --git a/script/DeployAndConfigure1155Receive.s.sol b/script/DeployAndConfigure1155Receive.s.sol index d96095b..8293eaa 100644 --- a/script/DeployAndConfigure1155Receive.s.sol +++ b/script/DeployAndConfigure1155Receive.s.sol @@ -5,7 +5,7 @@ import {Script} from "forge-std/Script.sol"; import {Test} from "forge-std/Test.sol"; import {ItemType} from "seaport-types/src/lib/ConsiderationEnums.sol"; import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; -import {CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; +import {Campaign, CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; import {BURN_ADDRESS} from "../src/lib/RedeemablesConstants.sol"; import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; import {ERC721OwnerMintable} from "../src/test/ERC721OwnerMintable.sol"; @@ -60,18 +60,18 @@ contract DeployAndConfigure1155Receive is Script, Test { requirements[0].consideration = consideration; CampaignParams memory params = CampaignParams({ - requirements: requirements, - signer: address(0), startTime: 0, endTime: 0, maxCampaignRedemptions: 1_000, - manager: msg.sender + manager: msg.sender, + signer: address(0) }); - receiveToken.createCampaign(params, "ipfs://QmQjubc6guHReNW5Es5ZrgDtJRwXk2Aia7BkVoLJGaCRqP"); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + receiveToken.createCampaign(campaign, "ipfs://QmQjubc6guHReNW5Es5ZrgDtJRwXk2Aia7BkVoLJGaCRqP"); // To test updateCampaign, update to proper start/end times. - params.startTime = uint32(block.timestamp); - params.endTime = uint32(block.timestamp + 1_000_000); - receiveToken.updateCampaign(1, params, ""); + campaign.params.startTime = uint32(block.timestamp); + campaign.params.endTime = uint32(block.timestamp + 1_000_000); + receiveToken.updateCampaign(1, campaign, ""); } } diff --git a/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol b/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol index 8ad813e..bdd0f92 100644 --- a/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol +++ b/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol @@ -5,7 +5,7 @@ import {Script} from "forge-std/Script.sol"; import {Test} from "forge-std/Test.sol"; import {ItemType} from "seaport-types/src/lib/ConsiderationEnums.sol"; import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; -import {CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; +import {Campaign, CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; import {BURN_ADDRESS} from "../src/lib/RedeemablesConstants.sol"; import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; import {ERC721OwnerMintable} from "../src/test/ERC721OwnerMintable.sol"; @@ -46,18 +46,21 @@ contract DeployAndRedeemTokens_CampaignOnReceiveToken is Script, Test { requirements[0].consideration = consideration; CampaignParams memory params = CampaignParams({ - requirements: requirements, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1_000_000), maxCampaignRedemptions: 1_000, - manager: msg.sender + manager: msg.sender, + signer: address(0) }); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); uint256 campaignId = - receiveToken.createCampaign(params, "ipfs://QmQKc93y2Ev5k9Kz54mCw48ZM487bwGDktZYPLtrjJ3r1d"); + receiveToken.createCampaign(campaign, "ipfs://QmbFxYgQMoBSUNFyW7WRWGaAWwJiRPM6HbK86aFkSJSq5N"); // Mint token 1 to redeem for token 1. redeemToken.mint(msg.sender, 1); + redeemToken.mint(msg.sender, 2); + redeemToken.mint(msg.sender, 3); + redeemToken.mint(msg.sender, 4); // Let's redeem them! uint256[] memory traitRedemptionTokenIds; diff --git a/script/DeployAndRedeemTokens.s.sol b/script/DeployAndRedeemTokens.s.sol index ace0719..68c00ed 100644 --- a/script/DeployAndRedeemTokens.s.sol +++ b/script/DeployAndRedeemTokens.s.sol @@ -5,7 +5,7 @@ import {Script} from "forge-std/Script.sol"; import {Test} from "forge-std/Test.sol"; import {ItemType} from "seaport-types/src/lib/ConsiderationEnums.sol"; import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; -import {CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; +import {Campaign, CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; import {BURN_ADDRESS} from "../src/lib/RedeemablesConstants.sol"; import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; import {ERC721ShipyardRedeemableOwnerMintable} from "../src/test/ERC721ShipyardRedeemableOwnerMintable.sol"; @@ -53,14 +53,14 @@ contract DeployAndRedeemTokens is Script, Test { requirements[0].consideration = consideration; CampaignParams memory params = CampaignParams({ - requirements: requirements, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1_000_000), maxCampaignRedemptions: 1_000, - manager: msg.sender + manager: msg.sender, + signer: address(0) }); - redeemToken.createCampaign(params, ""); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + redeemToken.createCampaign(campaign, ""); // Mint token 1 to redeem for token 1. redeemToken.mint(msg.sender, 1); diff --git a/script/DeployAndRedeemTrait.s.sol b/script/DeployAndRedeemTrait.s.sol index 673021d..d8b4a0d 100644 --- a/script/DeployAndRedeemTrait.s.sol +++ b/script/DeployAndRedeemTrait.s.sol @@ -6,7 +6,7 @@ import {Test} from "forge-std/Test.sol"; import {ItemType} from "seaport-types/src/lib/ConsiderationEnums.sol"; import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {IERC7496} from "shipyard-core/src/dynamic-traits/interfaces/IERC7496.sol"; -import {CampaignParams, CampaignRequirements, TraitRedemption} from "../src/lib/RedeemablesStructs.sol"; +import {Campaign, CampaignParams, CampaignRequirements, TraitRedemption} from "../src/lib/RedeemablesStructs.sol"; import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; import {ERC721ShipyardRedeemablePreapprovedTraitSetters} from @@ -76,14 +76,14 @@ contract DeployAndRedeemTrait is Script, Test { requirements[0].traitRedemptions = traitRedemptions; CampaignParams memory params = CampaignParams({ - requirements: requirements, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1_000_000), maxCampaignRedemptions: 1_000, - manager: msg.sender + manager: msg.sender, + signer: address(0) }); - receiveToken.createCampaign(params, ""); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + receiveToken.createCampaign(campaign, ""); // Mint token 1 to redeem for token 1. redeemToken.mint(msg.sender, 1); diff --git a/script/DeployERC721ReceiveTokenWithPredeployedSeadropRedeemToken.s.sol b/script/DeployERC721ReceiveTokenWithPredeployedSeadropRedeemToken.s.sol index 405d652..132ffd5 100644 --- a/script/DeployERC721ReceiveTokenWithPredeployedSeadropRedeemToken.s.sol +++ b/script/DeployERC721ReceiveTokenWithPredeployedSeadropRedeemToken.s.sol @@ -5,7 +5,7 @@ import {Script} from "forge-std/Script.sol"; import {Test} from "forge-std/Test.sol"; import {ItemType} from "seaport-types/src/lib/ConsiderationEnums.sol"; import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; -import {CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; +import {Campaign, CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; import {BURN_ADDRESS} from "../src/lib/RedeemablesConstants.sol"; import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; import {ERC721OwnerMintable} from "../src/test/ERC721OwnerMintable.sol"; @@ -47,15 +47,15 @@ contract DeployERC721ReceiveTokenWithPredeployedSeaDropRedeemToken is Script, Te requirements[0].consideration = consideration; CampaignParams memory params = CampaignParams({ - requirements: requirements, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1_000_000), maxCampaignRedemptions: 1_000, - manager: msg.sender + manager: msg.sender, + signer: address(0) }); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); uint256 campaignId = - receiveToken.createCampaign(params, "ipfs://QmQKc93y2Ev5k9Kz54mCw48ZM487bwGDktZYPLtrjJ3r1d"); + receiveToken.createCampaign(campaign, "ipfs://QmQKc93y2Ev5k9Kz54mCw48ZM487bwGDktZYPLtrjJ3r1d"); // redeemToken.setBaseURI( // "ipfs://QmYTSupCtriDLBHgPBBhZ98wYdp6N9S8jTL5sKSZwbASeT" diff --git a/src/ERC1155SeaDropRedeemable.sol b/src/ERC1155SeaDropRedeemable.sol index 020d9a4..8c039b2 100644 --- a/src/ERC1155SeaDropRedeemable.sol +++ b/src/ERC1155SeaDropRedeemable.sol @@ -6,20 +6,20 @@ import {ERC1155SeaDropContractOfferer} from "seadrop/src/lib/ERC1155SeaDropContr import {IERC7498} from "./interfaces/IERC7498.sol"; import {ERC7498NFTRedeemables} from "./lib/ERC7498NFTRedeemables.sol"; import {DynamicTraits} from "shipyard-core/src/dynamic-traits/DynamicTraits.sol"; -import {CampaignParams} from "./lib/RedeemablesStructs.sol"; +import {Campaign} from "./lib/RedeemablesStructs.sol"; contract ERC1155SeaDropRedeemable is ERC1155SeaDrop, ERC7498NFTRedeemables { constructor(address allowedConfigurer, address allowedSeaport, string memory _name, string memory _symbol) ERC1155SeaDrop(allowedConfigurer, allowedSeaport, _name, _symbol) {} - function createCampaign(CampaignParams calldata params, string calldata uri) + function createCampaign(Campaign calldata campaign, string calldata metadataURI) public override onlyOwner returns (uint256 campaignId) { - campaignId = ERC7498NFTRedeemables.createCampaign(params, uri); + campaignId = ERC7498NFTRedeemables.createCampaign(campaign, metadataURI); } function setTrait(uint256 tokenId, bytes32 traitKey, bytes32 value) public virtual override onlyOwner { diff --git a/src/ERC1155ShipyardRedeemable.sol b/src/ERC1155ShipyardRedeemable.sol index 14d8484..afaa9d6 100644 --- a/src/ERC1155ShipyardRedeemable.sol +++ b/src/ERC1155ShipyardRedeemable.sol @@ -5,18 +5,18 @@ import {ERC1155ShipyardContractMetadata} from "./lib/ERC1155ShipyardContractMeta import {Ownable} from "solady/src/auth/Ownable.sol"; import {ERC7498NFTRedeemables} from "./lib/ERC7498NFTRedeemables.sol"; import {DynamicTraits} from "shipyard-core/src/dynamic-traits/DynamicTraits.sol"; -import {CampaignParams} from "./lib/RedeemablesStructs.sol"; +import {Campaign} from "./lib/RedeemablesStructs.sol"; contract ERC1155ShipyardRedeemable is ERC1155ShipyardContractMetadata, ERC7498NFTRedeemables { constructor(string memory name_, string memory symbol_) ERC1155ShipyardContractMetadata(name_, symbol_) {} - function createCampaign(CampaignParams calldata params, string calldata uri_) + function createCampaign(Campaign calldata campaign, string calldata metadataURI) public override onlyOwner returns (uint256 campaignId) { - campaignId = ERC7498NFTRedeemables.createCampaign(params, uri_); + campaignId = ERC7498NFTRedeemables.createCampaign(campaign, metadataURI); } function setTrait(uint256 tokenId, bytes32 traitKey, bytes32 value) public virtual override onlyOwner { diff --git a/src/ERC721SeaDropRedeemable.sol b/src/ERC721SeaDropRedeemable.sol index e9755d4..0fb2235 100644 --- a/src/ERC721SeaDropRedeemable.sol +++ b/src/ERC721SeaDropRedeemable.sol @@ -6,7 +6,7 @@ import {ERC721SeaDropContractOfferer} from "seadrop/src/lib/ERC721SeaDropContrac import {IERC7498} from "./interfaces/IERC7498.sol"; import {ERC7498NFTRedeemables} from "./lib/ERC7498NFTRedeemables.sol"; import {DynamicTraits} from "shipyard-core/src/dynamic-traits/DynamicTraits.sol"; -import {CampaignParams} from "./lib/RedeemablesStructs.sol"; +import {Campaign} from "./lib/RedeemablesStructs.sol"; contract ERC721SeaDropRedeemable is ERC721SeaDrop, ERC7498NFTRedeemables { /// @dev Revert if the token does not exist. @@ -16,13 +16,13 @@ contract ERC721SeaDropRedeemable is ERC721SeaDrop, ERC7498NFTRedeemables { ERC721SeaDrop(allowedConfigurer, allowedSeaport, _name, _symbol) {} - function createCampaign(CampaignParams calldata params, string calldata uri) + function createCampaign(Campaign calldata campaign, string calldata metadataURI) public override onlyOwner returns (uint256 campaignId) { - campaignId = ERC7498NFTRedeemables.createCampaign(params, uri); + campaignId = ERC7498NFTRedeemables.createCampaign(campaign, metadataURI); } function setTrait(uint256 tokenId, bytes32 traitKey, bytes32 value) public virtual override onlyOwner { diff --git a/src/ERC721ShipyardRedeemable.sol b/src/ERC721ShipyardRedeemable.sol index 520f71c..746e39e 100644 --- a/src/ERC721ShipyardRedeemable.sol +++ b/src/ERC721ShipyardRedeemable.sol @@ -4,18 +4,18 @@ pragma solidity ^0.8.19; import {ERC721ShipyardContractMetadata} from "./lib/ERC721ShipyardContractMetadata.sol"; import {ERC7498NFTRedeemables} from "./lib/ERC7498NFTRedeemables.sol"; import {DynamicTraits} from "shipyard-core/src/dynamic-traits/DynamicTraits.sol"; -import {CampaignParams} from "./lib/RedeemablesStructs.sol"; +import {Campaign} from "./lib/RedeemablesStructs.sol"; contract ERC721ShipyardRedeemable is ERC721ShipyardContractMetadata, ERC7498NFTRedeemables { constructor(string memory name_, string memory symbol_) ERC721ShipyardContractMetadata(name_, symbol_) {} - function createCampaign(CampaignParams calldata params, string calldata uri) + function createCampaign(Campaign calldata campaign, string calldata metadataURI) public override onlyOwner returns (uint256 campaignId) { - campaignId = ERC7498NFTRedeemables.createCampaign(params, uri); + campaignId = ERC7498NFTRedeemables.createCampaign(campaign, metadataURI); } function setTrait(uint256 tokenId, bytes32 traitKey, bytes32 value) public virtual override onlyOwner { diff --git a/src/RedeemableContractOfferer.sol.txt b/src/RedeemableContractOfferer.sol.txt index ea3967d..800506f 100644 --- a/src/RedeemableContractOfferer.sol.txt +++ b/src/RedeemableContractOfferer.sol.txt @@ -62,7 +62,7 @@ contract RedeemableContractOfferer is _SEAPORT = seaport; } - function createCampaign(CampaignParams calldata params, string calldata uri) + function createCampaign(CampaignParams calldata params, string calldata metadataURI) external returns (uint256 campaignId) { @@ -88,7 +88,7 @@ contract RedeemableContractOfferer is emit CampaignUpdated(campaignId, params, _campaignURIs[campaignId]); } - function updateCampaign(uint256 campaignId, CampaignParams calldata params, string calldata uri) external { + function updateCampaign(uint256 campaignId, CampaignParams calldata params, string calldata metadataURI) external { // Revert if the campaign id is invalid. if (campaignId == 0 || campaignId >= _nextCampaignId) { revert InvalidCampaignId(); @@ -170,7 +170,7 @@ contract RedeemableContractOfferer is } } - function updateCampaignURI(uint256 campaignId, string calldata uri) external { + function updateCampaignURI(uint256 campaignId, string calldata metadataURI) external { CampaignParams storage params = _campaignParams[campaignId]; if (params.manager != msg.sender) revert NotManager(); diff --git a/src/interfaces/IERC7498.sol b/src/interfaces/IERC7498.sol index 9fbf9cf..839f465 100644 --- a/src/interfaces/IERC7498.sol +++ b/src/interfaces/IERC7498.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.19; import {OfferItem, ConsiderationItem, SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; -import {CampaignParams, TraitRedemption} from "../lib/RedeemablesStructs.sol"; +import {Campaign, TraitRedemption} from "../lib/RedeemablesStructs.sol"; interface IERC7498 { - event CampaignUpdated(uint256 indexed campaignId, CampaignParams params, string uri); + event CampaignUpdated(uint256 indexed campaignId, Campaign campaign, string metadataURI); event Redemption( uint256 indexed campaignId, uint256 requirementsIndex, @@ -15,16 +15,16 @@ interface IERC7498 { address redeemedBy ); - function createCampaign(CampaignParams calldata params, string calldata uri) + function createCampaign(Campaign calldata campaign, string calldata metadataURI) external returns (uint256 campaignId); - function updateCampaign(uint256 campaignId, CampaignParams calldata params, string calldata uri) external; + function updateCampaign(uint256 campaignId, Campaign calldata campaign, string calldata metadataURI) external; function getCampaign(uint256 campaignId) external view - returns (CampaignParams memory params, string memory uri, uint256 totalRedemptions); + returns (Campaign memory campaign, string memory metadataURI, uint256 totalRedemptions); function redeem(uint256[] calldata considerationTokenIds, address recipient, bytes calldata extraData) external diff --git a/src/interfaces/IRedeemableContractOfferer.sol b/src/interfaces/IRedeemableContractOfferer.sol index 4f44f75..956cbce 100644 --- a/src/interfaces/IRedeemableContractOfferer.sol +++ b/src/interfaces/IRedeemableContractOfferer.sol @@ -14,19 +14,19 @@ import {CampaignParams, TraitRedemption} from "../lib/RedeemablesStructs.sol"; interface IRedeemableContractOfferer { /* Events */ - event CampaignUpdated(uint256 indexed campaignId, CampaignParams params, string URI); + event CampaignUpdated(uint256 indexed campaignId, CampaignParams params, string metadataURI); event Redemption(uint256 indexed campaignId, bytes32 redemptionHash); /* Getters */ function getCampaign(uint256 campaignId) external view - returns (CampaignParams memory params, string memory uri, uint256 totalRedemptions); + returns (CampaignParams memory params, string memory metadataURI, uint256 totalRedemptions); /* Setters */ - function createCampaign(CampaignParams calldata params, string calldata uri) + function createCampaign(CampaignParams calldata params, string calldata metadataURI) external returns (uint256 campaignId); - function updateCampaign(uint256 campaignId, CampaignParams calldata params, string calldata uri) external; + function updateCampaign(uint256 campaignId, CampaignParams calldata params, string calldata metadataURI) external; } diff --git a/src/lib/ERC7498NFTRedeemables.sol b/src/lib/ERC7498NFTRedeemables.sol index acd41c6..8982886 100644 --- a/src/lib/ERC7498NFTRedeemables.sol +++ b/src/lib/ERC7498NFTRedeemables.sol @@ -15,18 +15,18 @@ import {IERC7496} from "shipyard-core/src/dynamic-traits/interfaces/IERC7496.sol import {IERC7498} from "../interfaces/IERC7498.sol"; import {IRedemptionMintable} from "../interfaces/IRedemptionMintable.sol"; import {RedeemablesErrors} from "./RedeemablesErrors.sol"; -import {CampaignParams, CampaignRequirements, TraitRedemption} from "./RedeemablesStructs.sol"; +import {Campaign, CampaignParams, CampaignRequirements, TraitRedemption} from "./RedeemablesStructs.sol"; import {BURN_ADDRESS} from "./RedeemablesConstants.sol"; contract ERC7498NFTRedeemables is IERC165, IERC7498, DynamicTraits, RedeemablesErrors { /// @dev Counter for next campaign id. uint256 private _nextCampaignId = 1; - /// @dev The campaign parameters by campaign id. - mapping(uint256 campaignId => CampaignParams params) private _campaignParams; + /// @dev The campaign by campaign id. + mapping(uint256 campaignId => Campaign campaign) private _campaigns; - /// @dev The campaign URIs by campaign id. - mapping(uint256 campaignId => string campaignURI) private _campaignURIs; + /// @dev The campaign metadata URI. + string private _campaignMetadataURI; /// @dev The total current redemptions by campaign id. mapping(uint256 campaignId => uint256 count) private _totalRedemptions; @@ -52,27 +52,21 @@ contract ERC7498NFTRedeemables is IERC165, IERC7498, DynamicTraits, RedeemablesE /*bytes memory signature */ ) = abi.decode(extraData, (uint256, uint256, bytes32, uint256[], uint256, bytes)); - // Get the campaign params. - CampaignParams storage params = _campaignParams[campaignId]; + // Get the campaign. + Campaign storage campaign = _campaigns[campaignId]; - // Validate the campaign time and total redemptions. - _validateRedemption(campaignId, params); - - // Increment totalRedemptions. - ++_totalRedemptions[campaignId]; - - // Get the campaign requirements. - if (requirementsIndex >= params.requirements.length) { + // Validate the requirements index is valid. + if (requirementsIndex >= campaign.requirements.length) { revert RequirementsIndexOutOfBounds(); } - // CampaignRequirements storage requirements = params.requirements[ - // requirementsIndex - // ]; + + // Validate the campaign time and total redemptions. + _validateRedemption(campaignId, campaign); // Process the redemption. _processRedemption( campaignId, - params.requirements[requirementsIndex], + campaign.requirements[requirementsIndex], considerationTokenIds, traitRedemptionTokenIds, recipient @@ -85,19 +79,21 @@ contract ERC7498NFTRedeemables is IERC165, IERC7498, DynamicTraits, RedeemablesE } function getCampaign(uint256 campaignId) - external + public view override - returns (CampaignParams memory params, string memory uri, uint256 totalRedemptions) + returns (Campaign memory campaign, string memory metadataURI, uint256 totalRedemptions) { // Revert if campaign id is invalid. - if (campaignId >= _nextCampaignId) revert InvalidCampaignId(); + if (campaignId == 0 || campaignId >= _nextCampaignId) { + revert InvalidCampaignId(); + } - // Get the campaign params. - params = _campaignParams[campaignId]; + // Get the campaign. + campaign = _campaigns[campaignId]; - // Get the campaign URI. - uri = _campaignURIs[campaignId]; + // Get the campaign metadata uri. + metadataURI = _campaignMetadataURI; // Get the total redemptions. totalRedemptions = _totalRedemptions[campaignId]; @@ -106,69 +102,74 @@ contract ERC7498NFTRedeemables is IERC165, IERC7498, DynamicTraits, RedeemablesE /** * @notice Create a new redeemable campaign. * @dev IMPORTANT: Override this method with access role restriction. - * @param params The campaign parameters. - * @param uri The campaign metadata URI. + * @param campaign The campaign. + * @param metadataURI The campaign metadata uri. */ - function createCampaign(CampaignParams calldata params, string calldata uri) + function createCampaign(Campaign calldata campaign, string calldata metadataURI) public virtual returns (uint256 campaignId) { // Validate the campaign params, reverts if invalid. - _validateCampaignParams(params); + _validateCampaign(campaign); // Set the campaignId and increment the next one. campaignId = _nextCampaignId; ++_nextCampaignId; // Set the campaign params. - _campaignParams[campaignId] = params; + _campaigns[campaignId] = campaign; - // Set the campaign URI. - _campaignURIs[campaignId] = uri; + // Set the campaign metadata uri if provided. + if (bytes(metadataURI).length != 0) { + _campaignMetadataURI = metadataURI; + } - emit CampaignUpdated(campaignId, params, uri); + emit CampaignUpdated(campaignId, campaign, _campaignMetadataURI); } - function updateCampaign(uint256 campaignId, CampaignParams calldata params, string calldata uri) external { + function updateCampaign(uint256 campaignId, Campaign calldata campaign, string calldata metadataURI) external { // Revert if the campaign id is invalid. if (campaignId == 0 || campaignId >= _nextCampaignId) { revert InvalidCampaignId(); } // Revert if msg.sender is not the manager. - address existingManager = _campaignParams[campaignId].manager; - if (params.manager != msg.sender && (existingManager != address(0) && existingManager != params.manager)) { + address existingManager = _campaigns[campaignId].params.manager; + if ( + campaign.params.manager != msg.sender + && (existingManager != address(0) && existingManager != campaign.params.manager) + ) { revert NotManager(); } // Validate the campaign params and revert if invalid. - _validateCampaignParams(params); + _validateCampaign(campaign); - // Set the campaign params. - _campaignParams[campaignId] = params; + // Set the campaign. + _campaigns[campaignId] = campaign; - // Update the campaign uri if it was provided. - if (bytes(uri).length != 0) { - _campaignURIs[campaignId] = uri; + // Update the campaign metadataURI if it was provided. + if (bytes(metadataURI).length != 0) { + _campaignMetadataURI = metadataURI; } - emit CampaignUpdated(campaignId, params, _campaignURIs[campaignId]); + emit CampaignUpdated(campaignId, campaign, _campaignMetadataURI); } - function _validateCampaignParams(CampaignParams memory params) internal pure { + function _validateCampaign(Campaign memory campaign) internal pure { // Revert if startTime is past endTime. - if (params.startTime > params.endTime) { + if (campaign.params.startTime > campaign.params.endTime) { revert InvalidTime(); } // Iterate over the requirements. - for (uint256 i = 0; i < params.requirements.length;) { - CampaignRequirements memory requirements = params.requirements[i]; + for (uint256 i = 0; i < campaign.requirements.length;) { + CampaignRequirements memory requirement = campaign.requirements[i]; // Validate each consideration item. - for (uint256 j = 0; j < requirements.consideration.length;) { - ConsiderationItem memory c = requirements.consideration[j]; + for (uint256 j = 0; j < requirement.consideration.length;) { + ConsiderationItem memory c = requirement.consideration[j]; // Revert if any of the consideration item recipients is the zero address. // 0xdead address should be used instead. @@ -185,26 +186,26 @@ contract ERC7498NFTRedeemables is IERC165, IERC7498, DynamicTraits, RedeemablesE if (c.startAmount != c.endAmount) { revert NonMatchingConsiderationItemAmounts(i, c.startAmount, c.endAmount); } - unchecked { ++j; } } - unchecked { ++i; } } } - function _validateRedemption(uint256 campaignId, CampaignParams memory params) internal view { - if (_isInactive(params.startTime, params.endTime)) { - revert NotActive_(block.timestamp, params.startTime, params.endTime); + function _validateRedemption(uint256 campaignId, Campaign storage campaign) internal view { + if (_isInactive(campaign.params.startTime, campaign.params.endTime)) { + revert NotActive_(block.timestamp, campaign.params.startTime, campaign.params.endTime); } // Revert if max total redemptions would be exceeded. - if (_totalRedemptions[campaignId] + 1 > params.maxCampaignRedemptions) { - revert MaxCampaignRedemptionsReached(_totalRedemptions[campaignId] + 1, params.maxCampaignRedemptions); + if (_totalRedemptions[campaignId] + 1 > campaign.params.maxCampaignRedemptions) { + revert MaxCampaignRedemptionsReached( + _totalRedemptions[campaignId] + 1, campaign.params.maxCampaignRedemptions + ); } } @@ -297,6 +298,9 @@ contract ERC7498NFTRedeemables is IERC165, IERC7498, DynamicTraits, RedeemablesE uint256[] memory traitRedemptionTokenIds, address recipient ) internal { + // Increment the campaign's total redemptions. + ++_totalRedemptions[campaignId]; + if (requirements.traitRedemptions.length > 0) { // Process the trait redemptions. _processTraitRedemptions(requirements.traitRedemptions, traitRedemptionTokenIds); diff --git a/src/lib/RedeemablesStructs.sol b/src/lib/RedeemablesStructs.sol index 942a77a..0c18d8d 100644 --- a/src/lib/RedeemablesStructs.sol +++ b/src/lib/RedeemablesStructs.sol @@ -3,13 +3,17 @@ pragma solidity ^0.8.19; import {OfferItem, ConsiderationItem, SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; +struct Campaign { + CampaignParams params; + CampaignRequirements[] requirements; +} + struct CampaignParams { uint32 startTime; uint32 endTime; uint32 maxCampaignRedemptions; address manager; address signer; - CampaignRequirements[] requirements; } struct CampaignRequirements { diff --git a/test/ERC7498-DynamicTraits.t.sol b/test/ERC7498-DynamicTraits.t.sol index 73bd007..8f856f6 100644 --- a/test/ERC7498-DynamicTraits.t.sol +++ b/test/ERC7498-DynamicTraits.t.sol @@ -3,27 +3,19 @@ pragma solidity ^0.8.19; import {BaseRedeemablesTest} from "./utils/BaseRedeemablesTest.sol"; import {Solarray} from "solarray/Solarray.sol"; -import {ERC721} from "solady/src/tokens/ERC721.sol"; -import {IERC165} from "openzeppelin-contracts/contracts/interfaces/IERC165.sol"; -import {TestERC20} from "./utils/mocks/TestERC20.sol"; -import {TestERC721} from "./utils/mocks/TestERC721.sol"; -import {TestERC1155} from "./utils/mocks/TestERC1155.sol"; -import {IERC721A} from "seadrop/lib/ERC721A/contracts/IERC721A.sol"; -import {IERC721} from "openzeppelin-contracts/contracts/interfaces/IERC721.sol"; -import {IERC1155} from "openzeppelin-contracts/contracts/interfaces/IERC1155.sol"; import {IERC7496} from "shipyard-core/src/dynamic-traits/interfaces/IERC7496.sol"; -import {IERC7498} from "../src/interfaces/IERC7498.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 {IERC7498} from "../src/interfaces/IERC7498.sol"; +import {Campaign, CampaignParams, CampaignRequirements, TraitRedemption} from "../src/lib/RedeemablesStructs.sol"; import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; +import {ERC1155ShipyardRedeemableMintable} from "../src/extensions/ERC1155ShipyardRedeemableMintable.sol"; import {ERC721ShipyardRedeemableOwnerMintable} from "../src/test/ERC721ShipyardRedeemableOwnerMintable.sol"; import {ERC1155ShipyardRedeemableOwnerMintable} from "../src/test/ERC1155ShipyardRedeemableOwnerMintable.sol"; import {ERC721ShipyardRedeemablePreapprovedTraitSetters} from "../src/test/ERC721ShipyardRedeemablePreapprovedTraitSetters.sol"; -import {ERC1155ShipyardRedeemableMintable} from "../src/extensions/ERC1155ShipyardRedeemableMintable.sol"; contract ERC7498_DynamicTraits is BaseRedeemablesTest { using OfferItemLib for OfferItem; @@ -32,19 +24,7 @@ contract ERC7498_DynamicTraits is BaseRedeemablesTest { using ConsiderationItemLib for ConsiderationItem[]; uint256 tokenId = 2; - - event Redemption( - uint256 indexed campaignId, - uint256 requirementsIndex, - bytes32 redemptionHash, - uint256[] considerationTokenIds, - uint256[] traitRedemptionTokenIds, - address redeemedBy - ); - - function setUp() public virtual override { - super.setUp(); - } + bytes32 traitKey = bytes32("hasRedeemed"); function testErc721TraitRedemptionForErc721() public { for (uint256 i; i < erc7498Tokens.length; i++) { @@ -58,20 +38,14 @@ contract ERC7498_DynamicTraits is BaseRedeemablesTest { function erc721TraitRedemptionSubstandardOneForErc721(RedeemablesContext memory context) public { address[] memory allowedTraitSetters = new address[](1); allowedTraitSetters[0] = address(context.erc7498Token); - ERC721ShipyardRedeemablePreapprovedTraitSetters redeemToken = new ERC721ShipyardRedeemablePreapprovedTraitSetters( "", "", allowedTraitSetters ); - redeemToken.mint(address(this), tokenId); - + _mintToken(address(redeemToken), tokenId); TraitRedemption[] memory traitRedemptions = new TraitRedemption[](1); - - // trait key is "hasRedeemed" - bytes32 traitKey = bytes32("hasRedeemed"); - // previous trait value (`substandardValue`) should be 0 // new trait value should be 1 traitRedemptions[0] = TraitRedemption({ @@ -81,45 +55,42 @@ contract ERC7498_DynamicTraits is BaseRedeemablesTest { traitValue: bytes32(uint256(1)), substandardValue: bytes32(uint256(0)) }); - CampaignRequirements[] memory requirements = new CampaignRequirements[]( 1 ); - // consideration is empty ConsiderationItem[] memory consideration = new ConsiderationItem[](0); - requirements[0] = CampaignRequirements({ offer: defaultCampaignOffer, consideration: consideration, traitRedemptions: traitRedemptions }); - CampaignParams memory params = CampaignParams({ - requirements: requirements, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1000), maxCampaignRedemptions: 5, - manager: address(this) + manager: address(this), + signer: address(0) }); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + context.erc7498Token.createCampaign(campaign, ""); - context.erc7498Token.createCampaign(params, ""); - - uint256[] memory considerationTokenIds = new uint256[](0); + uint256[] memory considerationTokenIds; uint256[] memory traitRedemptionTokenIds = Solarray.uint256s(tokenId); - - // campaignId: 1 - // requirementsIndex: 0 - bytes memory extraData = abi.encode(1, 0, bytes32(0), traitRedemptionTokenIds, uint256(0), bytes("")); - + bytes memory extraData = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + traitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); vm.expectEmit(true, true, true, true); emit Redemption(1, 0, bytes32(0), considerationTokenIds, traitRedemptionTokenIds, address(this)); - context.erc7498Token.redeem(considerationTokenIds, address(this), extraData); - bytes32 actualTraitValue = IERC7496(address(redeemToken)).getTraitValue(tokenId, traitKey); + bytes32 actualTraitValue = redeemToken.getTraitValue(tokenId, traitKey); assertEq(bytes32(uint256(1)), actualTraitValue); - assertEq(IERC721(address(receiveToken721)).ownerOf(1), address(this)); + assertEq(receiveToken721.ownerOf(1), address(this)); } } diff --git a/test/ERC7498-GetAndUpdateCampaign.t.sol b/test/ERC7498-GetAndUpdateCampaign.t.sol new file mode 100644 index 0000000..e8b1305 --- /dev/null +++ b/test/ERC7498-GetAndUpdateCampaign.t.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {BaseRedeemablesTest} from "./utils/BaseRedeemablesTest.sol"; +import {Solarray} from "solarray/Solarray.sol"; +import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {OfferItemLib} from "seaport-sol/src/lib/OfferItemLib.sol"; +import {ConsiderationItemLib} from "seaport-sol/src/lib/ConsiderationItemLib.sol"; +import {IERC7498} from "../src/interfaces/IERC7498.sol"; +import {Campaign, CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; +import {RedeemablesErrors} from "../src/lib/RedeemablesErrors.sol"; + +contract ERC7498_GetAndUpdateCampaign is BaseRedeemablesTest { + using OfferItemLib for OfferItem; + using OfferItemLib for OfferItem[]; + using ConsiderationItemLib for ConsiderationItem; + using ConsiderationItemLib for ConsiderationItem[]; + + function testGetAndUpdateCampaign() public { + for (uint256 i; i < erc7498Tokens.length; i++) { + testRedeemable(this.getAndUpdateCampaign, RedeemablesContext({erc7498Token: IERC7498(erc7498Tokens[i])})); + } + } + + function getAndUpdateCampaign(RedeemablesContext memory context) external { + // Should revert if the campaign does not exist. + for (uint256 i = 0; i < 3; i++) { + vm.expectRevert(InvalidCampaignId.selector); + context.erc7498Token.getCampaign(i); + } + + ConsiderationItem[] memory consideration = new ConsiderationItem[](1); + consideration[0] = _getCampaignConsiderationItem(address(context.erc7498Token)); + CampaignRequirements[] memory requirements = new CampaignRequirements[](1); + requirements[0] = CampaignRequirements({ + offer: defaultCampaignOffer, + consideration: consideration, + traitRedemptions: defaultTraitRedemptions + }); + CampaignParams memory params = CampaignParams({ + startTime: uint32(block.timestamp), + endTime: uint32(block.timestamp + 1000), + maxCampaignRedemptions: 5, + manager: address(this), + signer: address(0) + }); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + uint256 campaignId = context.erc7498Token.createCampaign(campaign, "test123"); + + (Campaign memory gotCampaign, string memory metadataURI, uint256 totalRedemptions) = + IERC7498(context.erc7498Token).getCampaign(campaignId); + assertEq(keccak256(abi.encode(gotCampaign)), keccak256(abi.encode(campaign))); + assertEq(metadataURI, "test123"); + assertEq(totalRedemptions, 0); + + // Should revert if the campaign does not exist. + vm.expectRevert(InvalidCampaignId.selector); + context.erc7498Token.getCampaign(campaignId + 1); + + // Should revert if trying to get campaign id 0, since it starts at 1. + vm.expectRevert(InvalidCampaignId.selector); + context.erc7498Token.getCampaign(0); + } +} diff --git a/test/ERC7498-MultiRedeem.t.sol b/test/ERC7498-MultiRedeem.t.sol index 168f57f..18cc513 100644 --- a/test/ERC7498-MultiRedeem.t.sol +++ b/test/ERC7498-MultiRedeem.t.sol @@ -4,23 +4,16 @@ pragma solidity ^0.8.19; 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 {IERC165} from "openzeppelin-contracts/contracts/interfaces/IERC165.sol"; -import {IERC721A} from "seadrop/lib/ERC721A/contracts/IERC721A.sol"; -import {IERC721} from "openzeppelin-contracts/contracts/interfaces/IERC721.sol"; -import {IERC1155} from "openzeppelin-contracts/contracts/interfaces/IERC1155.sol"; -import {IERC7498} from "../src/interfaces/IERC7498.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 {IERC7498} from "../src/interfaces/IERC7498.sol"; +import {Campaign, CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; +import {ERC1155ShipyardRedeemableMintable} from "../src/extensions/ERC1155ShipyardRedeemableMintable.sol"; import {ERC721ShipyardRedeemableOwnerMintable} from "../src/test/ERC721ShipyardRedeemableOwnerMintable.sol"; import {ERC1155ShipyardRedeemableOwnerMintable} from "../src/test/ERC1155ShipyardRedeemableOwnerMintable.sol"; -import {ERC1155ShipyardRedeemableMintable} from "../src/extensions/ERC1155ShipyardRedeemableMintable.sol"; contract ERC7498_MultiRedeem is BaseRedeemablesTest { using OfferItemLib for OfferItem; @@ -30,19 +23,6 @@ contract ERC7498_MultiRedeem is BaseRedeemablesTest { uint256 tokenId = 2; - event Redemption( - uint256 indexed campaignId, - uint256 requirementsIndex, - bytes32 redemptionHash, - uint256[] considerationTokenIds, - uint256[] traitRedemptionTokenIds, - address redeemedBy - ); - - function setUp() public virtual override { - super.setUp(); - } - function testBurnMultiErc721OrErc1155RedeemSingleErc721() public { for (uint256 i; i < erc7498Tokens.length; i++) { testRedeemable( @@ -54,39 +34,34 @@ contract ERC7498_MultiRedeem is BaseRedeemablesTest { function burnMultiErc721OrErc1155RedeemSingleErc721(RedeemablesContext memory context) public { address secondRedeemTokenAddress; + _mintToken(address(context.erc7498Token), tokenId); if (_isERC721(address(context.erc7498Token))) { - ERC721ShipyardRedeemableOwnerMintable(address(context.erc7498Token)).mint(address(this), tokenId); - ERC721ShipyardRedeemableOwnerMintable secondRedeemToken721 = new ERC721ShipyardRedeemableOwnerMintable( "", "" ); secondRedeemTokenAddress = address(secondRedeemToken721); - secondRedeemToken721.mint(address(this), tokenId); - - vm.label(address(secondRedeemToken721), "secondRedeemToken721"); + vm.label(secondRedeemTokenAddress, "secondRedeemToken721"); secondRedeemToken721.setApprovalForAll(address(context.erc7498Token), true); } else { - ERC1155ShipyardRedeemableOwnerMintable(address(context.erc7498Token)).mint(address(this), tokenId, 1); ERC1155ShipyardRedeemableOwnerMintable secondRedeemToken1155 = new ERC1155ShipyardRedeemableOwnerMintable( "", "" ); secondRedeemTokenAddress = address(secondRedeemToken1155); - secondRedeemToken1155.mint(address(this), tokenId, 1); + vm.label(secondRedeemTokenAddress, "secondRedeemToken1155"); secondRedeemToken1155.setApprovalForAll(address(context.erc7498Token), true); } + _mintToken(secondRedeemTokenAddress, tokenId); ERC721RedemptionMintable receiveToken = new ERC721RedemptionMintable( "", "", erc7498Tokens ); - ConsiderationItem[] memory consideration = new ConsiderationItem[](2); consideration[0] = _getCampaignConsiderationItem(address(context.erc7498Token)); consideration[1] = _getCampaignConsiderationItem(secondRedeemTokenAddress); - CampaignRequirements[] memory requirements = new CampaignRequirements[]( 1 ); @@ -94,30 +69,29 @@ contract ERC7498_MultiRedeem is BaseRedeemablesTest { offer[0] = defaultCampaignOffer[0].withToken(address(receiveToken)); requirements[0].offer = offer; 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) + manager: address(this), + signer: address(0) }); - - IERC7498(context.erc7498Token).createCampaign(params, ""); - // campaignId: 1 - // requirementsIndex: 0 - bytes memory extraData = abi.encode(1, 0, bytes32(0), defaultTraitRedemptionTokenIds, uint256(0), bytes("")); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + context.erc7498Token.createCampaign(campaign, ""); + bytes memory extraData = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + defaultTraitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); consideration[0].identifierOrCriteria = tokenId; - uint256[] memory tokenIds = Solarray.uint256s(tokenId, tokenId); - - IERC7498(context.erc7498Token).redeem(tokenIds, address(this), extraData); + context.erc7498Token.redeem(tokenIds, address(this), extraData); _checkTokenDoesNotExist(address(context.erc7498Token), tokenId); - _checkTokenSentToBurnAddress(secondRedeemTokenAddress, tokenId); - assertEq(receiveToken.ownerOf(1), address(this)); assertEq(receiveToken.balanceOf(address(this)), 1); } @@ -133,13 +107,11 @@ contract ERC7498_MultiRedeem is BaseRedeemablesTest { function burnOneErc721OrErc1155RedeemMultiErc1155(RedeemablesContext memory context) public { _mintToken(address(context.erc7498Token), tokenId); - ERC1155ShipyardRedeemableMintable receiveToken = new ERC1155ShipyardRedeemableMintable( "", "" ); ERC721(address(context.erc7498Token)).setApprovalForAll(address(receiveToken), true); - OfferItem[] memory offer = new OfferItem[](3); offer[0] = OfferItem({ itemType: ItemType.ERC1155_WITH_CRITERIA, @@ -162,37 +134,36 @@ contract ERC7498_MultiRedeem is BaseRedeemablesTest { startAmount: 1, endAmount: 1 }); - ConsiderationItem[] memory consideration = new ConsiderationItem[](1); consideration[0] = _getCampaignConsiderationItem(address(context.erc7498Token)); - CampaignRequirements[] memory requirements = new CampaignRequirements[]( 1 ); requirements[0].offer = offer; 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) + manager: address(this), + signer: address(0) }); - - IERC7498(receiveToken).createCampaign(params, ""); - // campaignId: 1 - // requirementsIndex: 0 - bytes memory extraData = abi.encode(1, 0, bytes32(0), defaultTraitRedemptionTokenIds, uint256(0), bytes("")); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + IERC7498(receiveToken).createCampaign(campaign, ""); + + bytes memory extraData = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + defaultTraitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); consideration[0].identifierOrCriteria = tokenId; - uint256[] memory tokenIds = Solarray.uint256s(tokenId); - IERC7498(receiveToken).redeem(tokenIds, address(this), extraData); _checkTokenDoesNotExist(address(context.erc7498Token), tokenId); - assertEq(receiveToken.balanceOf(address(this), 1), 1); assertEq(receiveToken.balanceOf(address(this), 2), 1); assertEq(receiveToken.balanceOf(address(this), 3), 1); diff --git a/test/ERC7498-RedemptionMintable.t.sol b/test/ERC7498-RedemptionMintable.t.sol index 0b17efb..57de6c6 100644 --- a/test/ERC7498-RedemptionMintable.t.sol +++ b/test/ERC7498-RedemptionMintable.t.sol @@ -13,10 +13,6 @@ contract TestERC7498_RedemptionMintable is BaseRedeemablesTest { using ConsiderationItemLib for ConsiderationItem; using ConsiderationItemLib for ConsiderationItem[]; - function setUp() public virtual override { - super.setUp(); - } - function testSupportsInterfaceId() public { assertTrue(receiveToken721.supportsInterface(type(IRedemptionMintable).interfaceId)); assertTrue(receiveToken1155.supportsInterface(type(IRedemptionMintable).interfaceId)); diff --git a/test/ERC7498-Revert.t.sol b/test/ERC7498-Revert.t.sol index 3ffa147..023d4be 100644 --- a/test/ERC7498-Revert.t.sol +++ b/test/ERC7498-Revert.t.sol @@ -6,15 +6,12 @@ import {Solarray} from "solarray/Solarray.sol"; import {ERC721} from "solady/src/tokens/ERC721.sol"; import {IERC7498} from "../src/interfaces/IERC7498.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 {BURN_ADDRESS} from "../src/lib/RedeemablesConstants.sol"; -import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; +import {Campaign, CampaignParams, CampaignRequirements, TraitRedemption} from "../src/lib/RedeemablesStructs.sol"; import {ERC721ShipyardRedeemableOwnerMintable} from "../src/test/ERC721ShipyardRedeemableOwnerMintable.sol"; contract ERC7498_Revert is BaseRedeemablesTest { @@ -25,48 +22,36 @@ contract ERC7498_Revert is BaseRedeemablesTest { uint256 tokenId = 2; - event Redemption( - uint256 indexed campaignId, - uint256 requirementsIndex, - bytes32 redemptionHash, - uint256[] considerationTokenIds, - uint256[] traitRedemptionTokenIds, - address redeemedBy - ); - - function setUp() public virtual override { - super.setUp(); - } - function testRevert721ConsiderationItemInsufficientBalance() public { - ERC721ShipyardRedeemableOwnerMintable(erc7498Tokens[0]).mint(address(this), tokenId); - + _mintToken(erc7498Tokens[0], tokenId); uint256 invalidTokenId = tokenId + 1; - ERC721ShipyardRedeemableOwnerMintable(erc7498Tokens[0]).mint(dillon.addr, invalidTokenId); + _mintToken(erc7498Tokens[0], invalidTokenId, dillon.addr); 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) + manager: address(this), + signer: address(0) }); - - IERC7498(erc7498Tokens[0]).createCampaign(params, ""); - // campaignId: 1 - // requirementsIndex: 0 - bytes memory extraData = abi.encode(1, 0, bytes32(0), defaultTraitRedemptionTokenIds, uint256(0), bytes("")); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + IERC7498(erc7498Tokens[0]).createCampaign(campaign, ""); + bytes memory extraData = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + defaultTraitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); uint256[] memory tokenIds = Solarray.uint256s(invalidTokenId); vm.expectRevert( @@ -80,19 +65,16 @@ contract ERC7498_Revert is BaseRedeemablesTest { IERC7498(erc7498Tokens[0]).redeem(tokenIds, address(this), extraData); assertEq(ERC721(erc7498Tokens[0]).ownerOf(tokenId), address(this)); - vm.expectRevert(ERC721.TokenDoesNotExist.selector); receiveToken721.ownerOf(1); } function testRevertConsiderationLengthNotMet() public { - ERC721ShipyardRedeemableOwnerMintable(erc7498Tokens[0]).mint(address(this), tokenId); - + _mintToken(erc7498Tokens[0], tokenId); ERC721ShipyardRedeemableOwnerMintable secondRedeemToken = new ERC721ShipyardRedeemableOwnerMintable( "", "" ); - ConsiderationItem[] memory consideration = new ConsiderationItem[](2); consideration[0] = ConsiderationItem({ itemType: ItemType.ERC721_WITH_CRITERIA, @@ -110,44 +92,42 @@ contract ERC7498_Revert is BaseRedeemablesTest { endAmount: 1, recipient: payable(BURN_ADDRESS) }); - CampaignRequirements[] memory requirements = new CampaignRequirements[]( 1 ); 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) + manager: address(this), + signer: address(0) }); - - IERC7498(erc7498Tokens[0]).createCampaign(params, ""); - - // campaignId: 1 - // requirementsIndex: 0 - bytes memory extraData = abi.encode(1, 0, bytes32(0), defaultTraitRedemptionTokenIds, uint256(0), bytes("")); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + IERC7498(erc7498Tokens[0]).createCampaign(campaign, ""); + + bytes memory extraData = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + defaultTraitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); consideration[0].identifierOrCriteria = tokenId; - uint256[] memory tokenIds = Solarray.uint256s(tokenId); vm.expectRevert(abi.encodeWithSelector(ConsiderationTokenIdsDontMatchConsiderationLength.selector, 2, 1)); - IERC7498(erc7498Tokens[0]).redeem(tokenIds, address(this), extraData); assertEq(ERC721(erc7498Tokens[0]).ownerOf(tokenId), address(this)); - vm.expectRevert(ERC721.TokenDoesNotExist.selector); receiveToken721.ownerOf(1); } function testRevertInvalidTxValue() public { - ERC721ShipyardRedeemableOwnerMintable(erc7498Tokens[0]).mint(address(this), tokenId); - + _mintToken(erc7498Tokens[0], tokenId); OfferItem[] memory offer = new OfferItem[](1); offer[0] = OfferItem({ itemType: ItemType.ERC721_WITH_CRITERIA, @@ -156,7 +136,6 @@ contract ERC7498_Revert is BaseRedeemablesTest { startAmount: 1, endAmount: 1 }); - ConsiderationItem[] memory consideration = new ConsiderationItem[](2); consideration[0] = ConsiderationItem({ itemType: ItemType.ERC721_WITH_CRITERIA, @@ -174,46 +153,43 @@ contract ERC7498_Revert is BaseRedeemablesTest { endAmount: 0.1 ether, recipient: payable(dillon.addr) }); - CampaignRequirements[] memory requirements = new CampaignRequirements[]( 1 ); requirements[0].offer = offer; 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) + manager: address(this), + signer: address(0) }); - - IERC7498(erc7498Tokens[0]).createCampaign(params, ""); - - // campaignId: 1 - // requirementsIndex: 0 - bytes memory extraData = abi.encode(1, 0, bytes32(0), defaultTraitRedemptionTokenIds, uint256(0), bytes("")); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + IERC7498(erc7498Tokens[0]).createCampaign(campaign, ""); + + bytes memory extraData = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + defaultTraitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); consideration[0].identifierOrCriteria = tokenId; - uint256[] memory considerationTokenIds = Solarray.uint256s(tokenId, 0); vm.expectRevert(abi.encodeWithSelector(InvalidTxValue.selector, 0.05 ether, 0.1 ether)); IERC7498(erc7498Tokens[0]).redeem{value: 0.05 ether}(considerationTokenIds, address(this), extraData); - vm.expectRevert(ERC721.TokenDoesNotExist.selector); receiveToken721.ownerOf(1); - assertEq(ERC721(erc7498Tokens[0]).ownerOf(tokenId), address(this)); } function testRevertErc20ConsiderationItemInsufficientBalance() public { - ERC721ShipyardRedeemableOwnerMintable(erc7498Tokens[0]).mint(address(this), tokenId); - + _mintToken(erc7498Tokens[0], tokenId); TestERC20 redeemErc20 = new TestERC20(); redeemErc20.mint(address(this), 0.05 ether); - OfferItem[] memory offer = new OfferItem[](1); offer[0] = OfferItem({ itemType: ItemType.ERC721_WITH_CRITERIA, @@ -222,7 +198,6 @@ contract ERC7498_Revert is BaseRedeemablesTest { startAmount: 1, endAmount: 1 }); - ConsiderationItem[] memory consideration = new ConsiderationItem[](2); consideration[0] = ConsiderationItem({ itemType: ItemType.ERC721_WITH_CRITERIA, @@ -240,143 +215,131 @@ contract ERC7498_Revert is BaseRedeemablesTest { endAmount: 0.1 ether, recipient: payable(dillon.addr) }); - CampaignRequirements[] memory requirements = new CampaignRequirements[]( 1 ); requirements[0].offer = offer; 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) - }); - - IERC7498(erc7498Tokens[0]).createCampaign(params, ""); - } - - // campaignId: 1 - // requirementsIndex: 0 - bytes memory extraData = abi.encode(1, 0, bytes32(0), defaultTraitRedemptionTokenIds, uint256(0), bytes("")); + CampaignParams memory params = CampaignParams({ + startTime: uint32(block.timestamp), + endTime: uint32(block.timestamp + 1000), + maxCampaignRedemptions: 5, + manager: address(this), + signer: address(0) + }); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + IERC7498(erc7498Tokens[0]).createCampaign(campaign, ""); + + bytes memory extraData = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + defaultTraitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); consideration[0].identifierOrCriteria = tokenId; - uint256[] memory considerationTokenIds = Solarray.uint256s(tokenId, 0); - vm.expectRevert( abi.encodeWithSelector( ConsiderationItemInsufficientBalance.selector, address(redeemErc20), 0.05 ether, 0.1 ether ) ); IERC7498(erc7498Tokens[0]).redeem(considerationTokenIds, address(this), extraData); - vm.expectRevert(ERC721.TokenDoesNotExist.selector); receiveToken721.ownerOf(1); - assertEq(ERC721(erc7498Tokens[0]).ownerOf(tokenId), address(this)); } function testRevertErc721InvalidConsiderationTokenIdSupplied() public { uint256 considerationTokenId = 1; - ERC721ShipyardRedeemableOwnerMintable(erc7498Tokens[0]).mint(address(this), tokenId); - ERC721ShipyardRedeemableOwnerMintable(erc7498Tokens[0]).mint(address(this), considerationTokenId); - + _mintToken(erc7498Tokens[0], tokenId); + _mintToken(erc7498Tokens[0], considerationTokenId); 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) + manager: address(this), + signer: address(0) }); - - IERC7498(erc7498Tokens[0]).createCampaign(params, ""); - - // campaignId: 1 - // requirementsIndex: 0 - bytes memory extraData = abi.encode(1, 0, bytes32(0), defaultTraitRedemptionTokenIds, uint256(0), bytes("")); - + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + IERC7498(erc7498Tokens[0]).createCampaign(campaign, ""); + + bytes memory extraData = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + defaultTraitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); uint256[] memory considerationTokenIds = Solarray.uint256s(tokenId); - vm.expectRevert( abi.encodeWithSelector( InvalidConsiderationTokenIdSupplied.selector, address(erc7498Tokens[0]), tokenId, considerationTokenId ) ); IERC7498(erc7498Tokens[0]).redeem(considerationTokenIds, address(this), extraData); - vm.expectRevert(ERC721.TokenDoesNotExist.selector); receiveToken721.ownerOf(1); - assertEq(ERC721(erc7498Tokens[0]).ownerOf(tokenId), address(this)); assertEq(ERC721(erc7498Tokens[0]).ownerOf(considerationTokenId), address(this)); } function testRevertErc1155InvalidConsiderationTokenIdSupplied() public { uint256 considerationTokenId = 1; - erc1155s[0].mint(address(this), tokenId, 1 ether); - erc1155s[0].mint(address(this), considerationTokenId, 1 ether); - + _mintToken(address(erc1155s[0]), tokenId); + _mintToken(address(erc1155s[0]), considerationTokenId); CampaignRequirements[] memory requirements = new CampaignRequirements[]( 1 ); - ConsiderationItem[] memory consideration = new ConsiderationItem[](1); consideration[0] = defaultCampaignConsideration[0].withToken(address(erc1155s[0])).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) + manager: address(this), + signer: address(0) }); - - IERC7498(erc7498Tokens[0]).createCampaign(params, ""); - - // campaignId: 1 - // requirementsIndex: 0 - bytes memory extraData = abi.encode(1, 0, bytes32(0), defaultTraitRedemptionTokenIds, uint256(0), bytes("")); - + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + IERC7498(erc7498Tokens[0]).createCampaign(campaign, ""); + + bytes memory extraData = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + defaultTraitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); uint256[] memory considerationTokenIds = Solarray.uint256s(tokenId); - vm.expectRevert( abi.encodeWithSelector( InvalidConsiderationTokenIdSupplied.selector, address(erc1155s[0]), tokenId, considerationTokenId ) ); IERC7498(erc7498Tokens[0]).redeem(considerationTokenIds, address(this), extraData); - vm.expectRevert(ERC721.TokenDoesNotExist.selector); receiveToken721.ownerOf(1); - - assertEq(erc1155s[0].balanceOf(address(this), tokenId), 1 ether); - assertEq(erc1155s[0].balanceOf(address(this), considerationTokenId), 1 ether); + assertEq(erc1155s[0].balanceOf(address(this), tokenId), 1); + assertEq(erc1155s[0].balanceOf(address(this), considerationTokenId), 1); } } diff --git a/test/ERC7498-SimpleRedeem.t.sol b/test/ERC7498-SimpleRedeem.t.sol index 49ca69c..2a27c80 100644 --- a/test/ERC7498-SimpleRedeem.t.sol +++ b/test/ERC7498-SimpleRedeem.t.sol @@ -4,24 +4,15 @@ pragma solidity ^0.8.19; import {BaseRedeemablesTest} from "./utils/BaseRedeemablesTest.sol"; import {Solarray} from "solarray/Solarray.sol"; import {ERC721} from "solady/src/tokens/ERC721.sol"; -import {IERC165} from "openzeppelin-contracts/contracts/interfaces/IERC165.sol"; -import {IERC721A} from "seadrop/lib/ERC721A/contracts/IERC721A.sol"; import {IERC721} from "openzeppelin-contracts/contracts/interfaces/IERC721.sol"; -import {IERC1155} from "openzeppelin-contracts/contracts/interfaces/IERC1155.sol"; -import {IERC7498} from "../src/interfaces/IERC7498.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 {IERC7498} from "../src/interfaces/IERC7498.sol"; import {BURN_ADDRESS} from "../src/lib/RedeemablesConstants.sol"; -import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; +import {Campaign, CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; import {ERC721ShipyardRedeemableOwnerMintable} from "../src/test/ERC721ShipyardRedeemableOwnerMintable.sol"; -import {ERC1155ShipyardRedeemableOwnerMintable} from "../src/test/ERC1155ShipyardRedeemableOwnerMintable.sol"; -import {IERC7498} from "../src/interfaces/IERC7498.sol"; contract ERC7498_SimpleRedeem is BaseRedeemablesTest { using OfferItemLib for OfferItem; @@ -31,19 +22,6 @@ contract ERC7498_SimpleRedeem is BaseRedeemablesTest { uint256 tokenId = 2; - event Redemption( - uint256 indexed campaignId, - uint256 requirementsIndex, - bytes32 redemptionHash, - uint256[] considerationTokenIds, - uint256[] traitRedemptionTokenIds, - address redeemedBy - ); - - function setUp() public virtual override { - super.setUp(); - } - function testBurnErc721OrErc1155RedeemErc721() public { for (uint256 i; i < erc7498Tokens.length; i++) { testRedeemable( @@ -54,44 +32,47 @@ contract ERC7498_SimpleRedeem is BaseRedeemablesTest { function burnErc721OrErc1155RedeemErc721(RedeemablesContext memory context) external { _mintToken(address(context.erc7498Token), tokenId); - ConsiderationItem[] memory consideration = new ConsiderationItem[](1); consideration[0] = _getCampaignConsiderationItem(address(context.erc7498Token)); - CampaignRequirements[] memory requirements = new CampaignRequirements[]( 1 ); - 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) + manager: address(this), + signer: address(0) }); - - context.erc7498Token.createCampaign(params, ""); - - // campaignId: 1, - // requirementsIndex: 0 - bytes memory extraData = abi.encode(1, 0, bytes32(0), defaultTraitRedemptionTokenIds, uint256(0), bytes("")); - + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + context.erc7498Token.createCampaign(campaign, ""); + + (,, uint256 totalRedemptionsPreRedeem) = context.erc7498Token.getCampaign(1); + assertEq(totalRedemptionsPreRedeem, 0); + + bytes memory extraData = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + defaultTraitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); uint256[] memory considerationTokenIds = Solarray.uint256s(tokenId); - vm.expectEmit(true, true, true, true); emit Redemption(1, 0, bytes32(0), considerationTokenIds, defaultTraitRedemptionTokenIds, address(this)); - context.erc7498Token.redeem(considerationTokenIds, address(this), extraData); + // Using address(0) for recipient should assign to msg.sender. + context.erc7498Token.redeem(considerationTokenIds, address(0), extraData); _checkTokenDoesNotExist(address(context.erc7498Token), tokenId); - assertEq(receiveToken721.ownerOf(1), address(this)); + (,, uint256 totalRedemptionsPostRedeem) = context.erc7498Token.getCampaign(1); + assertEq(totalRedemptionsPostRedeem, 1); } function testBurnErc721RedeemErc721WithSecondRequirementsIndex() public { @@ -112,8 +93,7 @@ contract ERC7498_SimpleRedeem is BaseRedeemablesTest { firstRequirementRedeemToken.setApprovalForAll(address(context.erc7498Token), true); _mintToken(address(context.erc7498Token), tokenId); - - firstRequirementRedeemToken.mint(address(this), tokenId); + _mintToken(address(firstRequirementRedeemToken), tokenId); ConsiderationItem[] memory firstRequirementConsideration = new ConsiderationItem[](1); firstRequirementConsideration[0] = ConsiderationItem({ @@ -124,10 +104,8 @@ contract ERC7498_SimpleRedeem is BaseRedeemablesTest { endAmount: 1, recipient: payable(BURN_ADDRESS) }); - ConsiderationItem[] memory secondRequirementConsideration = new ConsiderationItem[](1); secondRequirementConsideration[0] = _getCampaignConsiderationItem(address(context.erc7498Token)); - CampaignRequirements[] memory requirements = new CampaignRequirements[]( 2 ); @@ -136,36 +114,47 @@ contract ERC7498_SimpleRedeem is BaseRedeemablesTest { consideration: firstRequirementConsideration, traitRedemptions: defaultTraitRedemptions }); - requirements[1] = CampaignRequirements({ offer: defaultCampaignOffer, consideration: secondRequirementConsideration, 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) + manager: address(this), + signer: address(0) }); - - IERC7498(context.erc7498Token).createCampaign(params, ""); - - // campaignId: 1 - // requirementsIndex: 1 - bytes memory extraData = abi.encode(1, 1, bytes32(0), defaultTraitRedemptionTokenIds, uint256(0), bytes("")); - + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + context.erc7498Token.createCampaign(campaign, ""); + + // Redeeming with an invalid requirementsIndex should revert. + vm.expectRevert(RequirementsIndexOutOfBounds.selector); + bytes memory extraData = abi.encode( + 1, // campaignId + 3, // requirementsIndex + bytes32(0), // redemptionHash + defaultTraitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); uint256[] memory tokenIds = Solarray.uint256s(tokenId); - - IERC7498(context.erc7498Token).redeem(tokenIds, address(this), extraData); + context.erc7498Token.redeem(tokenIds, address(this), extraData); + + // Valid requirementsIndex should succeed. + extraData = abi.encode( + 1, // campaignId + 1, // requirementsIndex + bytes32(0), // redemptionHash + defaultTraitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); + context.erc7498Token.redeem(tokenIds, address(this), extraData); _checkTokenDoesNotExist(address(context.erc7498Token), tokenId); - assertEq(firstRequirementRedeemToken.ownerOf(tokenId), address(this)); - assertEq(receiveToken721.ownerOf(1), address(this)); } @@ -177,35 +166,35 @@ contract ERC7498_SimpleRedeem is BaseRedeemablesTest { function burnErc20RedeemErc721(RedeemablesContext memory /* context */ ) public { erc20s[0].mint(address(this), 0.5 ether); - CampaignRequirements[] memory requirements = new CampaignRequirements[]( 1 ); - ConsiderationItem[] memory consideration = new ConsiderationItem[](1); consideration[0] = defaultCampaignConsideration[0].withToken(address(erc20s[0])).withItemType(ItemType.ERC20) .withStartAmount(0.5 ether).withEndAmount(0.5 ether); - 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) + manager: address(this), + signer: address(0) }); - - IERC7498(erc7498Tokens[0]).createCampaign(params, ""); - - // campaignId: 1 - // requirementsIndex: 0 - bytes memory extraData = abi.encode(1, 0, bytes32(0), defaultTraitRedemptionTokenIds, uint256(0), bytes("")); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + IERC7498(erc7498Tokens[0]).createCampaign(campaign, ""); + + bytes memory extraData = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + defaultTraitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); uint256[] memory considerationTokenIds = Solarray.uint256s(0); @@ -215,41 +204,39 @@ contract ERC7498_SimpleRedeem is BaseRedeemablesTest { vm.expectRevert(ERC721.TokenDoesNotExist.selector); IERC721(erc7498Tokens[0]).ownerOf(tokenId); - assertEq(receiveToken721.ownerOf(1), address(this)); } function testBurnErc721RedeemErc1155() public { - ERC721ShipyardRedeemableOwnerMintable(erc7498Tokens[0]).mint(address(this), tokenId); - + _mintToken(address(erc7498Tokens[0]), tokenId); CampaignRequirements[] memory requirements = new CampaignRequirements[]( 1 ); - OfferItem[] memory offer = new OfferItem[](1); offer[0] = defaultCampaignOffer[0].withItemType(ItemType.ERC1155).withToken(address(receiveToken1155)); - requirements[0] = CampaignRequirements({ offer: offer, 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) + manager: address(this), + signer: address(0) }); - - IERC7498(erc7498Tokens[0]).createCampaign(params, ""); - - // campaignId: 1 - // requirementsIndex: 0 - bytes memory extraData = abi.encode(1, 0, bytes32(0), defaultTraitRedemptionTokenIds, uint256(0), bytes("")); - + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + IERC7498(erc7498Tokens[0]).createCampaign(campaign, ""); + + bytes memory extraData = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + defaultTraitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); uint256[] memory considerationTokenIds = Solarray.uint256s(tokenId); vm.expectEmit(true, true, true, true); @@ -258,7 +245,6 @@ contract ERC7498_SimpleRedeem is BaseRedeemablesTest { vm.expectRevert(ERC721.TokenDoesNotExist.selector); IERC721(erc7498Tokens[0]).ownerOf(tokenId); - assertEq(receiveToken1155.balanceOf(address(this), 1), 1); } } diff --git a/test/ERC7498.t.sol b/test/ERC7498.t.sol index 5c25c43..42b71be 100644 --- a/test/ERC7498.t.sol +++ b/test/ERC7498.t.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.19; import {BaseRedeemablesTest} from "./utils/BaseRedeemablesTest.sol"; import {IERC165} from "openzeppelin-contracts/contracts/interfaces/IERC165.sol"; -import {IERC721} from "openzeppelin-contracts/contracts/token/ERC721/IERC721.sol"; import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {OfferItemLib} from "seaport-sol/src/lib/OfferItemLib.sol"; import {ConsiderationItemLib} from "seaport-sol/src/lib/ConsiderationItemLib.sol"; @@ -15,10 +14,6 @@ contract TestERC7498 is BaseRedeemablesTest { using ConsiderationItemLib for ConsiderationItem; using ConsiderationItemLib for ConsiderationItem[]; - function setUp() public virtual override { - super.setUp(); - } - function testSupportsInterfaceId() public { for (uint256 i; i < erc7498Tokens.length; i++) { testRedeemable(this.supportsInterfaceId, RedeemablesContext({erc7498Token: IERC7498(erc7498Tokens[i])})); diff --git a/test/RedeemableContractOfferer-1155.t.sol.txt b/test/RedeemableContractOfferer-1155.t.sol.txt index a75ce5c..7999f31 100644 --- a/test/RedeemableContractOfferer-1155.t.sol.txt +++ b/test/RedeemableContractOfferer-1155.t.sol.txt @@ -85,11 +85,11 @@ contract TestRedeemableContractOfferer_1155 is BaseOrderTest, RedeemablesErrors CampaignParams memory params = CampaignParams({ offer: offer, consideration: consideration, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1000), maxCampaignRedemptions: 5, - manager: address(this) + manager: address(this), + signer: address(0) }); offerer.createCampaign(params, ""); @@ -207,11 +207,11 @@ contract TestRedeemableContractOfferer_1155 is BaseOrderTest, RedeemablesErrors CampaignParams memory params = CampaignParams({ offer: campaignOffer, consideration: campaignConsideration, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1000), maxCampaignRedemptions: 5, - manager: address(this) + manager: address(this), + signer: address(0) }); offerer.createCampaign(params, ""); @@ -317,11 +317,11 @@ contract TestRedeemableContractOfferer_1155 is BaseOrderTest, RedeemablesErrors CampaignParams memory params = CampaignParams({ offer: offer, consideration: consideration, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1000), maxCampaignRedemptions: 5, - manager: address(this) + manager: address(this), + signer: address(0) }); offerer.createCampaign(params, ""); @@ -445,11 +445,11 @@ contract TestRedeemableContractOfferer_1155 is BaseOrderTest, RedeemablesErrors // CampaignParams memory params = CampaignParams({ // offer: offer, // consideration: consideration, - // signer: address(0), // startTime: uint32(block.timestamp), // endTime: uint32(block.timestamp + 1000), // maxCampaignRedemptions: 5, - // manager: address(this) + // manager: address(this), + // signer: address(0) // }); // // Call createCampaign on the offerer and pass in the CampaignParams @@ -596,11 +596,11 @@ contract TestRedeemableContractOfferer_1155 is BaseOrderTest, RedeemablesErrors // CampaignParams memory params = CampaignParams({ // offer: offer, // consideration: consideration, - // signer: address(0), // startTime: uint32(block.timestamp), // endTime: uint32(block.timestamp + 1000), // maxCampaignRedemptions: 5, - // manager: address(this) + // manager: address(this), + // signer: address(0) // }); // // Call createCampaign on the offerer and pass in the CampaignParams @@ -751,11 +751,11 @@ contract TestRedeemableContractOfferer_1155 is BaseOrderTest, RedeemablesErrors // CampaignParams memory params = CampaignParams({ // offer: offer, // consideration: consideration, - // signer: address(0), // startTime: uint32(block.timestamp), // endTime: uint32(block.timestamp + 1000), // maxCampaignRedemptions: 5, - // manager: address(this) + // manager: address(this), + // signer: address(0) // }); // // Call createCampaign on the offerer and pass in the CampaignParams @@ -904,11 +904,11 @@ contract TestRedeemableContractOfferer_1155 is BaseOrderTest, RedeemablesErrors // CampaignParams memory params = CampaignParams({ // offer: offer, // consideration: consideration, - // signer: address(0), // startTime: uint32(block.timestamp), // endTime: uint32(block.timestamp + 1000), // maxCampaignRedemptions: 5, - // manager: address(this) + // manager: address(this), + // signer: address(0) // }); // // Call createCampaign on the offerer and pass in the CampaignParams @@ -1040,11 +1040,11 @@ contract TestRedeemableContractOfferer_1155 is BaseOrderTest, RedeemablesErrors // CampaignParams memory params = CampaignParams({ // offer: offer, // consideration: consideration, - // signer: address(0), // startTime: uint32(block.timestamp), // endTime: uint32(block.timestamp + 1000), // maxCampaignRedemptions: 5, - // manager: address(this) + // manager: address(this), + // signer: address(0) // }); // // Call createCampaign on the offerer and pass in the CampaignParams @@ -1181,11 +1181,11 @@ contract TestRedeemableContractOfferer_1155 is BaseOrderTest, RedeemablesErrors // CampaignParams memory params = CampaignParams({ // offer: offer, // consideration: consideration, - // signer: address(0), // startTime: uint32(block.timestamp), // endTime: uint32(block.timestamp + 1000), // maxCampaignRedemptions: 5, - // manager: address(this) + // manager: address(this), + // signer: address(0) // }); // offerer.createCampaign(params, ""); diff --git a/test/RedeemableContractOfferer-721.t.sol.txt b/test/RedeemableContractOfferer-721.t.sol.txt index b7defde..a61e880 100644 --- a/test/RedeemableContractOfferer-721.t.sol.txt +++ b/test/RedeemableContractOfferer-721.t.sol.txt @@ -56,7 +56,7 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { function testRedeemWithSeaport() public { uint256 tokenId = 2; - redeemableToken.mint(address(this), tokenId); + _mintToken(address(redeemableToken), tokenId); redeemableToken.setApprovalForAll(address(conduit), true); OfferItem[] memory offer = new OfferItem[](1); @@ -82,11 +82,11 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { CampaignParams memory params = CampaignParams({ offer: offer, consideration: consideration, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1000), maxCampaignRedemptions: 5, - manager: address(this) + manager: address(this), + signer: address(0) }); offerer.createCampaign(params, ""); @@ -156,7 +156,7 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { function testRedeemAndSendErc20ToThirdAddressViaSeaport() public { uint256 tokenId = 2; - redeemableToken.mint(address(this), tokenId); + _mintToken(address(redeemableToken), tokenId); redeemableToken.setApprovalForAll(address(conduit), true); // Deploy the ERC20 @@ -200,11 +200,11 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { CampaignParams memory params = CampaignParams({ offer: campaignOffer, consideration: campaignConsideration, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1000), maxCampaignRedemptions: 5, - manager: address(this) + manager: address(this), + signer: address(0) }); offerer.createCampaign(params, ""); @@ -272,7 +272,7 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { // TODO: fix redemptionToken being minted with merkle root function testRedeemWithCriteriaResolversViaSeaport() public { uint256 tokenId = 2; - redeemableToken.mint(address(this), tokenId); + _mintToken(address(redeemableToken), tokenId); redeemableToken.setApprovalForAll(address(conduit), true); ERC721RedemptionMintable redemptionTokenWithCounter = new ERC721RedemptionMintable( @@ -314,11 +314,11 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { CampaignParams memory params = CampaignParams({ offer: offer, consideration: consideration, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1000), maxCampaignRedemptions: 5, - manager: address(this) + manager: address(this), + signer: address(0) }); offerer.createCampaign(params, ""); @@ -400,8 +400,8 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { uint256 burnTokenId1 = 3; // Mint two redeemableTokens of tokenId burnTokenId0 and burnTokenId1 to the test contract - redeemableToken.mint(address(this), burnTokenId0); - redeemableToken.mint(address(this), burnTokenId1); + _mintToken(address(redeemableToken), burnTokenId0); + _mintToken(address(redeemableToken), burnTokenId1); // Approve the conduit to transfer the redeemableTokens on behalf of the test contract redeemableToken.setApprovalForAll(address(conduit), true); @@ -441,11 +441,11 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { CampaignParams memory params = CampaignParams({ offer: offer, consideration: consideration, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1000), maxCampaignRedemptions: 5, - manager: address(this) + manager: address(this), + signer: address(0) }); // Call createCampaign on the offerer and pass in the CampaignParams @@ -544,7 +544,7 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { ); // Mint a redeemableToken of tokenId redeemableTokenId0 to the test contract - redeemableToken.mint(address(this), redeemableTokenId0); + _mintToken(address(redeemableToken), redeemableTokenId0); // Approve the conduit to transfer the redeemableTokens on behalf of the test contract redeemableToken.setApprovalForAll(address(conduit), true); @@ -583,11 +583,11 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { CampaignParams memory params = CampaignParams({ offer: offer, consideration: consideration, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1000), maxCampaignRedemptions: 5, - manager: address(this) + manager: address(this), + signer: address(0) }); // Call createCampaign on the offerer and pass in the CampaignParams @@ -678,8 +678,8 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { TestERC721 redeemableTokenTwo = new TestERC721(); // Mint one redeemableToken ane one redeemableTokenTwo of tokenId burnTokenId0 to the test contract - redeemableToken.mint(address(this), burnTokenId0); - redeemableTokenTwo.mint(address(this), burnTokenId0); + _mintToken(address(redeemableToken), redeemableTokenId0); + _mintToken(address(redeemableTokenTwo), redeemableTokenId0); // Approve the conduit to transfer the redeemableTokens on behalf of the test contract redeemableToken.setApprovalForAll(address(conduit), true); @@ -720,11 +720,11 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { CampaignParams memory params = CampaignParams({ offer: offer, consideration: consideration, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1000), maxCampaignRedemptions: 5, - manager: address(this) + manager: address(this), + signer: address(0) }); // Call createCampaign on the offerer and pass in the CampaignParams @@ -825,7 +825,7 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { ); // Mint a redeemableToken of tokenId redeemableTokenId to the test contract - redeemableToken.mint(address(this), redeemableTokenId); + _mintToken(address(redeemableToken), redeemableTokenId0); // Approve the conduit to transfer the redeemableTokens on behalf of the test contract redeemableToken.setApprovalForAll(address(conduit), true); @@ -864,11 +864,11 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { CampaignParams memory params = CampaignParams({ offer: offer, consideration: consideration, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1000), maxCampaignRedemptions: 5, - manager: address(this) + manager: address(this), + signer: address(0) }); // Call createCampaign on the offerer and pass in the CampaignParams @@ -965,7 +965,7 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { ); // Mint a dynamicTraitsToken of tokenId redeemableTokenId0 to the test contract - dynamicTraitsToken.mint(address(this), redeemableTokenId0); + _mintToken(address(dynamicTraitsToken), redeemableTokenId0); // Approve the conduit to transfer the redeemableTokens on behalf of the test contract dynamicTraitsToken.setApprovalForAll(address(conduit), true); @@ -988,11 +988,11 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { CampaignParams memory params = CampaignParams({ offer: offer, consideration: consideration, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1000), maxCampaignRedemptions: 5, - manager: address(this) + manager: address(this), + signer: address(0) }); // Call createCampaign on the offerer and pass in the CampaignParams @@ -1113,7 +1113,7 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { for (uint256 i; i < 5; i++) { tokenId = i; - redeemableToken.mint(address(this), tokenId); + _mintToken(address(redeemableToken), tokenId); bytes memory extraData = abi.encode(campaignId, redemptionHash); AdvancedOrder memory order = AdvancedOrder({ @@ -1130,11 +1130,11 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { CampaignParams memory params = CampaignParams({ offer: offer, consideration: consideration, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1000), maxCampaignRedemptions: 5, - manager: address(this) + manager: address(this), + signer: address(0) }); offerer.createCampaign(params, ""); diff --git a/test/RedeemableContractOfferer-Revert.t.sol.txt b/test/RedeemableContractOfferer-Revert.t.sol.txt index 7b8e25a..5c58737 100644 --- a/test/RedeemableContractOfferer-Revert.t.sol.txt +++ b/test/RedeemableContractOfferer-Revert.t.sol.txt @@ -54,7 +54,7 @@ contract TestRedeemableContractOfferer_Revert is BaseOrderTest, RedeemablesError function testRevertRedeemWithCriteriaResolversViaSeaport() public { uint256 tokenId = 7; - redeemableToken.mint(address(this), tokenId); + _mintToken(address(redeemableToken), tokenId); redeemableToken.setApprovalForAll(address(conduit), true); CriteriaResolver[] memory resolvers = new CriteriaResolver[](1); @@ -92,11 +92,11 @@ contract TestRedeemableContractOfferer_Revert is BaseOrderTest, RedeemablesError CampaignParams memory params = CampaignParams({ offer: offer, consideration: consideration, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1000), maxCampaignRedemptions: 5, - manager: address(this) + manager: address(this), + signer: address(0) }); offerer.createCampaign(params, ""); @@ -170,9 +170,9 @@ contract TestRedeemableContractOfferer_Revert is BaseOrderTest, RedeemablesError } function testRevertMaxCampaignRedemptionsReached() public { - redeemableToken.mint(address(this), 0); - redeemableToken.mint(address(this), 1); - redeemableToken.mint(address(this), 2); + _mintToken(address(redeemableToken), 0); + _mintToken(address(redeemableToken), 1); + _mintToken(address(redeemableToken), 2); redeemableToken.setApprovalForAll(address(conduit), true); ERC721RedemptionMintable redemptionTokenWithCounter = new ERC721RedemptionMintable( @@ -203,11 +203,11 @@ contract TestRedeemableContractOfferer_Revert is BaseOrderTest, RedeemablesError CampaignParams memory params = CampaignParams({ offer: offer, consideration: consideration, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1000), maxCampaignRedemptions: 2, - manager: address(this) + manager: address(this), + signer: address(0) }); offerer.createCampaign(params, ""); @@ -325,7 +325,7 @@ contract TestRedeemableContractOfferer_Revert is BaseOrderTest, RedeemablesError function testRevertConsiderationItemRecipientCannotBeZeroAddress() public { uint256 tokenId = 2; - redeemableToken.mint(address(this), tokenId); + _mintToken(address(redeemableToken), tokenId); redeemableToken.setApprovalForAll(address(conduit), true); OfferItem[] memory offer = new OfferItem[](1); @@ -351,11 +351,11 @@ contract TestRedeemableContractOfferer_Revert is BaseOrderTest, RedeemablesError CampaignParams memory params = CampaignParams({ offer: offer, consideration: consideration, - signer: address(0), startTime: uint32(block.timestamp), endTime: uint32(block.timestamp + 1000), maxCampaignRedemptions: 5, - manager: address(this) + manager: address(this), + signer: address(0) }); vm.expectRevert(abi.encodeWithSelector(ConsiderationItemRecipientCannotBeZeroAddress.selector)); diff --git a/test/utils/BaseRedeemablesTest.sol b/test/utils/BaseRedeemablesTest.sol index 0f44df3..7ce7ced 100644 --- a/test/utils/BaseRedeemablesTest.sol +++ b/test/utils/BaseRedeemablesTest.sol @@ -38,22 +38,29 @@ contract BaseRedeemablesTest is RedeemablesErrors, BaseOrderTest { IERC7498 erc7498Token; } + event Redemption( + uint256 indexed campaignId, + uint256 requirementsIndex, + bytes32 redemptionHash, + uint256[] considerationTokenIds, + uint256[] traitRedemptionTokenIds, + address redeemedBy + ); + bytes32 private constant CAMPAIGN_PARAMS_MAP_POSITION = keccak256("CampaignParamsDefault"); address[] erc7498Tokens; - ERC721ShipyardRedeemableOwnerMintable erc721ShipyardRedeemable; ERC721SeaDropRedeemableOwnerMintable erc721SeaDropRedeemable; ERC1155ShipyardRedeemableOwnerMintable erc1155ShipyardRedeemable; ERC1155SeaDropRedeemableOwnerMintable erc1155SeaDropRedeemable; - ERC721RedemptionMintable receiveToken721; ERC1155RedemptionMintable receiveToken1155; OfferItem[] defaultCampaignOffer; ConsiderationItem[] defaultCampaignConsideration; TraitRedemption[] defaultTraitRedemptions; - uint256[] defaultTraitRedemptionTokenIds = new uint256[](0); + uint256[] defaultTraitRedemptionTokenIds; CampaignRequirements[] defaultCampaignRequirements; // CampaignParams defaultCampaignParams; @@ -109,22 +116,16 @@ contract BaseRedeemablesTest is RedeemablesErrors, BaseOrderTest { OfferItemLib.fromDefault(SINGLE_ERC721).withToken(address(receiveToken721)).withItemType( ItemType.ERC721_WITH_CRITERIA ).saveDefault(DEFAULT_ERC721_CAMPAIGN_OFFER); - ConsiderationItemLib.fromDefault(SINGLE_ERC721).withToken(address(erc7498Tokens[0])).withRecipient(BURN_ADDRESS) .withItemType(ItemType.ERC721_WITH_CRITERIA).saveDefault(DEFAULT_ERC721_CAMPAIGN_CONSIDERATION); - defaultCampaignOffer.push(OfferItemLib.fromDefault(DEFAULT_ERC721_CAMPAIGN_OFFER)); - defaultCampaignConsideration.push(ConsiderationItemLib.fromDefault(DEFAULT_ERC721_CAMPAIGN_CONSIDERATION)); } function testRedeemable(function(RedeemablesContext memory) external fn, RedeemablesContext memory context) internal { - try fn(context) {} - catch (bytes memory reason) { - assertPass(reason); - } + fn(context); } function _campaignParamsMap() private pure returns (mapping(string => CampaignParams) storage campaignParamsMap) { @@ -151,7 +152,6 @@ contract BaseRedeemablesTest is RedeemablesErrors, BaseOrderTest { erc1155s[i].setApprovalForAll(address(erc7498Tokens[j]), true); } } - vm.stopPrank(); } @@ -197,12 +197,21 @@ contract BaseRedeemablesTest is RedeemablesErrors, BaseOrderTest { function _mintToken(address token, uint256 tokenId) internal { if (_isERC721(token)) { ERC721ShipyardRedeemableOwnerMintable(address(token)).mint(address(this), tokenId); - } else if (IERC165(token).supportsInterface(type(IERC1155).interfaceId)) { + } else { // token is ERC1155 ERC1155ShipyardRedeemableOwnerMintable(address(token)).mint(address(this), tokenId, 1); } } + function _mintToken(address token, uint256 tokenId, address recipient) internal { + if (_isERC721(token)) { + ERC721ShipyardRedeemableOwnerMintable(address(token)).mint(recipient, tokenId); + } else { + // token is ERC1155 + ERC1155ShipyardRedeemableOwnerMintable(address(token)).mint(recipient, tokenId, 1); + } + } + function _isERC721(address token) internal view returns (bool isERC721) { isERC721 = IERC165(token).supportsInterface(type(IERC721).interfaceId); } From a98fb8c72fd681f6fd60875eb4277c46c305e8cd Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Wed, 15 Nov 2023 12:11:30 -0800 Subject: [PATCH 3/6] progress, simplify extension contracts, more coverage --- script/DeployAndConfigure1155Receive.s.sol | 2 +- ...eployAndConfigureExampleCampaign.s.sol.txt | 4 +- ...dRedeemTokens-CampaignOnReceiveToken.s.sol | 2 +- script/DeployAndRedeemTokens.s.sol | 8 +-- script/DeployAndRedeemTrait.s.sol | 8 +-- ...kenWithPredeployedSeadropRedeemToken.s.sol | 2 +- script/RedeemTokens.s.sol | 2 +- src/ERC721SeaDropRedeemable.sol | 8 +-- src/ERC721ShipyardRedeemable.sol | 8 +-- src/extensions/ERC1155RedemptionMintable.sol | 59 ---------------- .../ERC1155ShipyardRedeemableMintable.sol | 32 +++++++-- src/extensions/ERC721RedemptionMintable.sol | 59 ---------------- .../ERC721ShipyardRedeemableMintable.sol | 31 +++++++-- src/lib/ERC1155ShipyardContractMetadata.sol | 16 +++++ src/lib/ERC721ShipyardContractMetadata.sol | 21 ++++++ src/lib/ERC7498NFTRedeemables.sol | 7 +- src/lib/RedeemablesErrors.sol | 1 - ... ERC721ShipyardRedeemableTraitSetters.sol} | 46 +++++-------- test/ERC7498-GetAndUpdateCampaign.t.sol | 67 +++++++++++++++++++ test/ERC7498-MultiRedeem.t.sol | 8 +-- test/ERC7498-SimpleRedeem.t.sol | 29 ++++++-- ...ts.t.sol => ERC7498-TraitRedemption.t.sol} | 27 ++++++-- test/RedeemableContractOfferer-1155.t.sol.txt | 16 ++--- test/RedeemableContractOfferer-721.t.sol.txt | 16 ++--- ...RedeemableContractOfferer-Revert.t.sol.txt | 10 +-- test/utils/BaseRedeemablesTest.sol | 15 +++-- 26 files changed, 270 insertions(+), 234 deletions(-) delete mode 100644 src/extensions/ERC1155RedemptionMintable.sol delete mode 100644 src/extensions/ERC721RedemptionMintable.sol rename src/test/{ERC721ShipyardRedeemablePreapprovedTraitSetters.sol => ERC721ShipyardRedeemableTraitSetters.sol} (54%) rename test/{ERC7498-DynamicTraits.t.sol => ERC7498-TraitRedemption.t.sol} (79%) diff --git a/script/DeployAndConfigure1155Receive.s.sol b/script/DeployAndConfigure1155Receive.s.sol index 8293eaa..4ed3d7e 100644 --- a/script/DeployAndConfigure1155Receive.s.sol +++ b/script/DeployAndConfigure1155Receive.s.sol @@ -7,7 +7,7 @@ import {ItemType} from "seaport-types/src/lib/ConsiderationEnums.sol"; import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {Campaign, CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; import {BURN_ADDRESS} from "../src/lib/RedeemablesConstants.sol"; -import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; +import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; import {ERC721OwnerMintable} from "../src/test/ERC721OwnerMintable.sol"; import {ERC1155ShipyardRedeemableMintable} from "../src/extensions/ERC1155ShipyardRedeemableMintable.sol"; diff --git a/script/DeployAndConfigureExampleCampaign.s.sol.txt b/script/DeployAndConfigureExampleCampaign.s.sol.txt index 426ad7e..5892cc0 100644 --- a/script/DeployAndConfigureExampleCampaign.s.sol.txt +++ b/script/DeployAndConfigureExampleCampaign.s.sol.txt @@ -8,7 +8,7 @@ import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationS import {RedeemableContractOfferer} from "../src/RedeemableContractOfferer.sol"; import {CampaignParams} from "../src/lib/RedeemablesStructs.sol"; import {BURN_ADDRESS} from "../src/lib/RedeemablesConstants.sol"; -import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; +import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; import {TestERC721} from "../test/utils/mocks/TestERC721.sol"; contract DeployAndConfigureExampleCampaign is Script { @@ -26,7 +26,7 @@ contract DeployAndConfigureExampleCampaign is Script { seaport ); TestERC721 redeemableToken = new TestERC721(); - ERC721RedemptionMintable redemptionToken = new ERC721RedemptionMintable( + ERC721ShipyardRedeemableMintable redemptionToken = new ERC721ShipyardRedeemableMintable( address(offerer), address(redeemableToken) ); diff --git a/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol b/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol index bdd0f92..bf9bc53 100644 --- a/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol +++ b/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol @@ -7,7 +7,7 @@ import {ItemType} from "seaport-types/src/lib/ConsiderationEnums.sol"; import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {Campaign, CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; import {BURN_ADDRESS} from "../src/lib/RedeemablesConstants.sol"; -import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; +import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; import {ERC721OwnerMintable} from "../src/test/ERC721OwnerMintable.sol"; import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; diff --git a/script/DeployAndRedeemTokens.s.sol b/script/DeployAndRedeemTokens.s.sol index 68c00ed..2cd2da3 100644 --- a/script/DeployAndRedeemTokens.s.sol +++ b/script/DeployAndRedeemTokens.s.sol @@ -7,7 +7,7 @@ import {ItemType} from "seaport-types/src/lib/ConsiderationEnums.sol"; import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {Campaign, CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; import {BURN_ADDRESS} from "../src/lib/RedeemablesConstants.sol"; -import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; +import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; import {ERC721ShipyardRedeemableOwnerMintable} from "../src/test/ERC721ShipyardRedeemableOwnerMintable.sol"; contract DeployAndRedeemTokens is Script, Test { @@ -20,11 +20,11 @@ contract DeployAndRedeemTokens is Script, Test { ); address[] memory redeemTokens = new address[](1); redeemTokens[0] = address(redeemToken); - ERC721RedemptionMintable receiveToken = new ERC721RedemptionMintable( + ERC721ShipyardRedeemableMintable receiveToken = new ERC721ShipyardRedeemableMintable( "TestRedeemablesRecieveToken", - "TEST", - redeemTokens + "TEST" ); + receiveToken.setRedeemablesContracts(redeemTokens); // Configure the campaign. OfferItem[] memory offer = new OfferItem[](1); diff --git a/script/DeployAndRedeemTrait.s.sol b/script/DeployAndRedeemTrait.s.sol index d8b4a0d..f9484fa 100644 --- a/script/DeployAndRedeemTrait.s.sol +++ b/script/DeployAndRedeemTrait.s.sol @@ -7,10 +7,9 @@ import {ItemType} from "seaport-types/src/lib/ConsiderationEnums.sol"; import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {IERC7496} from "shipyard-core/src/dynamic-traits/interfaces/IERC7496.sol"; import {Campaign, CampaignParams, CampaignRequirements, TraitRedemption} from "../src/lib/RedeemablesStructs.sol"; -import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; -import {ERC721ShipyardRedeemablePreapprovedTraitSetters} from - "../src/test/ERC721ShipyardRedeemablePreapprovedTraitSetters.sol"; +import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; +import {ERC721ShipyardRedeemableTraitSetters} from "../src/test/ERC721ShipyardRedeemableTraitSetters.sol"; contract DeployAndRedeemTrait is Script, Test { function run() external { @@ -27,8 +26,7 @@ contract DeployAndRedeemTrait is Script, Test { allowedTraitSetters[0] = address(receiveToken); // deploy the redeem token with the receive token as an allowed trait setter - ERC721ShipyardRedeemablePreapprovedTraitSetters redeemToken = - new ERC721ShipyardRedeemablePreapprovedTraitSetters( + ERC721ShipyardRedeemableTraitSetters redeemToken = new ERC721ShipyardRedeemableTraitSetters( "DynamicTraitsRedeemToken", "TEST", allowedTraitSetters diff --git a/script/DeployERC721ReceiveTokenWithPredeployedSeadropRedeemToken.s.sol b/script/DeployERC721ReceiveTokenWithPredeployedSeadropRedeemToken.s.sol index 132ffd5..a5ea12d 100644 --- a/script/DeployERC721ReceiveTokenWithPredeployedSeadropRedeemToken.s.sol +++ b/script/DeployERC721ReceiveTokenWithPredeployedSeadropRedeemToken.s.sol @@ -7,7 +7,7 @@ import {ItemType} from "seaport-types/src/lib/ConsiderationEnums.sol"; import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {Campaign, CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; import {BURN_ADDRESS} from "../src/lib/RedeemablesConstants.sol"; -import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; +import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; import {ERC721OwnerMintable} from "../src/test/ERC721OwnerMintable.sol"; import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; diff --git a/script/RedeemTokens.s.sol b/script/RedeemTokens.s.sol index 8b4e364..63a1505 100644 --- a/script/RedeemTokens.s.sol +++ b/script/RedeemTokens.s.sol @@ -6,7 +6,7 @@ import {Test} from "forge-std/Test.sol"; import {ItemType} from "seaport-types/src/lib/ConsiderationEnums.sol"; import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; -import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; +import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; import {ERC721OwnerMintable} from "../src/test/ERC721OwnerMintable.sol"; import {ERC1155ShipyardRedeemableMintable} from "../src/extensions/ERC1155ShipyardRedeemableMintable.sol"; diff --git a/src/ERC721SeaDropRedeemable.sol b/src/ERC721SeaDropRedeemable.sol index 0fb2235..48835f2 100644 --- a/src/ERC721SeaDropRedeemable.sol +++ b/src/ERC721SeaDropRedeemable.sol @@ -26,9 +26,7 @@ contract ERC721SeaDropRedeemable is ERC721SeaDrop, ERC7498NFTRedeemables { } function setTrait(uint256 tokenId, bytes32 traitKey, bytes32 value) public virtual override onlyOwner { - if (!_exists(tokenId)) { - revert TokenDoesNotExist(); - } + if (!_exists(tokenId)) revert TokenDoesNotExist(); DynamicTraits.setTrait(tokenId, traitKey, value); } @@ -40,9 +38,7 @@ contract ERC721SeaDropRedeemable is ERC721SeaDrop, ERC7498NFTRedeemables { override returns (bytes32 traitValue) { - if (!_exists(tokenId)) { - revert TokenDoesNotExist(); - } + if (!_exists(tokenId)) revert TokenDoesNotExist(); traitValue = DynamicTraits.getTraitValue(tokenId, traitKey); } diff --git a/src/ERC721ShipyardRedeemable.sol b/src/ERC721ShipyardRedeemable.sol index 746e39e..77e039b 100644 --- a/src/ERC721ShipyardRedeemable.sol +++ b/src/ERC721ShipyardRedeemable.sol @@ -19,9 +19,7 @@ contract ERC721ShipyardRedeemable is ERC721ShipyardContractMetadata, ERC7498NFTR } function setTrait(uint256 tokenId, bytes32 traitKey, bytes32 value) public virtual override onlyOwner { - if (!_exists(tokenId)) { - revert TokenDoesNotExist(); - } + if (!_exists(tokenId)) revert TokenDoesNotExist(); DynamicTraits.setTrait(tokenId, traitKey, value); } @@ -33,9 +31,7 @@ contract ERC721ShipyardRedeemable is ERC721ShipyardContractMetadata, ERC7498NFTR override returns (bytes32 traitValue) { - if (!_exists(tokenId)) { - revert TokenDoesNotExist(); - } + if (!_exists(tokenId)) revert TokenDoesNotExist(); traitValue = DynamicTraits.getTraitValue(tokenId, traitKey); } diff --git a/src/extensions/ERC1155RedemptionMintable.sol b/src/extensions/ERC1155RedemptionMintable.sol deleted file mode 100644 index 7eee4c3..0000000 --- a/src/extensions/ERC1155RedemptionMintable.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import {IERC165} from "openzeppelin-contracts/contracts/interfaces/IERC165.sol"; -import {ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; -import {ERC1155ShipyardContractMetadata} from "../lib/ERC1155ShipyardContractMetadata.sol"; -import {IRedemptionMintable} from "../interfaces/IRedemptionMintable.sol"; -import {TraitRedemption} from "../lib/RedeemablesStructs.sol"; - -contract ERC1155RedemptionMintable is ERC1155ShipyardContractMetadata, IRedemptionMintable { - /// @dev The ERC-7498 redeemables contract. - address[] internal _ERC7498_REDEEMABLES_CONTRACTS; - - /// @dev The next token id to mint. - uint256 internal _nextTokenId = 1; - - /// @dev Revert if the sender of mintRedemption is not the redeemable contract offerer. - error InvalidSender(); - - constructor(string memory name_, string memory symbol_, address[] memory redeemableContractAddresses) - ERC1155ShipyardContractMetadata(name_, symbol_) - { - // Set the redeemables contract addresses. - _ERC7498_REDEEMABLES_CONTRACTS = redeemableContractAddresses; - } - - function mintRedemption( - uint256, /* campaignId */ - address recipient, - ConsiderationItem[] calldata, /* consideration */ - TraitRedemption[] calldata /* traitRedemptions */ - ) external { - bool validSender; - for (uint256 i; i < _ERC7498_REDEEMABLES_CONTRACTS.length; i++) { - if (msg.sender == _ERC7498_REDEEMABLES_CONTRACTS[i]) { - validSender = true; - } - } - if (!validSender) { - revert InvalidSender(); - } - - // Increment nextTokenId first so more of the same token id cannot be minted through reentrancy. - ++_nextTokenId; - - _mint(recipient, _nextTokenId - 1, 1, ""); - } - - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(ERC1155ShipyardContractMetadata) - returns (bool) - { - return ERC1155ShipyardContractMetadata.supportsInterface(interfaceId) - || interfaceId == type(IRedemptionMintable).interfaceId; - } -} diff --git a/src/extensions/ERC1155ShipyardRedeemableMintable.sol b/src/extensions/ERC1155ShipyardRedeemableMintable.sol index a6a47f8..aac2961 100644 --- a/src/extensions/ERC1155ShipyardRedeemableMintable.sol +++ b/src/extensions/ERC1155ShipyardRedeemableMintable.sol @@ -13,8 +13,8 @@ import {IRedemptionMintable} from "../interfaces/IRedemptionMintable.sol"; import {TraitRedemption} from "../lib/RedeemablesStructs.sol"; contract ERC1155ShipyardRedeemableMintable is ERC1155ShipyardRedeemable, IRedemptionMintable { - /// @dev Revert if the sender of mintRedemption is not this contract. - error InvalidSender(); + /// @dev The ERC-7498 redeemables contracts. + address[] internal _erc7498RedeemablesContracts; /// @dev The next token id to mint. Each token will have a supply of 1. uint256 _nextTokenId = 1; @@ -27,9 +27,8 @@ contract ERC1155ShipyardRedeemableMintable is ERC1155ShipyardRedeemable, IRedemp ConsiderationItem[] calldata, /* consideration */ TraitRedemption[] calldata /* traitRedemptions */ ) external { - if (msg.sender != address(this)) { - revert InvalidSender(); - } + // Require that msg.sender is valid. + _requireValidRedeemablesCaller(); // Increment nextTokenId first so more of the same token id cannot be minted through reentrancy. ++_nextTokenId; @@ -37,6 +36,29 @@ contract ERC1155ShipyardRedeemableMintable is ERC1155ShipyardRedeemable, IRedemp _mint(recipient, _nextTokenId - 1, 1, ""); } + function getRedeemablesContracts() external view returns (address[] memory) { + return _erc7498RedeemablesContracts; + } + + function setRedeemablesContracts(address[] calldata redeemablesContracts) external onlyOwner { + _erc7498RedeemablesContracts = redeemablesContracts; + } + + function _requireValidRedeemablesCaller() internal view { + // Allow the contract to call itself. + if (msg.sender == address(this)) return; + + bool validCaller; + for (uint256 i; i < _erc7498RedeemablesContracts.length; i++) { + if (msg.sender == _erc7498RedeemablesContracts[i]) { + validCaller = true; + } + } + if (!validCaller) { + revert InvalidCaller(msg.sender); + } + } + function supportsInterface(bytes4 interfaceId) public view diff --git a/src/extensions/ERC721RedemptionMintable.sol b/src/extensions/ERC721RedemptionMintable.sol deleted file mode 100644 index 84e4a5b..0000000 --- a/src/extensions/ERC721RedemptionMintable.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import {IERC165} from "openzeppelin-contracts/contracts/interfaces/IERC165.sol"; -import {ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; -import {ERC721ShipyardContractMetadata} from "../lib/ERC721ShipyardContractMetadata.sol"; -import {IRedemptionMintable} from "../interfaces/IRedemptionMintable.sol"; -import {TraitRedemption} from "../lib/RedeemablesStructs.sol"; - -contract ERC721RedemptionMintable is ERC721ShipyardContractMetadata, IRedemptionMintable { - /// @dev The ERC-7498 redeemables contract. - address[] internal _ERC7498_REDEEMABLES_CONTRACTS; - - /// @dev The next token id to mint. - uint256 internal _nextTokenId = 1; - - /// @dev Revert if the sender of mintRedemption is not the redeemable contract offerer. - error InvalidSender(); - - constructor(string memory name_, string memory symbol_, address[] memory redeemablesContractAddresses) - ERC721ShipyardContractMetadata(name_, symbol_) - { - // Set the redeemables contract address. - _ERC7498_REDEEMABLES_CONTRACTS = redeemablesContractAddresses; - } - - function mintRedemption( - uint256, /* campaignId */ - address recipient, - ConsiderationItem[] calldata, /* consideration */ - TraitRedemption[] calldata /* traitRedemptions */ - ) external { - bool validSender; - for (uint256 i; i < _ERC7498_REDEEMABLES_CONTRACTS.length; i++) { - if (msg.sender == _ERC7498_REDEEMABLES_CONTRACTS[i]) { - validSender = true; - } - } - if (!validSender) { - revert InvalidSender(); - } - - // Increment nextTokenId first so more of the same token id cannot be minted through reentrancy. - ++_nextTokenId; - - _mint(recipient, _nextTokenId - 1); - } - - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(ERC721ShipyardContractMetadata) - returns (bool) - { - return interfaceId == type(IRedemptionMintable).interfaceId - || ERC721ShipyardContractMetadata.supportsInterface(interfaceId); - } -} diff --git a/src/extensions/ERC721ShipyardRedeemableMintable.sol b/src/extensions/ERC721ShipyardRedeemableMintable.sol index fb9a2ca..8f17910 100644 --- a/src/extensions/ERC721ShipyardRedeemableMintable.sol +++ b/src/extensions/ERC721ShipyardRedeemableMintable.sol @@ -13,8 +13,8 @@ import {IRedemptionMintable} from "../interfaces/IRedemptionMintable.sol"; import {TraitRedemption} from "../lib/RedeemablesStructs.sol"; contract ERC721ShipyardRedeemableMintable is ERC721ShipyardRedeemable, IRedemptionMintable { - /// @dev Revert if the sender of mintRedemption is not this contract. - error InvalidSender(); + /// @dev The ERC-7498 redeemables contracts. + address[] internal _erc7498RedeemablesContracts; /// @dev The next token id to mint. uint256 _nextTokenId = 1; @@ -27,15 +27,36 @@ contract ERC721ShipyardRedeemableMintable is ERC721ShipyardRedeemable, IRedempti ConsiderationItem[] calldata, /* consideration */ TraitRedemption[] calldata /* traitRedemptions */ ) external { - if (msg.sender != address(this)) { - revert InvalidSender(); - } + // Require that msg.sender is valid. + _requireValidRedeemablesCaller(); + // Increment nextTokenId first so more of the same token id cannot be minted through reentrancy. ++_nextTokenId; _mint(recipient, _nextTokenId - 1); } + function getRedeemablesContracts() external view returns (address[] memory) { + return _erc7498RedeemablesContracts; + } + + function setRedeemablesContracts(address[] calldata redeemablesContracts) external onlyOwner { + _erc7498RedeemablesContracts = redeemablesContracts; + } + + function _requireValidRedeemablesCaller() internal view { + // Allow the contract to call itself. + if (msg.sender == address(this)) return; + + bool validCaller; + for (uint256 i; i < _erc7498RedeemablesContracts.length; i++) { + if (msg.sender == _erc7498RedeemablesContracts[i]) { + validCaller = true; + } + } + if (!validCaller) revert InvalidCaller(msg.sender); + } + function supportsInterface(bytes4 interfaceId) public view diff --git a/src/lib/ERC1155ShipyardContractMetadata.sol b/src/lib/ERC1155ShipyardContractMetadata.sol index d010bf9..4325cc4 100644 --- a/src/lib/ERC1155ShipyardContractMetadata.sol +++ b/src/lib/ERC1155ShipyardContractMetadata.sol @@ -46,14 +46,25 @@ contract ERC1155ShipyardContractMetadata is ERC1155ConduitPreapproved_Solady, ER _initializeOwner(msg.sender); } + /** + * @notice Returns the name of this token contract. + */ function name() public view returns (string memory) { return _name; } + /** + * @notice Returns the symbol of this token contract. + */ function symbol() public view returns (string memory) { return _symbol; } + /** + * @notice Sets the base URI for the token metadata and emits an event. + * + * @param newURI The new base URI to set. + */ function setBaseURI(string calldata newURI) external onlyOwner { baseURI = newURI; @@ -61,6 +72,11 @@ contract ERC1155ShipyardContractMetadata is ERC1155ConduitPreapproved_Solady, ER emit BatchMetadataUpdate(0, type(uint256).max); } + /** + * @notice Sets the contract URI for contract metadata. + * + * @param newURI The new contract URI. + */ function setContractURI(string calldata newURI) external onlyOwner { // Set the new contract URI. contractURI = newURI; diff --git a/src/lib/ERC721ShipyardContractMetadata.sol b/src/lib/ERC721ShipyardContractMetadata.sol index f2139ce..d220b6b 100644 --- a/src/lib/ERC721ShipyardContractMetadata.sol +++ b/src/lib/ERC721ShipyardContractMetadata.sol @@ -46,14 +46,25 @@ contract ERC721ShipyardContractMetadata is ERC721ConduitPreapproved_Solady, ERC2 _initializeOwner(msg.sender); } + /** + * @notice Returns the name of this token contract. + */ function name() public view override returns (string memory) { return _name; } + /** + * @notice Returns the symbol of this token contract. + */ function symbol() public view override returns (string memory) { return _symbol; } + /** + * @notice Sets the base URI for the token metadata and emits an event. + * + * @param newURI The new base URI to set. + */ function setBaseURI(string calldata newURI) external onlyOwner { baseURI = newURI; @@ -61,6 +72,11 @@ contract ERC721ShipyardContractMetadata is ERC721ConduitPreapproved_Solady, ERC2 emit BatchMetadataUpdate(0, type(uint256).max); } + /** + * @notice Sets the contract URI for contract metadata. + * + * @param newURI The new contract URI. + */ function setContractURI(string calldata newURI) external onlyOwner { // Set the new contract URI. contractURI = newURI; @@ -97,6 +113,11 @@ contract ERC721ShipyardContractMetadata is ERC721ConduitPreapproved_Solady, ERC2 emit ProvenanceHashUpdated(oldProvenanceHash, newProvenanceHash); } + /** + * @notice Returns the token URI for token metadata. + * + * @param tokenId The token id to get the token URI for. + */ function tokenURI(uint256 tokenId) public view virtual override returns (string memory uri) { // Revert if the tokenId doesn't exist. if (!_exists(tokenId)) revert TokenDoesNotExist(); diff --git a/src/lib/ERC7498NFTRedeemables.sol b/src/lib/ERC7498NFTRedeemables.sol index 8982886..9fab898 100644 --- a/src/lib/ERC7498NFTRedeemables.sol +++ b/src/lib/ERC7498NFTRedeemables.sol @@ -136,10 +136,7 @@ contract ERC7498NFTRedeemables is IERC165, IERC7498, DynamicTraits, RedeemablesE // Revert if msg.sender is not the manager. address existingManager = _campaigns[campaignId].params.manager; - if ( - campaign.params.manager != msg.sender - && (existingManager != address(0) && existingManager != campaign.params.manager) - ) { + if (existingManager != msg.sender) { revert NotManager(); } @@ -164,7 +161,7 @@ contract ERC7498NFTRedeemables is IERC165, IERC7498, DynamicTraits, RedeemablesE } // Iterate over the requirements. - for (uint256 i = 0; i < campaign.requirements.length;) { + for (uint256 i; i < campaign.requirements.length;) { CampaignRequirements memory requirement = campaign.requirements[i]; // Validate each consideration item. diff --git a/src/lib/RedeemablesErrors.sol b/src/lib/RedeemablesErrors.sol index afec498..5f4c1a3 100644 --- a/src/lib/RedeemablesErrors.sol +++ b/src/lib/RedeemablesErrors.sol @@ -26,7 +26,6 @@ interface RedeemablesErrors { error InvalidRequiredTraitValue( address token, uint256 tokenId, bytes32 traitKey, bytes32 gotTraitValue, bytes32 wantTraitValue ); - //error InvalidSubstandard(uint256 substandard); error InvalidTraitRedemption(); error InvalidTraitRedemptionToken(address token); error ConsiderationRecipientNotFound(address token); diff --git a/src/test/ERC721ShipyardRedeemablePreapprovedTraitSetters.sol b/src/test/ERC721ShipyardRedeemableTraitSetters.sol similarity index 54% rename from src/test/ERC721ShipyardRedeemablePreapprovedTraitSetters.sol rename to src/test/ERC721ShipyardRedeemableTraitSetters.sol index e6893ad..9332745 100644 --- a/src/test/ERC721ShipyardRedeemablePreapprovedTraitSetters.sol +++ b/src/test/ERC721ShipyardRedeemableTraitSetters.sol @@ -6,26 +6,22 @@ import {ERC7498NFTRedeemables} from "../lib/ERC7498NFTRedeemables.sol"; import {DynamicTraits} from "shipyard-core/src/dynamic-traits/DynamicTraits.sol"; import {CampaignParams} from "../lib/RedeemablesStructs.sol"; -contract ERC721ShipyardRedeemablePreapprovedTraitSetters is ERC721ShipyardRedeemableOwnerMintable { +contract ERC721ShipyardRedeemableTraitSetters is ERC721ShipyardRedeemableOwnerMintable { // TODO add the `allowedTraitSetters` logic to DynamicTraits.sol contract in shipyard-core // with getAllowedTraitSetters() and setAllowedTraitSetters(). add `is DynamicTraits` to // ERC721ShipyardRedeemable and ERC721SeaDropRedeemable contracts with onlyOwner on setAllowedTraitSetters(). - address[] public allowedTraitSetters; + address[] _allowedTraitSetters; - constructor(string memory name_, string memory symbol_, address[] memory allowedTraitSetters_) + constructor(string memory name_, string memory symbol_, address[] memory allowedTraitSetters) ERC721ShipyardRedeemableOwnerMintable(name_, symbol_) { - allowedTraitSetters = allowedTraitSetters_; + _allowedTraitSetters = allowedTraitSetters; } function setTrait(uint256 tokenId, bytes32 traitKey, bytes32 value) public virtual override { - if (!_exists(tokenId)) { - revert TokenDoesNotExist(); - } + if (!_exists(tokenId)) revert TokenDoesNotExist(); - if (!_isPreapprovedTraitSetter(msg.sender)) { - revert InvalidCaller(msg.sender); - } + _requireAllowedTraitSetter(); DynamicTraits.setTrait(tokenId, traitKey, value); } @@ -37,33 +33,21 @@ contract ERC721ShipyardRedeemablePreapprovedTraitSetters is ERC721ShipyardRedeem override returns (bytes32 traitValue) { - if (!_exists(tokenId)) { - revert TokenDoesNotExist(); - } + if (!_exists(tokenId)) revert TokenDoesNotExist(); traitValue = DynamicTraits.getTraitValue(tokenId, traitKey); } - function _useInternalBurn() internal pure virtual override returns (bool) { - return true; - } + function _requireAllowedTraitSetter() internal view { + // Allow the contract to call itself. + if (msg.sender == address(this)) return; - function _internalBurn( - address, - /* from */ - uint256 id, - uint256 /* amount */ - ) internal virtual override { - _burn(id); - } - - function _isPreapprovedTraitSetter(address traitSetter) internal view returns (bool) { - for (uint256 i = 0; i < allowedTraitSetters.length; i++) { - if (allowedTraitSetters[i] == traitSetter) { - return true; + bool validCaller; + for (uint256 i; i < _allowedTraitSetters.length; i++) { + if (_allowedTraitSetters[i] == msg.sender) { + validCaller = true; } } - - return false; + if (!validCaller) revert InvalidCaller(msg.sender); } } diff --git a/test/ERC7498-GetAndUpdateCampaign.t.sol b/test/ERC7498-GetAndUpdateCampaign.t.sol index e8b1305..a866d25 100644 --- a/test/ERC7498-GetAndUpdateCampaign.t.sol +++ b/test/ERC7498-GetAndUpdateCampaign.t.sol @@ -7,6 +7,7 @@ import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationS import {OfferItemLib} from "seaport-sol/src/lib/OfferItemLib.sol"; import {ConsiderationItemLib} from "seaport-sol/src/lib/ConsiderationItemLib.sol"; import {IERC7498} from "../src/interfaces/IERC7498.sol"; +import {BURN_ADDRESS} from "../src/lib/RedeemablesConstants.sol"; import {Campaign, CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; import {RedeemablesErrors} from "../src/lib/RedeemablesErrors.sol"; @@ -60,5 +61,71 @@ contract ERC7498_GetAndUpdateCampaign is BaseRedeemablesTest { // Should revert if trying to get campaign id 0, since it starts at 1. vm.expectRevert(InvalidCampaignId.selector); context.erc7498Token.getCampaign(0); + + // Should revert if updating an invalid campaign id. + vm.expectRevert(InvalidCampaignId.selector); + context.erc7498Token.updateCampaign(0, campaign, "test111"); + vm.expectRevert(InvalidCampaignId.selector); + context.erc7498Token.updateCampaign(campaignId + 1, campaign, "test111"); + + // Update the campaign. + campaign.params.endTime = 0; + campaign.params.manager = address(0); + // Should expect revert with InvalidTime since endTime > startTime. + vm.expectRevert(InvalidTime.selector); + context.erc7498Token.updateCampaign(campaignId, campaign, "test456"); + + campaign.params.startTime = 0; + context.erc7498Token.updateCampaign(campaignId, campaign, "test456"); + + (gotCampaign, metadataURI, totalRedemptions) = IERC7498(context.erc7498Token).getCampaign(campaignId); + assertEq(keccak256(abi.encode(gotCampaign)), keccak256(abi.encode(campaign))); + assertEq(metadataURI, "test456"); + assertEq(totalRedemptions, 0); + + // Updating the campaign again should fail since the manager is now the null address. + vm.expectRevert(NotManager.selector); + context.erc7498Token.updateCampaign(campaignId, campaign, "test456"); + } + + function testCampaignReverts() public { + for (uint256 i; i < erc7498Tokens.length; i++) { + testRedeemable(this.campaignReverts, RedeemablesContext({erc7498Token: IERC7498(erc7498Tokens[i])})); + } + } + + function campaignReverts(RedeemablesContext memory context) external { + ConsiderationItem[] memory consideration = new ConsiderationItem[](1); + consideration[0] = _getCampaignConsiderationItem(address(context.erc7498Token)); + CampaignRequirements[] memory requirements = new CampaignRequirements[](1); + requirements[0] = CampaignRequirements({ + offer: defaultCampaignOffer, + consideration: consideration, + traitRedemptions: defaultTraitRedemptions + }); + CampaignParams memory params = CampaignParams({ + startTime: uint32(block.timestamp), + endTime: uint32(block.timestamp + 1000), + maxCampaignRedemptions: 5, + manager: address(this), + signer: address(0) + }); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + + consideration[0].recipient = payable(address(0)); + vm.expectRevert(ConsiderationItemRecipientCannotBeZeroAddress.selector); + context.erc7498Token.createCampaign(campaign, "test123"); + consideration[0].recipient = payable(BURN_ADDRESS); + + consideration[0].startAmount = 0; + consideration[0].endAmount = 0; + vm.expectRevert(ConsiderationItemAmountCannotBeZero.selector); + context.erc7498Token.createCampaign(campaign, "test123"); + consideration[0].startAmount = 1; + consideration[0].endAmount = 1; + + consideration[0].endAmount = 2; + vm.expectRevert(abi.encodeWithSelector(NonMatchingConsiderationItemAmounts.selector, 0, 1, 2)); + context.erc7498Token.createCampaign(campaign, "test123"); } } diff --git a/test/ERC7498-MultiRedeem.t.sol b/test/ERC7498-MultiRedeem.t.sol index 18cc513..7f98d31 100644 --- a/test/ERC7498-MultiRedeem.t.sol +++ b/test/ERC7498-MultiRedeem.t.sol @@ -10,7 +10,7 @@ import {OfferItemLib} from "seaport-sol/src/lib/OfferItemLib.sol"; import {ConsiderationItemLib} from "seaport-sol/src/lib/ConsiderationItemLib.sol"; import {IERC7498} from "../src/interfaces/IERC7498.sol"; import {Campaign, CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; -import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; +import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; import {ERC1155ShipyardRedeemableMintable} from "../src/extensions/ERC1155ShipyardRedeemableMintable.sol"; import {ERC721ShipyardRedeemableOwnerMintable} from "../src/test/ERC721ShipyardRedeemableOwnerMintable.sol"; import {ERC1155ShipyardRedeemableOwnerMintable} from "../src/test/ERC1155ShipyardRedeemableOwnerMintable.sol"; @@ -54,11 +54,11 @@ contract ERC7498_MultiRedeem is BaseRedeemablesTest { } _mintToken(secondRedeemTokenAddress, tokenId); - ERC721RedemptionMintable receiveToken = new ERC721RedemptionMintable( + ERC721ShipyardRedeemableMintable receiveToken = new ERC721ShipyardRedeemableMintable( "", - "", - erc7498Tokens + "" ); + receiveToken.setRedeemablesContracts(erc7498Tokens); ConsiderationItem[] memory consideration = new ConsiderationItem[](2); consideration[0] = _getCampaignConsiderationItem(address(context.erc7498Token)); consideration[1] = _getCampaignConsiderationItem(secondRedeemTokenAddress); diff --git a/test/ERC7498-SimpleRedeem.t.sol b/test/ERC7498-SimpleRedeem.t.sol index 2a27c80..7e5b3f6 100644 --- a/test/ERC7498-SimpleRedeem.t.sol +++ b/test/ERC7498-SimpleRedeem.t.sol @@ -43,14 +43,14 @@ contract ERC7498_SimpleRedeem is BaseRedeemablesTest { traitRedemptions: defaultTraitRedemptions }); CampaignParams memory params = CampaignParams({ - startTime: uint32(block.timestamp), - endTime: uint32(block.timestamp + 1000), - maxCampaignRedemptions: 5, + startTime: 0, + endTime: 0, // will revert with NotActive until updated + maxCampaignRedemptions: 1, manager: address(this), signer: address(0) }); Campaign memory campaign = Campaign({params: params, requirements: requirements}); - context.erc7498Token.createCampaign(campaign, ""); + uint256 campaignId = context.erc7498Token.createCampaign(campaign, ""); (,, uint256 totalRedemptionsPreRedeem) = context.erc7498Token.getCampaign(1); assertEq(totalRedemptionsPreRedeem, 0); @@ -64,6 +64,18 @@ contract ERC7498_SimpleRedeem is BaseRedeemablesTest { bytes("") // signature ); uint256[] memory considerationTokenIds = Solarray.uint256s(tokenId); + + vm.expectRevert( + abi.encodeWithSelector( + NotActive_.selector, block.timestamp, campaign.params.startTime, campaign.params.endTime + ) + ); + context.erc7498Token.redeem(considerationTokenIds, address(0), extraData); + + // Update the campaign to an active endTime. + campaign.params.endTime = uint32(block.timestamp + 1000); + context.erc7498Token.updateCampaign(campaignId, campaign, ""); + vm.expectEmit(true, true, true, true); emit Redemption(1, 0, bytes32(0), considerationTokenIds, defaultTraitRedemptionTokenIds, address(this)); // Using address(0) for recipient should assign to msg.sender. @@ -73,6 +85,15 @@ contract ERC7498_SimpleRedeem is BaseRedeemablesTest { assertEq(receiveToken721.ownerOf(1), address(this)); (,, uint256 totalRedemptionsPostRedeem) = context.erc7498Token.getCampaign(1); assertEq(totalRedemptionsPostRedeem, 1); + + // Redeeming one more should exceed maxCampaignRedemptions of 1. + tokenId = 3; + _mintToken(address(context.erc7498Token), tokenId); + considerationTokenIds[0] = tokenId; + vm.expectRevert(abi.encodeWithSelector(MaxCampaignRedemptionsReached.selector, 2, 1)); + context.erc7498Token.redeem(considerationTokenIds, address(0), extraData); + // Reset tokenId back to its original value. + tokenId = 2; } function testBurnErc721RedeemErc721WithSecondRequirementsIndex() public { diff --git a/test/ERC7498-DynamicTraits.t.sol b/test/ERC7498-TraitRedemption.t.sol similarity index 79% rename from test/ERC7498-DynamicTraits.t.sol rename to test/ERC7498-TraitRedemption.t.sol index 8f856f6..59de32e 100644 --- a/test/ERC7498-DynamicTraits.t.sol +++ b/test/ERC7498-TraitRedemption.t.sol @@ -10,14 +10,13 @@ import {OfferItemLib} from "seaport-sol/src/lib/OfferItemLib.sol"; import {ConsiderationItemLib} from "seaport-sol/src/lib/ConsiderationItemLib.sol"; import {IERC7498} from "../src/interfaces/IERC7498.sol"; import {Campaign, CampaignParams, CampaignRequirements, TraitRedemption} from "../src/lib/RedeemablesStructs.sol"; -import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; +import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; import {ERC1155ShipyardRedeemableMintable} from "../src/extensions/ERC1155ShipyardRedeemableMintable.sol"; import {ERC721ShipyardRedeemableOwnerMintable} from "../src/test/ERC721ShipyardRedeemableOwnerMintable.sol"; import {ERC1155ShipyardRedeemableOwnerMintable} from "../src/test/ERC1155ShipyardRedeemableOwnerMintable.sol"; -import {ERC721ShipyardRedeemablePreapprovedTraitSetters} from - "../src/test/ERC721ShipyardRedeemablePreapprovedTraitSetters.sol"; +import {ERC721ShipyardRedeemableTraitSetters} from "../src/test/ERC721ShipyardRedeemableTraitSetters.sol"; -contract ERC7498_DynamicTraits is BaseRedeemablesTest { +contract ERC7498_TraitRedemption is BaseRedeemablesTest { using OfferItemLib for OfferItem; using OfferItemLib for OfferItem[]; using ConsiderationItemLib for ConsiderationItem; @@ -38,8 +37,7 @@ contract ERC7498_DynamicTraits is BaseRedeemablesTest { function erc721TraitRedemptionSubstandardOneForErc721(RedeemablesContext memory context) public { address[] memory allowedTraitSetters = new address[](1); allowedTraitSetters[0] = address(context.erc7498Token); - ERC721ShipyardRedeemablePreapprovedTraitSetters redeemToken = - new ERC721ShipyardRedeemablePreapprovedTraitSetters( + ERC721ShipyardRedeemableTraitSetters redeemToken = new ERC721ShipyardRedeemableTraitSetters( "", "", allowedTraitSetters @@ -76,7 +74,8 @@ contract ERC7498_DynamicTraits is BaseRedeemablesTest { context.erc7498Token.createCampaign(campaign, ""); uint256[] memory considerationTokenIds; - uint256[] memory traitRedemptionTokenIds = Solarray.uint256s(tokenId); + // First test reverts with TraitRedemptionTokenIdsDontMatchTraitRedemptionsLength. + uint256[] memory traitRedemptionTokenIds = Solarray.uint256s(tokenId, tokenId + 1); bytes memory extraData = abi.encode( 1, // campaignId 0, // requirementsIndex @@ -85,6 +84,20 @@ contract ERC7498_DynamicTraits is BaseRedeemablesTest { uint256(0), // salt bytes("") // signature ); + + vm.expectRevert(abi.encodeWithSelector(TraitRedemptionTokenIdsDontMatchTraitRedemptionsLength.selector, 1, 2)); + context.erc7498Token.redeem(considerationTokenIds, address(this), extraData); + + // Now test with valid trait redemptions token ids length. + traitRedemptionTokenIds = Solarray.uint256s(tokenId); + extraData = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + traitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); vm.expectEmit(true, true, true, true); emit Redemption(1, 0, bytes32(0), considerationTokenIds, traitRedemptionTokenIds, address(this)); context.erc7498Token.redeem(considerationTokenIds, address(this), extraData); diff --git a/test/RedeemableContractOfferer-1155.t.sol.txt b/test/RedeemableContractOfferer-1155.t.sol.txt index 7999f31..5bf7714 100644 --- a/test/RedeemableContractOfferer-1155.t.sol.txt +++ b/test/RedeemableContractOfferer-1155.t.sol.txt @@ -22,9 +22,9 @@ import {RedeemableContractOfferer} from "../src/RedeemableContractOfferer.sol"; import {CampaignParams, TraitRedemption} from "../src/lib/RedeemablesStructs.sol"; import {BURN_ADDRESS} from "../src/lib/RedeemablesConstants.sol"; import {RedeemablesErrors} from "../src/lib/RedeemablesErrors.sol"; -import {ERC1155RedemptionMintable} from "../src/lib/ERC1155RedemptionMintable.sol"; -import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; -import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; +import {ERC1155ShipyardRedeemableMintable} from "../src/lib/ERC1155ShipyardRedeemableMintable.sol"; +import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; +import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; import {Merkle} from "../lib/murky/src/Merkle.sol"; contract TestRedeemableContractOfferer_1155 is BaseOrderTest, RedeemablesErrors { @@ -34,7 +34,7 @@ contract TestRedeemableContractOfferer_1155 is BaseOrderTest, RedeemablesErrors RedeemableContractOfferer offerer; TestERC1155 redeemableToken; - ERC1155RedemptionMintable redemptionToken; + ERC1155ShipyardRedeemableMintable redemptionToken; CriteriaResolver[] criteriaResolvers; Merkle merkle = new Merkle(); @@ -48,7 +48,7 @@ contract TestRedeemableContractOfferer_1155 is BaseOrderTest, RedeemablesErrors address(seaport) ); redeemableToken = new TestERC1155(); - redemptionToken = new ERC1155RedemptionMintable( + redemptionToken = new ERC1155ShipyardRedeemableMintable( address(offerer), address(redeemableToken) ); @@ -551,7 +551,7 @@ contract TestRedeemableContractOfferer_1155 is BaseOrderTest, RedeemablesErrors // // Set the tokenId to be burned to the first tokenId to be redeemed // uint256 redeemableTokenId0 = redemptionTokenId0; - // ERC721RedemptionMintable redemptionTokenWithCounter = new ERC721RedemptionMintable( + // ERC721ShipyardRedeemableMintable redemptionTokenWithCounter = new ERC721ShipyardRedeemableMintable( // address(offerer), // address(redeemableToken) // ); @@ -858,8 +858,8 @@ contract TestRedeemableContractOfferer_1155 is BaseOrderTest, RedeemablesErrors // // Set the tokenId to be burned to the first tokenId to be redeemed // uint256 redeemableTokenId = redemptionTokenId; - // // Create a new ERC721RedemptionMintable redemptionTokenTwo - // ERC721RedemptionMintable redemptionTokenTwo = new ERC721RedemptionMintable( + // // Create a new ERC721ShipyardRedeemableMintable redemptionTokenTwo + // ERC721ShipyardRedeemableMintable redemptionTokenTwo = new ERC721ShipyardRedeemableMintable( // address(offerer), // address(redeemableToken) // ); diff --git a/test/RedeemableContractOfferer-721.t.sol.txt b/test/RedeemableContractOfferer-721.t.sol.txt index a61e880..d682718 100644 --- a/test/RedeemableContractOfferer-721.t.sol.txt +++ b/test/RedeemableContractOfferer-721.t.sol.txt @@ -21,8 +21,8 @@ import {RedeemableContractOfferer} from "../src/RedeemableContractOfferer.sol"; import {CampaignParams, TraitRedemption} from "../src/lib/RedeemablesStructs.sol"; import {BURN_ADDRESS} from "../src/lib/RedeemablesConstants.sol"; import {RedeemablesErrors} from "../src/lib/RedeemablesErrors.sol"; -import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; -import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; +import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; +import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; import {Merkle} from "../lib/murky/src/Merkle.sol"; contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { @@ -32,7 +32,7 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { RedeemableContractOfferer offerer; TestERC721 redeemableToken; - ERC721RedemptionMintable redemptionToken; + ERC721ShipyardRedeemableMintable redemptionToken; CriteriaResolver[] criteriaResolvers; Merkle merkle = new Merkle(); @@ -46,7 +46,7 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { address(seaport) ); redeemableToken = new TestERC721(); - redemptionToken = new ERC721RedemptionMintable( + redemptionToken = new ERC721ShipyardRedeemableMintable( address(offerer), address(redeemableToken) ); @@ -275,7 +275,7 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { _mintToken(address(redeemableToken), tokenId); redeemableToken.setApprovalForAll(address(conduit), true); - ERC721RedemptionMintable redemptionTokenWithCounter = new ERC721RedemptionMintable( + ERC721ShipyardRedeemableMintable redemptionTokenWithCounter = new ERC721ShipyardRedeemableMintable( address(offerer), address(redeemableToken) ); @@ -538,7 +538,7 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { // Set the tokenId to be burned to the first tokenId to be redeemed uint256 redeemableTokenId0 = redemptionTokenId0; - ERC721RedemptionMintable redemptionTokenWithCounter = new ERC721RedemptionMintable( + ERC721ShipyardRedeemableMintable redemptionTokenWithCounter = new ERC721ShipyardRedeemableMintable( address(offerer), address(redeemableToken) ); @@ -818,8 +818,8 @@ contract TestRedeemableContractOfferer_721 is BaseOrderTest, RedeemablesErrors { // Set the tokenId to be burned to the first tokenId to be redeemed uint256 redeemableTokenId = redemptionTokenId; - // Create a new ERC721RedemptionMintable redemptionTokenTwo - ERC721RedemptionMintable redemptionTokenTwo = new ERC721RedemptionMintable( + // Create a new ERC721ShipyardRedeemableMintable redemptionTokenTwo + ERC721ShipyardRedeemableMintable redemptionTokenTwo = new ERC721ShipyardRedeemableMintable( address(offerer), address(redeemableToken) ); diff --git a/test/RedeemableContractOfferer-Revert.t.sol.txt b/test/RedeemableContractOfferer-Revert.t.sol.txt index 5c58737..2bf19fa 100644 --- a/test/RedeemableContractOfferer-Revert.t.sol.txt +++ b/test/RedeemableContractOfferer-Revert.t.sol.txt @@ -21,8 +21,8 @@ import {RedeemableContractOfferer} from "../src/RedeemableContractOfferer.sol"; import {CampaignParams} from "../src/lib/RedeemablesStructs.sol"; import {BURN_ADDRESS} from "../src/lib/RedeemablesConstants.sol"; import {RedeemablesErrors} from "../src/lib/RedeemablesErrors.sol"; -import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; -import {ERC721RedemptionMintable} from "../src/extensions/ERC721RedemptionMintable.sol"; +import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; +import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; import {Merkle} from "../lib/murky/src/Merkle.sol"; contract TestRedeemableContractOfferer_Revert is BaseOrderTest, RedeemablesErrors { @@ -32,7 +32,7 @@ contract TestRedeemableContractOfferer_Revert is BaseOrderTest, RedeemablesError RedeemableContractOfferer offerer; TestERC721 redeemableToken; - ERC721RedemptionMintable redemptionToken; + ERC721ShipyardRedeemableMintable redemptionToken; CriteriaResolver[] criteriaResolvers; Merkle merkle = new Merkle(); @@ -44,7 +44,7 @@ contract TestRedeemableContractOfferer_Revert is BaseOrderTest, RedeemablesError address(seaport) ); redeemableToken = new TestERC721(); - redemptionToken = new ERC721RedemptionMintable( + redemptionToken = new ERC721ShipyardRedeemableMintable( address(offerer), address(redeemableToken) ); @@ -175,7 +175,7 @@ contract TestRedeemableContractOfferer_Revert is BaseOrderTest, RedeemablesError _mintToken(address(redeemableToken), 2); redeemableToken.setApprovalForAll(address(conduit), true); - ERC721RedemptionMintable redemptionTokenWithCounter = new ERC721RedemptionMintable( + ERC721ShipyardRedeemableMintable redemptionTokenWithCounter = new ERC721ShipyardRedeemableMintable( address(offerer), address(redeemableToken) ); diff --git a/test/utils/BaseRedeemablesTest.sol b/test/utils/BaseRedeemablesTest.sol index 7ce7ced..8ba33d4 100644 --- a/test/utils/BaseRedeemablesTest.sol +++ b/test/utils/BaseRedeemablesTest.sol @@ -18,8 +18,8 @@ import {TestERC1155} from "../utils/mocks/TestERC1155.sol"; import {OfferItemLib, ConsiderationItemLib} from "seaport-sol/src/SeaportSol.sol"; import {OfferItem, ConsiderationItem} from "seaport-sol/src/SeaportStructs.sol"; import {ItemType} from "seaport-sol/src/SeaportEnums.sol"; -import {ERC721RedemptionMintable} from "../../src/extensions/ERC721RedemptionMintable.sol"; -import {ERC1155RedemptionMintable} from "../../src/extensions/ERC1155RedemptionMintable.sol"; +import {ERC721ShipyardRedeemableMintable} from "../../src/extensions/ERC721ShipyardRedeemableMintable.sol"; +import {ERC1155ShipyardRedeemableMintable} from "../../src/extensions/ERC1155ShipyardRedeemableMintable.sol"; import {ERC721SeaDropRedeemableOwnerMintable} from "../../src/test/ERC721SeaDropRedeemableOwnerMintable.sol"; import {ERC721ShipyardRedeemableOwnerMintable} from "../../src/test/ERC721ShipyardRedeemableOwnerMintable.sol"; import {ERC1155ShipyardRedeemableOwnerMintable} from "../../src/test/ERC1155ShipyardRedeemableOwnerMintable.sol"; @@ -54,8 +54,8 @@ contract BaseRedeemablesTest is RedeemablesErrors, BaseOrderTest { ERC721SeaDropRedeemableOwnerMintable erc721SeaDropRedeemable; ERC1155ShipyardRedeemableOwnerMintable erc1155ShipyardRedeemable; ERC1155SeaDropRedeemableOwnerMintable erc1155SeaDropRedeemable; - ERC721RedemptionMintable receiveToken721; - ERC1155RedemptionMintable receiveToken1155; + ERC721ShipyardRedeemableMintable receiveToken721; + ERC1155ShipyardRedeemableMintable receiveToken1155; OfferItem[] defaultCampaignOffer; ConsiderationItem[] defaultCampaignConsideration; @@ -95,6 +95,7 @@ contract BaseRedeemablesTest is RedeemablesErrors, BaseOrderTest { erc721SeaDropRedeemable.setMaxSupply(10); erc1155SeaDropRedeemable.setMaxSupply(1, 10); erc1155SeaDropRedeemable.setMaxSupply(2, 10); + erc1155SeaDropRedeemable.setMaxSupply(3, 10); erc7498Tokens = new address[](4); erc7498Tokens[0] = address(erc721ShipyardRedeemable); @@ -107,8 +108,10 @@ contract BaseRedeemablesTest is RedeemablesErrors, BaseOrderTest { vm.label(erc7498Tokens[2], "erc1155ShipyardRedeemable"); vm.label(erc7498Tokens[3], "erc1155SeaDropRedeemable"); - receiveToken721 = new ERC721RedemptionMintable("", "", erc7498Tokens); - receiveToken1155 = new ERC1155RedemptionMintable("", "", erc7498Tokens); + receiveToken721 = new ERC721ShipyardRedeemableMintable("", ""); + receiveToken1155 = new ERC1155ShipyardRedeemableMintable("", ""); + receiveToken721.setRedeemablesContracts(erc7498Tokens); + receiveToken1155.setRedeemablesContracts(erc7498Tokens); _setApprovals(address(this)); From 2a4e69d3e6d365dc811b5b1a7083915ce11bb3e1 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Wed, 15 Nov 2023 12:48:23 -0800 Subject: [PATCH 4/6] more coverage --- test/ERC7498-SimpleRedeem.t.sol | 104 +++++++++++++++++++++++++++-- test/ERC7498-TraitRedemption.t.sol | 8 +++ test/utils/BaseRedeemablesTest.sol | 16 +++++ test/utils/mocks/TestERC20.sol | 6 ++ 4 files changed, 128 insertions(+), 6 deletions(-) diff --git a/test/ERC7498-SimpleRedeem.t.sol b/test/ERC7498-SimpleRedeem.t.sol index 7e5b3f6..52f7838 100644 --- a/test/ERC7498-SimpleRedeem.t.sol +++ b/test/ERC7498-SimpleRedeem.t.sol @@ -96,6 +96,59 @@ contract ERC7498_SimpleRedeem is BaseRedeemablesTest { tokenId = 2; } + function testSendErc721OrErc1155RedeemErc721() public { + for (uint256 i; i < erc7498Tokens.length; i++) { + testRedeemable( + this.sendErc721OrErc1155RedeemErc721, RedeemablesContext({erc7498Token: IERC7498(erc7498Tokens[i])}) + ); + } + } + + function sendErc721OrErc1155RedeemErc721(RedeemablesContext memory context) external { + _mintToken(address(context.erc7498Token), tokenId); + ConsiderationItem[] memory consideration = new ConsiderationItem[](1); + consideration[0] = _getCampaignConsiderationItem(address(context.erc7498Token)); + // Set consideration recipient to greg. + address greg = makeAddr("greg"); + consideration[0].recipient = payable(greg); + CampaignRequirements[] memory requirements = new CampaignRequirements[]( + 1 + ); + requirements[0] = CampaignRequirements({ + offer: defaultCampaignOffer, + consideration: consideration, + traitRedemptions: defaultTraitRedemptions + }); + CampaignParams memory params = CampaignParams({ + startTime: uint32(block.timestamp), + endTime: uint32(block.timestamp + 1000), + maxCampaignRedemptions: 1, + manager: address(this), + signer: address(0) + }); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + context.erc7498Token.createCampaign(campaign, ""); + + // Grant approval to the erc7498Token. + IERC721(address(context.erc7498Token)).setApprovalForAll(address(context.erc7498Token), true); + + bytes memory extraData = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + defaultTraitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); + uint256[] memory considerationTokenIds = Solarray.uint256s(tokenId); + vm.expectEmit(true, true, true, true); + emit Redemption(1, 0, bytes32(0), considerationTokenIds, defaultTraitRedemptionTokenIds, address(this)); + context.erc7498Token.redeem(considerationTokenIds, address(0), extraData); + + _checkTokenIsOwnedBy(address(context.erc7498Token), tokenId, greg); + assertEq(receiveToken721.ownerOf(1), address(this)); + } + function testBurnErc721RedeemErc721WithSecondRequirementsIndex() public { for (uint256 i; i < erc7498Tokens.length; i++) { testRedeemable( @@ -180,12 +233,6 @@ contract ERC7498_SimpleRedeem is BaseRedeemablesTest { } function testBurnErc20RedeemErc721() public { - for (uint256 i; i < erc7498Tokens.length; i++) { - testRedeemable(this.burnErc20RedeemErc721, RedeemablesContext({erc7498Token: IERC7498(erc7498Tokens[i])})); - } - } - - function burnErc20RedeemErc721(RedeemablesContext memory /* context */ ) public { erc20s[0].mint(address(this), 0.5 ether); CampaignRequirements[] memory requirements = new CampaignRequirements[]( 1 @@ -228,6 +275,51 @@ contract ERC7498_SimpleRedeem is BaseRedeemablesTest { assertEq(receiveToken721.ownerOf(1), address(this)); } + function testSendErc20RedeemErc721() public { + erc20s[0].mint(address(this), 0.5 ether); + CampaignRequirements[] memory requirements = new CampaignRequirements[]( + 1 + ); + ConsiderationItem[] memory consideration = new ConsiderationItem[](1); + consideration[0] = defaultCampaignConsideration[0].withToken(address(erc20s[0])).withItemType(ItemType.ERC20) + .withStartAmount(0.5 ether).withEndAmount(0.5 ether); + // Set consideration recipient to greg. + address greg = makeAddr("greg"); + consideration[0].recipient = payable(greg); + requirements[0] = CampaignRequirements({ + offer: defaultCampaignOffer, + consideration: consideration, + traitRedemptions: defaultTraitRedemptions + }); + CampaignParams memory params = CampaignParams({ + startTime: uint32(block.timestamp), + endTime: uint32(block.timestamp + 1000), + maxCampaignRedemptions: 5, + manager: address(this), + signer: address(0) + }); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + IERC7498(erc7498Tokens[0]).createCampaign(campaign, ""); + + bytes memory extraData = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + defaultTraitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); + + uint256[] memory considerationTokenIds = Solarray.uint256s(0); + + vm.expectEmit(true, true, true, true); + emit Redemption(1, 0, bytes32(0), considerationTokenIds, defaultTraitRedemptionTokenIds, address(this)); + IERC7498(erc7498Tokens[0]).redeem(considerationTokenIds, address(this), extraData); + + _checkTokenIsOwnedBy(address(erc20s[0]), tokenId, greg); + assertEq(receiveToken721.ownerOf(1), address(this)); + } + function testBurnErc721RedeemErc1155() public { _mintToken(address(erc7498Tokens[0]), tokenId); CampaignRequirements[] memory requirements = new CampaignRequirements[]( diff --git a/test/ERC7498-TraitRedemption.t.sol b/test/ERC7498-TraitRedemption.t.sol index 59de32e..7fb0ff1 100644 --- a/test/ERC7498-TraitRedemption.t.sol +++ b/test/ERC7498-TraitRedemption.t.sol @@ -105,5 +105,13 @@ contract ERC7498_TraitRedemption is BaseRedeemablesTest { bytes32 actualTraitValue = redeemToken.getTraitValue(tokenId, traitKey); assertEq(bytes32(uint256(1)), actualTraitValue); assertEq(receiveToken721.ownerOf(1), address(this)); + + // Redeeming one more time should fail with InvalidRequiredTraitValue since it is already 1. + vm.expectRevert( + abi.encodeWithSelector( + InvalidRequiredTraitValue.selector, redeemToken, tokenId, traitKey, bytes32(uint256(1)), bytes32(0) + ) + ); + context.erc7498Token.redeem(considerationTokenIds, address(this), extraData); } } diff --git a/test/utils/BaseRedeemablesTest.sol b/test/utils/BaseRedeemablesTest.sol index 8ba33d4..fecad6d 100644 --- a/test/utils/BaseRedeemablesTest.sol +++ b/test/utils/BaseRedeemablesTest.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.19; import {Solarray} from "solarray/Solarray.sol"; import {ERC721} from "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol"; import {ERC1155} from "openzeppelin-contracts/contracts/token/ERC1155/ERC1155.sol"; +import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; import {IERC165} from "openzeppelin-contracts/contracts/interfaces/IERC165.sol"; import {IERC721A} from "seadrop/lib/ERC721A/contracts/IERC721A.sol"; import {IERC721} from "openzeppelin-contracts/contracts/interfaces/IERC721.sol"; @@ -197,6 +198,17 @@ contract BaseRedeemablesTest is RedeemablesErrors, BaseOrderTest { } } + function _checkTokenIsOwnedBy(address token, uint256 tokenId, address owner) internal { + if (_isERC721(token)) { + assertEq(IERC721(address(token)).ownerOf(tokenId), owner); + } else if (_isERC20(token)) { + assertGt(IERC20(address(token)).balanceOf(owner), 0); + } else { + // token is ERC1155 + assertGt(IERC1155(address(token)).balanceOf(owner, tokenId), 0); + } + } + function _mintToken(address token, uint256 tokenId) internal { if (_isERC721(token)) { ERC721ShipyardRedeemableOwnerMintable(address(token)).mint(address(this), tokenId); @@ -218,4 +230,8 @@ contract BaseRedeemablesTest is RedeemablesErrors, BaseOrderTest { function _isERC721(address token) internal view returns (bool isERC721) { isERC721 = IERC165(token).supportsInterface(type(IERC721).interfaceId); } + + function _isERC20(address token) internal view returns (bool isERC20) { + isERC20 = IERC165(token).supportsInterface(type(IERC20).interfaceId); + } } diff --git a/test/utils/mocks/TestERC20.sol b/test/utils/mocks/TestERC20.sol index bb00468..6175ab7 100644 --- a/test/utils/mocks/TestERC20.sol +++ b/test/utils/mocks/TestERC20.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.19; +import {IERC165} from "openzeppelin-contracts/contracts/interfaces/IERC165.sol"; +import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; import {ERC20} from "solady/src/tokens/ERC20.sol"; // Used for minting test ERC20s in our tests @@ -56,4 +58,8 @@ contract TestERC20 is ERC20 { function symbol() public view virtual override returns (string memory) { return "TST20"; } + + function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { + return interfaceId == type(IERC20).interfaceId; + } } From ed195d5af8b4db76889f78ebc4dc5c896666f6ae Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Wed, 15 Nov 2023 15:15:52 -0800 Subject: [PATCH 5/6] coverage for IShipyardContractMetadata, more misc coverage --- script/DeployAndConfigure1155Receive.s.sol | 1 - ...dRedeemTokens-CampaignOnReceiveToken.s.sol | 8 +- script/DeployAndRedeemTrait.s.sol | 1 - ...kenWithPredeployedSeadropRedeemToken.s.sol | 3 +- script/RedeemTokens.s.sol | 2 +- src/ERC1155SeaDropRedeemable.sol | 10 -- src/ERC1155ShipyardRedeemable.sol | 10 -- src/ERC721SeaDropRedeemable.sol | 12 ++ src/ERC721ShipyardRedeemable.sol | 12 ++ src/interfaces/IShipyardContractMetadata.sol | 37 +++++ src/lib/ERC1155ShipyardContractMetadata.sol | 25 +-- src/lib/ERC721ShipyardContractMetadata.sol | 29 ++-- src/test/ERC721OwnerMintable.sol | 43 ----- .../ERC721ShipyardRedeemableTraitSetters.sol | 12 -- test/ERC7498-RedemptionMintable.t.sol | 6 +- test/ERC7498-SimpleRedeem.t.sol | 14 ++ test/ERC7498-TraitRedemption.t.sol | 30 ++++ test/ShipyardContractMetadata.t.sol | 154 ++++++++++++++++++ test/utils/BaseRedeemablesTest.sol | 14 +- 19 files changed, 299 insertions(+), 124 deletions(-) create mode 100644 src/interfaces/IShipyardContractMetadata.sol delete mode 100644 src/test/ERC721OwnerMintable.sol create mode 100644 test/ShipyardContractMetadata.t.sol diff --git a/script/DeployAndConfigure1155Receive.s.sol b/script/DeployAndConfigure1155Receive.s.sol index 4ed3d7e..17b3000 100644 --- a/script/DeployAndConfigure1155Receive.s.sol +++ b/script/DeployAndConfigure1155Receive.s.sol @@ -8,7 +8,6 @@ import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationS import {Campaign, CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; import {BURN_ADDRESS} from "../src/lib/RedeemablesConstants.sol"; import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; -import {ERC721OwnerMintable} from "../src/test/ERC721OwnerMintable.sol"; import {ERC1155ShipyardRedeemableMintable} from "../src/extensions/ERC1155ShipyardRedeemableMintable.sol"; contract DeployAndConfigure1155Receive is Script, Test { diff --git a/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol b/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol index bf9bc53..33ef668 100644 --- a/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol +++ b/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol @@ -8,16 +8,16 @@ import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationS import {Campaign, CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; import {BURN_ADDRESS} from "../src/lib/RedeemablesConstants.sol"; import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; -import {ERC721OwnerMintable} from "../src/test/ERC721OwnerMintable.sol"; -import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; +import {ERC721ShipyardRedeemableOwnerMintable} from "../src/test/ERC721ShipyardRedeemableOwnerMintable.sol"; contract DeployAndRedeemTokens_CampaignOnReceiveToken is Script, Test { function run() external { vm.startBroadcast(); - ERC721OwnerMintable redeemToken = new ERC721OwnerMintable(); + ERC721ShipyardRedeemableOwnerMintable redeemToken = + new ERC721ShipyardRedeemableOwnerMintable("TestRedeemablesRedeemToken", "TEST-RDM"); ERC721ShipyardRedeemableMintable receiveToken = - new ERC721ShipyardRedeemableMintable("TestRedeemablesReceiveToken", "TEST"); + new ERC721ShipyardRedeemableMintable("TestRedeemablesReceiveToken", "TEST-RCV"); // Configure the campaign. OfferItem[] memory offer = new OfferItem[](1); diff --git a/script/DeployAndRedeemTrait.s.sol b/script/DeployAndRedeemTrait.s.sol index f9484fa..8ef6356 100644 --- a/script/DeployAndRedeemTrait.s.sol +++ b/script/DeployAndRedeemTrait.s.sol @@ -8,7 +8,6 @@ import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationS import {IERC7496} from "shipyard-core/src/dynamic-traits/interfaces/IERC7496.sol"; import {Campaign, CampaignParams, CampaignRequirements, TraitRedemption} from "../src/lib/RedeemablesStructs.sol"; import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; -import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; import {ERC721ShipyardRedeemableTraitSetters} from "../src/test/ERC721ShipyardRedeemableTraitSetters.sol"; contract DeployAndRedeemTrait is Script, Test { diff --git a/script/DeployERC721ReceiveTokenWithPredeployedSeadropRedeemToken.s.sol b/script/DeployERC721ReceiveTokenWithPredeployedSeadropRedeemToken.s.sol index a5ea12d..55b558d 100644 --- a/script/DeployERC721ReceiveTokenWithPredeployedSeadropRedeemToken.s.sol +++ b/script/DeployERC721ReceiveTokenWithPredeployedSeadropRedeemToken.s.sol @@ -8,8 +8,7 @@ import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationS import {Campaign, CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; import {BURN_ADDRESS} from "../src/lib/RedeemablesConstants.sol"; import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; -import {ERC721OwnerMintable} from "../src/test/ERC721OwnerMintable.sol"; -import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; +import {ERC721ShipyardRedeemableOwnerMintable} from "../src/test/ERC721ShipyardRedeemableOwnerMintable.sol"; contract DeployERC721ReceiveTokenWithPredeployedSeaDropRedeemToken is Script, Test { function run() external { diff --git a/script/RedeemTokens.s.sol b/script/RedeemTokens.s.sol index 63a1505..113e4f9 100644 --- a/script/RedeemTokens.s.sol +++ b/script/RedeemTokens.s.sol @@ -7,7 +7,7 @@ import {ItemType} from "seaport-types/src/lib/ConsiderationEnums.sol"; import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {CampaignParams, CampaignRequirements} from "../src/lib/RedeemablesStructs.sol"; import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; -import {ERC721OwnerMintable} from "../src/test/ERC721OwnerMintable.sol"; +import {ERC721ShipyardRedeemableOwnerMintable} from "../src/test/ERC721ShipyardRedeemableOwnerMintable.sol"; import {ERC1155ShipyardRedeemableMintable} from "../src/extensions/ERC1155ShipyardRedeemableMintable.sol"; contract RedeemTokens is Script, Test { diff --git a/src/ERC1155SeaDropRedeemable.sol b/src/ERC1155SeaDropRedeemable.sol index 8c039b2..e7ff578 100644 --- a/src/ERC1155SeaDropRedeemable.sol +++ b/src/ERC1155SeaDropRedeemable.sol @@ -26,16 +26,6 @@ contract ERC1155SeaDropRedeemable is ERC1155SeaDrop, ERC7498NFTRedeemables { DynamicTraits.setTrait(tokenId, traitKey, value); } - function getTraitValue(uint256 tokenId, bytes32 traitKey) - public - view - virtual - override - returns (bytes32 traitValue) - { - traitValue = DynamicTraits.getTraitValue(tokenId, traitKey); - } - function _useInternalBurn() internal pure virtual override returns (bool) { return true; } diff --git a/src/ERC1155ShipyardRedeemable.sol b/src/ERC1155ShipyardRedeemable.sol index afaa9d6..5d7eb41 100644 --- a/src/ERC1155ShipyardRedeemable.sol +++ b/src/ERC1155ShipyardRedeemable.sol @@ -23,16 +23,6 @@ contract ERC1155ShipyardRedeemable is ERC1155ShipyardContractMetadata, ERC7498NF DynamicTraits.setTrait(tokenId, traitKey, value); } - function getTraitValue(uint256 tokenId, bytes32 traitKey) - public - view - virtual - override - returns (bytes32 traitValue) - { - traitValue = DynamicTraits.getTraitValue(tokenId, traitKey); - } - function _useInternalBurn() internal pure virtual override returns (bool) { return true; } diff --git a/src/ERC721SeaDropRedeemable.sol b/src/ERC721SeaDropRedeemable.sol index 48835f2..a2329fb 100644 --- a/src/ERC721SeaDropRedeemable.sol +++ b/src/ERC721SeaDropRedeemable.sol @@ -43,6 +43,18 @@ contract ERC721SeaDropRedeemable is ERC721SeaDrop, ERC7498NFTRedeemables { traitValue = DynamicTraits.getTraitValue(tokenId, traitKey); } + function getTraitValues(uint256 tokenId, bytes32[] calldata traitKeys) + public + view + virtual + override + returns (bytes32[] memory traitValues) + { + if (!_exists(tokenId)) revert TokenDoesNotExist(); + + traitValues = DynamicTraits.getTraitValues(tokenId, traitKeys); + } + function _useInternalBurn() internal pure virtual override returns (bool) { return true; } diff --git a/src/ERC721ShipyardRedeemable.sol b/src/ERC721ShipyardRedeemable.sol index 77e039b..69d7b1a 100644 --- a/src/ERC721ShipyardRedeemable.sol +++ b/src/ERC721ShipyardRedeemable.sol @@ -36,6 +36,18 @@ contract ERC721ShipyardRedeemable is ERC721ShipyardContractMetadata, ERC7498NFTR traitValue = DynamicTraits.getTraitValue(tokenId, traitKey); } + function getTraitValues(uint256 tokenId, bytes32[] calldata traitKeys) + public + view + virtual + override + returns (bytes32[] memory traitValues) + { + if (!_exists(tokenId)) revert TokenDoesNotExist(); + + traitValues = DynamicTraits.getTraitValues(tokenId, traitKeys); + } + function _useInternalBurn() internal pure virtual override returns (bool) { return true; } diff --git a/src/interfaces/IShipyardContractMetadata.sol b/src/interfaces/IShipyardContractMetadata.sol new file mode 100644 index 0000000..a85c12e --- /dev/null +++ b/src/interfaces/IShipyardContractMetadata.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +interface IShipyardContractMetadata { + /// @dev Emit an event for token metadata reveals/updates, according to EIP-4906. + event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); + + /// @dev Emit an event when the URI for the collection-level metadata is updated. + event ContractURIUpdated(string uri); + + /// @dev Emit an event when the provenance hash is updated. + event ProvenanceHashUpdated(bytes32 oldProvenanceHash, bytes32 newProvenanceHash); + + /// @dev Emit an event when the royalties info is updated. + event RoyaltyInfoUpdated(address receiver, uint256 basisPoints); + + /// @dev Revert with an error when attempting to set the provenance hash after it has already been set. + error ProvenanceHashCannotBeSetAfterAlreadyBeingSet(); + + function name() external view returns (string memory); + + function symbol() external view returns (string memory); + + function baseURI() external view returns (string memory); + + function contractURI() external view returns (string memory); + + function provenanceHash() external view returns (bytes32); + + function setBaseURI(string calldata newURI) external; + + function setContractURI(string calldata newURI) external; + + function setProvenanceHash(bytes32 newProvenanceHash) external; + + function setDefaultRoyalty(address receiver, uint96 feeNumerator) external; +} diff --git a/src/lib/ERC1155ShipyardContractMetadata.sol b/src/lib/ERC1155ShipyardContractMetadata.sol index 4325cc4..1552453 100644 --- a/src/lib/ERC1155ShipyardContractMetadata.sol +++ b/src/lib/ERC1155ShipyardContractMetadata.sol @@ -5,8 +5,14 @@ import {ERC1155ConduitPreapproved_Solady} from "shipyard-core/src/tokens/erc1155 import {ERC1155} from "solady/src/tokens/ERC1155.sol"; import {ERC2981} from "solady/src/tokens/ERC2981.sol"; import {Ownable} from "solady/src/auth/Ownable.sol"; - -contract ERC1155ShipyardContractMetadata is ERC1155ConduitPreapproved_Solady, ERC2981, Ownable { +import {IShipyardContractMetadata} from "../interfaces/IShipyardContractMetadata.sol"; + +contract ERC1155ShipyardContractMetadata is + ERC1155ConduitPreapproved_Solady, + IShipyardContractMetadata, + ERC2981, + Ownable +{ /// @dev The token name string internal _name; @@ -22,21 +28,6 @@ contract ERC1155ShipyardContractMetadata is ERC1155ConduitPreapproved_Solady, ER /// @dev The provenance hash for guaranteeing metadata order for random reveals. bytes32 public provenanceHash; - /// @dev Emit an event for token metadata reveals/updates, according to EIP-4906. - event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); - - /// @dev Emit an event when the URI for the collection-level metadata is updated. - event ContractURIUpdated(string uri); - - /// @dev Emit an event when the provenance hash is updated. - event ProvenanceHashUpdated(bytes32 oldProvenanceHash, bytes32 newProvenanceHash); - - /// @dev Emit an event when the royalties info is updated. - event RoyaltyInfoUpdated(address receiver, uint256 bps); - - /// @dev Revert with an error when attempting to set the provenance hash after it has already been set. - error ProvenanceHashCannotBeSetAfterAlreadyBeingSet(); - constructor(string memory name_, string memory symbol_) ERC1155ConduitPreapproved_Solady() { // Set the token name and symbol. _name = name_; diff --git a/src/lib/ERC721ShipyardContractMetadata.sol b/src/lib/ERC721ShipyardContractMetadata.sol index d220b6b..0823fb4 100644 --- a/src/lib/ERC721ShipyardContractMetadata.sol +++ b/src/lib/ERC721ShipyardContractMetadata.sol @@ -5,8 +5,14 @@ import {ERC721ConduitPreapproved_Solady} from "shipyard-core/src/tokens/erc721/E import {ERC721} from "solady/src/tokens/ERC721.sol"; import {ERC2981} from "solady/src/tokens/ERC2981.sol"; import {Ownable} from "solady/src/auth/Ownable.sol"; - -contract ERC721ShipyardContractMetadata is ERC721ConduitPreapproved_Solady, ERC2981, Ownable { +import {IShipyardContractMetadata} from "../interfaces/IShipyardContractMetadata.sol"; + +contract ERC721ShipyardContractMetadata is + ERC721ConduitPreapproved_Solady, + IShipyardContractMetadata, + ERC2981, + Ownable +{ /// @dev The token name string internal _name; @@ -22,21 +28,6 @@ contract ERC721ShipyardContractMetadata is ERC721ConduitPreapproved_Solady, ERC2 /// @dev The provenance hash for guaranteeing metadata order for random reveals. bytes32 public provenanceHash; - /// @dev Emit an event for token metadata reveals/updates, according to EIP-4906. - event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); - - /// @dev Emit an event when the URI for the collection-level metadata is updated. - event ContractURIUpdated(string uri); - - /// @dev Emit an event when the provenance hash is updated. - event ProvenanceHashUpdated(bytes32 oldProvenanceHash, bytes32 newProvenanceHash); - - /// @dev Emit an event when the royalties info is updated. - event RoyaltyInfoUpdated(address receiver, uint256 bps); - - /// @dev Revert with an error when attempting to set the provenance hash after it has already been set. - error ProvenanceHashCannotBeSetAfterAlreadyBeingSet(); - constructor(string memory name_, string memory symbol_) ERC721ConduitPreapproved_Solady() { // Set the token name and symbol. _name = name_; @@ -49,14 +40,14 @@ contract ERC721ShipyardContractMetadata is ERC721ConduitPreapproved_Solady, ERC2 /** * @notice Returns the name of this token contract. */ - function name() public view override returns (string memory) { + function name() public view override(ERC721, IShipyardContractMetadata) returns (string memory) { return _name; } /** * @notice Returns the symbol of this token contract. */ - function symbol() public view override returns (string memory) { + function symbol() public view override(ERC721, IShipyardContractMetadata) returns (string memory) { return _symbol; } diff --git a/src/test/ERC721OwnerMintable.sol b/src/test/ERC721OwnerMintable.sol deleted file mode 100644 index d77efcf..0000000 --- a/src/test/ERC721OwnerMintable.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import {ERC721ConduitPreapproved_Solady} from "shipyard-core/src/tokens/erc721/ERC721ConduitPreapproved_Solady.sol"; -import {Ownable} from "solady/src/auth/Ownable.sol"; - -contract ERC721OwnerMintable is ERC721ConduitPreapproved_Solady, Ownable { - /// @dev The address that can burn tokens without needing approval. - address private _burnAddress; - - constructor() ERC721ConduitPreapproved_Solady() { - _initializeOwner(msg.sender); - } - - function mint(address to, uint256 tokenId) public onlyOwner { - _mint(to, tokenId); - } - - function setBurnAddress(address burnAddress) public onlyOwner { - _burnAddress = burnAddress; - } - - function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { - bool approved = super.isApprovedForAll(owner, operator); - return operator == _burnAddress ? !approved : approved; - } - - function _by(address from) internal view override returns (address result) { - return msg.sender == _burnAddress ? address(0) : super._by(from); - } - - function name() public pure override returns (string memory) { - return "ERC721OwnerMintable"; - } - - function symbol() public pure override returns (string memory) { - return "ERC721-OM"; - } - - function tokenURI(uint256 /* tokenId */ ) public pure override returns (string memory) { - return "https://example.com/"; - } -} diff --git a/src/test/ERC721ShipyardRedeemableTraitSetters.sol b/src/test/ERC721ShipyardRedeemableTraitSetters.sol index 9332745..c20c18c 100644 --- a/src/test/ERC721ShipyardRedeemableTraitSetters.sol +++ b/src/test/ERC721ShipyardRedeemableTraitSetters.sol @@ -26,18 +26,6 @@ contract ERC721ShipyardRedeemableTraitSetters is ERC721ShipyardRedeemableOwnerMi DynamicTraits.setTrait(tokenId, traitKey, value); } - function getTraitValue(uint256 tokenId, bytes32 traitKey) - public - view - virtual - override - returns (bytes32 traitValue) - { - if (!_exists(tokenId)) revert TokenDoesNotExist(); - - traitValue = DynamicTraits.getTraitValue(tokenId, traitKey); - } - function _requireAllowedTraitSetter() internal view { // Allow the contract to call itself. if (msg.sender == address(this)) return; diff --git a/test/ERC7498-RedemptionMintable.t.sol b/test/ERC7498-RedemptionMintable.t.sol index 57de6c6..d5c8f66 100644 --- a/test/ERC7498-RedemptionMintable.t.sol +++ b/test/ERC7498-RedemptionMintable.t.sol @@ -5,7 +5,8 @@ import {BaseRedeemablesTest} from "./utils/BaseRedeemablesTest.sol"; import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {OfferItemLib} from "seaport-sol/src/lib/OfferItemLib.sol"; import {ConsiderationItemLib} from "seaport-sol/src/lib/ConsiderationItemLib.sol"; -import {IRedemptionMintable} from "src/interfaces/IRedemptionMintable.sol"; +import {IERC7498} from "../src/interfaces/IERC7498.sol"; +import {IRedemptionMintable} from "../src/interfaces/IRedemptionMintable.sol"; contract TestERC7498_RedemptionMintable is BaseRedeemablesTest { using OfferItemLib for OfferItem; @@ -16,5 +17,8 @@ contract TestERC7498_RedemptionMintable is BaseRedeemablesTest { function testSupportsInterfaceId() public { assertTrue(receiveToken721.supportsInterface(type(IRedemptionMintable).interfaceId)); assertTrue(receiveToken1155.supportsInterface(type(IRedemptionMintable).interfaceId)); + + assertTrue(receiveToken721.supportsInterface(type(IERC7498).interfaceId)); + assertTrue(receiveToken1155.supportsInterface(type(IERC7498).interfaceId)); } } diff --git a/test/ERC7498-SimpleRedeem.t.sol b/test/ERC7498-SimpleRedeem.t.sol index 52f7838..63ed102 100644 --- a/test/ERC7498-SimpleRedeem.t.sol +++ b/test/ERC7498-SimpleRedeem.t.sol @@ -76,6 +76,13 @@ contract ERC7498_SimpleRedeem is BaseRedeemablesTest { campaign.params.endTime = uint32(block.timestamp + 1000); context.erc7498Token.updateCampaign(campaignId, campaign, ""); + // Clear the receiveToken mintRedemption allowed callers to check for error coverage. + receiveToken721.setRedeemablesContracts(new address[](0)); + vm.expectRevert(abi.encodeWithSelector(InvalidCaller.selector, address(context.erc7498Token))); + context.erc7498Token.redeem(considerationTokenIds, address(0), extraData); + // Re-add allowed callers + receiveToken721.setRedeemablesContracts(erc7498Tokens); + vm.expectEmit(true, true, true, true); emit Redemption(1, 0, bytes32(0), considerationTokenIds, defaultTraitRedemptionTokenIds, address(this)); // Using address(0) for recipient should assign to msg.sender. @@ -352,6 +359,13 @@ contract ERC7498_SimpleRedeem is BaseRedeemablesTest { ); uint256[] memory considerationTokenIds = Solarray.uint256s(tokenId); + // Clear the receiveToken mintRedemption allowed callers to check for error coverage. + receiveToken1155.setRedeemablesContracts(new address[](0)); + vm.expectRevert(abi.encodeWithSelector(InvalidCaller.selector, erc7498Tokens[0])); + IERC7498(erc7498Tokens[0]).redeem(considerationTokenIds, address(0), extraData); + // Re-add allowed callers + receiveToken1155.setRedeemablesContracts(erc7498Tokens); + vm.expectEmit(true, true, true, true); emit Redemption(1, 0, bytes32(0), considerationTokenIds, defaultTraitRedemptionTokenIds, address(this)); IERC7498(erc7498Tokens[0]).redeem(considerationTokenIds, address(this), extraData); diff --git a/test/ERC7498-TraitRedemption.t.sol b/test/ERC7498-TraitRedemption.t.sol index 7fb0ff1..90e135b 100644 --- a/test/ERC7498-TraitRedemption.t.sol +++ b/test/ERC7498-TraitRedemption.t.sol @@ -8,6 +8,8 @@ import {OfferItem, ConsiderationItem} from "seaport-types/src/lib/ConsiderationS 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 {ERC721} from "solady/src/tokens/ERC721.sol"; +import {IERC7496} from "shipyard-core/src/dynamic-traits/interfaces/IERC7496.sol"; import {IERC7498} from "../src/interfaces/IERC7498.sol"; import {Campaign, CampaignParams, CampaignRequirements, TraitRedemption} from "../src/lib/RedeemablesStructs.sol"; import {ERC721ShipyardRedeemableMintable} from "../src/extensions/ERC721ShipyardRedeemableMintable.sol"; @@ -25,6 +27,34 @@ contract ERC7498_TraitRedemption is BaseRedeemablesTest { uint256 tokenId = 2; bytes32 traitKey = bytes32("hasRedeemed"); + function testGetAndSetTrait() public { + for (uint256 i; i < erc7498Tokens.length; i++) { + testRedeemable(this.getAndSetTrait, RedeemablesContext({erc7498Token: IERC7498(erc7498Tokens[i])})); + } + } + + function getAndSetTrait(RedeemablesContext memory context) public { + if (_isERC721(address(context.erc7498Token))) { + vm.expectRevert(ERC721.TokenDoesNotExist.selector); + IERC7496(address(context.erc7498Token)).getTraitValue(tokenId, traitKey); + vm.expectRevert(ERC721.TokenDoesNotExist.selector); + IERC7496(address(context.erc7498Token)).getTraitValues(tokenId, Solarray.bytes32s(traitKey)); + } + + _mintToken(address(context.erc7498Token), tokenId); + bytes32 traitValue = IERC7496(address(context.erc7498Token)).getTraitValue(tokenId, traitKey); + assertEq(traitValue, bytes32(0)); + + IERC7496(address(context.erc7498Token)).setTrait(tokenId, traitKey, bytes32(uint256(1))); + traitValue = IERC7496(address(context.erc7498Token)).getTraitValue(tokenId, traitKey); + assertEq(traitValue, bytes32(uint256(1))); + + bytes32[] memory traitValues = + IERC7496(address(context.erc7498Token)).getTraitValues(tokenId, Solarray.bytes32s(traitKey)); + assertEq(traitValues.length, 1); + assertEq(traitValues[0], bytes32(uint256(1))); + } + function testErc721TraitRedemptionForErc721() public { for (uint256 i; i < erc7498Tokens.length; i++) { testRedeemable( diff --git a/test/ShipyardContractMetadata.t.sol b/test/ShipyardContractMetadata.t.sol new file mode 100644 index 0000000..e85f308 --- /dev/null +++ b/test/ShipyardContractMetadata.t.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {BaseRedeemablesTest} from "./utils/BaseRedeemablesTest.sol"; +import {Solarray} from "solarray/Solarray.sol"; +import {Ownable} from "solady/src/auth/Ownable.sol"; +import {IERC721Metadata} from "openzeppelin-contracts/contracts/interfaces/IERC721Metadata.sol"; +import {IERC1155MetadataURI} from "openzeppelin-contracts/contracts/interfaces/IERC1155MetadataURI.sol"; +import {IERC2981} from "openzeppelin-contracts/contracts/interfaces/IERC2981.sol"; +import {ERC721ShipyardContractMetadata} from "../src/lib/ERC721ShipyardContractMetadata.sol"; +import {ERC1155ShipyardContractMetadata} from "../src/lib/ERC1155ShipyardContractMetadata.sol"; +import {IShipyardContractMetadata} from "../src/interfaces/IShipyardContractMetadata.sol"; + +contract ERC721ShipyardContractMetadataOwnerMintable is ERC721ShipyardContractMetadata { + constructor(string memory name_, string memory symbol_) ERC721ShipyardContractMetadata(name_, symbol_) {} + + function mint(address to, uint256 tokenId) external onlyOwner { + _mint(to, tokenId); + } +} + +contract ERC1155ShipyardContractMetadataOwnerMintable is ERC1155ShipyardContractMetadata { + constructor(string memory name_, string memory symbol_) ERC1155ShipyardContractMetadata(name_, symbol_) {} + + function mint(address to, uint256 tokenId, uint256 amount) external onlyOwner { + _mint(to, tokenId, amount, ""); + } +} + +contract TestShipyardContractMetadata is BaseRedeemablesTest { + ERC721ShipyardContractMetadataOwnerMintable token721; + ERC1155ShipyardContractMetadataOwnerMintable token1155; + address[] tokens; + + event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); + event ContractURIUpdated(string uri); + event ProvenanceHashUpdated(bytes32 oldProvenanceHash, bytes32 newProvenanceHash); + event RoyaltyInfoUpdated(address receiver, uint256 basisPoints); + + function setUp() public override { + token721 = new ERC721ShipyardContractMetadataOwnerMintable("Test", "TST"); + token1155 = new ERC1155ShipyardContractMetadataOwnerMintable("Test", "TST"); + tokens = new address[](2); + tokens[0] = address(token721); + tokens[1] = address(token1155); + } + + function testNameAndSymbol() external { + for (uint256 i; i < tokens.length; i++) { + this.nameAndSymbol(IShipyardContractMetadata(tokens[i])); + } + } + + function nameAndSymbol(IShipyardContractMetadata token) external { + assertEq(token.name(), "Test"); + assertEq(token.symbol(), "TST"); + } + + function testOwner() external { + for (uint256 i; i < tokens.length; i++) { + this.owner(IShipyardContractMetadata(tokens[i])); + } + } + + function owner(IShipyardContractMetadata token) external { + assertEq(Ownable(address(token)).owner(), address(this)); + } + + function testBaseURI() external { + for (uint256 i; i < tokens.length; i++) { + this.baseURI(IShipyardContractMetadata(tokens[i])); + } + } + + function baseURI(IShipyardContractMetadata token) external { + uint256 tokenId = 1; + _mintToken(address(token), tokenId); + + if (_isERC721(address(token))) { + assertEq(IERC721Metadata(address(token)).tokenURI(tokenId), ""); + } else { + // token is 1155 + assertEq(IERC1155MetadataURI(address(token)).uri(tokenId), ""); + } + + assertEq(token.baseURI(), ""); + vm.expectEmit(true, true, true, true); + emit BatchMetadataUpdate(0, type(uint256).max); + token.setBaseURI("https://example.com/"); + assertEq(token.baseURI(), "https://example.com/"); + + if (_isERC721(address(token))) { + assertEq(IERC721Metadata(address(token)).tokenURI(tokenId), string.concat("https://example.com/", "1")); + + // For ERC721, without the slash shouldn't append tokenId, + // for e.g. prereveal states of when all tokens have the same metadata. + // For ERC1155, {id} substitution defined in spec can be used. + token.setBaseURI("https://example.com"); + assertEq(token.baseURI(), "https://example.com"); + assertEq(IERC721Metadata(address(token)).tokenURI(tokenId), "https://example.com"); + } else { + // token is 1155 + assertEq(IERC1155MetadataURI(address(token)).uri(tokenId), string.concat("https://example.com/")); + } + } + + function testContractURI() external { + for (uint256 i; i < tokens.length; i++) { + this.contractURI(IShipyardContractMetadata(tokens[i])); + } + } + + function contractURI(IShipyardContractMetadata token) external { + assertEq(token.contractURI(), ""); + vm.expectEmit(true, true, true, true); + emit ContractURIUpdated("https://example.com/"); + token.setContractURI("https://example.com/"); + assertEq(token.contractURI(), "https://example.com/"); + } + + function testSetProvenanceHash() external { + for (uint256 i; i < tokens.length; i++) { + this.setProvenanceHash(IShipyardContractMetadata(tokens[i])); + } + } + + function setProvenanceHash(IShipyardContractMetadata token) external { + assertEq(token.provenanceHash(), ""); + vm.expectEmit(true, true, true, true); + emit ProvenanceHashUpdated(bytes32(0), bytes32(uint256(1234))); + token.setProvenanceHash(bytes32(uint256(1234))); + assertEq(token.provenanceHash(), bytes32(uint256(1234))); + + // Setting the provenance hash again should revert. + vm.expectRevert(IShipyardContractMetadata.ProvenanceHashCannotBeSetAfterAlreadyBeingSet.selector); + token.setProvenanceHash(bytes32(uint256(5678))); + } + + function testSetDefaultRoyalty() external { + for (uint256 i; i < tokens.length; i++) { + this.setDefaultRoyalty(IShipyardContractMetadata(tokens[i])); + } + } + + function setDefaultRoyalty(IShipyardContractMetadata token) external { + address greg = makeAddr("greg"); + vm.expectEmit(true, true, true, true); + emit RoyaltyInfoUpdated(greg, 9_000); + token.setDefaultRoyalty(greg, 9_000); + (address receiver, uint256 amount) = IERC2981(address(token)).royaltyInfo(0, 100); + assertEq(receiver, greg); + assertEq(amount, 100 * 9_000 / 10_000); + } +} diff --git a/test/utils/BaseRedeemablesTest.sol b/test/utils/BaseRedeemablesTest.sol index fecad6d..a487103 100644 --- a/test/utils/BaseRedeemablesTest.sol +++ b/test/utils/BaseRedeemablesTest.sol @@ -55,6 +55,8 @@ contract BaseRedeemablesTest is RedeemablesErrors, BaseOrderTest { ERC721SeaDropRedeemableOwnerMintable erc721SeaDropRedeemable; ERC1155ShipyardRedeemableOwnerMintable erc1155ShipyardRedeemable; ERC1155SeaDropRedeemableOwnerMintable erc1155SeaDropRedeemable; + + address[] receiveTokens; ERC721ShipyardRedeemableMintable receiveToken721; ERC1155ShipyardRedeemableMintable receiveToken1155; @@ -103,7 +105,6 @@ contract BaseRedeemablesTest is RedeemablesErrors, BaseOrderTest { erc7498Tokens[1] = address(erc721SeaDropRedeemable); erc7498Tokens[2] = address(erc1155ShipyardRedeemable); erc7498Tokens[3] = address(erc1155SeaDropRedeemable); - vm.label(erc7498Tokens[0], "erc721ShipyardRedeemable"); vm.label(erc7498Tokens[1], "erc721SeaDropRedeemable"); vm.label(erc7498Tokens[2], "erc1155ShipyardRedeemable"); @@ -111,8 +112,15 @@ contract BaseRedeemablesTest is RedeemablesErrors, BaseOrderTest { receiveToken721 = new ERC721ShipyardRedeemableMintable("", ""); receiveToken1155 = new ERC1155ShipyardRedeemableMintable("", ""); - receiveToken721.setRedeemablesContracts(erc7498Tokens); - receiveToken1155.setRedeemablesContracts(erc7498Tokens); + receiveTokens = new address[](2); + receiveTokens[0] = address(receiveToken721); + receiveTokens[1] = address(receiveToken1155); + vm.label(receiveTokens[0], "erc721ShipyardRedeemableMintable"); + vm.label(receiveTokens[1], "erc1155ShipyardRedeemableMintable"); + for (uint256 i = 0; i < receiveTokens.length; ++i) { + ERC721ShipyardRedeemableMintable(receiveTokens[i]).setRedeemablesContracts(erc7498Tokens); + assertEq(ERC721ShipyardRedeemableMintable(receiveTokens[i]).getRedeemablesContracts(), erc7498Tokens); + } _setApprovals(address(this)); From f20a06695cd19398012893420dae07e74e3320af Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Wed, 15 Nov 2023 16:19:50 -0800 Subject: [PATCH 6/6] more coverage --- script/DeployAndRedeemTrait.s.sol | 7 +- src/lib/ERC7498NFTRedeemables.sol | 5 +- ...{SignedRedeem.sol => SignedRedeem.sol.txt} | 0 ...ol => SignedRedeemContractOfferer.sol.txt} | 0 ...emableOwnerMintableWithoutInternalBurn.sol | 14 + .../ERC721ShipyardRedeemableTraitSetters.sol | 14 +- test/ERC7498-TraitRedemption.t.sol | 263 +++++++++++++++++- test/utils/BaseRedeemablesTest.sol | 27 +- 8 files changed, 302 insertions(+), 28 deletions(-) rename src/lib/{SignedRedeem.sol => SignedRedeem.sol.txt} (100%) rename src/lib/{SignedRedeemContractOfferer.sol => SignedRedeemContractOfferer.sol.txt} (100%) create mode 100644 src/test/ERC721ShipyardRedeemableOwnerMintableWithoutInternalBurn.sol diff --git a/script/DeployAndRedeemTrait.s.sol b/script/DeployAndRedeemTrait.s.sol index 8ef6356..f213f73 100644 --- a/script/DeployAndRedeemTrait.s.sol +++ b/script/DeployAndRedeemTrait.s.sol @@ -24,12 +24,13 @@ contract DeployAndRedeemTrait is Script, Test { address[] memory allowedTraitSetters = new address[](1); allowedTraitSetters[0] = address(receiveToken); - // deploy the redeem token with the receive token as an allowed trait setter + // deploy the redeem token ERC721ShipyardRedeemableTraitSetters redeemToken = new ERC721ShipyardRedeemableTraitSetters( "DynamicTraitsRedeemToken", - "TEST", - allowedTraitSetters + "TEST" ); + // set the receive token as an allowed trait setter + redeemToken.setAllowedTraitSetters(allowedTraitSetters); // configure the campaign. OfferItem[] memory offer = new OfferItem[](1); diff --git a/src/lib/ERC7498NFTRedeemables.sol b/src/lib/ERC7498NFTRedeemables.sol index 9fab898..dc600ba 100644 --- a/src/lib/ERC7498NFTRedeemables.sol +++ b/src/lib/ERC7498NFTRedeemables.sol @@ -461,12 +461,13 @@ contract ERC7498NFTRedeemables is IERC165, IERC7498, DynamicTraits, RedeemablesE // Decrement the trait by the trait value. IERC7496(token).setTrait(identifier, traitRedemptions[i].traitKey, bytes32(newTraitValue)); } else if (substandard == 4) { - // Revert if the current trait value is not equal to the substandard value. - if (currentTraitValue != substandardValue) { + // Revert if the current trait value is not equal to the trait value. + if (currentTraitValue != traitValue) { revert InvalidRequiredTraitValue( token, identifier, traitKey, currentTraitValue, substandardValue ); } + // No-op: substandard 4 has no set trait action. } } diff --git a/src/lib/SignedRedeem.sol b/src/lib/SignedRedeem.sol.txt similarity index 100% rename from src/lib/SignedRedeem.sol rename to src/lib/SignedRedeem.sol.txt diff --git a/src/lib/SignedRedeemContractOfferer.sol b/src/lib/SignedRedeemContractOfferer.sol.txt similarity index 100% rename from src/lib/SignedRedeemContractOfferer.sol rename to src/lib/SignedRedeemContractOfferer.sol.txt diff --git a/src/test/ERC721ShipyardRedeemableOwnerMintableWithoutInternalBurn.sol b/src/test/ERC721ShipyardRedeemableOwnerMintableWithoutInternalBurn.sol new file mode 100644 index 0000000..0b61e7f --- /dev/null +++ b/src/test/ERC721ShipyardRedeemableOwnerMintableWithoutInternalBurn.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {ERC7498NFTRedeemables} from "../lib/ERC7498NFTRedeemables.sol"; +import {ERC721ShipyardRedeemableOwnerMintable} from "./ERC721ShipyardRedeemableOwnerMintable.sol"; + +contract ERC721ShipyardRedeemableOwnerMintableWithoutInternalBurn is ERC721ShipyardRedeemableOwnerMintable { + constructor(string memory name_, string memory symbol_) ERC721ShipyardRedeemableOwnerMintable(name_, symbol_) {} + + function _useInternalBurn() internal pure virtual override returns (bool) { + // For coverage of ERC7498NFTRedeemables._useInternalBurn, return default value of false. + return ERC7498NFTRedeemables._useInternalBurn(); + } +} diff --git a/src/test/ERC721ShipyardRedeemableTraitSetters.sol b/src/test/ERC721ShipyardRedeemableTraitSetters.sol index c20c18c..542e630 100644 --- a/src/test/ERC721ShipyardRedeemableTraitSetters.sol +++ b/src/test/ERC721ShipyardRedeemableTraitSetters.sol @@ -12,11 +12,7 @@ contract ERC721ShipyardRedeemableTraitSetters is ERC721ShipyardRedeemableOwnerMi // ERC721ShipyardRedeemable and ERC721SeaDropRedeemable contracts with onlyOwner on setAllowedTraitSetters(). address[] _allowedTraitSetters; - constructor(string memory name_, string memory symbol_, address[] memory allowedTraitSetters) - ERC721ShipyardRedeemableOwnerMintable(name_, symbol_) - { - _allowedTraitSetters = allowedTraitSetters; - } + constructor(string memory name_, string memory symbol_) ERC721ShipyardRedeemableOwnerMintable(name_, symbol_) {} function setTrait(uint256 tokenId, bytes32 traitKey, bytes32 value) public virtual override { if (!_exists(tokenId)) revert TokenDoesNotExist(); @@ -26,6 +22,14 @@ contract ERC721ShipyardRedeemableTraitSetters is ERC721ShipyardRedeemableOwnerMi DynamicTraits.setTrait(tokenId, traitKey, value); } + function getAllowedTraitSetters() public view returns (address[] memory) { + return _allowedTraitSetters; + } + + function setAllowedTraitSetters(address[] memory allowedTraitSetters) public onlyOwner { + _allowedTraitSetters = allowedTraitSetters; + } + function _requireAllowedTraitSetter() internal view { // Allow the contract to call itself. if (msg.sender == address(this)) return; diff --git a/test/ERC7498-TraitRedemption.t.sol b/test/ERC7498-TraitRedemption.t.sol index 90e135b..36a1266 100644 --- a/test/ERC7498-TraitRedemption.t.sol +++ b/test/ERC7498-TraitRedemption.t.sol @@ -55,7 +55,7 @@ contract ERC7498_TraitRedemption is BaseRedeemablesTest { assertEq(traitValues[0], bytes32(uint256(1))); } - function testErc721TraitRedemptionForErc721() public { + function testErc721TraitRedemptionSubstandardOneForErc721() public { for (uint256 i; i < erc7498Tokens.length; i++) { testRedeemable( this.erc721TraitRedemptionSubstandardOneForErc721, @@ -65,13 +65,14 @@ contract ERC7498_TraitRedemption is BaseRedeemablesTest { } function erc721TraitRedemptionSubstandardOneForErc721(RedeemablesContext memory context) public { - address[] memory allowedTraitSetters = new address[](1); - allowedTraitSetters[0] = address(context.erc7498Token); + address[] memory allowedTraitSetters = Solarray.addresses(address(context.erc7498Token)); ERC721ShipyardRedeemableTraitSetters redeemToken = new ERC721ShipyardRedeemableTraitSetters( "", - "", - allowedTraitSetters + "" ); + assertEq(redeemToken.getAllowedTraitSetters(), new address[](0)); + redeemToken.setAllowedTraitSetters(allowedTraitSetters); + assertEq(redeemToken.getAllowedTraitSetters(), allowedTraitSetters); _mintToken(address(redeemToken), tokenId); TraitRedemption[] memory traitRedemptions = new TraitRedemption[](1); // previous trait value (`substandardValue`) should be 0 @@ -144,4 +145,256 @@ contract ERC7498_TraitRedemption is BaseRedeemablesTest { ); context.erc7498Token.redeem(considerationTokenIds, address(this), extraData); } + + function testErc721TraitRedemptionSubstandardTwoForErc721() public { + for (uint256 i; i < erc7498Tokens.length; i++) { + testRedeemable( + this.erc721TraitRedemptionSubstandardTwoForErc721, + RedeemablesContext({erc7498Token: IERC7498(erc7498Tokens[i])}) + ); + } + } + + function erc721TraitRedemptionSubstandardTwoForErc721(RedeemablesContext memory context) public { + address[] memory allowedTraitSetters = Solarray.addresses(address(context.erc7498Token), address(this)); + ERC721ShipyardRedeemableTraitSetters redeemToken = new ERC721ShipyardRedeemableTraitSetters( + "", + "" + ); + redeemToken.setAllowedTraitSetters(allowedTraitSetters); + _mintToken(address(redeemToken), tokenId); + redeemToken.setTrait(tokenId, traitKey, bytes32(uint256(1))); + TraitRedemption[] memory traitRedemptions = new TraitRedemption[](1); + // previous trait value should not be greater than 1 (`substandardValue`) + // new trait value should be 2 (adding traitValue of 1) + traitRedemptions[0] = TraitRedemption({ + substandard: 2, + token: address(redeemToken), + traitKey: traitKey, + traitValue: bytes32(uint256(1)), + substandardValue: bytes32(uint256(1)) + }); + CampaignRequirements[] memory requirements = new CampaignRequirements[]( + 1 + ); + // consideration is empty + ConsiderationItem[] memory consideration = new ConsiderationItem[](0); + requirements[0] = CampaignRequirements({ + offer: defaultCampaignOffer, + consideration: consideration, + traitRedemptions: traitRedemptions + }); + CampaignParams memory params = CampaignParams({ + startTime: uint32(block.timestamp), + endTime: uint32(block.timestamp + 1000), + maxCampaignRedemptions: 5, + manager: address(this), + signer: address(0) + }); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + context.erc7498Token.createCampaign(campaign, ""); + + uint256[] memory considerationTokenIds; + uint256[] memory traitRedemptionTokenIds = Solarray.uint256s(tokenId); + bytes memory extraData = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + traitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); + vm.expectEmit(true, true, true, true); + emit Redemption(1, 0, bytes32(0), considerationTokenIds, traitRedemptionTokenIds, address(this)); + context.erc7498Token.redeem(considerationTokenIds, address(this), extraData); + + bytes32 actualTraitValue = redeemToken.getTraitValue(tokenId, traitKey); + assertEq(bytes32(uint256(2)), actualTraitValue); + assertEq(receiveToken721.ownerOf(1), address(this)); + + // Redeeming one more time should fail with InvalidRequiredTraitValue since it is already 2. + vm.expectRevert( + abi.encodeWithSelector( + InvalidRequiredTraitValue.selector, + redeemToken, + tokenId, + traitKey, + bytes32(uint256(2)), + bytes32(uint256(1)) + ) + ); + context.erc7498Token.redeem(considerationTokenIds, address(this), extraData); + } + + function testErc721TraitRedemptionSubstandardThreeForErc721() public { + for (uint256 i; i < erc7498Tokens.length; i++) { + testRedeemable( + this.erc721TraitRedemptionSubstandardThreeForErc721, + RedeemablesContext({erc7498Token: IERC7498(erc7498Tokens[i])}) + ); + } + } + + function erc721TraitRedemptionSubstandardThreeForErc721(RedeemablesContext memory context) public { + address[] memory allowedTraitSetters = Solarray.addresses(address(context.erc7498Token), address(this)); + ERC721ShipyardRedeemableTraitSetters redeemToken = new ERC721ShipyardRedeemableTraitSetters( + "", + "" + ); + redeemToken.setAllowedTraitSetters(allowedTraitSetters); + _mintToken(address(redeemToken), tokenId); + redeemToken.setTrait(tokenId, traitKey, bytes32(uint256(5))); + TraitRedemption[] memory traitRedemptions = new TraitRedemption[](1); + // previous trait value should not be less than 4 (`substandardValue`) + // new trait value should be 4 (adding traitValue of 1) + traitRedemptions[0] = TraitRedemption({ + substandard: 3, + token: address(redeemToken), + traitKey: traitKey, + traitValue: bytes32(uint256(1)), + substandardValue: bytes32(uint256(5)) + }); + CampaignRequirements[] memory requirements = new CampaignRequirements[]( + 1 + ); + // consideration is empty + ConsiderationItem[] memory consideration = new ConsiderationItem[](0); + requirements[0] = CampaignRequirements({ + offer: defaultCampaignOffer, + consideration: consideration, + traitRedemptions: traitRedemptions + }); + CampaignParams memory params = CampaignParams({ + startTime: uint32(block.timestamp), + endTime: uint32(block.timestamp + 1000), + maxCampaignRedemptions: 5, + manager: address(this), + signer: address(0) + }); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + context.erc7498Token.createCampaign(campaign, ""); + + uint256[] memory considerationTokenIds; + uint256[] memory traitRedemptionTokenIds = Solarray.uint256s(tokenId); + bytes memory extraData = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + traitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); + vm.expectEmit(true, true, true, true); + emit Redemption(1, 0, bytes32(0), considerationTokenIds, traitRedemptionTokenIds, address(this)); + context.erc7498Token.redeem(considerationTokenIds, address(this), extraData); + + bytes32 actualTraitValue = redeemToken.getTraitValue(tokenId, traitKey); + assertEq(bytes32(uint256(4)), actualTraitValue); + assertEq(receiveToken721.ownerOf(1), address(this)); + + // Redeeming one more time should fail with InvalidRequiredTraitValue since it is now 4. + vm.expectRevert( + abi.encodeWithSelector( + InvalidRequiredTraitValue.selector, + redeemToken, + tokenId, + traitKey, + bytes32(uint256(4)), + bytes32(uint256(5)) + ) + ); + context.erc7498Token.redeem(considerationTokenIds, address(this), extraData); + } + + function testErc721TraitRedemptionSubstandardFourForErc721() public { + for (uint256 i; i < erc7498Tokens.length; i++) { + testRedeemable( + this.erc721TraitRedemptionSubstandardFourForErc721, + RedeemablesContext({erc7498Token: IERC7498(erc7498Tokens[i])}) + ); + } + } + + function erc721TraitRedemptionSubstandardFourForErc721(RedeemablesContext memory context) public { + address[] memory allowedTraitSetters = Solarray.addresses(address(context.erc7498Token), address(this)); + ERC721ShipyardRedeemableTraitSetters redeemToken = new ERC721ShipyardRedeemableTraitSetters( + "", + "" + ); + redeemToken.setAllowedTraitSetters(allowedTraitSetters); + _mintToken(address(redeemToken), tokenId); + redeemToken.setTrait(tokenId, traitKey, bytes32(uint256(4))); + TraitRedemption[] memory traitRedemptions = new TraitRedemption[](1); + // previous trait value should be the trait value + // trait value does not change in substandard 4 + traitRedemptions[0] = TraitRedemption({ + substandard: 4, + token: address(redeemToken), + traitKey: traitKey, + traitValue: bytes32(uint256(5)), + substandardValue: bytes32(0) // unused in substandard 4 + }); + CampaignRequirements[] memory requirements = new CampaignRequirements[]( + 1 + ); + // consideration is empty + ConsiderationItem[] memory consideration = new ConsiderationItem[](0); + requirements[0] = CampaignRequirements({ + offer: defaultCampaignOffer, + consideration: consideration, + traitRedemptions: traitRedemptions + }); + CampaignParams memory params = CampaignParams({ + startTime: uint32(block.timestamp), + endTime: uint32(block.timestamp + 1000), + maxCampaignRedemptions: 5, + manager: address(this), + signer: address(0) + }); + Campaign memory campaign = Campaign({params: params, requirements: requirements}); + context.erc7498Token.createCampaign(campaign, ""); + + uint256[] memory considerationTokenIds; + uint256[] memory traitRedemptionTokenIds = Solarray.uint256s(tokenId); + bytes memory extraData = abi.encode( + 1, // campaignId + 0, // requirementsIndex + bytes32(0), // redemptionHash + traitRedemptionTokenIds, + uint256(0), // salt + bytes("") // signature + ); + + // Redeeming should fail since the trait value does not match. + vm.expectRevert( + abi.encodeWithSelector( + InvalidRequiredTraitValue.selector, + redeemToken, + tokenId, + traitKey, + bytes32(uint256(4)), + bytes32(uint256(0)) + ) + ); + context.erc7498Token.redeem(considerationTokenIds, address(this), extraData); + + // Update the trait value, now it should match. + redeemToken.setTrait(tokenId, traitKey, bytes32(uint256(5))); + + vm.expectEmit(true, true, true, true); + emit Redemption(1, 0, bytes32(0), considerationTokenIds, traitRedemptionTokenIds, address(this)); + context.erc7498Token.redeem(considerationTokenIds, address(this), extraData); + + bytes32 actualTraitValue = redeemToken.getTraitValue(tokenId, traitKey); + assertEq(bytes32(uint256(5)), actualTraitValue); + assertEq(receiveToken721.ownerOf(1), address(this)); + + // Redeeming one more time should succeed since it has not changed. + emit Redemption(1, 0, bytes32(0), considerationTokenIds, traitRedemptionTokenIds, address(this)); + context.erc7498Token.redeem(considerationTokenIds, address(this), extraData); + + actualTraitValue = redeemToken.getTraitValue(tokenId, traitKey); + assertEq(bytes32(uint256(5)), actualTraitValue); + assertEq(receiveToken721.ownerOf(2), address(this)); + } } diff --git a/test/utils/BaseRedeemablesTest.sol b/test/utils/BaseRedeemablesTest.sol index a487103..908f92e 100644 --- a/test/utils/BaseRedeemablesTest.sol +++ b/test/utils/BaseRedeemablesTest.sol @@ -25,6 +25,8 @@ import {ERC721SeaDropRedeemableOwnerMintable} from "../../src/test/ERC721SeaDrop import {ERC721ShipyardRedeemableOwnerMintable} from "../../src/test/ERC721ShipyardRedeemableOwnerMintable.sol"; import {ERC1155ShipyardRedeemableOwnerMintable} from "../../src/test/ERC1155ShipyardRedeemableOwnerMintable.sol"; import {ERC1155SeaDropRedeemableOwnerMintable} from "../../src/test/ERC1155SeaDropRedeemableOwnerMintable.sol"; +import {ERC721ShipyardRedeemableOwnerMintableWithoutInternalBurn} from + "../../src/test/ERC721ShipyardRedeemableOwnerMintableWithoutInternalBurn.sol"; import {RedeemablesErrors} from "../../src/lib/RedeemablesErrors.sol"; import {CampaignParams, CampaignRequirements, TraitRedemption} from "../../src/lib/RedeemablesStructs.sol"; import {BURN_ADDRESS} from "../../src/lib/RedeemablesConstants.sol"; @@ -48,13 +50,12 @@ contract BaseRedeemablesTest is RedeemablesErrors, BaseOrderTest { address redeemedBy ); - bytes32 private constant CAMPAIGN_PARAMS_MAP_POSITION = keccak256("CampaignParamsDefault"); - address[] erc7498Tokens; ERC721ShipyardRedeemableOwnerMintable erc721ShipyardRedeemable; ERC721SeaDropRedeemableOwnerMintable erc721SeaDropRedeemable; ERC1155ShipyardRedeemableOwnerMintable erc1155ShipyardRedeemable; ERC1155SeaDropRedeemableOwnerMintable erc1155SeaDropRedeemable; + ERC721ShipyardRedeemableOwnerMintableWithoutInternalBurn erc721ShipyardRedeemableWithoutInternalBurn; address[] receiveTokens; ERC721ShipyardRedeemableMintable receiveToken721; @@ -65,9 +66,6 @@ contract BaseRedeemablesTest is RedeemablesErrors, BaseOrderTest { TraitRedemption[] defaultTraitRedemptions; uint256[] defaultTraitRedemptionTokenIds; - CampaignRequirements[] defaultCampaignRequirements; - // CampaignParams defaultCampaignParams; - string constant DEFAULT_ERC721_CAMPAIGN_OFFER = "default erc721 campaign offer"; string constant DEFAULT_ERC721_CAMPAIGN_CONSIDERATION = "default erc721 campaign consideration"; @@ -94,21 +92,31 @@ contract BaseRedeemablesTest is RedeemablesErrors, BaseOrderTest { "", "" ); + erc721ShipyardRedeemableWithoutInternalBurn = new ERC721ShipyardRedeemableOwnerMintableWithoutInternalBurn( + "", + "" + ); + // Not using internal burn needs approval for the contract itself to transfer tokens on users' behalf. + erc721ShipyardRedeemableWithoutInternalBurn.setApprovalForAll( + address(erc721ShipyardRedeemableWithoutInternalBurn), true + ); erc721SeaDropRedeemable.setMaxSupply(10); erc1155SeaDropRedeemable.setMaxSupply(1, 10); erc1155SeaDropRedeemable.setMaxSupply(2, 10); erc1155SeaDropRedeemable.setMaxSupply(3, 10); - erc7498Tokens = new address[](4); + erc7498Tokens = new address[](5); erc7498Tokens[0] = address(erc721ShipyardRedeemable); erc7498Tokens[1] = address(erc721SeaDropRedeemable); erc7498Tokens[2] = address(erc1155ShipyardRedeemable); erc7498Tokens[3] = address(erc1155SeaDropRedeemable); + erc7498Tokens[4] = address(erc721ShipyardRedeemableWithoutInternalBurn); vm.label(erc7498Tokens[0], "erc721ShipyardRedeemable"); vm.label(erc7498Tokens[1], "erc721SeaDropRedeemable"); vm.label(erc7498Tokens[2], "erc1155ShipyardRedeemable"); vm.label(erc7498Tokens[3], "erc1155SeaDropRedeemable"); + vm.label(erc7498Tokens[4], "erc721ShipyardRedeemableWithoutInternalBurn"); receiveToken721 = new ERC721ShipyardRedeemableMintable("", ""); receiveToken1155 = new ERC1155ShipyardRedeemableMintable("", ""); @@ -140,13 +148,6 @@ contract BaseRedeemablesTest is RedeemablesErrors, BaseOrderTest { fn(context); } - function _campaignParamsMap() private pure returns (mapping(string => CampaignParams) storage campaignParamsMap) { - bytes32 position = CAMPAIGN_PARAMS_MAP_POSITION; - assembly { - campaignParamsMap.slot := position - } - } - function _setApprovals(address _owner) internal virtual override { vm.startPrank(_owner); for (uint256 i = 0; i < erc20s.length; ++i) {