From be6f40aa51b892eeeb4b870246927fd80c6863ce Mon Sep 17 00:00:00 2001 From: Stanley Szeto Date: Wed, 9 Oct 2024 08:59:11 -0700 Subject: [PATCH] Audit fixes (#173) * [M-1] The SequentialTokenIdERC1155 module fails to apply the correct tokenId when installed after initial minting - updated sequentialTokenIdERC1155 to now be able to initialize nextTokenId * [H-1] ClaimableERC20 and MintableERC20 modules incorrectly handle tokens with decimals other than 18 - uses IERC20.decimals instead of 1e18 * [H-2] Claimable modules lead to storage collisions when being updgraded * [H-3] BatchMetadata modules may apply baseURI to incorrect token ids * [Q-1] FallbackFunction array of Claimable modules can be reduced * [Q-2] Nitpicks --- src/callback/BeforeMintCallbackERC721.sol | 1 + ...BeforeMintWithSignatureCallbackERC1155.sol | 1 + .../BeforeMintWithSignatureCallbackERC721.sol | 1 + src/core/token/ERC20Base.sol | 4 + .../token/metadata/BatchMetadataERC1155.sol | 8 +- .../token/metadata/BatchMetadataERC721.sol | 110 +++---- src/module/token/minting/ClaimableERC1155.sol | 10 +- src/module/token/minting/ClaimableERC20.sol | 35 ++- src/module/token/minting/ClaimableERC721.sol | 10 +- src/module/token/minting/MintableERC20.sol | 13 +- src/module/token/royalty/RoyaltyERC1155.sol | 4 +- src/module/token/royalty/RoyaltyERC721.sol | 4 +- .../tokenId/SequentialTokenIdERC1155.sol | 25 ++ .../metadata/BatchMetadataERC1155.t.sol | 41 +-- .../module/metadata/BatchMetadataERC721.t.sol | 286 +++++++++++++----- .../tokenId/SequentialTokenIdERC1155.t.sol | 16 +- 16 files changed, 370 insertions(+), 199 deletions(-) diff --git a/src/callback/BeforeMintCallbackERC721.sol b/src/callback/BeforeMintCallbackERC721.sol index 9691be15..6cf25fcb 100644 --- a/src/callback/BeforeMintCallbackERC721.sol +++ b/src/callback/BeforeMintCallbackERC721.sol @@ -17,6 +17,7 @@ contract BeforeMintCallbackERC721 { * @notice The beforeMintERC721 hook that is called by a core token before minting tokens. * * @param _to The address that is minting tokens. + * @param _startTokenId The token ID being minted. * @param _amount The amount of tokens to mint. * @param _data Optional extra data passed to the hook. * @return result Abi encoded bytes result of the hook. diff --git a/src/callback/BeforeMintWithSignatureCallbackERC1155.sol b/src/callback/BeforeMintWithSignatureCallbackERC1155.sol index 82e3d20d..e75846ea 100644 --- a/src/callback/BeforeMintWithSignatureCallbackERC1155.sol +++ b/src/callback/BeforeMintWithSignatureCallbackERC1155.sol @@ -17,6 +17,7 @@ contract BeforeMintWithSignatureCallbackERC1155 { * @notice The beforeMintWithSignatureERC1155 hook that is called by a core token before minting tokens. * * @param _to The address that is minting tokens. + * @param _id The token ID being minted. * @param _amount The quantity of tokens to mint. * @param _data Optional extra data passed to the hook. * @param _signer The address that signed the minting request. diff --git a/src/callback/BeforeMintWithSignatureCallbackERC721.sol b/src/callback/BeforeMintWithSignatureCallbackERC721.sol index b43a6937..f2731885 100644 --- a/src/callback/BeforeMintWithSignatureCallbackERC721.sol +++ b/src/callback/BeforeMintWithSignatureCallbackERC721.sol @@ -17,6 +17,7 @@ contract BeforeMintWithSignatureCallbackERC721 { * @notice The beforeMintWithSignatureERC721 hook that is called by a core token before minting tokens. * * @param _to The address that is minting tokens. + * @param _startTokenId The token ID being minted. * @param _amount The amount of tokens to mint. * @param _data Optional extra data passed to the hook. * @param _signer The address that signed the minting request. diff --git a/src/core/token/ERC20Base.sol b/src/core/token/ERC20Base.sol index 95f61514..952c027f 100644 --- a/src/core/token/ERC20Base.sol +++ b/src/core/token/ERC20Base.sol @@ -234,6 +234,10 @@ contract ERC20Base is ERC20, Multicallable, Core, EIP712 { * @param owner The account approving the tokens * @param spender The address to approve * @param amount Amount of tokens to approve + * @param deadline Deadline after which the approval is no longer valid + * @param v Signature param + * @param r Signature param + * @param s Signature param */ function permit(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public diff --git a/src/module/token/metadata/BatchMetadataERC1155.sol b/src/module/token/metadata/BatchMetadataERC1155.sol index 656af62a..497d3fc1 100644 --- a/src/module/token/metadata/BatchMetadataERC1155.sol +++ b/src/module/token/metadata/BatchMetadataERC1155.sol @@ -22,9 +22,9 @@ contract BatchMetadataERC1155 is BatchMetadataERC721, UpdateMetadataCallbackERC1 FallbackFunction({selector: this.setBaseURI.selector, permissionBits: Role._MANAGER_ROLE}); config.fallbackFunctions[2] = FallbackFunction({selector: this.getAllMetadataBatches.selector, permissionBits: 0}); - config.fallbackFunctions[3] = FallbackFunction({selector: this.nextTokenIdToMint.selector, permissionBits: 0}); - config.fallbackFunctions[4] = FallbackFunction({selector: this.getBatchId.selector, permissionBits: 0}); - config.fallbackFunctions[5] = FallbackFunction({selector: this.getBatchRange.selector, permissionBits: 0}); + config.fallbackFunctions[3] = FallbackFunction({selector: this.getMetadataBatch.selector, permissionBits: 0}); + config.fallbackFunctions[4] = FallbackFunction({selector: this.nextTokenIdToMint.selector, permissionBits: 0}); + config.fallbackFunctions[5] = FallbackFunction({selector: this.getBatchIndex.selector, permissionBits: 0}); config.requiredInterfaces = new bytes4[](1); config.requiredInterfaces[0] = 0xd9b67a26; // ERC1155 @@ -44,7 +44,7 @@ contract BatchMetadataERC1155 is BatchMetadataERC721, UpdateMetadataCallbackERC1 if (_startTokenId < _batchMetadataStorage().nextTokenIdRangeStart) { revert BatchMetadataMetadataAlreadySet(); } - _setMetadata(_quantity, _baseURI); + _setMetadata(_startTokenId, _quantity, _baseURI); } } diff --git a/src/module/token/metadata/BatchMetadataERC721.sol b/src/module/token/metadata/BatchMetadataERC721.sol index bf47dcea..38382745 100644 --- a/src/module/token/metadata/BatchMetadataERC721.sol +++ b/src/module/token/metadata/BatchMetadataERC721.sol @@ -14,12 +14,10 @@ library BatchMetadataStorage { keccak256(abi.encode(uint256(keccak256("token.metadata.batch")) - 1)) & ~bytes32(uint256(0xff)); struct Data { - // tokenId range end - uint256[] tokenIdRangeEnd; // next tokenId as range start uint256 nextTokenIdRangeStart; - // tokenId range end => baseURI of range - mapping(uint256 => string) baseURIOfTokenIdRange; + // metadata batches + BatchMetadataERC721.MetadataBatch[] metadataBatches; } function data() internal pure returns (Data storage data_) { @@ -42,7 +40,7 @@ contract BatchMetadataERC721 is Module, UpdateMetadataCallbackERC721 { /** * @notice MetadataBatch struct to store metadata for a range of tokenIds. * @param startTokenIdInclusive The first tokenId in the range. - * @param endTokenIdNonInclusive The last tokenId in the range. + * @param endTokenIdInclusive The last tokenId in the range. * @param baseURI The base URI for the range. */ struct MetadataBatch { @@ -69,7 +67,7 @@ contract BatchMetadataERC721 is Module, UpdateMetadataCallbackERC721 { //////////////////////////////////////////////////////////////*/ /// @dev ERC-4906 Metadata Update. - event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); + event BatchMetadataUpdate(uint256 startTokenIdIncluside, uint256 endTokenIdInclusive, string baseURI); /*////////////////////////////////////////////////////////////// MODULE CONFIG @@ -89,9 +87,9 @@ contract BatchMetadataERC721 is Module, UpdateMetadataCallbackERC721 { FallbackFunction({selector: this.setBaseURI.selector, permissionBits: Role._MANAGER_ROLE}); config.fallbackFunctions[2] = FallbackFunction({selector: this.getAllMetadataBatches.selector, permissionBits: 0}); - config.fallbackFunctions[3] = FallbackFunction({selector: this.nextTokenIdToMint.selector, permissionBits: 0}); - config.fallbackFunctions[4] = FallbackFunction({selector: this.getBatchId.selector, permissionBits: 0}); - config.fallbackFunctions[5] = FallbackFunction({selector: this.getBatchRange.selector, permissionBits: 0}); + config.fallbackFunctions[3] = FallbackFunction({selector: this.getMetadataBatch.selector, permissionBits: 0}); + config.fallbackFunctions[4] = FallbackFunction({selector: this.nextTokenIdToMint.selector, permissionBits: 0}); + config.fallbackFunctions[5] = FallbackFunction({selector: this.getBatchIndex.selector, permissionBits: 0}); config.requiredInterfaces = new bytes4[](1); config.requiredInterfaces[0] = 0x80ac58cd; // ERC721. @@ -121,7 +119,7 @@ contract BatchMetadataERC721 is Module, UpdateMetadataCallbackERC721 { if (_startTokenId < _batchMetadataStorage().nextTokenIdRangeStart) { revert BatchMetadataMetadataAlreadySet(); } - _setMetadata(_quantity, _baseURI); + _setMetadata(_startTokenId, _quantity, _baseURI); } /*////////////////////////////////////////////////////////////// @@ -130,71 +128,42 @@ contract BatchMetadataERC721 is Module, UpdateMetadataCallbackERC721 { /// @notice Returns all metadata batches for a token. function getAllMetadataBatches() external view returns (MetadataBatch[] memory) { - uint256[] memory rangeEnds = _batchMetadataStorage().tokenIdRangeEnd; - uint256 numOfBatches = rangeEnds.length; - - MetadataBatch[] memory batches = new MetadataBatch[](rangeEnds.length); - - uint256 rangeStart = 0; - for (uint256 i = 0; i < numOfBatches; i += 1) { - batches[i] = MetadataBatch({ - startTokenIdInclusive: rangeStart, - endTokenIdInclusive: rangeEnds[i] - 1, - baseURI: _batchMetadataStorage().baseURIOfTokenIdRange[rangeEnds[i]] - }); - rangeStart = rangeEnds[i]; - } + return _batchMetadataStorage().metadataBatches; + } - return batches; + /// @dev returns the metadata batch for a given batchIndex + function getMetadataBatch(uint256 _batchIndex) public view returns (MetadataBatch memory) { + return _batchMetadataStorage().metadataBatches[_batchIndex]; } /// @notice Uploads metadata for a range of tokenIds. function uploadMetadata(uint256 _amount, string calldata _baseURI) external virtual { - _setMetadata(_amount, _baseURI); + _setMetadata(_batchMetadataStorage().nextTokenIdRangeStart, _amount, _baseURI); } function nextTokenIdToMint() external view returns (uint256) { return _batchMetadataStorage().nextTokenIdRangeStart; } - /// @dev Returns the id for the batch of tokens the given tokenId belongs to. - function getBatchId(uint256 _tokenId) public view virtual returns (uint256 batchId, uint256 index) { - uint256[] memory rangeEnds = _batchMetadataStorage().tokenIdRangeEnd; - uint256 numOfBatches = rangeEnds.length; - - for (uint256 i = 0; i < numOfBatches; i += 1) { - if (_tokenId < rangeEnds[i]) { - index = i; - batchId = rangeEnds[i]; - - return (batchId, index); - } - } - revert BatchMetadataNoMetadataForTokenId(); - } - - /// @dev returns the starting tokenId of a given batchId. - function getBatchRange(uint256 _batchID) public view returns (uint256, uint256) { - uint256[] memory rangeEnds = _batchMetadataStorage().tokenIdRangeEnd; - uint256 numOfBatches = rangeEnds.length; + /// @dev Returns the index for the batch of tokens the given tokenId belongs to. + function getBatchIndex(uint256 _tokenId) public view virtual returns (uint256) { + MetadataBatch[] memory batches = _batchMetadataStorage().metadataBatches; + uint256 numOfBatches = batches.length; for (uint256 i = 0; i < numOfBatches; i += 1) { - if (_batchID == rangeEnds[i]) { - if (i > 0) { - return (rangeEnds[i - 1], rangeEnds[i] - 1); - } - return (0, rangeEnds[i] - 1); + if (_tokenId >= batches[i].startTokenIdInclusive && _tokenId <= batches[i].endTokenIdInclusive) { + return i; } } - revert BatchMetadataNoMetadataForTokenId(); } - /// @dev Sets the base URI for the batch of tokens with the given batchId. - function setBaseURI(uint256 _batchId, string memory _baseURI) external virtual { - _batchMetadataStorage().baseURIOfTokenIdRange[_batchId] = _baseURI; - (uint256 startTokenId,) = getBatchRange(_batchId); - emit BatchMetadataUpdate(startTokenId, _batchId); + /// @dev Sets the base URI for the batch based on the batchIndex. + function setBaseURI(uint256 _batchIndex, string memory _baseURI) external virtual { + MetadataBatch memory batch = _batchMetadataStorage().metadataBatches[_batchIndex]; + batch.baseURI = _baseURI; + _batchMetadataStorage().metadataBatches[_batchIndex] = batch; + emit BatchMetadataUpdate(batch.startTokenIdInclusive, batch.endTokenIdInclusive, batch.baseURI); } /*////////////////////////////////////////////////////////////// @@ -203,35 +172,34 @@ contract BatchMetadataERC721 is Module, UpdateMetadataCallbackERC721 { /// @dev Returns the baseURI for a token. The intended metadata URI for the token is baseURI + indexInBatch. function _getBaseURI(uint256 _tokenId) internal view returns (string memory baseUri, uint256 indexInBatch) { - uint256[] memory rangeEnds = _batchMetadataStorage().tokenIdRangeEnd; - uint256 numOfBatches = rangeEnds.length; + BatchMetadataERC721.MetadataBatch[] memory batches = _batchMetadataStorage().metadataBatches; + uint256 numOfBatches = batches.length; for (uint256 i = 0; i < numOfBatches; i += 1) { - if (_tokenId < rangeEnds[i]) { - uint256 rangeStart = 0; - if (i > 0) { - rangeStart = rangeEnds[i - 1]; - } - return (_batchMetadataStorage().baseURIOfTokenIdRange[rangeEnds[i]], _tokenId - rangeStart); + if (_tokenId >= batches[i].startTokenIdInclusive && _tokenId <= batches[i].endTokenIdInclusive) { + return (batches[i].baseURI, _tokenId - batches[i].startTokenIdInclusive); } } revert BatchMetadataNoMetadataForTokenId(); } /// @notice sets the metadata for a range of tokenIds. - function _setMetadata(uint256 _amount, string calldata _baseURI) internal virtual { + function _setMetadata(uint256 _rangeStart, uint256 _amount, string calldata _baseURI) internal virtual { if (_amount == 0) { revert BatchMetadataZeroAmount(); } - uint256 rangeStart = _batchMetadataStorage().nextTokenIdRangeStart; - uint256 rangeEndNonInclusive = rangeStart + _amount; + uint256 rangeEndNonInclusive = _rangeStart + _amount; + MetadataBatch memory batch = MetadataBatch({ + startTokenIdInclusive: _rangeStart, + endTokenIdInclusive: rangeEndNonInclusive - 1, + baseURI: _baseURI + }); + _batchMetadataStorage().metadataBatches.push(batch); _batchMetadataStorage().nextTokenIdRangeStart = rangeEndNonInclusive; - _batchMetadataStorage().tokenIdRangeEnd.push(rangeEndNonInclusive); - _batchMetadataStorage().baseURIOfTokenIdRange[rangeEndNonInclusive] = _baseURI; - emit BatchMetadataUpdate(rangeStart, rangeEndNonInclusive - 1); + emit BatchMetadataUpdate(_rangeStart, rangeEndNonInclusive - 1, _baseURI); } function _batchMetadataStorage() internal pure returns (BatchMetadataStorage.Data storage) { diff --git a/src/module/token/minting/ClaimableERC1155.sol b/src/module/token/minting/ClaimableERC1155.sol index 71c725fc..2f710964 100644 --- a/src/module/token/minting/ClaimableERC1155.sol +++ b/src/module/token/minting/ClaimableERC1155.sol @@ -19,14 +19,14 @@ library ClaimableStorage { keccak256(abi.encode(uint256(keccak256("token.minting.claimable.erc1155")) - 1)) & ~bytes32(uint256(0xff)); struct Data { - // sale config: primary sale recipient, and platform fee recipient + BPS. - ClaimableERC1155.SaleConfig saleConfig; - // token ID => claim condition - mapping(uint256 => ClaimableERC1155.ClaimCondition) claimConditionByTokenId; // UID => whether it has been used mapping(bytes32 => bool) uidUsed; // address => uint256 => how many tokens have been minted mapping(address => mapping(uint256 => uint256)) totalMinted; + // sale config: primary sale recipient, and platform fee recipient + BPS. + ClaimableERC1155.SaleConfig saleConfig; + // token ID => claim condition + mapping(uint256 => ClaimableERC1155.ClaimCondition) claimConditionByTokenId; } function data() internal pure returns (Data storage data_) { @@ -161,7 +161,7 @@ contract ClaimableERC1155 is /// @notice Returns all implemented callback and fallback functions. function getModuleConfig() external pure override returns (ModuleConfig memory config) { config.callbackFunctions = new CallbackFunction[](2); - config.fallbackFunctions = new FallbackFunction[](5); + config.fallbackFunctions = new FallbackFunction[](4); config.callbackFunctions[0] = CallbackFunction(this.beforeMintERC1155.selector); config.callbackFunctions[1] = CallbackFunction(this.beforeMintWithSignatureERC1155.selector); diff --git a/src/module/token/minting/ClaimableERC20.sol b/src/module/token/minting/ClaimableERC20.sol index 0d6e6359..9b90feff 100644 --- a/src/module/token/minting/ClaimableERC20.sol +++ b/src/module/token/minting/ClaimableERC20.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.20; import {Module} from "../../../Module.sol"; import {Role} from "../../../Role.sol"; + import {IInstallationCallback} from "../../../interface/IInstallationCallback.sol"; import {OwnableRoles} from "@solady/auth/OwnableRoles.sol"; import {MerkleProofLib} from "@solady/utils/MerkleProofLib.sol"; @@ -12,6 +13,12 @@ import {SafeTransferLib} from "@solady/utils/SafeTransferLib.sol"; import {BeforeMintCallbackERC20} from "../../../callback/BeforeMintCallbackERC20.sol"; import {BeforeMintWithSignatureCallbackERC20} from "../../../callback/BeforeMintWithSignatureCallbackERC20.sol"; +interface IERC20 { + + function decimals() external view returns (uint8); + +} + library ClaimableStorage { /// @custom:storage-location erc7201:token.minting.claimable.erc20 @@ -19,14 +26,14 @@ library ClaimableStorage { keccak256(abi.encode(uint256(keccak256("token.minting.claimable.erc20")) - 1)) & ~bytes32(uint256(0xff)); struct Data { - // sale config: primary sale recipient, and platform fee recipient + BPS. - ClaimableERC20.SaleConfig saleConfig; - // claim condition - ClaimableERC20.ClaimCondition claimCondition; // UID => whether it has been used mapping(bytes32 => bool) uidUsed; // address => how many tokens have been minted mapping(address => uint256) totalMinted; + // sale config: primary sale recipient, and platform fee recipient + BPS. + ClaimableERC20.SaleConfig saleConfig; + // claim condition + ClaimableERC20.ClaimCondition claimCondition; } function data() internal pure returns (Data storage data_) { @@ -83,9 +90,8 @@ contract ClaimableERC20 is * * @param startTimestamp The timestamp at which the minting request is valid. * @param endTimestamp The timestamp at which the minting request expires. - * @param recipient The address that will receive the minted tokens. - * @param amount The amount of tokens to mint. * @param currency The address of the currency used to pay for the minted tokens. + * @param maxMintPerWallet The maximum number of tokens that can be minted per wallet. * @param pricePerUnit The price per unit of the minted tokens. * @param uid A unique identifier for the minting request. */ @@ -101,8 +107,9 @@ contract ClaimableERC20 is /** * @notice The parameters sent to the `beforeMintERC20` callback function. * - * @param request The minting request. - * @param signature The signature produced from signing the minting request. + * @param currency The address of the currency used to pay for the minted tokens. + * @param pricePerUnit The price per unit of the minted tokens. + * @param recipientAllowlistProof The proof of the recipient's address in the allowlist. */ struct ClaimParamsERC20 { address currency; @@ -160,7 +167,7 @@ contract ClaimableERC20 is /// @notice Returns all implemented callback and fallback functions. function getModuleConfig() external pure override returns (ModuleConfig memory config) { config.callbackFunctions = new CallbackFunction[](2); - config.fallbackFunctions = new FallbackFunction[](5); + config.fallbackFunctions = new FallbackFunction[](4); config.callbackFunctions[0] = CallbackFunction(this.beforeMintERC20.selector); config.callbackFunctions[1] = CallbackFunction(this.beforeMintWithSignatureERC20.selector); @@ -194,10 +201,12 @@ contract ClaimableERC20 is _validateClaimCondition(_to, _amount, _params.currency, _params.pricePerUnit, _params.recipientAllowlistProof); - _distributeMintPrice(msg.sender, _params.currency, (_amount * _params.pricePerUnit) / 1e18); + _distributeMintPrice( + msg.sender, _params.currency, (_amount * _params.pricePerUnit) / (10 ** IERC20(address(this)).decimals()) + ); } - /// @notice Callback function for the ERC20Core.mint function. + /// @notice Callback function for the ERC20Core.mintWithSignature function. function beforeMintWithSignatureERC20(address _to, uint256 _amount, bytes memory _data, address _signer) external payable @@ -213,7 +222,9 @@ contract ClaimableERC20 is _validateClaimSignatureParams(_params, _to, _amount); - _distributeMintPrice(msg.sender, _params.currency, (_amount * _params.pricePerUnit) / 1e18); + _distributeMintPrice( + msg.sender, _params.currency, (_amount * _params.pricePerUnit) / (10 ** IERC20(address(this)).decimals()) + ); } /// @dev Called by a Core into an Module during the installation of the Module. diff --git a/src/module/token/minting/ClaimableERC721.sol b/src/module/token/minting/ClaimableERC721.sol index e8f75143..7de4ac9e 100644 --- a/src/module/token/minting/ClaimableERC721.sol +++ b/src/module/token/minting/ClaimableERC721.sol @@ -19,14 +19,14 @@ library ClaimableStorage { keccak256(abi.encode(uint256(keccak256("token.minting.claimable.erc721")) - 1)) & ~bytes32(uint256(0xff)); struct Data { - // sale config: primary sale recipient, and platform fee recipient + BPS. - ClaimableERC721.SaleConfig saleConfig; - // claim condition - ClaimableERC721.ClaimCondition claimCondition; // UID => whether it has been used mapping(bytes32 => bool) uidUsed; // address => how many tokens have been minted mapping(address => uint256) totalMinted; + // sale config: primary sale recipient + ClaimableERC721.SaleConfig saleConfig; + // claim condition + ClaimableERC721.ClaimCondition claimCondition; } function data() internal pure returns (Data storage data_) { @@ -155,7 +155,7 @@ contract ClaimableERC721 is /// @notice Returns all implemented callback and fallback functions. function getModuleConfig() external pure override returns (ModuleConfig memory config) { config.callbackFunctions = new CallbackFunction[](2); - config.fallbackFunctions = new FallbackFunction[](5); + config.fallbackFunctions = new FallbackFunction[](4); config.callbackFunctions[0] = CallbackFunction(this.beforeMintERC721.selector); config.callbackFunctions[1] = CallbackFunction(this.beforeMintWithSignatureERC721.selector); diff --git a/src/module/token/minting/MintableERC20.sol b/src/module/token/minting/MintableERC20.sol index de8dbb33..d3f1f585 100644 --- a/src/module/token/minting/MintableERC20.sol +++ b/src/module/token/minting/MintableERC20.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.20; import {Module} from "../../../Module.sol"; import {Role} from "../../../Role.sol"; + import {IInstallationCallback} from "../../../interface/IInstallationCallback.sol"; import {OwnableRoles} from "@solady/auth/OwnableRoles.sol"; import {SafeTransferLib} from "@solady/utils/SafeTransferLib.sol"; @@ -11,6 +12,12 @@ import {SafeTransferLib} from "@solady/utils/SafeTransferLib.sol"; import {BeforeMintCallbackERC20} from "../../../callback/BeforeMintCallbackERC20.sol"; import {BeforeMintWithSignatureCallbackERC20} from "../../../callback/BeforeMintWithSignatureCallbackERC20.sol"; +interface IERC20 { + + function decimals() external view returns (uint8); + +} + library MintableStorage { /// @custom:storage-location erc7201:token.minting.mintable @@ -49,8 +56,6 @@ contract MintableERC20 is * * @param startTimestamp The timestamp at which the minting request is valid. * @param endTimestamp The timestamp at which the minting request expires. - * @param recipient The address that will receive the minted tokens. - * @param amount The amount of tokens to mint. * @param currency The address of the currency used to pay for the minted tokens. * @param pricePerUnit The price per unit of the minted tokens. * @param uid A unique identifier for the minting request. @@ -153,7 +158,9 @@ contract MintableERC20 is MintSignatureParamsERC20 memory _params = abi.decode(_data, (MintSignatureParamsERC20)); _mintWithSignatureERC20(_params); - _distributeMintPrice(msg.sender, _params.currency, (_amount * _params.pricePerUnit) / 1e18); + _distributeMintPrice( + msg.sender, _params.currency, (_amount * _params.pricePerUnit) / (10 ** IERC20(address(this)).decimals()) + ); } /// @dev Called by a Core into an Module during the installation of the Module. diff --git a/src/module/token/royalty/RoyaltyERC1155.sol b/src/module/token/royalty/RoyaltyERC1155.sol index befdd601..456bd86a 100644 --- a/src/module/token/royalty/RoyaltyERC1155.sol +++ b/src/module/token/royalty/RoyaltyERC1155.sol @@ -26,10 +26,10 @@ library RoyaltyStorage { struct Data { // default royalty info RoyaltyERC1155.RoyaltyInfo defaultRoyaltyInfo; - // tokenId => royalty info - mapping(uint256 => RoyaltyERC1155.RoyaltyInfo) royaltyInfoForToken; // the addresss of the transfer validator address transferValidator; + // tokenId => royalty info + mapping(uint256 => RoyaltyERC1155.RoyaltyInfo) royaltyInfoForToken; } function data() internal pure returns (Data storage data_) { diff --git a/src/module/token/royalty/RoyaltyERC721.sol b/src/module/token/royalty/RoyaltyERC721.sol index 21c67bdf..ca9b8379 100644 --- a/src/module/token/royalty/RoyaltyERC721.sol +++ b/src/module/token/royalty/RoyaltyERC721.sol @@ -25,10 +25,10 @@ library RoyaltyStorage { struct Data { // default royalty info RoyaltyERC721.RoyaltyInfo defaultRoyaltyInfo; - // tokenId => royalty info - mapping(uint256 => RoyaltyERC721.RoyaltyInfo) royaltyInfoForToken; // the addresss of the transfer validator address transferValidator; + // tokenId => royalty info + mapping(uint256 => RoyaltyERC721.RoyaltyInfo) royaltyInfoForToken; } function data() internal pure returns (Data storage data_) { diff --git a/src/module/token/tokenId/SequentialTokenIdERC1155.sol b/src/module/token/tokenId/SequentialTokenIdERC1155.sol index a96676bf..a1d5309d 100644 --- a/src/module/token/tokenId/SequentialTokenIdERC1155.sol +++ b/src/module/token/tokenId/SequentialTokenIdERC1155.sol @@ -51,6 +51,8 @@ contract SequentialTokenIdERC1155 is Module, UpdateTokenIdCallbackERC1155 { config.requiredInterfaces = new bytes4[](1); config.requiredInterfaces[0] = 0xd9b67a26; // ERC1155 + + config.registerInstallationCallback = true; } /*////////////////////////////////////////////////////////////// @@ -73,6 +75,29 @@ contract SequentialTokenIdERC1155 is Module, UpdateTokenIdCallbackERC1155 { return _tokenId; } + /// @dev Called by a Core into an Module during the installation of the Module. + function onInstall(bytes calldata data) external { + uint256 nextTokenId = abi.decode(data, (uint256)); + _tokenIdStorage().nextTokenId = nextTokenId; + } + + /// @dev Called by a Core into an Module during the uninstallation of the Module. + function onUninstall(bytes calldata data) external {} + + /*////////////////////////////////////////////////////////////// + Encode install / uninstall data + //////////////////////////////////////////////////////////////*/ + + /// @dev Returns bytes encoded install params, to be sent to `onInstall` function + function encodeBytesOnInstall(uint256 startTokenId) external pure returns (bytes memory) { + return abi.encode(startTokenId); + } + + /// @dev Returns bytes encoded uninstall params, to be sent to `onUninstall` function + function encodeBytesOnUninstall() external pure returns (bytes memory) { + return ""; + } + /*////////////////////////////////////////////////////////////// FALLBACK FUNCTIONS //////////////////////////////////////////////////////////////*/ diff --git a/test/module/metadata/BatchMetadataERC1155.t.sol b/test/module/metadata/BatchMetadataERC1155.t.sol index dfcdb7db..645e7259 100644 --- a/test/module/metadata/BatchMetadataERC1155.t.sol +++ b/test/module/metadata/BatchMetadataERC1155.t.sol @@ -53,52 +53,53 @@ contract BatchMetadataERC1155Test is Test { } /*/////////////////////////////////////////////////////////////// - Unit tests: `getBatchId` + Unit tests: `getBatchIndex` //////////////////////////////////////////////////////////////*/ - function test_state_getBatchId() public { + function test_state_getBatchIndex() public { vm.prank(owner); BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); - (uint256 batchId, uint256 index) = BatchMetadataExt(address(core)).getBatchId(0); + uint256 batchIndex = BatchMetadataExt(address(core)).getBatchIndex(50); - assertEq(batchId, 100); - assertEq(index, 0); + assertEq(batchIndex, 0); } - function test_revert_getBatchId() public { + function test_revert_getBatchIndex_noMetadata() public { vm.prank(owner); BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); vm.expectRevert(BatchMetadataERC721.BatchMetadataNoMetadataForTokenId.selector); - (uint256 batchId, uint256 index) = BatchMetadataExt(address(core)).getBatchId(101); + BatchMetadataExt(address(core)).getBatchIndex(101); } /*/////////////////////////////////////////////////////////////// - Unit tests: `getBatchRange` + Unit tests: `getMetadataBatch` //////////////////////////////////////////////////////////////*/ - function test_state_getBatchRange() public { + function test_state_getMetadataBatch() public { vm.startPrank(owner); BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); - BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); + BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base2/"); vm.stopPrank(); - (uint256 startTokenId1, uint256 endTokenId1) = BatchMetadataExt(address(core)).getBatchRange(100); - (uint256 startTokenId2, uint256 endTokenId2) = BatchMetadataExt(address(core)).getBatchRange(200); + BatchMetadataERC1155.MetadataBatch memory batch = BatchMetadataExt(address(core)).getMetadataBatch(0); + BatchMetadataERC1155.MetadataBatch memory batch2 = BatchMetadataExt(address(core)).getMetadataBatch(1); - assertEq(startTokenId1, 0); - assertEq(endTokenId1, 99); - assertEq(startTokenId2, 100); - assertEq(endTokenId2, 199); + assertEq(batch.startTokenIdInclusive, 0); + assertEq(batch.endTokenIdInclusive, 99); + assertEq(batch.baseURI, "ipfs://base/"); + assertEq(batch2.startTokenIdInclusive, 100); + assertEq(batch2.endTokenIdInclusive, 199); + assertEq(batch2.baseURI, "ipfs://base2/"); } - function test_revert_getBatchRange() public { + function test_revert_getMetadataBatch_invalidIndex() public { vm.prank(owner); BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); - vm.expectRevert(BatchMetadataERC721.BatchMetadataNoMetadataForTokenId.selector); + vm.expectRevert(); vm.prank(owner); - BatchMetadataExt(address(core)).getBatchRange(101); + BatchMetadataExt(address(core)).getMetadataBatch(1); } /*/////////////////////////////////////////////////////////////// @@ -116,7 +117,7 @@ contract BatchMetadataERC1155Test is Test { assertEq(batches[0].baseURI, "ipfs://base/"); vm.prank(owner); - BatchMetadataExt(address(core)).setBaseURI(100, "ipfs://base2/"); + BatchMetadataExt(address(core)).setBaseURI(0, "ipfs://base2/"); // get metadata batches BatchMetadataExt.MetadataBatch[] memory batches2 = BatchMetadataExt(address(core)).getAllMetadataBatches(); diff --git a/test/module/metadata/BatchMetadataERC721.t.sol b/test/module/metadata/BatchMetadataERC721.t.sol index c2bd3a27..8acd83f0 100644 --- a/test/module/metadata/BatchMetadataERC721.t.sol +++ b/test/module/metadata/BatchMetadataERC721.t.sol @@ -1,12 +1,10 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import "lib/forge-std/src/console.sol"; - import {Test} from "forge-std/Test.sol"; +import "lib/forge-std/src/console.sol"; -// Target contract - +// Target contracts import {Core} from "src/Core.sol"; import {Module} from "src/Module.sol"; import {Role} from "src/Role.sol"; @@ -17,7 +15,14 @@ import {IModuleConfig} from "src/interface/IModuleConfig.sol"; import {BatchMetadataERC721} from "src/module/token/metadata/BatchMetadataERC721.sol"; import {MintableERC721} from "src/module/token/minting/MintableERC721.sol"; -contract BatchMetadataExt is BatchMetadataERC721 {} +contract BatchMetadataExt is BatchMetadataERC721 { + + // Expose internal functions for testing + function exposed_getBaseURI(uint256 _tokenId) external view returns (string memory baseUri, uint256 indexInBatch) { + return _getBaseURI(_tokenId); + } + +} contract BatchMetadataERC721Test is Test { @@ -30,6 +35,8 @@ contract BatchMetadataERC721Test is Test { address public permissionedActor = address(0x2); address public unpermissionedActor = address(0x3); + event BatchMetadataUpdate(uint256 startTokenIdIncluside, uint256 endTokenIdInclusive, string baseURI); + function setUp() public { address[] memory modules; bytes[] memory moduleData; @@ -38,7 +45,7 @@ contract BatchMetadataERC721Test is Test { batchMetadataModule = new BatchMetadataExt(); mintableModule = new MintableERC721(); - // install module + // Install modules vm.prank(owner); core.installModule(address(batchMetadataModule), ""); @@ -51,158 +58,289 @@ contract BatchMetadataERC721Test is Test { } /*/////////////////////////////////////////////////////////////// - Unit tests: `getBatchId` + Unit tests: `updateMetadata` //////////////////////////////////////////////////////////////*/ - function test_state_getBatchId() public { + function test_state_updateMetadata() public { vm.prank(owner); - BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); - (uint256 batchId, uint256 index) = BatchMetadataExt(address(core)).getBatchId(0); + vm.expectEmit(true, true, true, true); + emit BatchMetadataUpdate(0, 0, "ipfs://base/"); + core.mint(owner, 1, "ipfs://base/", ""); + + assertEq(core.tokenURI(0), "ipfs://base/0"); - assertEq(batchId, 100); - assertEq(index, 0); + // Test case: user mints 10 tokens with no baseURI + // user mints another 10 tokens with baseURI + // Expected: tokenURI for tokenId 15 should be ipfs://base2/4 + vm.startPrank(owner); + core.mint(owner, 10, "", ""); + + vm.expectEmit(true, true, true, true); + emit BatchMetadataUpdate(11, 20, "ipfs://base2/"); + core.mint(owner, 10, "ipfs://base2/", ""); + vm.stopPrank(); + + assertEq(core.tokenURI(15), "ipfs://base2/4"); } - function test_revert_getBatchId() public { + function test_revert_updateMetadata() public { vm.prank(owner); - BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); + core.mint(owner, 1, "", ""); vm.expectRevert(BatchMetadataERC721.BatchMetadataNoMetadataForTokenId.selector); - (uint256 batchId, uint256 index) = BatchMetadataExt(address(core)).getBatchId(101); + core.tokenURI(0); + + vm.prank(owner); + BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); + + vm.prank(owner); + vm.expectRevert(BatchMetadataERC721.BatchMetadataMetadataAlreadySet.selector); + core.mint(owner, 1, "ipfs://base/fail", ""); } /*/////////////////////////////////////////////////////////////// - Unit tests: `getBatchRange` + Unit tests: `uploadMetadata` //////////////////////////////////////////////////////////////*/ - function test_state_getBatchRange() public { - vm.startPrank(owner); - BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); + function test_state_uploadMetadata() public { + vm.prank(owner); BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); - vm.stopPrank(); - (uint256 startTokenId1, uint256 endTokenId1) = BatchMetadataExt(address(core)).getBatchRange(100); - (uint256 startTokenId2, uint256 endTokenId2) = BatchMetadataExt(address(core)).getBatchRange(200); + // Read state from core + assertEq(core.tokenURI(1), "ipfs://base/1"); + assertEq(core.tokenURI(99), "ipfs://base/99"); - assertEq(startTokenId1, 0); - assertEq(endTokenId1, 99); - assertEq(startTokenId2, 100); - assertEq(endTokenId2, 199); + // Upload another batch + vm.prank(owner); + BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base2/"); + + // Read state from core + assertEq(core.tokenURI(1), "ipfs://base/1"); + assertEq(core.tokenURI(99), "ipfs://base/99"); + // fails here + assertEq(core.tokenURI(100), "ipfs://base2/0"); + assertEq(core.tokenURI(199), "ipfs://base2/99"); } - function test_revert_getBatchStartId() public { - vm.prank(owner); + function test_revert_uploadMetadata() public { + vm.expectRevert(0x82b42900); // `Unauthorized()` BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); + } - vm.expectRevert(BatchMetadataERC721.BatchMetadataNoMetadataForTokenId.selector); + function test_revert_uploadMetadata_zeroAmount() public { vm.prank(owner); - BatchMetadataExt(address(core)).getBatchRange(101); + vm.expectRevert(BatchMetadataERC721.BatchMetadataZeroAmount.selector); + BatchMetadataExt(address(core)).uploadMetadata(0, "ipfs://base/"); } /*/////////////////////////////////////////////////////////////// - Unit tests: `setBaseURI` + Unit tests: `getAllMetadataBatches` //////////////////////////////////////////////////////////////*/ - function test_state_setBaseURI() public { + function test_getAllMetadataBatches() public { vm.prank(owner); BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); - // get metadata batches + // Get metadata batches BatchMetadataExt.MetadataBatch[] memory batches = BatchMetadataExt(address(core)).getAllMetadataBatches(); assertEq(batches.length, 1); + assertEq(batches[0].startTokenIdInclusive, 0); + assertEq(batches[0].endTokenIdInclusive, 99); assertEq(batches[0].baseURI, "ipfs://base/"); + } + + /*/////////////////////////////////////////////////////////////// + Unit tests: `nextTokenIdToMint` + //////////////////////////////////////////////////////////////*/ + function test_nextTokenIdToMint() public { vm.prank(owner); - BatchMetadataExt(address(core)).setBaseURI(100, "ipfs://base2/"); + BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); - // get metadata batches - BatchMetadataExt.MetadataBatch[] memory batches2 = BatchMetadataExt(address(core)).getAllMetadataBatches(); + // Get nextTokenIdToMint + uint256 nextTokenIdToMint = BatchMetadataExt(address(core)).nextTokenIdToMint(); - assertEq(batches2.length, 1); - assertEq(batches2[0].baseURI, "ipfs://base2/"); + assertEq(nextTokenIdToMint, 100); } /*/////////////////////////////////////////////////////////////// - Unit tests: `updateMetadata` + Unit tests: `getMetadataBatch` //////////////////////////////////////////////////////////////*/ - function test_state_updateMetadata() public { + function test_state_getMetadataBatch() public { vm.prank(owner); - core.mint(owner, 1, "ipfs://base/", ""); + BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); - assertEq(core.tokenURI(0), "ipfs://base/0"); + // Get metadata batch at index 0 + BatchMetadataExt.MetadataBatch memory batch = BatchMetadataExt(address(core)).getMetadataBatch(0); + + assertEq(batch.startTokenIdInclusive, 0); + assertEq(batch.endTokenIdInclusive, 99); + assertEq(batch.baseURI, "ipfs://base/"); } - function test_revert_updateMetadata() public { - address saleRecipient = address(0x987); + function test_revert_getMetadataBatch_invalidIndex() public { + vm.prank(owner); + BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); + + // Try to get batch at invalid index + vm.expectRevert(); + BatchMetadataExt(address(core)).getMetadataBatch(1); + } + /*/////////////////////////////////////////////////////////////// + Unit tests: `getBatchIndex` + //////////////////////////////////////////////////////////////*/ + + function test_state_getBatchIndex() public { vm.prank(owner); - MintableERC721(address(core)).setSaleConfig(saleRecipient); + BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); + // Check batch index for token IDs within the batch + uint256 batchIndex = BatchMetadataExt(address(core)).getBatchIndex(50); + assertEq(batchIndex, 0); + + // Upload another batch vm.prank(owner); - core.mint(owner, 1, "", ""); + BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base2/"); + + // Check batch index for token IDs in the second batch + batchIndex = BatchMetadataExt(address(core)).getBatchIndex(150); + assertEq(batchIndex, 1); + } + function test_revert_getBatchIndex_noMetadata() public { + // No metadata uploaded yet vm.expectRevert(BatchMetadataERC721.BatchMetadataNoMetadataForTokenId.selector); - core.tokenURI(0); + BatchMetadataExt(address(core)).getBatchIndex(0); + // Upload metadata vm.prank(owner); BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); + // Test tokenId beyond range + vm.expectRevert(BatchMetadataERC721.BatchMetadataNoMetadataForTokenId.selector); + BatchMetadataExt(address(core)).getBatchIndex(200); + } + + /*/////////////////////////////////////////////////////////////// + Unit tests: `setBaseURI` + //////////////////////////////////////////////////////////////*/ + + function test_state_setBaseURI() public { vm.prank(owner); - vm.expectRevert(BatchMetadataERC721.BatchMetadataMetadataAlreadySet.selector); - core.mint(owner, 1, "ipfs://base/fail", ""); + BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); + + // Set new baseURI for batch 0 + vm.prank(owner); + BatchMetadataExt(address(core)).setBaseURI(0, "ipfs://newbase/"); + + // Check that the baseURI has been updated + BatchMetadataExt.MetadataBatch memory batch = BatchMetadataExt(address(core)).getMetadataBatch(0); + assertEq(batch.baseURI, "ipfs://newbase/"); + + // Check that tokenURI returns the updated baseURI + assertEq(core.tokenURI(0), "ipfs://newbase/0"); + } + + function test_event_setBaseURI() public { + vm.prank(owner); + BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); + + // Expect BatchMetadataUpdate event + vm.prank(owner); + vm.expectEmit(true, true, true, true); + emit BatchMetadataUpdate(0, 99, "ipfs://newbase/"); + + BatchMetadataExt(address(core)).setBaseURI(0, "ipfs://newbase/"); + } + + function test_revert_setBaseURI_unauthorized() public { + vm.prank(owner); + BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); + + // Try to set baseURI as unpermissionedActor + vm.prank(unpermissionedActor); + vm.expectRevert(0x82b42900); // Unauthorized() + BatchMetadataExt(address(core)).setBaseURI(0, "ipfs://hack/"); + } + + function test_revert_setBaseURI_invalidIndex() public { + vm.prank(owner); + BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); + + vm.prank(owner); + vm.expectRevert(); + BatchMetadataExt(address(core)).setBaseURI(1, "ipfs://newbase/"); } /*/////////////////////////////////////////////////////////////// - Unit tests: `uploadMetadata` + Unit tests: TokenURI edge cases //////////////////////////////////////////////////////////////*/ - function test_state_uploadMetadata() public { + function test_state_tokenURI_multipleBatches() public { vm.prank(owner); BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); - // read state from core - assertEq(core.tokenURI(1), "ipfs://base/1"); - assertEq(core.tokenURI(99), "ipfs://base/99"); + vm.prank(owner); + BatchMetadataExt(address(core)).uploadMetadata(50, "ipfs://base2/"); - // upload another batch + // Mint tokens vm.prank(owner); - BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base2/"); + core.mint(owner, 150, "", ""); - // read state from core - assertEq(core.tokenURI(1), "ipfs://base/1"); + // Check tokenURI for tokens in first batch + assertEq(core.tokenURI(0), "ipfs://base/0"); assertEq(core.tokenURI(99), "ipfs://base/99"); + + // Check tokenURI for tokens in second batch assertEq(core.tokenURI(100), "ipfs://base2/0"); - assertEq(core.tokenURI(199), "ipfs://base2/99"); + assertEq(core.tokenURI(149), "ipfs://base2/49"); + + // tokenURI for token beyond batches should revert + vm.expectRevert(BatchMetadataERC721.BatchMetadataNoMetadataForTokenId.selector); + core.tokenURI(150); } - function test_revert_uploadMetadata() public { - vm.expectRevert(0x82b42900); // `Unauthorized()` - BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); + function test_revert_tokenURI_noMetadata() public { + vm.prank(owner); + core.mint(owner, 1, "", ""); + + vm.expectRevert(BatchMetadataERC721.BatchMetadataNoMetadataForTokenId.selector); + core.tokenURI(0); } - function test_getAllMetadataBatches() public { + function test_revert_tokenURI_noMetadataBeyondUploaded() public { vm.prank(owner); BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); - // get metadata batches - BatchMetadataExt.MetadataBatch[] memory batches = BatchMetadataExt(address(core)).getAllMetadataBatches(); + // Mint 200 tokens + vm.prank(owner); + core.mint(owner, 200, "", ""); - assertEq(batches.length, 1); - assertEq(batches[0].startTokenIdInclusive, 0); - assertEq(batches[0].endTokenIdInclusive, 99); - assertEq(batches[0].baseURI, "ipfs://base/"); + // tokenURI for tokenId 150 should revert + vm.expectRevert(BatchMetadataERC721.BatchMetadataNoMetadataForTokenId.selector); + core.tokenURI(150); } - function test_nextTokenIdToMint() public { - vm.prank(owner); + /*/////////////////////////////////////////////////////////////// + Unit tests: Unauthorized access checks + //////////////////////////////////////////////////////////////*/ + + function test_revert_uploadMetadata_unauthorized() public { + vm.prank(unpermissionedActor); + vm.expectRevert(0x82b42900); // Unauthorized() BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); + } - // get metadata batches - uint256 nextTokenIdToMint = BatchMetadataExt(address(core)).nextTokenIdToMint(); + function test_revert_setBaseURI_unauthorizedActor() public { + vm.prank(owner); + BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/"); - assertEq(nextTokenIdToMint, 100); + vm.prank(unpermissionedActor); + vm.expectRevert(0x82b42900); // Unauthorized() + BatchMetadataExt(address(core)).setBaseURI(0, "ipfs://newbase/"); } } diff --git a/test/module/tokenId/SequentialTokenIdERC1155.t.sol b/test/module/tokenId/SequentialTokenIdERC1155.t.sol index e993d867..2a18fdd2 100644 --- a/test/module/tokenId/SequentialTokenIdERC1155.t.sol +++ b/test/module/tokenId/SequentialTokenIdERC1155.t.sol @@ -78,14 +78,28 @@ contract MintableERC1155Test is Test { vm.prank(owner); core.installModule(address(batchMetadataModule), ""); + bytes memory encodedTokenIdInstallParams = sequentialTokenIdModule.encodeBytesOnInstall(0); vm.prank(owner); - core.installModule(address(sequentialTokenIdModule), ""); + core.installModule(address(sequentialTokenIdModule), encodedTokenIdInstallParams); // Give permissioned actor minter role vm.prank(owner); core.grantRoles(owner, Role._MINTER_ROLE); } + /*////////////////////////////////////////////////////////////// + Tests: onInstall + //////////////////////////////////////////////////////////////*/ + + function test_onInstall() public { + vm.startPrank(owner); + core.uninstallModule(address(sequentialTokenIdModule), ""); + core.installModule(address(sequentialTokenIdModule), sequentialTokenIdModule.encodeBytesOnInstall(5)); + vm.stopPrank(); + + assertEq(SequentialTokenIdERC1155(address(core)).getNextTokenId(), 5); + } + /*////////////////////////////////////////////////////////////// Tests: beforeMintERC1155 //////////////////////////////////////////////////////////////*/