diff --git a/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol b/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol index 6a73bca..a98be77 100644 --- a/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol +++ b/script/DeployAndRedeemTokens-CampaignOnReceiveToken.s.sol @@ -17,7 +17,8 @@ contract DeployAndRedeemTokens_CampaignOnReceiveToken is Script, Test { vm.startBroadcast(); ERC721OwnerMintable redeemToken = new ERC721OwnerMintable(); - ERC721ShipyardRedeemableMintable receiveToken = new ERC721ShipyardRedeemableMintable(); + ERC721ShipyardRedeemableMintable receiveToken = + new ERC721ShipyardRedeemableMintable("TestRedeemablesReceiveToken", "TEST"); // Configure the campaign. OfferItem[] memory offer = new OfferItem[](1); @@ -53,7 +54,7 @@ contract DeployAndRedeemTokens_CampaignOnReceiveToken is Script, Test { maxCampaignRedemptions: 1_000, manager: msg.sender }); - receiveToken.createCampaign(params, ""); + receiveToken.createCampaign(params, "ipfs://QmQKc93y2Ev5k9Kz54mCw48ZM487bwGDktZYPLtrjJ3r1d"); // Mint token 1 to redeem for token 1. redeemToken.mint(msg.sender, 1); @@ -68,8 +69,8 @@ contract DeployAndRedeemTokens_CampaignOnReceiveToken is Script, Test { tokenIds[0] = 1; // Individual user approvals not needed when setting the burn address. - // redeemToken.setApprovalForAll(address(receiveToken), true); - redeemToken.setBurnAddress(address(receiveToken)); + redeemToken.setApprovalForAll(address(receiveToken), true); + // redeemToken.setBurnAddress(address(receiveToken)); receiveToken.redeem(tokenIds, msg.sender, data); diff --git a/script/DeployAndRedeemTokens.s.sol b/script/DeployAndRedeemTokens.s.sol index 8ef986a..626f49b 100644 --- a/script/DeployAndRedeemTokens.s.sol +++ b/script/DeployAndRedeemTokens.s.sol @@ -15,8 +15,10 @@ contract DeployAndRedeemTokens is Script, Test { function run() external { vm.startBroadcast(); - ERC721ShipyardRedeemableOwnerMintable redeemToken = new ERC721ShipyardRedeemableOwnerMintable(); + ERC721ShipyardRedeemableOwnerMintable redeemToken = + new ERC721ShipyardRedeemableOwnerMintable("TestRedeemablesRedeemToken", "TEST"); ERC721RedemptionMintable receiveToken = new ERC721RedemptionMintable( + "TestRedeemablesRecieveToken", "TEST", address(redeemToken) ); diff --git a/src/ERC1155ShipyardRedeemable.sol b/src/ERC1155ShipyardRedeemable.sol new file mode 100644 index 0000000..9427f8f --- /dev/null +++ b/src/ERC1155ShipyardRedeemable.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {ERC1155ShipyardContractMetadata} from "./lib/ERC1155ShipyardContractMetadata.sol"; +import {Ownable} from "solady/src/auth/Ownable.sol"; +import {ERC7498NFTRedeemables} from "./lib/ERC7498NFTRedeemables.sol"; +import {CampaignParams} 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_) + public + override + onlyOwner + returns (uint256 campaignId) + { + campaignId = ERC7498NFTRedeemables.createCampaign(params, uri_); + } + + function _useInternalBurn() internal pure virtual override returns (bool) { + return true; + } + + function _internalBurn(address from, uint256 id, uint256 amount) internal virtual override { + _burn(from, id, amount); + } + + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(ERC1155ShipyardContractMetadata, ERC7498NFTRedeemables) + returns (bool) + { + return ERC1155ShipyardContractMetadata.supportsInterface(interfaceId) + || ERC7498NFTRedeemables.supportsInterface(interfaceId); + } +} diff --git a/src/ERC721SeaDropRedeemable.sol b/src/ERC721SeaDropRedeemable.sol index 2ebfccf..02fac75 100644 --- a/src/ERC721SeaDropRedeemable.sol +++ b/src/ERC721SeaDropRedeemable.sol @@ -30,7 +30,7 @@ contract ERC721SeaDropRedeemable is ERC721SeaDrop, ERC7498NFTRedeemables, Dynami return true; } - function _internalBurn(uint256 id, uint256 /* amount */ ) internal virtual override { + function _internalBurn(address, /* from */ uint256 id, uint256 /* amount */ ) internal virtual override { _burn(id); } diff --git a/src/ERC721ShipyardRedeemable.sol b/src/ERC721ShipyardRedeemable.sol index 2674f1e..26d1df8 100644 --- a/src/ERC721ShipyardRedeemable.sol +++ b/src/ERC721ShipyardRedeemable.sol @@ -1,28 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {ERC721ConduitPreapproved_Solady} from "shipyard-core/src/tokens/erc721/ERC721ConduitPreapproved_Solady.sol"; -import {ERC721} from "solady/src/tokens/ERC721.sol"; -import {Ownable} from "solady/src/auth/Ownable.sol"; +import {ERC721ShipyardContractMetadata} from "./lib/ERC721ShipyardContractMetadata.sol"; import {ERC7498NFTRedeemables} from "./lib/ERC7498NFTRedeemables.sol"; import {CampaignParams} from "./lib/RedeemablesStructs.sol"; -contract ERC721ShipyardRedeemable is ERC721ConduitPreapproved_Solady, ERC7498NFTRedeemables, Ownable { - constructor() ERC721ConduitPreapproved_Solady() { - _initializeOwner(msg.sender); - } - - function name() public pure override returns (string memory) { - return "ERC721ShipyardRedeemable"; - } - - function symbol() public pure override returns (string memory) { - return "SY-RDM"; - } - - function tokenURI(uint256 /* tokenId */ ) public pure override returns (string memory) { - return "https://example.com/"; - } +contract ERC721ShipyardRedeemable is ERC721ShipyardContractMetadata, ERC7498NFTRedeemables { + constructor(string memory name_, string memory symbol_) ERC721ShipyardContractMetadata(name_, symbol_) {} function createCampaign(CampaignParams calldata params, string calldata uri) public @@ -37,7 +21,7 @@ contract ERC721ShipyardRedeemable is ERC721ConduitPreapproved_Solady, ERC7498NFT return true; } - function _internalBurn(uint256 id, uint256 /* amount */ ) internal virtual override { + function _internalBurn(address, /* from */ uint256 id, uint256 /* amount */ ) internal virtual override { _burn(id); } @@ -45,9 +29,10 @@ contract ERC721ShipyardRedeemable is ERC721ConduitPreapproved_Solady, ERC7498NFT public view virtual - override(ERC721, ERC7498NFTRedeemables) + override(ERC721ShipyardContractMetadata, ERC7498NFTRedeemables) returns (bool) { - return ERC721.supportsInterface(interfaceId) || ERC7498NFTRedeemables.supportsInterface(interfaceId); + return ERC721ShipyardContractMetadata.supportsInterface(interfaceId) + || ERC7498NFTRedeemables.supportsInterface(interfaceId); } } diff --git a/src/extensions/ERC1155RedemptionMintable.sol b/src/extensions/ERC1155RedemptionMintable.sol index 0b9eb30..42b0b5d 100644 --- a/src/extensions/ERC1155RedemptionMintable.sol +++ b/src/extensions/ERC1155RedemptionMintable.sol @@ -1,18 +1,22 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {ERC1155} from "solady/src/tokens/ERC1155.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 ERC1155, IRedemptionMintable { +contract ERC1155RedemptionMintable is ERC1155ShipyardContractMetadata, IRedemptionMintable { + /// @dev The ERC-7498 redeemables contract. address internal immutable _ERC7498_REDEEMABLES_CONTRACT; /// @dev Revert if the sender of mintRedemption is not the redeemable contract offerer. error InvalidSender(); - constructor(address redeemableContractOfferer) { + constructor(string memory name_, string memory symbol_, address redeemableContractOfferer) + ERC1155ShipyardContractMetadata(name_, symbol_) + { + // Set the redeemables contract address. _ERC7498_REDEEMABLES_CONTRACT = redeemableContractOfferer; } @@ -36,7 +40,14 @@ contract ERC1155RedemptionMintable is ERC1155, IRedemptionMintable { } } - function uri(uint256 id) public pure override returns (string memory) { - return string(abi.encodePacked("https://example.com/", id)); + 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 new file mode 100644 index 0000000..d82866e --- /dev/null +++ b/src/extensions/ERC1155ShipyardRedeemableMintable.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {ERC721ConduitPreapproved_Solady} from "shipyard-core/src/tokens/erc721/ERC721ConduitPreapproved_Solady.sol"; +import {ConsiderationItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {Ownable} from "solady/src/auth/Ownable.sol"; +import {ERC7498NFTRedeemables} from "../lib/ERC7498NFTRedeemables.sol"; +import {CampaignParams} from "../lib/RedeemablesStructs.sol"; +import {IRedemptionMintable} from "../interfaces/IRedemptionMintable.sol"; +import {ERC1155ShipyardRedeemable} from "../ERC1155ShipyardRedeemable.sol"; +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 next token id to mint. Each token will have a supply of 1. + uint256 _nextTokenId = 1; + + function mintRedemption( + uint256, /* campaignId */ + address recipient, + ConsiderationItem[] calldata, /* consideration */ + TraitRedemption[] calldata /* traitRedemptions */ + ) external { + if (msg.sender != address(this)) { + revert InvalidSender(); + } + + // Increment nextTokenId first so more of the same token id cannot be minted through reentrancy. + ++_nextTokenId; + + _mint(recipient, _nextTokenId - 1, 1, ""); + } + + constructor(string memory name_, string memory symbol_) ERC1155ShipyardRedeemable(name_, symbol_) {} +} diff --git a/src/extensions/ERC721RedemptionMintable.sol b/src/extensions/ERC721RedemptionMintable.sol index 8dbf7de..58ddd76 100644 --- a/src/extensions/ERC721RedemptionMintable.sol +++ b/src/extensions/ERC721RedemptionMintable.sol @@ -1,19 +1,25 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {ERC721} from "solady/src/tokens/ERC721.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 ERC721, IRedemptionMintable { +contract ERC721RedemptionMintable is ERC721ShipyardContractMetadata, IRedemptionMintable { + /// @dev The ERC-7498 redeemables contract. address internal immutable _ERC7498_REDEEMABLES_CONTRACT; + + /// @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(address redeemablesContractAddress) { + constructor(string memory name_, string memory symbol_, address redeemablesContractAddress) + ERC721ShipyardContractMetadata(name_, symbol_) + { + // Set the redeemables contract address. _ERC7498_REDEEMABLES_CONTRACT = redeemablesContractAddress; } @@ -26,19 +32,21 @@ contract ERC721RedemptionMintable is ERC721, IRedemptionMintable { if (msg.sender != _ERC7498_REDEEMABLES_CONTRACT) { revert InvalidSender(); } - _mint(recipient, _nextTokenId); - ++_nextTokenId; - } - function name() public pure override returns (string memory) { - return "ERC721RedemptionMintable"; - } + // Increment nextTokenId first so more of the same token id cannot be minted through reentrancy. + ++_nextTokenId; - function symbol() public pure override returns (string memory) { - return "721RM"; + _mint(recipient, _nextTokenId - 1); } - function tokenURI(uint256 tokenId) public pure override returns (string memory) { - return string(abi.encodePacked("https://example.com/", tokenId)); + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(ERC721ShipyardContractMetadata) + returns (bool) + { + return ERC721ShipyardContractMetadata.supportsInterface(interfaceId) + || interfaceId == type(IRedemptionMintable).interfaceId; } } diff --git a/src/extensions/ERC721ShipyardRedeemableMintable.sol b/src/extensions/ERC721ShipyardRedeemableMintable.sol index e5557fc..c88e31b 100644 --- a/src/extensions/ERC721ShipyardRedeemableMintable.sol +++ b/src/extensions/ERC721ShipyardRedeemableMintable.sol @@ -27,9 +27,5 @@ contract ERC721ShipyardRedeemableMintable is ERC721ShipyardRedeemable, IRedempti _mint(recipient, 1); } - constructor() ERC721ShipyardRedeemable() {} - - function _useInternalBurn() internal pure override returns (bool) { - return false; - } + constructor(string memory name_, string memory symbol_) ERC721ShipyardRedeemable(name_, symbol_) {} } diff --git a/src/lib/ERC1155ShipyardContractMetadata.sol b/src/lib/ERC1155ShipyardContractMetadata.sol new file mode 100644 index 0000000..d010bf9 --- /dev/null +++ b/src/lib/ERC1155ShipyardContractMetadata.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {ERC1155ConduitPreapproved_Solady} from "shipyard-core/src/tokens/erc1155/ERC1155ConduitPreapproved_Solady.sol"; +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 { + /// @dev The token name + string internal _name; + + /// @dev The token symbol + string internal _symbol; + + /// @dev The base URI. + string public baseURI; + + /// @dev The contract URI. + string public contractURI; + + /// @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_; + _symbol = symbol_; + + // Initialize the owner of the contract. + _initializeOwner(msg.sender); + } + + function name() public view returns (string memory) { + return _name; + } + + function symbol() public view returns (string memory) { + return _symbol; + } + + function setBaseURI(string calldata newURI) external onlyOwner { + baseURI = newURI; + + // Emit an event with the update. + emit BatchMetadataUpdate(0, type(uint256).max); + } + + function setContractURI(string calldata newURI) external onlyOwner { + // Set the new contract URI. + contractURI = newURI; + + // Emit an event with the update. + emit ContractURIUpdated(newURI); + } + + /** + * @notice Sets the provenance hash and emits an event. + * + * The provenance hash is used for random reveals, which + * is a hash of the ordered metadata to show it has not been + * modified after mint started. + * + * This function will revert if the provenance hash has already + * been set, so be sure to carefully set it only once. + * + * @param newProvenanceHash The new provenance hash to set. + */ + function setProvenanceHash(bytes32 newProvenanceHash) external onlyOwner { + // Keep track of the old provenance hash for emitting with the event. + bytes32 oldProvenanceHash = provenanceHash; + + // Revert if the provenance hash has already been set. + if (oldProvenanceHash != bytes32(0)) { + revert ProvenanceHashCannotBeSetAfterAlreadyBeingSet(); + } + + // Set the new provenance hash. + provenanceHash = newProvenanceHash; + + // Emit an event with the update. + emit ProvenanceHashUpdated(oldProvenanceHash, newProvenanceHash); + } + + /** + * @notice Returns the URI for token metadata. + * + * This implementation returns the same URI for *all* token types. + * It relies on the token type ID substitution mechanism defined + * in the EIP to replace {id} with the token id. + * + * @custom:param tokenId The token id to get the URI for. + */ + function uri(uint256 /* tokenId */ ) public view virtual override returns (string memory) { + // Return the base URI. + return baseURI; + } + + /** + * @notice Sets the default royalty information. + * + * Requirements: + * + * - `receiver` cannot be the zero address. + * - `feeNumerator` cannot be greater than the fee denominator of 10_000 basis points. + */ + function setDefaultRoyalty(address receiver, uint96 feeNumerator) external onlyOwner { + // Set the default royalty. + // ERC2981 implementation ensures feeNumerator <= feeDenominator + // and receiver != address(0). + _setDefaultRoyalty(receiver, feeNumerator); + + // Emit an event with the updated params. + emit RoyaltyInfoUpdated(receiver, feeNumerator); + } + + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155, ERC2981) returns (bool) { + return ERC1155.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId); + } +} diff --git a/src/lib/ERC721ShipyardContractMetadata.sol b/src/lib/ERC721ShipyardContractMetadata.sol new file mode 100644 index 0000000..f2139ce --- /dev/null +++ b/src/lib/ERC721ShipyardContractMetadata.sol @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {ERC721ConduitPreapproved_Solady} from "shipyard-core/src/tokens/erc721/ERC721ConduitPreapproved_Solady.sol"; +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 { + /// @dev The token name + string internal _name; + + /// @dev The token symbol + string internal _symbol; + + /// @dev The base URI. + string public baseURI; + + /// @dev The contract URI. + string public contractURI; + + /// @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_; + _symbol = symbol_; + + // Initialize the owner of the contract. + _initializeOwner(msg.sender); + } + + function name() public view override returns (string memory) { + return _name; + } + + function symbol() public view override returns (string memory) { + return _symbol; + } + + function setBaseURI(string calldata newURI) external onlyOwner { + baseURI = newURI; + + // Emit an event with the update. + emit BatchMetadataUpdate(0, type(uint256).max); + } + + function setContractURI(string calldata newURI) external onlyOwner { + // Set the new contract URI. + contractURI = newURI; + + // Emit an event with the update. + emit ContractURIUpdated(newURI); + } + + /** + * @notice Sets the provenance hash and emits an event. + * + * The provenance hash is used for random reveals, which + * is a hash of the ordered metadata to show it has not been + * modified after mint started. + * + * This function will revert if the provenance hash has already + * been set, so be sure to carefully set it only once. + * + * @param newProvenanceHash The new provenance hash to set. + */ + function setProvenanceHash(bytes32 newProvenanceHash) external onlyOwner { + // Keep track of the old provenance hash for emitting with the event. + bytes32 oldProvenanceHash = provenanceHash; + + // Revert if the provenance hash has already been set. + if (oldProvenanceHash != bytes32(0)) { + revert ProvenanceHashCannotBeSetAfterAlreadyBeingSet(); + } + + // Set the new provenance hash. + provenanceHash = newProvenanceHash; + + // Emit an event with the update. + emit ProvenanceHashUpdated(oldProvenanceHash, newProvenanceHash); + } + + function tokenURI(uint256 tokenId) public view virtual override returns (string memory uri) { + // Revert if the tokenId doesn't exist. + if (!_exists(tokenId)) revert TokenDoesNotExist(); + + // Put the baseURI on the stack. + uri = baseURI; + + // Return empty if baseURI is empty. + if (bytes(uri).length == 0) { + return ""; + } + + // If the last character of the baseURI is not a slash, then return + // the baseURI to signal the same metadata for all tokens, such as + // for a prereveal state. + if (bytes(uri)[bytes(uri).length - 1] != bytes("/")[0]) { + return uri; + } + + // Append the tokenId to the baseURI and return. + uri = string.concat(uri, _toString(tokenId)); + } + + /** + * @notice Sets the default royalty information. + * + * Requirements: + * + * - `receiver` cannot be the zero address. + * - `feeNumerator` cannot be greater than the fee denominator of 10_000 basis points. + */ + function setDefaultRoyalty(address receiver, uint96 feeNumerator) external onlyOwner { + // Set the default royalty. + // ERC2981 implementation ensures feeNumerator <= feeDenominator + // and receiver != address(0). + _setDefaultRoyalty(receiver, feeNumerator); + + // Emit an event with the updated params. + emit RoyaltyInfoUpdated(receiver, feeNumerator); + } + + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC2981) returns (bool) { + return ERC721.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId); + } + + /** + * @dev Converts a uint256 to its ASCII string decimal representation. + */ + function _toString(uint256 value) internal pure virtual returns (string memory str) { + assembly { + // The maximum value of a uint256 contains 78 digits (1 byte per digit), but + // we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned. + // We will need 1 word for the trailing zeros padding, 1 word for the length, + // and 3 words for a maximum of 78 digits. Total: 5 * 0x20 = 0xa0. + let m := add(mload(0x40), 0xa0) + // Update the free memory pointer to allocate. + mstore(0x40, m) + // Assign the `str` to the end. + str := sub(m, 0x20) + // Zeroize the slot after the string. + mstore(str, 0) + + // Cache the end of the memory to calculate the length later. + let end := str + + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + // prettier-ignore + for { let temp := value } 1 {} { + str := sub(str, 1) + // Write the character to the pointer. + // The ASCII index of the '0' character is 48. + mstore8(str, add(48, mod(temp, 10))) + // Keep dividing `temp` until zero. + temp := div(temp, 10) + // prettier-ignore + if iszero(temp) { break } + } + + let length := sub(end, str) + // Move the pointer 32 bytes leftwards to make room for the length. + str := sub(str, 0x20) + // Store the length. + mstore(str, length) + } + } +} diff --git a/src/lib/ERC7498NFTRedeemables.sol b/src/lib/ERC7498NFTRedeemables.sol index 117103d..94cf3b0 100644 --- a/src/lib/ERC7498NFTRedeemables.sol +++ b/src/lib/ERC7498NFTRedeemables.sol @@ -194,7 +194,7 @@ contract ERC7498NFTRedeemables is IERC7498, RedeemablesErrors { // If consideration item is this contract, recipient is burn address, and _useInternalBurn() fn returns true, // call the internal burn function and return. if (c.token == address(this) && c.recipient == payable(_BURN_ADDRESS) && _useInternalBurn()) { - _internalBurn(id, c.startAmount); + _internalBurn(msg.sender, id, c.startAmount); } else { // Transfer the token to the consideration recipient. if (c.itemType == ItemType.ERC721 || c.itemType == ItemType.ERC721_WITH_CRITERIA) { @@ -218,7 +218,7 @@ contract ERC7498NFTRedeemables is IERC7498, RedeemablesErrors { /// @dev Function that is called to burn amounts of a token internal to this inherited contract. /// Override with token implementation calling internal burn. - function _internalBurn(uint256 id, uint256 amount) internal virtual { + function _internalBurn(address from, uint256 id, uint256 amount) internal virtual { // Override with your token implementation calling internal burn. } diff --git a/src/test/ERC721ShipyardRedeemableOwnerMintable.sol b/src/test/ERC721ShipyardRedeemableOwnerMintable.sol index a1a29e5..4375d90 100644 --- a/src/test/ERC721ShipyardRedeemableOwnerMintable.sol +++ b/src/test/ERC721ShipyardRedeemableOwnerMintable.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.19; import {ERC721ShipyardRedeemable} from "../ERC721ShipyardRedeemable.sol"; contract ERC721ShipyardRedeemableOwnerMintable is ERC721ShipyardRedeemable { - constructor() ERC721ShipyardRedeemable() {} + constructor(string memory name_, string memory symbol_) ERC721ShipyardRedeemable(name_, symbol_) {} function mint(address to, uint256 tokenId) public onlyOwner { _mint(to, tokenId); diff --git a/test/ERC721ShipyardRedeemable.t.sol b/test/ERC721ShipyardRedeemable.t.sol index d9028f4..99a62d6 100644 --- a/test/ERC721ShipyardRedeemable.t.sol +++ b/test/ERC721ShipyardRedeemable.t.sol @@ -128,7 +128,7 @@ contract TestERC721ShipyardRedeemable is BaseRedeemablesTest { function testRevertConsiderationLengthNotMet() public { redeemToken.mint(address(this), tokenId); - ERC721ShipyardRedeemableOwnerMintable secondRedeemToken = new ERC721ShipyardRedeemableOwnerMintable(); + ERC721ShipyardRedeemableOwnerMintable secondRedeemToken = new ERC721ShipyardRedeemableOwnerMintable("", ""); ConsiderationItem[] memory consideration = new ConsiderationItem[](2); consideration[0] = ConsiderationItem({ @@ -184,7 +184,7 @@ contract TestERC721ShipyardRedeemable is BaseRedeemablesTest { } function testBurnWithSecondConsiderationItem() public { - ERC721ShipyardRedeemableOwnerMintable secondRedeemToken = new ERC721ShipyardRedeemableOwnerMintable(); + ERC721ShipyardRedeemableOwnerMintable secondRedeemToken = new ERC721ShipyardRedeemableOwnerMintable("", ""); vm.label(address(secondRedeemToken), "secondRedeemToken"); secondRedeemToken.setApprovalForAll(address(redeemToken), true); @@ -278,7 +278,7 @@ contract TestERC721ShipyardRedeemable is BaseRedeemablesTest { } function testBurnWithSecondRequirementsIndex() public { - ERC721ShipyardRedeemableOwnerMintable secondRedeemToken = new ERC721ShipyardRedeemableOwnerMintable(); + ERC721ShipyardRedeemableOwnerMintable secondRedeemToken = new ERC721ShipyardRedeemableOwnerMintable("", ""); vm.label(address(secondRedeemToken), "secondRedeemToken"); secondRedeemToken.setApprovalForAll(address(redeemToken), true); diff --git a/test/utils/BaseRedeemablesTest.sol b/test/utils/BaseRedeemablesTest.sol index 1836e64..bb7b57d 100644 --- a/test/utils/BaseRedeemablesTest.sol +++ b/test/utils/BaseRedeemablesTest.sol @@ -36,8 +36,8 @@ contract BaseRedeemablesTest is RedeemablesErrors, BaseOrderTest { function setUp() public virtual override { super.setUp(); - redeemToken = new ERC721ShipyardRedeemableOwnerMintable(); - receiveToken = new ERC721RedemptionMintable(address(redeemToken)); + redeemToken = new ERC721ShipyardRedeemableOwnerMintable("", ""); + receiveToken = new ERC721RedemptionMintable("", "", address(redeemToken)); vm.label(address(redeemToken), "redeemToken"); vm.label(address(receiveToken), "receiveToken");