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));