diff --git a/packages/manifold/contracts/physicalclaim/IPhysicalClaim.sol b/packages/manifold/contracts/physicalclaim/IPhysicalClaim.sol index 1a71f10e..c1d737e6 100644 --- a/packages/manifold/contracts/physicalclaim/IPhysicalClaim.sol +++ b/packages/manifold/contracts/physicalclaim/IPhysicalClaim.sol @@ -4,20 +4,118 @@ pragma solidity ^0.8.0; /// @author: manifold.xyz -import "./IPhysicalClaimCore.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; +import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +/** + * Burn Redeem Core interface + */ +interface IPhysicalClaim is IERC165, IERC721Receiver, IERC1155Receiver { + error InvalidToken(address, uint256); + error InvalidTokenSpec(); + error InvalidBurnSpec(); + error InvalidBurnAmount(); + error InvalidInput(); + error InvalidData(); + error InvalidPaymentAmount(); + error TransferFailure(); + error SoldOut(); + + error InvalidSignature(); // 0x8baa579f + error ExpiredSignature(); + error InvalidNonce(); + + enum TokenSpec { INVALID, ERC721, ERC1155, ERC721_NO_BURN } + + enum BurnSpec { INVALID, NONE, MANIFOLD, OPENZEPPELIN } + + struct BurnSubmission { + bytes signature; + bytes32 message; + uint256 instanceId; + BurnToken[] burnTokens; + uint8 variation; + uint64 variationLimit; + uint64 totalLimit; + address erc20; + uint256 price; + address payable fundsRecipient; + uint160 expiration; + bytes32 nonce; + } + + event Redeem(uint256 instanceId, address indexed redeemer, uint8 variationSelection, bytes32 nonce); + + /** + * @notice a `BurnItem` indicates which tokens are eligible to be burned + * @param contractAddress the contract address of the token to burn + * @param tokenId the token to burn + * @param tokenSpec the burn item token type + * @param burnSpec whether the contract for a token has a `burn` function and, if so, + * what interface + * @param amount (only for ERC1155 tokens) the amount (value) required to burn + */ + struct BurnToken { + address contractAddress; + uint256 tokenId; + TokenSpec tokenSpec; + BurnSpec burnSpec; + uint72 amount; + } + + /** + * @notice burn tokens and physical claims multiple times in a single transaction + * @param submissions the burn submission entries + */ + function burnRedeem(BurnSubmission[] calldata submissions) external payable; + + /** + * @notice burn tokens and physical claim + * @param submission the burn submission entries + */ + function burnRedeem(BurnSubmission calldata submission) external payable; + + /** + * @notice withdraw Manifold fee proceeds from the contract + * @param recipient recepient of the funds + * @param amount amount to withdraw in Wei + */ + function withdraw(address payable recipient, uint256 amount) external; + + /** + * @notice set the Manifold Membership contract address + * @param addr the address of the Manifold Membership contract + */ + function setMembershipAddress(address addr) external; + + /** + * @notice update the authorized signer + * @param signingAddress the authorized signer + */ + function updateSigner(address signingAddress) external; + + /** + * @notice recover a token that was sent to the contract without safeTransferFrom + * @param tokenAddress the address of the token contract + * @param tokenId the id of the token + * @param destination the address to send the token to + */ + function recover(address tokenAddress, uint256 tokenId, address destination) external; -interface IPhysicalClaim is IPhysicalClaimCore { /** - * @notice initialize a new physical claim, emit initialize event - * @param instanceId the instanceId of the physicalClaim for the physical claim - * @param physicalClaimParameters the parameters which will affect the redemption behavior of the physical claim + * @notice get the number of variations redeemed + * @param instanceId the instance id + * @param variations the variations + * @return the number of variations redeemed */ - function initializePhysicalClaim(uint256 instanceId, PhysicalClaimParameters calldata physicalClaimParameters) external; + function getVariationCounts(uint256 instanceId, uint8[] calldata variations) external view returns (uint64[] memory); /** - * @notice update an existing physical claim - * @param instanceId the instanceId of the physicalClaim for the physical claim - * @param physicalClaimParameters the parameters which will affect the redemption behavior of the physical claim + * @notice get the number of redemptions for a given instance + * @param instanceId the instance id + * @return the number of redemptions */ - function updatePhysicalClaim(uint256 instanceId, PhysicalClaimParameters calldata physicalClaimParameters) external; + function getTotalCount(uint256 instanceId) external view returns (uint64); + } diff --git a/packages/manifold/contracts/physicalclaim/IPhysicalClaimCore.sol b/packages/manifold/contracts/physicalclaim/IPhysicalClaimCore.sol deleted file mode 100644 index 21215659..00000000 --- a/packages/manifold/contracts/physicalclaim/IPhysicalClaimCore.sol +++ /dev/null @@ -1,258 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -/// @author: manifold.xyz - -import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -/** - * Burn Redeem Core interface - */ -interface IPhysicalClaimCore is IERC165, IERC721Receiver, IERC1155Receiver { - error InvalidInstance(); - error UnsupportedContractVersion(); - error InvalidToken(uint256); - error InvalidInput(); // 0xb4fa3fb3 - error InvalidBurnTokenSpec(); - error InvalidBurnFunctionSpec(); - error InvalidData(); - error TransferFailure(); - error ContractDeprecated(); - - error PhysicalClaimDoesNotExist(uint256); - error PhysicalClaimInactive(uint256); - - error InvalidBurnAmount(); // 0x2075cc10 - error InvalidRedeemAmount(); // 0x918e94c5 - error InvalidPaymentAmount(); // 0xfc512fde - error InvalidSignature(); // 0x8baa579f - error InvalidVariation(); // 0xc674e37c - - /** - * @notice the validation type used for a `BurnItem` - * CONTRACT any token from a specific contract is valid - * RANGE token IDs within a range (inclusive) are valid - * MERKLE_TREE various individual token IDs included in a merkle tree are valid - * ANY any token from any contract - */ - enum ValidationType { INVALID, CONTRACT, RANGE, MERKLE_TREE, ANY } - - enum BurnTokenSpec { ERC721, ERC1155, ERC721_NO_BURN } - - enum BurnFunctionSpec { NONE, MANIFOLD, OPENZEPPELIN } - - /** - * @notice a `BurnItem` indicates which tokens are eligible to be burned - * @param validationType which type of validation used to check that the burn item is - * satisfied - * @param tokenSpec the burn item token type - * @param burnSpec whether the contract for a token has a `burn` function and, if so, - * what interface - * @param amount (only for ERC1155 tokens) the amount (value) required to burn - * @param minTokenId (only for RANGE validation) the minimum valid token ID - * @param maxTokenId (only for RANGE validation) the maximum valid token ID - * @param merkleRoot (only for MERKLE_TREE validation) the root of the merkle tree of - * valid token IDs - */ - struct BurnItem { - ValidationType validationType; - address contractAddress; - BurnTokenSpec burnTokenSpec; - BurnFunctionSpec burnFunctionSpec; - uint72 amount; - uint256 minTokenId; - uint256 maxTokenId; - bytes32 merkleRoot; - } - - /** - * @param totalSupply the maximum number of times the variation can be redeemed (0 means no limit) - * @param redeemedCount the number of times the variation has been redeemed - * @param active whether the variation is active - */ - struct VariationState { - uint16 totalSupply; - uint16 redeemedCount; - bool active; - } - - /** - * @notice a `BurnGroup` is a group of valid `BurnItem`s - * @param requiredCount the number of `BurnItem`s (0 < requiredCount <= items.length) that - * need to be included in a burn - * @param items the list of `BurnItem`s - */ - struct BurnGroup { - uint256 requiredCount; - BurnItem[] items; - } - - /** - * @notice parameters for burn redeem intialization/updates - * @param paymentReceiver the address to forward proceeds from paid burn redeems - * @param totalSupply the maximum number of redemptions to redeem (0 for unlimited) - * @param startDate the starting time for the burn redeem (0 for immediately) - * @param endDate the end time for the burn redeem (0 for never) - * @param signer the address of the signer for the transaction details - * @param burnSet a list of `BurnGroup`s that must each be satisfied for a burn redeem - * @param variationLimits a list of `Variation` ids and limits - */ - struct PhysicalClaimParameters { - address payable paymentReceiver; - uint16 totalSupply; - uint48 startDate; - uint48 endDate; - address signer; - BurnGroup[] burnSet; - VariationLimit[] variationLimits; - } - - /** - * @notice parameters - */ - struct VariationLimit { - uint8 id; - uint16 totalSupply; - } - - /** - * @notice the state for a physical claim - * @param paymentReceiver the address to forward proceeds from paid burn redeems - * @param redeemedCount the amount currently redeemed - * @param totalSupply the maximum number of redemptions to redeem (0 for unlimited) - * @param startDate the starting time for the burn redeem (0 for immediately) - * @param endDate the end time for the burn redeem (0 for never) - * @param signer the address of the signer for the transaction details - * @param burnSet a list of `BurnGroup`s that must each be satisfied for a burn redeem - * @param variationIds a list of variation IDs for the redemptions - * @param variations a mapping of `Variation`s for the redemptions - */ - struct PhysicalClaim { - address payable paymentReceiver; - uint16 redeemedCount; - uint16 totalSupply; - uint48 startDate; - uint48 endDate; - address signer; - BurnGroup[] burnSet; - uint8[] variationIds; - mapping(uint8 => VariationState) variations; - } - - /** - * @notice the state for a physical claim - * @param paymentReceiver the address to forward proceeds from paid burn redeems - * @param redeemedCount the amount currently redeemed - * @param totalSupply the maximum number of redemptions to redeem (0 for unlimited) - * @param startDate the starting time for the burn redeem (0 for immediately) - * @param endDate the end time for the burn redeem (0 for never) - * @param signer the address of the signer for the transaction details - * @param burnSet a list of `BurnGroup`s that must each be satisfied for a burn redeem - * @param variationIds a list of variation IDs for the redemptions - * @param variations a mapping of `Variation`s for the redemptions - */ - struct PhysicalClaimView { - address payable paymentReceiver; - uint16 redeemedCount; - uint16 totalSupply; - uint48 startDate; - uint48 endDate; - address signer; - BurnGroup[] burnSet; - VariationState[] variationStates; - } - - /** - * @notice a submission for a physical claim - * @param instanceId the instanceId of the physical claim - * @param count the number of times to perform a claim for this instance - * @param currentClaimCount the current number of times the physical claim has been redeemed - * @param variation the variation to redeem - * @param data the data for the transaction - * @param signature the signature for the transaction - * @param message the message for the transaction - * @param nonce the nonce for the transaction - * @param totalCost the total cost for the transaction - * @param burnTokens the tokens to burn - */ - struct PhysicalClaimSubmission { - uint56 instanceId; - uint16 count; - uint16 currentClaimCount; - uint8 variation; - bytes data; - bytes signature; - bytes32 message; - bytes32 nonce; - uint256 totalCost; - BurnToken[] burnTokens; - } - - /** - * @notice a pointer to a `BurnItem` in a `BurnGroup` used in calls to `burnRedeem` - * @param groupIndex the index of the `BurnGroup` in `PhysicalClaim.burnSet` - * @param itemIndex the index of the `BurnItem` in `BurnGroup.items` - * @param contractAddress the address of the contract for the token - * @param id the token ID - * @param merkleProof the merkle proof for the token ID (only for MERKLE_TREE validation) - */ - struct BurnToken { - uint48 groupIndex; - uint48 itemIndex; - address contractAddress; - uint256 id; - bytes32[] merkleProof; - } - - struct TokensUsedQuery { - uint256 instanceId; - address[] contractAddresses; - uint256[] tokenIds; - } - - /** - * @notice get a physical claim corresponding to an instanceId - * @param instanceId the instanceId of the physical claim - * @return PhysicalClaim the physical claim object - */ - function getPhysicalClaim(uint256 instanceId) external view returns(PhysicalClaimView memory); - - /** - * @notice gets the number of redemptions for a physical claim for a given redeemer - * @param instanceId the instanceId of the physical claim - * @return redeemer the address who redeemed - */ - function getRedemptions(uint256 instanceId, address redeemer) external view returns(uint256); - - /** - * @notice gets the redemption state for a physical claim for a given variation - * @param instanceId the instanceId of the physical claim - * @param variation the variation - * @return VariationState the max and available for the variation - */ - function getVariationState(uint256 instanceId, uint8 variation) external view returns(VariationState memory); - - /** - * @notice gets the redemption state for a tokenId/contractAddress on an instance - * @param tokensUsedQuery the query for instance id and list of tokenIds and contracts - * @return bool[] true/false for each tokenId/contractAddress pair - */ - function getAreTokensUsed(TokensUsedQuery calldata tokensUsedQuery) external view returns(bool[] memory); - - /** - * @notice burn tokens and physical claims multiple times in a single transaction - * @param submissions the submissions for the physical claims - */ - function burnRedeem(PhysicalClaimSubmission[] calldata submissions) external payable; - - /** - * @notice recover a token that was sent to the contract without safeTransferFrom - * @param tokenAddress the address of the token contract - * @param tokenId the id of the token - * @param destination the address to send the token to - */ - function recover(address tokenAddress, uint256 tokenId, address destination) external; -} diff --git a/packages/manifold/contracts/physicalclaim/PhysicalClaim.sol b/packages/manifold/contracts/physicalclaim/PhysicalClaim.sol index 6ff967b8..78708c68 100644 --- a/packages/manifold/contracts/physicalclaim/PhysicalClaim.sol +++ b/packages/manifold/contracts/physicalclaim/PhysicalClaim.sol @@ -4,45 +4,421 @@ pragma solidity ^0.8.0; /// @author: manifold.xyz -import "@manifoldxyz/creator-core-solidity/contracts/core/IERC721CreatorCore.sol"; - -import "./PhysicalClaimCore.sol"; -import "./PhysicalClaimLib.sol"; +import "@manifoldxyz/libraries-solidity/contracts/access/AdminControl.sol"; +import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import ".././libraries/manifold-membership/IManifoldMembership.sol"; import "./IPhysicalClaim.sol"; +import "./Interfaces.sol"; // Let's get Physical 💋 -contract PhysicalClaim is PhysicalClaimCore, IPhysicalClaim { - using Strings for uint256; +contract PhysicalClaim is IPhysicalClaim, ReentrancyGuard, AdminControl { + using ECDSA for bytes32; + + uint256 public constant BURN_FEE = 690000000000000; + uint256 public constant MULTI_BURN_FEE = 990000000000000; + address public manifoldMembershipContract; + + address private _signingAddress; + mapping(uint256 => mapping(address => mapping(uint256 => bool))) private _usedTokens; + mapping(uint256 => mapping(bytes32 => bool)) private _usedNonces; + mapping(uint256 => mapping(uint8 => uint64)) private _variationCount; + mapping(uint256 => uint64) private _totalCount; + + constructor(address initialOwner, address signingAddress) { + _transferOwnership(initialOwner); + _signingAddress = signingAddress; + } + + function supportsInterface(bytes4 interfaceId) public view virtual override(AdminControl, IERC165) returns (bool) { + return interfaceId == type(IPhysicalClaim).interfaceId + || interfaceId == type(AdminControl).interfaceId + || interfaceId == type(IERC1155Receiver).interfaceId + || interfaceId == type(IERC721Receiver).interfaceId + || super.supportsInterface(interfaceId); + } + + /** + * @dev See {IPhysicalClaim-withdraw}. + */ + function withdraw(address payable recipient, uint256 amount) external override adminRequired { + _forwardValue(recipient, amount); + } + + /** + * @dev See {IPhysicalClaim-setManifoldMembership}. + */ + function setMembershipAddress(address addr) external override adminRequired { + manifoldMembershipContract = addr; + } + + /** + * @dev See {IPhysicalClaim-updateSigner}. + */ + function updateSigner(address signingAddress) external override adminRequired { + _signingAddress = signingAddress; + } + + /** + * @dev See {IPhysicalClaimCore-recover}. + */ + function recover(address tokenAddress, uint256 tokenId, address destination) external override adminRequired { + IERC721(tokenAddress).transferFrom(address(this), destination, tokenId); + } + + /** + * @dev See {IPhysicalClaimCore-burnRedeem}. + */ + function burnRedeem(BurnSubmission calldata submission) external payable override nonReentrant { + if (!_isVariationAvailable(submission)) revert SoldOut(); + _validateSubmission(submission); + _burnTokens(submission.instanceId, msg.sender, submission.burnTokens); + _redeem(msg.sender, submission, msg.value); + } + + /** + * @dev See {IPhysicalClaimCore-burnRedeem}. + */ + function burnRedeem(BurnSubmission[] calldata submissions) external payable override nonReentrant { + uint256 msgValue = msg.value; + for (uint256 i; i < submissions.length;) { + BurnSubmission calldata submission = submissions[i]; + if (_isVariationAvailable(submission)) { + // Only validate if the variation requested available + _validateSubmission(submission); + _burnTokens(submission.instanceId, msg.sender, submission.burnTokens); + msgValue = _redeem(msg.sender, submission, msgValue); + } + unchecked { ++i; } + } + if (msgValue > 0) { + // Return remaining unused funds + _forwardValue(payable(msg.sender), msgValue); + } + } + + function _validateSubmission(BurnSubmission memory submission) private { + if (block.timestamp > submission.expiration) revert ExpiredSignature(); + // Verify valid message based on input variables + bytes32 expectedMessage = keccak256(abi.encode(submission.instanceId, submission.burnTokens, submission.variation, submission.variationLimit, submission.totalLimit, submission.erc20, submission.price, submission.fundsRecipient, submission.expiration, submission.nonce)); + address signer = submission.message.recover(submission.signature); + if (submission.message != expectedMessage || signer != _signingAddress) revert InvalidSignature(); + if (_usedNonces[submission.instanceId][submission.nonce]) revert InvalidNonce(); + _usedNonces[submission.instanceId][submission.nonce] = true; + } + + function _isVariationAvailable(BurnSubmission memory submission) private view returns(bool) { + // Check variation limit + if (submission.variationLimit > 0 && _variationCount[submission.instanceId][submission.variation] >= submission.variationLimit) return false; + if (submission.totalLimit > 0 && _totalCount[submission.instanceId] >= submission.totalLimit) return false; + return true; + } + + function _burnTokens(uint256 instanceId, address from, BurnToken[] calldata burnTokens) private { + for (uint256 i; i < burnTokens.length;) { + BurnToken memory burnToken = burnTokens[i]; + _burn(instanceId, from, burnToken); + unchecked { ++i; } + } + } + + /** + * Helper to burn token + */ + function _burn(uint256 instanceId, address from, BurnToken memory burnToken) private { + if (burnToken.tokenSpec == TokenSpec.ERC1155) { + if (burnToken.burnSpec == BurnSpec.NONE) { + // Send to 0xdEaD to burn if contract doesn't have burn function + IERC1155(burnToken.contractAddress).safeTransferFrom(from, address(0xdEaD), burnToken.tokenId, burnToken.amount, ""); + + } else if (burnToken.burnSpec == BurnSpec.MANIFOLD) { + // Burn using the creator core's burn function + uint256[] memory tokenIds = new uint256[](1); + tokenIds[0] = burnToken.tokenId; + uint256[] memory amounts = new uint256[](1); + amounts[0] = burnToken.amount; + Manifold1155(burnToken.contractAddress).burn(from, tokenIds, amounts); + + } else if (burnToken.burnSpec == BurnSpec.OPENZEPPELIN) { + // Burn using OpenZeppelin's burn function + OZBurnable1155(burnToken.contractAddress).burn(from, burnToken.tokenId, burnToken.amount); + + } else { + revert InvalidBurnSpec(); + } + } else if (burnToken.tokenSpec == TokenSpec.ERC721) { + if (burnToken.amount != 1) revert InvalidToken(burnToken.contractAddress, burnToken.tokenId); + if (burnToken.burnSpec == BurnSpec.NONE) { + // Send to 0xdEaD to burn if contract doesn't have burn function + IERC721(burnToken.contractAddress).safeTransferFrom(from, address(0xdEaD), burnToken.tokenId, ""); + + } else if (burnToken.burnSpec == BurnSpec.MANIFOLD || burnToken.burnSpec == BurnSpec.OPENZEPPELIN) { + if (from != address(this)) { + // 721 `burn` functions do not have a `from` parameter, so we must verify the owner + if (IERC721(burnToken.contractAddress).ownerOf(burnToken.tokenId) != from) { + revert TransferFailure(); + } + } + // Burn using the contract's burn function + Burnable721(burnToken.contractAddress).burn(burnToken.tokenId); + + } else { + revert InvalidBurnSpec(); + } + } else if (burnToken.tokenSpec == TokenSpec.ERC721_NO_BURN) { + if (from != address(this)) { + // 721 `burn` functions do not have a `from` parameter, so we must verify the owner + if (IERC721(burnToken.contractAddress).ownerOf(burnToken.tokenId) != from) { + revert TransferFailure(); + } + } + // Make sure token hasn't previously been used + if (_usedTokens[instanceId][burnToken.contractAddress][burnToken.tokenId]) { + revert InvalidToken(burnToken.contractAddress, burnToken.tokenId); + } + // Mark token as used + _usedTokens[instanceId][burnToken.contractAddress][burnToken.tokenId] = true; + + } else { + revert InvalidTokenSpec(); + } + } + + /** + * @dev See {IERC721Receiver-onERC721Received}. + */ + function onERC721Received( + address, + address from, + uint256 id, + bytes calldata data + ) external override nonReentrant returns(bytes4) { + _onERC721Received(from, id, data); + return this.onERC721Received.selector; + } + + /** + * @dev See {IERC1155Receiver-onERC1155Received}. + */ + function onERC1155Received( + address, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external override nonReentrant returns(bytes4) { + // Do burn redeem + _onERC1155Received(from, id, value, data); + return this.onERC1155Received.selector; + } + + /** + * @dev See {IERC1155Receiver-onERC1155BatchReceived}. + */ + function onERC1155BatchReceived( + address, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external override nonReentrant returns(bytes4) { + // Do burn redeem + _onERC1155BatchReceived(from, ids, values, data); + return this.onERC1155BatchReceived.selector; + } + + /** + * @notice ERC721 token transfer callback + * @param from the person sending the tokens + * @param id the token id of the burn token + * @param data bytes indicating the target burnRedeem and, optionally, a merkle proof that the token is valid + */ + function _onERC721Received( + address from, + uint256 id, + bytes calldata data + ) private { + (BurnSubmission memory submission) = abi.decode(data, (BurnSubmission)); + + // A single ERC721 can only be sent in directly for a burn if: + // 1. There is no ETH price to the burn (because no payment can be sent with a transfer) + // 2. The burn only requires one NFT (one burnToken) + // 3. They are an active member (because no fee payment can be sent with a transfer) + _validateCanReceive(submission.erc20, submission.price, from); + if (submission.burnTokens.length != 1) { + revert InvalidInput(); + } + if (!_isVariationAvailable(submission)) revert SoldOut(); + _validateSubmission(submission); + + // Check that the burn token is valid + BurnToken memory burnToken = submission.burnTokens[0]; - constructor(address initialOwner) PhysicalClaimCore(initialOwner) {} + // Can only take in one burn item + if (burnToken.tokenSpec != TokenSpec.ERC721) { + revert InvalidInput(); + } + if (burnToken.contractAddress != msg.sender || burnToken.tokenId != id || burnToken.amount != 1) { + revert InvalidToken(burnToken.contractAddress, burnToken.tokenId); + } - function supportsInterface(bytes4 interfaceId) public view virtual override(PhysicalClaimCore, IERC165) returns (bool) { - return interfaceId == type(IPhysicalClaim).interfaceId || super.supportsInterface(interfaceId); + // Do burn and redeem + _burn(submission.instanceId, address(this), burnToken); + _redeem(from, submission, 0); } + + /** + * Execute onERC1155Received burn/redeem + */ + function _onERC1155Received(address from, uint256 tokenId, uint256 value, bytes calldata data) private { + (BurnSubmission memory submission) = abi.decode(data, (BurnSubmission)); + + // A single 1155 can only be sent in directly for a burn if: + // 1. There is no cost to the burn (because no payment can be sent with a transfer) + // 2. The burn only requires one NFT (one burnToken) + // 3. They are an active member (because no fee payment can be sent with a transfer) + _validateCanReceive(submission.erc20, submission.price, from); + if (submission.burnTokens.length != 1) { + revert InvalidInput(); + } + if (!_isVariationAvailable(submission)) revert SoldOut(); + _validateSubmission(submission); + + + // Check that the burn token is valid + BurnToken memory burnToken = submission.burnTokens[0]; + + // Can only take in one burn item + if (burnToken.tokenSpec != TokenSpec.ERC1155) { + revert InvalidInput(); + } + if (burnToken.contractAddress != msg.sender || burnToken.tokenId != tokenId) { + revert InvalidToken(burnToken.contractAddress, burnToken.tokenId); + } + if (burnToken.amount != value) { + revert InvalidBurnAmount(); + } + + // Do burn and redeem + _burn(submission.instanceId, address(this), burnToken); + _redeem(from, submission, 0); + } + + /** + * Execute onERC1155BatchReceived burn/redeem + */ + function _onERC1155BatchReceived(address from, uint256[] calldata tokenIds, uint256[] calldata values, bytes calldata data) private { + (BurnSubmission memory submission) = abi.decode(data, (BurnSubmission)); + + // A single 1155 can only be sent in directly for a burn if: + // 1. There is no cost to the burn (because no payment can be sent with a transfer) + // 2. We have the right data length + // 3. They are an active member (because no fee payment can be sent with a transfer) + _validateCanReceive(submission.erc20, submission.price, from); + if (submission.burnTokens.length != tokenIds.length) { + revert InvalidInput(); + } + if (!_isVariationAvailable(submission)) revert SoldOut(); + _validateSubmission(submission); + // Verify the values match what is needed and burn tokens + for (uint256 i; i < submission.burnTokens.length;) { + BurnToken memory burnToken = submission.burnTokens[i]; + if (burnToken.contractAddress != msg.sender || burnToken.tokenId != tokenIds[i]) { + revert InvalidToken(burnToken.contractAddress, burnToken.tokenId); + } + if (burnToken.amount != values[i]) { + revert InvalidBurnAmount(); + } + _burn(submission.instanceId, address(this), burnToken); + unchecked { ++i; } + } + + // Do redeem + _redeem(from, submission, 0); + } + /** - * @dev See {IPhysicalClaim-initializePhysicalClaim}. + * Helper to validate we can receive tokens direcly */ - function initializePhysicalClaim( - uint256 instanceId, - PhysicalClaimParameters calldata physicalClaimParameters - ) external { - // Max uint56 for instanceId - if (instanceId == 0 || instanceId > MAX_UINT_56) { + function _validateCanReceive(address erc20, uint256 price, address from) private view { + if ((erc20 == address(0) && price != 0) || !_isActiveMember(from)) { revert InvalidInput(); } + } + + /** + * Helper to check if the sender holds an active Manifold membership + */ + function _isActiveMember(address sender) private view returns(bool) { + return manifoldMembershipContract != address(0) && + IManifoldMembership(manifoldMembershipContract).isActiveMember(sender); + } + + /** + * Helper to perform the redeem and returns the remaining msg value after consumed amount + */ + function _redeem(address redeemer, BurnSubmission memory submission, uint256 msgValue) private returns (uint256) { + // Increment variation count + _variationCount[submission.instanceId][submission.variation]++; + // Increment total count + _totalCount[submission.instanceId]++; + + // Transfer funds + uint256 payableCost = submission.erc20 == address(0) ? submission.price : 0; + if (!_isActiveMember(redeemer)) { + payableCost += submission.burnTokens.length <= 1 ? BURN_FEE : MULTI_BURN_FEE; + } + + // Check we have sufficient funds + if (payableCost > msgValue) { + revert InvalidPaymentAmount(); + } + + // Send funds if necessary + if (submission.price > 0) { + if (submission.erc20 == address(0)) { + _forwardValue(submission.fundsRecipient, submission.price); + } else { + IERC20(submission.erc20).transferFrom(redeemer, submission.fundsRecipient, submission.price); + } + } + + // Redeem + emit Redeem(submission.instanceId, redeemer, submission.variation, submission.nonce); - _initialize(instanceId, physicalClaimParameters); + return msgValue - payableCost; } /** - * @dev See {IPhysicalClaim-updatePhysicalClaim}. + * Send funds to receiver */ - function updatePhysicalClaim( - uint256 instanceId, - PhysicalClaimParameters calldata physicalClaimParameters - ) external { - _validateAdmin(instanceId); - _update(instanceId, physicalClaimParameters); + function _forwardValue(address payable receiver, uint256 amount) private { + (bool sent, ) = receiver.call{value: amount}(""); + if (!sent) { + revert TransferFailure(); + } } + + /** + * @dev See {IPhysicalClaim-getVariationCounts}. + */ + function getVariationCounts(uint256 instanceId, uint8[] calldata variations) external view override returns(uint64[] memory counts) { + counts = new uint64[](variations.length); + for (uint256 i; i < variations.length;) { + counts[i] = _variationCount[instanceId][variations[i]]; + unchecked { ++i; } + } + } + + /** + * @dev See {IPhysicalClaim-getTotalCount}. + */ + function getTotalCount(uint256 instanceId) external view override returns(uint64) { + return _totalCount[instanceId]; + } + } diff --git a/packages/manifold/contracts/physicalclaim/PhysicalClaimCore.sol b/packages/manifold/contracts/physicalclaim/PhysicalClaimCore.sol deleted file mode 100644 index f018eb80..00000000 --- a/packages/manifold/contracts/physicalclaim/PhysicalClaimCore.sol +++ /dev/null @@ -1,517 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "@manifoldxyz/libraries-solidity/contracts/access/AdminControl.sol"; -import "@manifoldxyz/libraries-solidity/contracts/access/IAdminControl.sol"; - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; - -import "./PhysicalClaimLib.sol"; -import "./IPhysicalClaimCore.sol"; -import "./Interfaces.sol"; - -/** - * @title Physical Claim Core - * @author manifold.xyz - * @notice Core logic for Physical Claim shared extensions. - */ -abstract contract PhysicalClaimCore is ERC165, AdminControl, ReentrancyGuard, IPhysicalClaimCore { - using Strings for uint256; - using ECDSA for bytes32; - - uint256 internal constant MAX_UINT_16 = 0xffff; - uint256 internal constant MAX_UINT_56 = 0xffffffffffffff; - - bool public deprecated; - - // { instanceId => PhysicalClaim } - mapping(uint256 => PhysicalClaim) internal _physicalClaims; - - // { instanceId => creator } -> TODO: make it so multiple people can administer a physical claim - mapping(uint256 => address) internal _physicalClaimCreator; - - // { instanceId => { redeemer => uint256 } } - mapping(uint256 => mapping(address => uint256)) internal _redemptionCounts; - - // { instanceId => nonce => t/f } - mapping(uint256 => mapping(bytes32 => bool)) internal _usedMessages; - - // { instanceId => { contractAddress => { tokenId => t/f }} - // Track used tokens for a given instannceId - mapping(uint256 => mapping(address => mapping(uint256 => bool))) internal _usedTokens; - - constructor(address initialOwner) { - _transferOwnership(initialOwner); - } - - function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165, AdminControl) returns (bool) { - return interfaceId == type(IPhysicalClaimCore).interfaceId || - interfaceId == type(IERC721Receiver).interfaceId || - interfaceId == type(IERC1155Receiver).interfaceId || - super.supportsInterface(interfaceId); - } - - /** - * Admin function to deprecate the contract - */ - function deprecate(bool _deprecated) external adminRequired { - deprecated = _deprecated; - } - - /** - * Initialiazes a physical claim with base parameters - */ - function _initialize( - uint256 instanceId, - PhysicalClaimParameters calldata physicalClaimParameters - ) internal { - if (deprecated) { - revert ContractDeprecated(); - } - if (_physicalClaimCreator[instanceId] != address(0)) { - revert InvalidInstance(); - } - _physicalClaimCreator[instanceId] = msg.sender; - PhysicalClaimLib.initialize(instanceId, _physicalClaims[instanceId], physicalClaimParameters); - } - - /** - * Updates a physical claim with base parameters - */ - function _update( - uint256 instanceId, - PhysicalClaimParameters calldata physicalClaimParameters - ) internal { - PhysicalClaimLib.update(instanceId, _getPhysicalClaim(instanceId), physicalClaimParameters); - } - - /** - * Validates that this physical claim is managed by the user - */ - function _validateAdmin( - uint256 instanceId - ) internal view { - require(_physicalClaimCreator[instanceId] == msg.sender, "Must be admin"); - } - - /** - * See {IPhysicalClaimCore-getPhysicalClaim}. - */ - function getPhysicalClaim(uint256 instanceId) external override view returns(PhysicalClaimView memory) { - PhysicalClaim storage physicalClaimInstance = _getPhysicalClaim(instanceId); - VariationState[] memory variationStates = new VariationState[](physicalClaimInstance.variationIds.length); - for (uint256 i; i < physicalClaimInstance.variationIds.length;) { - variationStates[i] = physicalClaimInstance.variations[physicalClaimInstance.variationIds[i]]; - unchecked { ++i; } - } - return PhysicalClaimView({ - paymentReceiver: physicalClaimInstance.paymentReceiver, - redeemedCount: physicalClaimInstance.redeemedCount, - totalSupply: physicalClaimInstance.totalSupply, - startDate: physicalClaimInstance.startDate, - endDate: physicalClaimInstance.endDate, - signer: physicalClaimInstance.signer, - burnSet: physicalClaimInstance.burnSet, - variationStates: variationStates - }); - } - - /** - * See {IPhysicalClaimCore-getPhysicalClaim}. - */ - function getRedemptions(uint256 instanceId, address redeemer) external override view returns(uint256) { - return _redemptionCounts[instanceId][redeemer]; - } - - /** - * See {IPhysicalClaimCore-getVariationState}. - */ - function getVariationState(uint256 instanceId, uint8 variation) external override view returns(VariationState memory) { - return _getPhysicalClaim(instanceId).variations[variation]; - } - - function getAreTokensUsed(TokensUsedQuery calldata tokensUsedQuery) external override view returns(bool[] memory results) { - results = new bool[](tokensUsedQuery.tokenIds.length); - for (uint i = 0; i < results.length; i++) { - address contractAddress = tokensUsedQuery.contractAddresses[i]; - uint256 tokenId = tokensUsedQuery.tokenIds[i]; - results[i] = _usedTokens[tokensUsedQuery.instanceId][contractAddress][tokenId]; - } - } - - /** - * Helper to get physical claim instance - */ - function _getPhysicalClaim(uint256 instanceId) internal view returns(PhysicalClaim storage physicalClaimInstance) { - physicalClaimInstance = _physicalClaims[instanceId]; - if (physicalClaimInstance.paymentReceiver == address(0)) { - revert InvalidInstance(); - } - } - - /** - * (Batch overload) see {IPhysicalClaimCore-burnRedeem}. - */ - function burnRedeem(PhysicalClaimSubmission[] calldata submissions) external payable override nonReentrant { - if (submissions.length == 0) revert InvalidInput(); - - uint256 msgValueRemaining = msg.value; - for (uint256 i; i < submissions.length;) { - PhysicalClaimSubmission memory currentSub = submissions[i]; - uint256 instanceId = currentSub.instanceId; - - // The expectedCount must match the user's current redemption count to enforce idempotency - if (currentSub.currentClaimCount != _redemptionCounts[instanceId][msg.sender]) revert InvalidInput(); - - uint256 totalCost = currentSub.totalCost; - - // Check that we have enough funds for the redemption - if (totalCost > 0) { - if (msgValueRemaining < totalCost) { - revert InvalidPaymentAmount(); - } - msgValueRemaining -= totalCost; - } - _burnRedeem(currentSub); - unchecked { ++i; } - } - } - - function _burnRedeem(PhysicalClaimSubmission memory submission) private { - uint56 instanceId = submission.instanceId; - PhysicalClaim storage physicalClaimInstance = _getPhysicalClaim(instanceId); - - // Get the amount that can be burned - uint16 physicalClaimCount = _getAvailablePhysicalClaimCount(physicalClaimInstance, submission.variation, submission.count); - - // Signer being set means that the physical claim is a paid claim - if (physicalClaimInstance.signer != address(0)) { - // Check that the message value is what was signed... - _checkPriceSignature(instanceId, submission.signature, submission.message, submission.nonce, physicalClaimInstance.signer, submission.totalCost); - _forwardValue(physicalClaimInstance.paymentReceiver, submission.totalCost); - } - - // Do physical claim - _burnTokens(instanceId, physicalClaimInstance, submission.burnTokens, physicalClaimCount, msg.sender, submission.data); - _redeem(instanceId, physicalClaimInstance, msg.sender, submission.variation, physicalClaimCount, submission.data); - } - - function _checkPriceSignature(uint56 instanceId, bytes memory signature, bytes32 message, bytes32 nonce, address signingAddress, uint256 cost) internal { - // Verify valid message based on input variables - bytes32 expectedMessage = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", instanceId, cost)); - // Verify nonce usage/re-use - require(!_usedMessages[instanceId][nonce], "Cannot replay transaction"); - address signer = message.recover(signature); - if (message != expectedMessage || signer != signingAddress) revert InvalidSignature(); - _usedMessages[instanceId][nonce] = true; - } - - /** - * @dev See {IPhysicalClaimCore-recover}. - */ - function recover(address tokenAddress, uint256 tokenId, address destination) external override adminRequired { - IERC721(tokenAddress).transferFrom(address(this), destination, tokenId); - } - - /** - * @dev See {IERC721Receiver-onReceived}. - */ - function onERC721Received( - address, - address from, - uint256 id, - bytes calldata data - ) external override nonReentrant returns(bytes4) { - // Check calldata is valid - if (data.length % 32 != 0) { - revert InvalidData(); - } - - uint56 instanceId; - uint256 burnItemIndex; - bytes32[] memory merkleProof; - uint8 variation; - (instanceId, burnItemIndex, merkleProof, variation) = abi.decode(data, (uint56, uint256, bytes32[], uint8)); - - _onERC721Received(from, id, instanceId, burnItemIndex, merkleProof, variation); - return this.onERC721Received.selector; - } - - /** - * @notice token transfer callback - */ - function _onERC721Received( - address from, - uint256 tokenId, - uint56 instanceId, - uint256 burnItemIndex, - bytes32[] memory merkleProof, - uint8 variation - ) private { - PhysicalClaim storage physicalClaimInstance = _getPhysicalClaim(instanceId); - // Note: since safeTransferFrom can't take funds, we are restricted to non-signature mints - if (physicalClaimInstance.signer != address(0)) revert InvalidInput(); - - // A single can only be sent in directly for a burn if: - // 1. There is no cost to the burn (because no payment can be sent with a transfer) - // 2. The burn only requires one NFT (one burnSet element and one count) - _validateReceivedInput(physicalClaimInstance.burnSet.length, physicalClaimInstance.burnSet[0].requiredCount); - - // Validate we have remaining amounts available (will revert if not) - _getAvailablePhysicalClaimCount(physicalClaimInstance, variation, 1); - - // Check that the burn token is valid - BurnItem memory burnItem = physicalClaimInstance.burnSet[0].items[burnItemIndex]; - - // Can only take in one burn item - if (burnItem.burnTokenSpec != BurnTokenSpec.ERC721) { - revert InvalidInput(); - } - PhysicalClaimLib.validateBurnItem(burnItem, msg.sender, tokenId, merkleProof); - - // Do burn and redeem - _burn(instanceId, burnItem, address(this), msg.sender, tokenId, 1, ""); - _redeem(instanceId, physicalClaimInstance, from, variation, 1, ""); - } - - /** - * @dev See {IERC1155Receiver-onERC1155Received}. - */ - function onERC1155Received( - address, - address from, - uint256 id, - uint256 value, - bytes calldata data - ) external override nonReentrant returns(bytes4) { - // Check calldata is valid - if (data.length % 32 != 0) { - revert InvalidData(); - } - - uint56 instanceId; - uint16 burnRedeemCount; - uint256 burnItemIndex; - bytes32[] memory merkleProof; - uint8 variation; - (instanceId, burnRedeemCount, burnItemIndex, merkleProof, variation) = abi.decode(data, (uint56, uint16, uint256, bytes32[], uint8)); - - // Do burn redeem - _onERC1155Received(from, id, value, instanceId, burnRedeemCount, burnItemIndex, merkleProof, variation); - - return this.onERC1155Received.selector; - } - - /** - * Execute onERC1155Received burn/redeem - */ - function _onERC1155Received(address from, uint256 tokenId, uint256 value, uint56 instanceId, uint16 burnRedeemCount, uint256 burnItemIndex, bytes32[] memory merkleProof, uint8 variation) private { - PhysicalClaim storage physicalClaimInstance = _getPhysicalClaim(instanceId); - // Note: since safeTransferFrom can't take funds, we are restricted to non-signature mints - if (physicalClaimInstance.signer != address(0)) revert InvalidInput(); - - // A single 1155 can only be sent in directly for a burn if: - // 1. The burn only requires one NFT (one burn set element and one required count in the set) - // 2. They are an active member (because no fee payment can be sent with a transfer) - _validateReceivedInput(physicalClaimInstance.burnSet.length, physicalClaimInstance.burnSet[0].requiredCount); - - uint16 availableBurnRedeemCount = _getAvailablePhysicalClaimCount(physicalClaimInstance, variation, burnRedeemCount); - - // Check that the burn token is valid - BurnItem memory burnItem = physicalClaimInstance.burnSet[0].items[burnItemIndex]; - if (value != burnItem.amount * burnRedeemCount) { - revert InvalidBurnAmount(); - } - PhysicalClaimLib.validateBurnItem(burnItem, msg.sender, tokenId, merkleProof); - - // Do burn and redeem - _burn(instanceId, burnItem, address(this), msg.sender, tokenId, availableBurnRedeemCount, ""); - _redeem(instanceId, physicalClaimInstance, from, variation, availableBurnRedeemCount, ""); - - // Return excess amount - if (availableBurnRedeemCount != burnRedeemCount) { - IERC1155(msg.sender).safeTransferFrom(address(this), from, tokenId, (burnRedeemCount - availableBurnRedeemCount) * burnItem.amount, ""); - } - } - - /** - * @dev See {IERC1155Receiver-onERC1155BatchReceived}. - */ - function onERC1155BatchReceived( - address, - address from, - uint256[] calldata ids, - uint256[] calldata values, - bytes calldata data - ) external override nonReentrant returns(bytes4) { - // Do not support batch burning right now - revert InvalidInput(); - } - - function _validateReceivedInput(uint256 length, uint256 requiredCount) private pure { - if (length != 1 || requiredCount != 1) { - revert InvalidInput(); - } - } - - /** - * Send funds to receiver - */ - function _forwardValue(address payable receiver, uint256 amount) private { - (bool sent, ) = receiver.call{value: amount}(""); - if (!sent) { - revert TransferFailure(); - } - } - - /** - * Burn all listed tokens and check that the burn set is satisfied - */ - function _burnTokens(uint56 instanceId, PhysicalClaim storage burnRedeemInstance, BurnToken[] memory burnTokens, uint256 burnRedeemCount, address owner, bytes memory data) private { - // Check that each group in the burn set is satisfied - uint256[] memory groupCounts = new uint256[](burnRedeemInstance.burnSet.length); - - for (uint256 i; i < burnTokens.length;) { - BurnToken memory burnToken = burnTokens[i]; - BurnItem memory burnItem = burnRedeemInstance.burnSet[burnToken.groupIndex].items[burnToken.itemIndex]; - - PhysicalClaimLib.validateBurnItem(burnItem, burnToken.contractAddress, burnToken.id, burnToken.merkleProof); - - _burn(instanceId, burnItem, owner, burnToken.contractAddress, burnToken.id, burnRedeemCount, data); - groupCounts[burnToken.groupIndex] += burnRedeemCount; - - unchecked { ++i; } - } - for (uint256 i; i < groupCounts.length;) { - if (groupCounts[i] != burnRedeemInstance.burnSet[i].requiredCount * burnRedeemCount) { - revert InvalidBurnAmount(); - } - unchecked { ++i; } - } - } - - /** - * Helper to get the number of burn redeems the person can accomplish - */ - function _getAvailablePhysicalClaimCount(PhysicalClaim storage instance, uint8 variation, uint16 count) internal view returns(uint16 burnRedeemCount) { - uint16 remainingTotalCount; - if (instance.totalSupply == 0) { - // If totalSupply is 0, it means unlimited redemptions - remainingTotalCount = count; - } else { - // Get the remaining total redemptions - remainingTotalCount = (instance.totalSupply - instance.redeemedCount); - } - - // Get the max redemptions for this variation - VariationState memory variationState = instance.variations[variation]; - if (!variationState.active) revert InvalidVariation(); - - uint16 variationRemainingCount; - if (variationState.totalSupply == 0) { - // If totalSupply of variation is 0, it means unlimited available - variationRemainingCount = count; - } else { - // Get the remaining variation redemptions - variationRemainingCount = (variationState.totalSupply - variationState.redeemedCount); - } - - // Use whichever is lesser... - uint16 comparator = remainingTotalCount > variationRemainingCount ? variationRemainingCount : remainingTotalCount; - - // Use the lesser of what's available or the desired count - if (comparator > count) { - burnRedeemCount = count; - } else { - burnRedeemCount = comparator; - } - - // No more remaining - if (burnRedeemCount == 0) revert InvalidRedeemAmount(); - } - - /** - * Helper to burn token - */ - function _burn(uint56 instanceId, BurnItem memory burnItem, address from, address contractAddress, uint256 tokenId, uint256 burnRedeemCount, bytes memory data) private { - if (burnItem.burnTokenSpec == BurnTokenSpec.ERC1155) { - uint256 amount = burnItem.amount * burnRedeemCount; - - if (burnItem.burnFunctionSpec == BurnFunctionSpec.NONE) { - // Send to 0xdEaD to burn if contract doesn't have burn function - IERC1155(contractAddress).safeTransferFrom(from, address(0xdEaD), tokenId, amount, data); - - } else if (burnItem.burnFunctionSpec == BurnFunctionSpec.MANIFOLD) { - // Burn using the creator core's burn function - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = tokenId; - uint256[] memory amounts = new uint256[](1); - amounts[0] = amount; - Manifold1155(contractAddress).burn(from, tokenIds, amounts); - - } else if (burnItem.burnFunctionSpec == BurnFunctionSpec.OPENZEPPELIN) { - // Burn using OpenZeppelin's burn function - OZBurnable1155(contractAddress).burn(from, tokenId, amount); - - } else { - revert InvalidBurnFunctionSpec(); - } - } else if (burnItem.burnTokenSpec == BurnTokenSpec.ERC721) { - if (burnRedeemCount != 1) { - revert InvalidBurnAmount(); - } - if (burnItem.burnFunctionSpec == BurnFunctionSpec.NONE) { - // Send to 0xdEaD to burn if contract doesn't have burn function - IERC721(contractAddress).safeTransferFrom(from, address(0xdEaD), tokenId, data); - - } else if (burnItem.burnFunctionSpec == BurnFunctionSpec.MANIFOLD || burnItem.burnFunctionSpec == BurnFunctionSpec.OPENZEPPELIN) { - if (from != address(this)) { - // 721 `burn` functions do not have a `from` parameter, so we must verify the owner - if (IERC721(contractAddress).ownerOf(tokenId) != from) { - revert TransferFailure(); - } - } - // Burn using the contract's burn function - Burnable721(contractAddress).burn(tokenId); - - } else { - revert InvalidBurnFunctionSpec(); - } - } else if (burnItem.burnTokenSpec == BurnTokenSpec.ERC721_NO_BURN) { - if (burnRedeemCount != 1) { - revert InvalidBurnAmount(); - } - if (_usedTokens[instanceId][contractAddress][tokenId]) { - revert InvalidBurnAmount(); - } - if (IERC721(contractAddress).ownerOf(tokenId) != from) { - revert InvalidBurnAmount(); - } - _usedTokens[instanceId][contractAddress][tokenId] = true; - emit PhysicalClaimLib.PhysicalClaimTokenConsumed(instanceId, tokenId, contractAddress); - } else { - revert InvalidBurnTokenSpec(); - } - } - - /** - * Helper to redeem multiple redeem - */ - function _redeem(uint256 instanceId, PhysicalClaim storage physicalClaimInstance, address to, uint8 variation, uint16 count, bytes memory data) internal { - uint256 totalCount = count; - if (totalCount > MAX_UINT_16) { - revert InvalidInput(); - } - physicalClaimInstance.redeemedCount += uint16(totalCount); - physicalClaimInstance.variations[variation].redeemedCount += count; - _redemptionCounts[instanceId][to] += count; - emit PhysicalClaimLib.PhysicalClaimRedemption(instanceId, variation, count, data); - } -} \ No newline at end of file diff --git a/packages/manifold/contracts/physicalclaim/PhysicalClaimLib.sol b/packages/manifold/contracts/physicalclaim/PhysicalClaimLib.sol deleted file mode 100644 index e881e104..00000000 --- a/packages/manifold/contracts/physicalclaim/PhysicalClaimLib.sol +++ /dev/null @@ -1,166 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; -import "./IPhysicalClaimCore.sol"; - -/** - * @title Physical Claim Lib - * @author manifold.xyz - * @notice Library for Physical Claim shared extensions. - */ -library PhysicalClaimLib { - - event PhysicalClaimInitialized(uint256 indexed instanceId, address initializer); - event PhysicalClaimUpdated(uint256 indexed instanceId); - event PhysicalClaimRedemption(uint256 indexed instanceId, uint8 indexed variation, uint32 count, bytes data); - event PhysicalClaimTokenConsumed(uint256 indexed instanceId, uint256 tokenId, address contractAddress); - - error PhysicalClaimAlreadyInitialized(); - error InvalidBurnItem(); - error InvalidBurnToken(); - error InvalidMerkleProof(); - error InvalidStorageProtocol(); - error InvalidPaymentReceiver(); - error InvalidDates(); - error InvalidInput(); - - /** - * Initialiazes a burn redeem with base parameters - */ - function initialize( - uint256 instanceId, - IPhysicalClaimCore.PhysicalClaim storage physicalClaimInstance, - IPhysicalClaimCore.PhysicalClaimParameters calldata physicalClaimParameters - ) public { - _validateParameters(physicalClaimParameters); - - // Create the physical claim - _setParameters(physicalClaimInstance, physicalClaimParameters); - _setBurnGroups(physicalClaimInstance, physicalClaimParameters.burnSet); - - emit PhysicalClaimInitialized(instanceId, msg.sender); - } - - /** - * Updates a physical claim with base parameters - */ - function update( - uint256 instanceId, - IPhysicalClaimCore.PhysicalClaim storage physicalClaimInstance, - IPhysicalClaimCore.PhysicalClaimParameters calldata physicalClaimParameters - ) public { - _validateParameters(physicalClaimParameters); - - // Overwrite the existing burnRedeem - _setParameters(physicalClaimInstance, physicalClaimParameters); - _setBurnGroups(physicalClaimInstance, physicalClaimParameters.burnSet); - syncTotalSupply(physicalClaimInstance); - emit PhysicalClaimUpdated(instanceId); - } - - /** - * Helper to update total supply if redeemedCount exceeds totalSupply after airdrop or instance update. - */ - function syncTotalSupply(IPhysicalClaimCore.PhysicalClaim storage physicalClaimInstance) public { - if ( - physicalClaimInstance.totalSupply != 0 && - physicalClaimInstance.redeemedCount > physicalClaimInstance.totalSupply - ) { - physicalClaimInstance.totalSupply = physicalClaimInstance.redeemedCount; - } - } - - /* - * Helper to validate burn item - */ - function validateBurnItem(IPhysicalClaimCore.BurnItem memory burnItem, address contractAddress, uint256 tokenId, bytes32[] memory merkleProof) public pure { - if (burnItem.validationType == IPhysicalClaimCore.ValidationType.ANY) { - return; - } - if (contractAddress != burnItem.contractAddress) { - revert InvalidBurnToken(); - } - if (burnItem.validationType == IPhysicalClaimCore.ValidationType.CONTRACT) { - return; - } else if (burnItem.validationType == IPhysicalClaimCore.ValidationType.RANGE) { - if (tokenId < burnItem.minTokenId || tokenId > burnItem.maxTokenId) { - revert IPhysicalClaimCore.InvalidToken(tokenId); - } - return; - } else if (burnItem.validationType == IPhysicalClaimCore.ValidationType.MERKLE_TREE) { - bytes32 leaf = keccak256(abi.encodePacked(tokenId)); - if (!MerkleProof.verify(merkleProof, burnItem.merkleRoot, leaf)) { - revert InvalidMerkleProof(); - } - return; - } - revert InvalidBurnItem(); - } - - /** - * Helper to validate the parameters for a physical claim - */ - function _validateParameters(IPhysicalClaimCore.PhysicalClaimParameters calldata physicalClaimParameters) internal pure { - if (physicalClaimParameters.paymentReceiver == address(0)) { - revert InvalidPaymentReceiver(); - } - if (physicalClaimParameters.endDate != 0 && physicalClaimParameters.startDate >= physicalClaimParameters.endDate) { - revert InvalidDates(); - } - if (physicalClaimParameters.variationLimits.length == 0) { - revert InvalidInput(); - } - } - - /** - * Helper to set top level properties for a physical claim - */ - function _setParameters(IPhysicalClaimCore.PhysicalClaim storage physicalClaimInstance, IPhysicalClaimCore.PhysicalClaimParameters calldata physicalClaimParameters) private { - physicalClaimInstance.startDate = physicalClaimParameters.startDate; - physicalClaimInstance.endDate = physicalClaimParameters.endDate; - physicalClaimInstance.totalSupply = physicalClaimParameters.totalSupply; - physicalClaimInstance.paymentReceiver = physicalClaimParameters.paymentReceiver; - physicalClaimInstance.signer = physicalClaimParameters.signer; - - uint8[] memory currentVariationIds = physicalClaimInstance.variationIds; - for (uint256 i; i < currentVariationIds.length;) { - physicalClaimInstance.variations[currentVariationIds[i]].active = false; - unchecked { ++i; } - } - physicalClaimInstance.variationIds = new uint8[](physicalClaimParameters.variationLimits.length); - for (uint256 i; i < physicalClaimParameters.variationLimits.length;) { - IPhysicalClaimCore.VariationLimit memory variationLimit = physicalClaimParameters.variationLimits[i]; - IPhysicalClaimCore.VariationState storage variationState = physicalClaimInstance.variations[variationLimit.id]; - variationState.active = true; - physicalClaimInstance.variationIds[i] = variationLimit.id; - // Set the totalSupply. If params specify 0, we use 0, otherwise it's the max of the current redeemCount and the param's totalSupply - variationState.totalSupply = variationLimit.totalSupply == 0 ? 0 : (variationState.redeemedCount > variationLimit.totalSupply ? variationState.redeemedCount : variationLimit.totalSupply); - unchecked { ++i; } - } - } - - /** - * Helper to set the burn groups for a physical claim - */ - function _setBurnGroups(IPhysicalClaimCore.PhysicalClaim storage physicalClaimInstance, IPhysicalClaimCore.BurnGroup[] calldata burnGroups) private { - delete physicalClaimInstance.burnSet; - for (uint256 i; i < burnGroups.length;) { - physicalClaimInstance.burnSet.push(); - IPhysicalClaimCore.BurnGroup storage burnGroup = physicalClaimInstance.burnSet[i]; - if (burnGroups[i].requiredCount == 0 || burnGroups[i].requiredCount > burnGroups[i].items.length) { - revert InvalidInput(); - } - burnGroup.requiredCount = burnGroups[i].requiredCount; - for (uint256 j; j < burnGroups[i].items.length;) { - // IPhysicalClaimCore.BurnItem memory burnItem = burnGroups[i].items[j]; - // uint256 amount = burnItem.amount; - burnGroup.items.push(burnGroups[i].items[j]); - unchecked { ++j; } - } - unchecked { ++i; } - } - } - -} \ No newline at end of file diff --git a/packages/manifold/script/ERC1155LazyPayableClaim.s.sol b/packages/manifold/script/ERC1155LazyPayableClaim.s.sol index d6ad1edc..66b7affe 100644 --- a/packages/manifold/script/ERC1155LazyPayableClaim.s.sol +++ b/packages/manifold/script/ERC1155LazyPayableClaim.s.sol @@ -33,8 +33,8 @@ import "../contracts/lazyclaim/ERC1155LazyPayableClaim.sol"; // uint256 deployerPrivateKey = pk; // uncomment this when testing on goerli uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); // comment this out when testing on goerli vm.startBroadcast(deployerPrivateKey); - // forge script scripts/ERC1155LazyPayableClaim.s.sol --optimizer-runs 1000 --rpc-url --broadcast - // forge verify-contract --compiler-version 0.8.17 --optimizer-runs 1000 --chain goerli contracts/lazyclaim/ERC1155LazyPayableClaim.sol:ERC1155LazyPayableClaim --constructor-args $(cast abi-encode "constructor(address,address,address)" "${INITIAL_OWNER}" "0x00000000000076A84feF008CDAbe6409d2FE638B" "0x00000000000000447e69651d841bD8D104Bed493") --watch + // forge script scripts/ERC1155LazyPayableClaim.s.sol --optimizer-runs 800 --rpc-url --broadcast + // forge verify-contract --compiler-version 0.8.17 --optimizer-runs 800 --chain goerli contracts/lazyclaim/ERC1155LazyPayableClaim.sol:ERC1155LazyPayableClaim --constructor-args $(cast abi-encode "constructor(address,address,address)" "${INITIAL_OWNER}" "0x00000000000076A84feF008CDAbe6409d2FE638B" "0x00000000000000447e69651d841bD8D104Bed493") --watch new ERC1155LazyPayableClaim{salt: 0x455243313135354c617a7950617961626c65436c61696d455243313135354c61}(initialOwner, DELEGATION_REGISTRY, DELEGATION_REGISTRY_V2); vm.stopBroadcast(); } diff --git a/packages/manifold/script/ERC721LazyPayableClaim.s.sol b/packages/manifold/script/ERC721LazyPayableClaim.s.sol index 16737437..03d86e45 100644 --- a/packages/manifold/script/ERC721LazyPayableClaim.s.sol +++ b/packages/manifold/script/ERC721LazyPayableClaim.s.sol @@ -33,8 +33,8 @@ contract DeployERC721LazyPayableClaim is Script { // uint256 deployerPrivateKey = pk; // uncomment this when testing on goerli uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); // comment this out when testing on goerli vm.startBroadcast(deployerPrivateKey); - // forge script scripts/ERC721LazyPayableClaim.s.sol --optimizer-runs 1000 --rpc-url --broadcast - // forge verify-contract --compiler-version 0.8.17 --optimizer-runs 1000 --chain goerli contracts/lazyclaim/ERC721LazyPayableClaim.sol:ERC721LazyPayableClaim --constructor-args $(cast abi-encode "constructor(address,address,address)" "${INITIAL_OWNER}" "0x00000000000076A84feF008CDAbe6409d2FE638B" "0x00000000000000447e69651d841bD8D104Bed493") --watch + // forge script scripts/ERC721LazyPayableClaim.s.sol --optimizer-runs 800 --rpc-url --broadcast + // forge verify-contract --compiler-version 0.8.17 --optimizer-runs 800 --chain goerli contracts/lazyclaim/ERC721LazyPayableClaim.sol:ERC721LazyPayableClaim --constructor-args $(cast abi-encode "constructor(address,address,address)" "${INITIAL_OWNER}" "0x00000000000076A84feF008CDAbe6409d2FE638B", "0x00000000000000447e69651d841bD8D104Bed493") --watch new ERC721LazyPayableClaim{salt: 0x4552433732314c617a7950617961626c65436c61696d4552433732314c617a79}(initialOwner, DELEGATION_REGISTRY, DELEGATION_REGISTRY_V2); vm.stopBroadcast(); } diff --git a/packages/manifold/script/PhysicalClaim.s.sol b/packages/manifold/script/PhysicalClaim.s.sol index a273b4c6..c1c4e5e0 100644 --- a/packages/manifold/script/PhysicalClaim.s.sol +++ b/packages/manifold/script/PhysicalClaim.s.sol @@ -31,8 +31,8 @@ contract DeployPhysicalClaim is Script { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); // comment this out when testing on goerli vm.startBroadcast(deployerPrivateKey); // forge script script/PhysicalClaim.s.sol --optimizer-runs 1000 --rpc-url --broadcast - // forge verify-contract --compiler-version 0.8.17 --optimizer-runs 1000 --chain goerli contracts/physicalclaim/PhysicalClaim.sol:PhysicalClaim --constructor-args $(cast abi-encode "constructor(address)" "${INITIAL_OWNER}") --libraries contracts/physicalclaim/PhysicalClaimLib.sol:PhysicalClaimLib:
--watch - new PhysicalClaim{salt: 0x4c657427732067657420506879736963616c2000000000000000000000000000}(initialOwner); + // forge verify-contract --compiler-version 0.8.17 --optimizer-runs 1000 --chain goerli contracts/physicalclaim/PhysicalClaim.sol:PhysicalClaim --constructor-args $(cast abi-encode "constructor(address)" "${INITIAL_OWNER}") --watch + new PhysicalClaim{salt: 0x4c657427732067657420506879736963616c2000000000000000000000000000}(initialOwner, address(0)); vm.stopBroadcast(); } } \ No newline at end of file diff --git a/packages/manifold/script/PhysicalClaimLib.s.sol b/packages/manifold/script/PhysicalClaimLib.s.sol deleted file mode 100644 index f43e17b8..00000000 --- a/packages/manifold/script/PhysicalClaimLib.s.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "forge-std/Script.sol"; -import "../contracts/physicalclaim/PhysicalClaimLib.sol"; - -contract DeployPhysicalClaimLib is Script { - function run() external { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(deployerPrivateKey); - // Deploy using the create2 proxy @ 0x4e59b44847b379578588920cA78FbF26c0B4956C - // With the calldata being the salt + bytecode - // Deploy with 150 runs - // e.g. - // forge script script/PhysicalClaimLib.s.sol --optimizer-runs 1000 --rpc-url --broadcast - // forge verify-contract --compiler-version 0.8.17 --optimizer-runs 1000 --chain goerli contracts/physicalclaim/PhysicalClaimLib.sol:PhysicalClaimLib --watch - 0x4e59b44847b379578588920cA78FbF26c0B4956C.call(abi.encodePacked(bytes32(0x4c657427732067657420506879736963616c2000000000000000000000000000), vm.getCode("PhysicalClaimLib.sol:PhysicalClaimLib"))); - vm.stopBroadcast(); - } -} diff --git a/packages/manifold/test/physicalclaim/PhysicalClaim.t.sol b/packages/manifold/test/physicalclaim/PhysicalClaim.t.sol index 05fb30a0..2548108a 100644 --- a/packages/manifold/test/physicalclaim/PhysicalClaim.t.sol +++ b/packages/manifold/test/physicalclaim/PhysicalClaim.t.sol @@ -2,3128 +2,1218 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; +import "./PhysicalClaimBase.t.sol"; import "../../contracts/physicalclaim/PhysicalClaim.sol"; -import "../../contracts/physicalclaim/PhysicalClaimLib.sol"; -import "../../contracts/physicalclaim/IPhysicalClaimCore.sol"; +import "@manifoldxyz/libraries-solidity/contracts/access/AdminControl.sol"; import "@manifoldxyz/creator-core-solidity/contracts/ERC721Creator.sol"; import "@manifoldxyz/creator-core-solidity/contracts/ERC1155Creator.sol"; -import "../mocks/delegation-registry/DelegationRegistry.sol"; import "../mocks/Mock.sol"; -import "../../lib/murky/src/Merkle.sol"; -contract PhysicalClaimTest is Test { - PhysicalClaim public example; - ERC721Creator public creatorCore721; - ERC1155Creator public creatorCore1155; - - DelegationRegistry public delegationRegistry; - Merkle public merkle; - - address public owner = 0x6140F00e4Ff3936702E68744f2b5978885464cbB; - address public other = 0xc78Dc443c126af6E4f6Ed540c1e740C1b5be09cd; - address public other2 = 0x80AAC46bbd3C2FcE33681541a52CacBEd14bF425; - address public other3 = 0x5174cD462b60c536eb51D4ceC1D561D3Ea31004F; - - address public signerForCost = 0x6140F00E4Ff3936702e68744f2b5978885464CBc; - address public zeroSigner = address(0); - - address public zeroAddress = address(0); - - uint256 privateKey = 0x1010101010101010101010101010101010101010101010101010101010101010; - - uint56 instanceId = 1; - - function setUp() public { - vm.startPrank(owner); - creatorCore721 = new ERC721Creator("Token", "NFT"); - creatorCore1155 = new ERC1155Creator("Token", "NFT"); - - delegationRegistry = new DelegationRegistry(); - example = new PhysicalClaim(owner); - - merkle = new Merkle(); - - vm.deal(owner, 10 ether); - vm.deal(other, 10 ether); - vm.deal(other2, 10 ether); - vm.deal(other3, 10 ether); - vm.stopPrank(); - } - - function testAccess() public { - vm.startPrank(other); - vm.expectRevert("AdminControl: Must be owner or admin"); - example.recover(address(creatorCore721), 1, other); - vm.stopPrank(); - - // Accidentally send token to contract - vm.startPrank(owner); - - creatorCore721.mintBase(owner, ""); - creatorCore721.transferFrom(owner, address(example), 1); - - example.recover(address(creatorCore721), 1, owner); - - vm.stopPrank(); - } - - function testSupportsInterface() public { - vm.startPrank(owner); - - bytes4 interfaceId = type(IPhysicalClaimCore).interfaceId; - assertEq(example.supportsInterface(interfaceId), true); - assertEq(example.supportsInterface(0xffffffff), false); - - interfaceId = type(IERC721Receiver).interfaceId; - assertEq(example.supportsInterface(interfaceId), true); - - interfaceId = type(IERC1155Receiver).interfaceId; - assertEq(example.supportsInterface(interfaceId), true); - - vm.stopPrank(); - } - - function testInputs() public { - vm.startPrank(owner); - - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - burnSet: new IPhysicalClaimCore.BurnGroup[](0), - variationLimits: new IPhysicalClaimCore.VariationLimit[](0), - signer: zeroSigner - }); - - // Cannot do instanceId of 0 - vm.expectRevert(IPhysicalClaimCore.InvalidInput.selector); - example.initializePhysicalClaim(0, claimPs); - - // Cannot do largest instanceID - vm.expectRevert(IPhysicalClaimCore.InvalidInput.selector); - example.initializePhysicalClaim(2**56, claimPs); - - // Cannot do 0 variations - vm.expectRevert(IPhysicalClaimCore.InvalidInput.selector); - example.initializePhysicalClaim(1, claimPs); - - vm.stopPrank(); - } - - function testHappyCaseERC721Burn() public { - vm.startPrank(owner); - - // Mint token 1 to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 1, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 10 - }); - - // Create claim initialization parameters - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - // Can get the claim - IPhysicalClaimCore.PhysicalClaimView memory claim = example.getPhysicalClaim(instanceId); - assertEq(claim.paymentReceiver, owner); - - // Cannot get claim that doesn't exist - vm.expectRevert(IPhysicalClaimCore.InvalidInstance.selector); - example.getPhysicalClaim(2); - - // Can update - claimPs.totalSupply = 2; - example.updatePhysicalClaim(instanceId, claimPs); - - // Can't update _not_ your own - vm.stopPrank(); - vm.startPrank(other); - vm.expectRevert(bytes("Must be admin")); - example.updatePhysicalClaim(instanceId, claimPs); - // Actually do a burnRedeem - - // Approve token for burning - creatorCore721.approve(address(example), 1); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - example.burnRedeem(submissions); - - vm.stopPrank(); - - vm.startPrank(owner); - // Mint new token to "other" - creatorCore721.mintBase(other, ""); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 2); - - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 2, - merkleProof: new bytes32[](0) - }); - - submissions[0].currentClaimCount = 1; - - // Send a non-zero value burn - example.burnRedeem{value: 1 ether}(submissions); - - vm.stopPrank(); - - // Case where total supply is not unlimited and they use remaining supply - vm.startPrank(owner); - - claimPs.totalSupply = 1; - example.initializePhysicalClaim(instanceId+1, claimPs); - - creatorCore721.mintBase(owner, ""); - - // Approve token for burning - creatorCore721.approve(address(example), 3); - - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 3, - merkleProof: new bytes32[](0) - }); - - submissions[0].instanceId = uint56(instanceId+1); - - submissions[0].currentClaimCount = 0; - - example.burnRedeem(submissions); - - // Case where total supply is huge, and they just redeem 1 - claimPs.totalSupply = 10; - example.initializePhysicalClaim(instanceId+2, claimPs); - - creatorCore721.mintBase(owner, ""); - - // Approve token for burning - creatorCore721.approve(address(example), 4); - - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 4, - merkleProof: new bytes32[](0) - }); - - submissions[0].instanceId = uint56(instanceId+2); - - submissions[0].currentClaimCount = 0; - - example.burnRedeem(submissions); - - vm.stopPrank(); - } - - function testEmptySubmissionsArray() public { - vm.startPrank(owner); - - // Mint token 1 to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 1, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 10 - }); - - // Create claim initialization parameters - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 1); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](0); - - vm.expectRevert(IPhysicalClaimCore.InvalidInput.selector); - example.burnRedeem(submissions); - vm.stopPrank(); - } - - function testGetRedemptionsCountCorrect() public { - vm.startPrank(owner); - - // Mint token 1 to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 1, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 10 - }); - - // Create claim initialization parameters - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 1); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - example.burnRedeem(submissions); - - // Check get redemptions, should be 1 - uint redemptions = example.getRedemptions(instanceId, other); - assertEq(redemptions, 1); - - vm.stopPrank(); - } - - function testGetVariationState() public { - vm.startPrank(owner); - - // Mint token 1 to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 1, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 10 - }); - - // Create claim initialization parameters - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 1); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - example.burnRedeem(submissions); - - // Check get redemptions, should be 1 - IPhysicalClaimCore.VariationState memory variationStateReturn = example.getVariationState(instanceId, 1); - assertEq(variationStateReturn.totalSupply, 10); - assertEq(variationStateReturn.redeemedCount, 1); - assertEq(variationStateReturn.active, true); - - vm.stopPrank(); - } - - function testERC721SafeTransferFromWithSigner() public { - vm.startPrank(owner); - - // Mint token 1 to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 1, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 10 - }); - - // Create claim initialization parameters - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - burnSet: burnSet, - variationLimits: variations, - signer: signerForCost - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Actually do a burnRedeem - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - // Cannot burn via safeTransferFrom because we have a signer (paid burn) - vm.expectRevert(IPhysicalClaimCore.InvalidInput.selector); - creatorCore721.safeTransferFrom(other, address(example), 1, abi.encode(uint56(instanceId), uint256(0), "", uint8(1))); - - - vm.stopPrank(); - - } - - function testERC721SafeTransferFrom() public { - vm.startPrank(owner); - - // Mint token 1 to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 1, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 10 - }); - - // Create claim initialization parameters - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Burn via safeTransferFrom - creatorCore721.safeTransferFrom(other, address(example), 1, abi.encode(uint56(instanceId), uint256(0), "", uint8(1))); - - assertEq(creatorCore721.balanceOf(address(other)), 0); - - vm.stopPrank(); - - } - - function testERC721SafeTransferFromBadLengthData() public { - vm.startPrank(owner); - - // Mint token 1 to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 1, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 10 - }); - - // Create claim initialization parameters - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - vm.expectRevert(IPhysicalClaimCore.InvalidData.selector); - creatorCore721.safeTransferFrom(other, address(example), 1, "a"); - - vm.stopPrank(); - - } - - function testIdempotencyViaIncorrectCurrentClaimCount() public { - vm.startPrank(owner); - - // Mint token 2 to other - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 2, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 10 - }); - - // Create claim initialization parameters - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 1); - creatorCore721.approve(address(example), 2); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - example.burnRedeem(submissions); - - // Idempotency test, wrong currentClaimCount - submissions[0].burnTokens[0].id = 2; - vm.expectRevert(IPhysicalClaimCore.InvalidInput.selector); - example.burnRedeem(submissions); - - - vm.stopPrank(); - - } - - function testCannotInitializeTwice() public { - vm.startPrank(owner); - - // Mint token 1 to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 1, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 10 - }); - - // Create claim initialization parameters - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.expectRevert(IPhysicalClaimCore.InvalidInstance.selector); - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - } - - function testCannotInitializeAfterDeprecation() public { - vm.startPrank(owner); - - // Mint token 1 to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 1, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 10 - }); - - // Create claim initialization parameters - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - example.deprecate(true); - - vm.expectRevert(IPhysicalClaimCore.ContractDeprecated.selector); - example.initializePhysicalClaim(instanceId, claimPs); - - example.deprecate(false); - - example.initializePhysicalClaim(instanceId+1, claimPs); - - vm.stopPrank(); - - // Cannot deprecate if now admin - vm.startPrank(other); - vm.expectRevert(bytes("AdminControl: Must be owner or admin")); - example.deprecate(true); - vm.stopPrank(); - } - - function testBurnFinalToken() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 1, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 100 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 1, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 1); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - example.burnRedeem(submissions); - - // Approve token for burning - creatorCore721.approve(address(example), 2); - - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 2, - merkleProof: new bytes32[](0) - }); - - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 1; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - vm.expectRevert(IPhysicalClaimCore.InvalidRedeemAmount.selector); // should revert cause none remaining and the setting is to revert it... - example.burnRedeem(submissions); - - vm.stopPrank(); - } - - function testTwoForOneBurn() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](2); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 1, - merkleRoot: "" - }); - burnItems[1] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 2, - maxTokenId: 2, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 2, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 1 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 1, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 1); - creatorCore721.approve(address(example), 2); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](2); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - burnTokens[1] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 1, - contractAddress: address(creatorCore721), - id: 2, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - example.burnRedeem(submissions); - - vm.stopPrank(); - } - - function testClaimMoreThanOneAtOnce() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 10 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 2, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 1); - creatorCore721.approve(address(example), 2); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](2); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - IPhysicalClaimCore.BurnToken[] memory burnTokens2 = new IPhysicalClaimCore.BurnToken[](1); - burnTokens2[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 2, - merkleProof: new bytes32[](0) - }); - submissions[1].instanceId = uint56(instanceId); - submissions[1].count = 1; - submissions[1].currentClaimCount = 1; - submissions[1].burnTokens = burnTokens2; - submissions[1].variation = 1; - submissions[1].data = ""; - - example.burnRedeem(submissions); - - vm.stopPrank(); - } - - function testPhysicalClaimCount2() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 1 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 2, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve tokens for burning - creatorCore721.approve(address(example), 1); - creatorCore721.approve(address(example), 2); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](2); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - burnTokens[1] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 2, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 2; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - assertEq(creatorCore721.balanceOf(address(other)), 2); - - vm.expectRevert(IPhysicalClaimCore.InvalidBurnAmount.selector); - example.burnRedeem(submissions); - - vm.stopPrank(); - } - - function testTotalSupplyZero() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 1 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 1); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 2; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - example.burnRedeem(submissions); - vm.stopPrank(); - } - - function testERC1155() public { - vm.startPrank(owner); - - // Mint 10 tokens to other - address[] memory recipientsInput = new address[](1); - recipientsInput[0] = other; - uint[] memory mintsInput = new uint[](1); - mintsInput[0] = 10; - string[] memory urisInput = new string[](1); - urisInput[0] = ""; - // base mint something in between - creatorCore1155.mintBaseNew(recipientsInput, mintsInput, urisInput); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore1155), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC1155, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 3, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 2 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore1155.setApprovalForAll(address(example), true); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore1155), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 2; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - example.burnRedeem(submissions); - - assertEq(creatorCore1155.balanceOf(address(other), 1), 4); - vm.stopPrank(); - } - - function testERC1155SafeTransferFrom() public { - vm.startPrank(owner); - - // Mint 10 tokens to other - address[] memory recipientsInput = new address[](1); - recipientsInput[0] = other; - uint[] memory mintsInput = new uint[](1); - mintsInput[0] = 10; - string[] memory urisInput = new string[](1); - urisInput[0] = ""; - // base mint something in between - creatorCore1155.mintBaseNew(recipientsInput, mintsInput, urisInput); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore1155), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC1155, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 3, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 2 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Burn via safeTransferFrom - creatorCore1155.safeTransferFrom(address(other), address(example), 1, 6, abi.encode(uint56(instanceId), uint16(2), uint256(0), "", uint8(1))); - - assertEq(creatorCore1155.balanceOf(address(other), 1), 4); - vm.stopPrank(); - } - - function testTransfer721To1155Claim() public { - vm.startPrank(owner); - - // Mint 10 tokens to other - address[] memory recipientsInput = new address[](1); - recipientsInput[0] = other; - uint[] memory mintsInput = new uint[](1); - mintsInput[0] = 10; - string[] memory urisInput = new string[](1); - urisInput[0] = ""; - // base mint something in between - creatorCore1155.mintBaseNew(recipientsInput, mintsInput, urisInput); - - // Mint 721 to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore1155), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC1155, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 3, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 2 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Transfer in the 721 - vm.expectRevert(IPhysicalClaimCore.InvalidInput.selector); - creatorCore721.safeTransferFrom(other, address(example), 1, abi.encode(uint56(instanceId), uint256(0), "", uint8(1))); - vm.stopPrank(); - } - - - function testERC1155SafeTransferFromBadAmount() public { - vm.startPrank(owner); - - // Mint 10 tokens to other - address[] memory recipientsInput = new address[](1); - recipientsInput[0] = other; - uint[] memory mintsInput = new uint[](1); - mintsInput[0] = 10; - string[] memory urisInput = new string[](1); - urisInput[0] = ""; - // base mint something in between - creatorCore1155.mintBaseNew(recipientsInput, mintsInput, urisInput); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore1155), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC1155, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 3, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 2 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Burn via safeTransferFrom but with not enough tokens - // Note: this will revert with a non-ERC1155Receiver implementer error - vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); - creatorCore1155.safeTransferFrom(address(other), address(example), 1, 4, abi.encode(uint56(instanceId), uint16(2), uint256(0), "", uint8(1))); - - assertEq(creatorCore1155.balanceOf(address(other), 1), 10); - vm.stopPrank(); - } - - function testERC1155SafeTransferWithSigner() public { - vm.startPrank(owner); - - // Mint 10 tokens to other - address[] memory recipientsInput = new address[](1); - recipientsInput[0] = other; - uint[] memory mintsInput = new uint[](1); - mintsInput[0] = 10; - string[] memory urisInput = new string[](1); - urisInput[0] = ""; - // base mint something in between - creatorCore1155.mintBaseNew(recipientsInput, mintsInput, urisInput); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore1155), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC1155, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 3, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 2 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: signerForCost - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Burn via safeTransferFrom but with not enough tokens - // Note: this will revert with a non-ERC1155Receiver implementer error - vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); - creatorCore1155.safeTransferFrom(address(other), address(example), 1, 6, abi.encode(uint56(instanceId), uint16(2), uint256(0), "", uint8(1))); - - assertEq(creatorCore1155.balanceOf(address(other), 1), 10); - vm.stopPrank(); - } - - function testBurnFunctionSpecNoneTransferToDead() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.NONE, - amount: 1, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 1 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 1); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - example.burnRedeem(submissions); - vm.stopPrank(); - } - - function testBurnFunctionSpecOZ() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.OPENZEPPELIN, - amount: 1, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 1 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 1); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - example.burnRedeem(submissions); - vm.stopPrank(); - } - - function testInvalidContractAddress() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 1 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 1); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore1155), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - vm.expectRevert(PhysicalClaimLib.InvalidBurnToken.selector); - example.burnRedeem(submissions); - vm.stopPrank(); - } - - function testRangeValidationType() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.RANGE, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 2, - maxTokenId: 4, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 1 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 1); - creatorCore721.approve(address(example), 2); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - vm.expectRevert(abi.encodePacked(IPhysicalClaimCore.InvalidToken.selector, uint256(1))); - example.burnRedeem(submissions); - - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 2, - merkleProof: new bytes32[](0) - }); - example.burnRedeem(submissions); - - vm.stopPrank(); - } - - function testCreateWithInvalidParameters() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.RANGE, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 2, - maxTokenId: 4, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 1 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(0), - totalSupply: 0, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - vm.expectRevert(PhysicalClaimLib.InvalidPaymentReceiver.selector); - example.initializePhysicalClaim(instanceId, claimPs); - - claimPs.paymentReceiver = payable(owner); - claimPs.startDate = 2; - claimPs.endDate = 1; - - vm.expectRevert(PhysicalClaimLib.InvalidDates.selector); - example.initializePhysicalClaim(instanceId, claimPs); - - claimPs.startDate = 0; - claimPs.endDate = 0; - - burnSet[0].requiredCount = 0; - claimPs.burnSet = burnSet; - - vm.expectRevert(IPhysicalClaimCore.InvalidInput.selector); - example.initializePhysicalClaim(instanceId, claimPs); - - burnSet[0].requiredCount = 1; - burnSet[0].items = new IPhysicalClaimCore.BurnItem[](0); - claimPs.burnSet = burnSet; - - vm.expectRevert(IPhysicalClaimCore.InvalidInput.selector); - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - } - - function testSend0EthButHasCost() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 1 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - burnSet: burnSet, - variationLimits: variations, - signer: signerForCost - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 1); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].totalCost = 1; - submissions[0].variation = 1; - submissions[0].data = ""; - - vm.expectRevert(IPhysicalClaimCore.InvalidPaymentAmount.selector); - example.burnRedeem(submissions); - vm.stopPrank(); - } - - function testRightAmountOfEthNoSignatureTho() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 1 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - burnSet: burnSet, - variationLimits: variations, - signer: signerForCost - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 1); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].totalCost = 1; - submissions[0].variation = 1; - submissions[0].data = ""; - - vm.expectRevert("ECDSA: invalid signature length"); - example.burnRedeem{value: 1}(submissions); - vm.stopPrank(); - } - - function testWrongSigner() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 1 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - burnSet: burnSet, - variationLimits: variations, - signer: signerForCost - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 1); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].totalCost = 1; - submissions[0].variation = 1; - submissions[0].data = ""; - submissions[0].message = "Hello"; - submissions[0].nonce = ""; - - bytes32 message = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", instanceId, uint(1))); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, message); - bytes memory signature = abi.encodePacked(r, s, v); - - submissions[0].signature = signature; - - vm.expectRevert(IPhysicalClaimCore.InvalidSignature.selector); - example.burnRedeem{value: 1}(submissions); - vm.stopPrank(); - } - - function testRightSignerWrongMessage() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 1 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - burnSet: burnSet, - variationLimits: variations, - signer: vm.addr(privateKey) - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 1); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].totalCost = 1; - submissions[0].variation = 1; - submissions[0].data = ""; - submissions[0].message = "Hello"; - submissions[0].nonce = ""; - - bytes32 message = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", instanceId)); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, message); - bytes memory signature = abi.encodePacked(r, s, v); - - submissions[0].signature = signature; - - vm.expectRevert(IPhysicalClaimCore.InvalidSignature.selector); - example.burnRedeem{value: 1}(submissions); - vm.stopPrank(); - } - - function testAllCorrectWithPayment() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 1 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - burnSet: burnSet, - variationLimits: variations, - signer: vm.addr(privateKey) - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 1); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].totalCost = 1; - submissions[0].variation = 1; - submissions[0].data = ""; - submissions[0].nonce = "abcd"; - - bytes32 message = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", instanceId, uint(1))); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, message); - bytes memory signature = abi.encodePacked(r, s, v); - - submissions[0].signature = signature; - submissions[0].message = message; - - example.burnRedeem{value: 1}(submissions); - vm.stopPrank(); - } - - function testReUseNonce() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 2 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - burnSet: burnSet, - variationLimits: variations, - signer: vm.addr(privateKey) - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve token for burning - creatorCore721.approve(address(example), 1); - creatorCore721.approve(address(example), 2); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].totalCost = 1; - submissions[0].variation = 1; - submissions[0].data = ""; - submissions[0].nonce = "abcd"; - - bytes32 message = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", instanceId, uint(1))); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, message); - bytes memory signature = abi.encodePacked(r, s, v); - - submissions[0].signature = signature; - submissions[0].message = message; - - // Fine - example.burnRedeem{value: 1}(submissions); - - // Reuse nonce - submissions[0].currentClaimCount = 1; - submissions[0].burnTokens[0].id = 2; - vm.expectRevert("Cannot replay transaction"); - example.burnRedeem{value: 1}(submissions); - vm.stopPrank(); - } - - function testNoBurnSpec() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721_NO_BURN, // no burn! - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 10 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - burnSet: burnSet, - variationLimits: variations, - signer: vm.addr(privateKey) - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Does not need to approve token for burning - // creatorCore721.approve(address(example), 1); - // creatorCore721.approve(address(example), 2); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].totalCost = 1; - submissions[0].variation = 1; - submissions[0].data = ""; - submissions[0].nonce = "abcd"; - - bytes32 message = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", instanceId, uint(1))); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, message); - bytes memory signature = abi.encodePacked(r, s, v); - - submissions[0].signature = signature; - submissions[0].message = message; - - // Fine - example.burnRedeem{value: 1}(submissions); - - // Verify that the token still exists.. - assertEq(creatorCore721.ownerOf(1), other); - - // Cannot re-use token - submissions[0].currentClaimCount = 1; - submissions[0].nonce = "efgh"; - vm.expectRevert(IPhysicalClaimCore.InvalidBurnAmount.selector); - example.burnRedeem{value: 1}(submissions); - - uint[] memory tokenIdsQuery = new uint[](2); - tokenIdsQuery[0] = 1; - tokenIdsQuery[1] = 2; - - address[] memory contractAddressesQuery = new address[](2); - contractAddressesQuery[0] = address(creatorCore721); - contractAddressesQuery[1] = address(creatorCore721); - - // Check if it's been used... - IPhysicalClaimCore.TokensUsedQuery memory tuq = IPhysicalClaimCore.TokensUsedQuery({ - instanceId: uint56(instanceId), - tokenIds: tokenIdsQuery, - contractAddresses: contractAddressesQuery - }); - - bool[] memory tuqResponse = example.getAreTokensUsed(tuq); - assertEq(tuqResponse[0], true); - assertEq(tuqResponse[1], false); - - // Try with invalid burn redeem amount - burnTokens[0].id = 2; - submissions[0].count = 2; - vm.expectRevert(IPhysicalClaimCore.InvalidBurnAmount.selector); - example.burnRedeem{value: 1}(submissions); - - vm.stopPrank(); - } - - function testNoBurnSpecInvalidSender() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721_NO_BURN, // no burn! - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 10 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - burnSet: burnSet, - variationLimits: variations, - signer: vm.addr(privateKey) - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other2); - - // Send from non-token owner - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].totalCost = 1; - submissions[0].variation = 1; - submissions[0].data = ""; - submissions[0].nonce = "abcd"; - - bytes32 message = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", instanceId, uint(1))); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, message); - bytes memory signature = abi.encodePacked(r, s, v); - - submissions[0].signature = signature; - submissions[0].message = message; - - // Invalid sender - vm.expectRevert(IPhysicalClaimCore.InvalidBurnAmount.selector); - example.burnRedeem{value: 1}(submissions); - - vm.stopPrank(); - } - - function testNoGate() public { - vm.startPrank(owner); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](0); - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 10 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 0, - startDate: 0, - endDate: 0, - burnSet: burnSet, - variationLimits: variations, - signer: vm.addr(privateKey) - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](0); - - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].totalCost = 1; - submissions[0].variation = 1; - submissions[0].data = ""; - submissions[0].nonce = "abcd"; - - bytes32 message = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", instanceId, uint(1))); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, message); - bytes memory signature = abi.encodePacked(r, s, v); - - submissions[0].signature = signature; - submissions[0].message = message; - - // Fine - example.burnRedeem{value: 1}(submissions); - - vm.stopPrank(); - } - - - function testChangeVariationsToLower() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 10 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 20, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve tokens for burning - creatorCore721.approve(address(example), 1); - creatorCore721.approve(address(example), 2); - creatorCore721.approve(address(example), 3); - creatorCore721.approve(address(example), 4); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - assertEq(creatorCore721.balanceOf(address(other)), 4); - - // Burn 1 - example.burnRedeem(submissions); - - assertEq(creatorCore721.balanceOf(address(other)), 3); - - // Burn another - burnTokens[0].id = 2; - submissions[0].currentClaimCount = 1; - example.burnRedeem(submissions); - - assertEq(creatorCore721.balanceOf(address(other)), 2); - // Burn another - burnTokens[0].id = 3; - submissions[0].currentClaimCount = 2; - example.burnRedeem(submissions); - - // Change variations to lower... - vm.stopPrank(); - vm.startPrank(owner); - - variations[0].totalSupply = 2; - example.updatePhysicalClaim(instanceId, claimPs); - - // Get variations... - // Check get redemptions, should be 3 - IPhysicalClaimCore.VariationState memory variationStateReturn = example.getVariationState(instanceId, 1); - - // Total Supply isn't "lower" than redeem count, even though we "lowered" it to 2 - assertEq(variationStateReturn.totalSupply, 3); - assertEq(variationStateReturn.redeemedCount, 3); - assertEq(variationStateReturn.active, true); - // Total supply still unchanged - IPhysicalClaimCore.PhysicalClaimView memory claim = example.getPhysicalClaim(instanceId); - assertEq(claim.totalSupply, 20); - - vm.stopPrank(); - vm.startPrank(other); - - // Cant do another redemption - burnTokens[0].id = 4; - submissions[0].currentClaimCount = 3; - vm.expectRevert(IPhysicalClaimCore.InvalidRedeemAmount.selector); - example.burnRedeem(submissions); - - // If owner sets to unlimited for that variation, they can - - vm.stopPrank(); - vm.startPrank(owner); - - variations[0].totalSupply = 0; - example.updatePhysicalClaim(instanceId, claimPs); - - // Check get redemptions, should be 3 - variationStateReturn = example.getVariationState(instanceId, 1); - assertEq(variationStateReturn.totalSupply, 0); - assertEq(variationStateReturn.redeemedCount, 3); - assertEq(variationStateReturn.active, true); - - vm.stopPrank(); - vm.startPrank(other); - - example.burnRedeem(submissions); - - // Check get redemptions, should be 4 - variationStateReturn = example.getVariationState(instanceId, 1); - assertEq(variationStateReturn.totalSupply, 0); - assertEq(variationStateReturn.redeemedCount, 4); - assertEq(variationStateReturn.active, true); - - - vm.stopPrank(); - } - - function testMakeVariationInactive() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 10 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 20, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve tokens for burning - creatorCore721.approve(address(example), 1); - creatorCore721.approve(address(example), 2); - creatorCore721.approve(address(example), 3); - creatorCore721.approve(address(example), 4); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - assertEq(creatorCore721.balanceOf(address(other)), 4); - - // Burn 1 - example.burnRedeem(submissions); - - assertEq(creatorCore721.balanceOf(address(other)), 3); +contract PhysicalClaimTest is PhysicalClaimBase { + PhysicalClaim public example; + ERC721Creator public creatorCore721; + ERC1155Creator public creatorCore1155; + MockERC20 public mockERC20; + MockManifoldMembership public manifoldMembership; + + address public owner = 0x6140F00e4Ff3936702E68744f2b5978885464cbB; + address public other1 = 0xc78Dc443c126af6E4f6Ed540c1e740C1b5be09cd; + address public other2 = 0x80AAC46bbd3C2FcE33681541a52CacBEd14bF425; + address public seller = 0x5174cD462b60c536eb51D4ceC1D561D3Ea31004F; + + address public zeroSigner = address(0); + + address public zeroAddress = address(0); + + address public signingAddress; + + function setUp() public { + vm.startPrank(owner); + creatorCore721 = new ERC721Creator("Token721", "NFT721"); + creatorCore1155 = new ERC1155Creator("Token1155", "NFT1155"); + mockERC20 = new MockERC20("Token20", "ERC20"); + manifoldMembership = new MockManifoldMembership(); + + signingAddress = vm.addr(privateKey); + example = new PhysicalClaim(owner, signingAddress); + + vm.deal(owner, 10 ether); + vm.deal(other1, 10 ether); + vm.deal(other2, 10 ether); + vm.stopPrank(); + } + + function testAccess() public { + vm.startPrank(other1); + vm.expectRevert("AdminControl: Must be owner or admin"); + example.recover(address(creatorCore721), 1, other1); + vm.expectRevert("AdminControl: Must be owner or admin"); + example.withdraw(payable(other1), 1 ether); + vm.expectRevert("AdminControl: Must be owner or admin"); + example.setMembershipAddress(other1); + vm.expectRevert("AdminControl: Must be owner or admin"); + example.updateSigner(address(0)); + vm.stopPrank(); + + // Accidentally send token to contract + vm.startPrank(owner); + creatorCore721.mintBase(owner, ""); + creatorCore721.transferFrom(owner, address(example), 1); + example.recover(address(creatorCore721), 1, owner); + vm.stopPrank(); + + // Test withdraw + vm.deal(address(example), 10 ether); + vm.startPrank(owner); + example.withdraw(payable(other1), 1 ether); + assertEq(address(example).balance, 9 ether); + assertEq(other1.balance, 11 ether); + vm.stopPrank(); + + // Test setMembershipAddress + vm.startPrank(owner); + example.setMembershipAddress(other2); + assertEq(example.manifoldMembershipContract(), other2); + vm.stopPrank(); + + // Test updateSigner + vm.startPrank(owner); + example.updateSigner(seller); + vm.stopPrank(); + + } + + function testSupportsInterface() public { + vm.startPrank(owner); + + bytes4 interfaceId = type(IPhysicalClaim).interfaceId; + assertEq(example.supportsInterface(interfaceId), true); + assertEq(example.supportsInterface(0xffffffff), false); + + interfaceId = type(IERC721Receiver).interfaceId; + assertEq(example.supportsInterface(interfaceId), true); + + interfaceId = type(IERC1155Receiver).interfaceId; + assertEq(example.supportsInterface(interfaceId), true); + + interfaceId = type(AdminControl).interfaceId; + assertEq(example.supportsInterface(interfaceId), true); + + vm.stopPrank(); + } + + function testTransferFailures() public { + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem failure because token does not exist + vm.startPrank(other1); + vm.expectRevert("ERC721: invalid token ID"); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + // Test redeem failure because token not approved + vm.startPrank(other1); + vm.expectRevert("Caller is not owner or approved"); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + + // Test redeem failure because token is notowned by sender + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + vm.stopPrank(); + vm.startPrank(other2); + vm.expectRevert(IPhysicalClaim.TransferFailure.selector); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + } + + function testInvalidPaymentAmountFee() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + vm.expectRevert(IPhysicalClaim.InvalidPaymentAmount.selector); + example.burnRedeem{value: burnFee-1}(submission); + vm.stopPrank(); + } - // Change variation id to "2" - vm.stopPrank(); - vm.startPrank(owner); - - variations[0].id = 2; - variations[0].totalSupply = 69; - claimPs.variationLimits = variations; - example.updatePhysicalClaim(instanceId, claimPs); - - // Variation 1 should be inactive now... - IPhysicalClaimCore.VariationState memory variationStateReturn = example.getVariationState(instanceId, 1); - assertEq(variationStateReturn.totalSupply, 10); - assertEq(variationStateReturn.redeemedCount, 1); - assertEq(variationStateReturn.active, false); - - variationStateReturn = example.getVariationState(instanceId, 2); - assertEq(variationStateReturn.totalSupply, 69); - assertEq(variationStateReturn.redeemedCount, 0); - assertEq(variationStateReturn.active, true); - - - vm.stopPrank(); - vm.startPrank(other); - - // Cannot redeem for variation one... - // Burn another - burnTokens[0].id = 2; - submissions[0].currentClaimCount = 1; - - // Should fail - vm.expectRevert(IPhysicalClaimCore.InvalidVariation.selector); - example.burnRedeem(submissions); - - vm.stopPrank(); - } - - function testSyncTotalSupply() public { - vm.startPrank(owner); - - // Mint 2 tokens to other - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - creatorCore721.mintBase(other, ""); - - IPhysicalClaimCore.BurnItem[] memory burnItems = new IPhysicalClaimCore.BurnItem[](1); - burnItems[0] = IPhysicalClaimCore.BurnItem({ - validationType: IPhysicalClaimCore.ValidationType.CONTRACT, - contractAddress: address(creatorCore721), - burnTokenSpec: IPhysicalClaimCore.BurnTokenSpec.ERC721, - burnFunctionSpec: IPhysicalClaimCore.BurnFunctionSpec.MANIFOLD, - amount: 1, - minTokenId: 1, - maxTokenId: 3, - merkleRoot: "" - }); - - IPhysicalClaimCore.BurnGroup[] memory burnSet = new IPhysicalClaimCore.BurnGroup[](1); - burnSet[0] = IPhysicalClaimCore.BurnGroup({ - requiredCount: 1, - items: burnItems - }); - - IPhysicalClaimCore.VariationLimit[] memory variations = new IPhysicalClaimCore.VariationLimit[](1); - variations[0] = IPhysicalClaimCore.VariationLimit({ - id: 1, - totalSupply: 10 - }); - - // Create claim initialization parameters. Total supply is 1 so they will use the whole supply - IPhysicalClaimCore.PhysicalClaimParameters memory claimPs = IPhysicalClaimCore.PhysicalClaimParameters({ - paymentReceiver: payable(owner), - totalSupply: 20, - startDate: 0, - endDate: 0, - - burnSet: burnSet, - variationLimits: variations, - signer: zeroSigner - }); - - // Initialize the physical claim - example.initializePhysicalClaim(instanceId, claimPs); - - vm.stopPrank(); - vm.startPrank(other); - - // Approve tokens for burning - creatorCore721.approve(address(example), 1); - creatorCore721.approve(address(example), 2); - creatorCore721.approve(address(example), 3); - creatorCore721.approve(address(example), 4); - - IPhysicalClaimCore.BurnToken[] memory burnTokens = new IPhysicalClaimCore.BurnToken[](1); - burnTokens[0] = IPhysicalClaimCore.BurnToken({ - groupIndex: 0, - itemIndex: 0, - contractAddress: address(creatorCore721), - id: 1, - merkleProof: new bytes32[](0) - }); - - IPhysicalClaimCore.PhysicalClaimSubmission[] memory submissions = new IPhysicalClaimCore.PhysicalClaimSubmission[](1); - submissions[0].instanceId = uint56(instanceId); - submissions[0].count = 1; - submissions[0].currentClaimCount = 0; - submissions[0].burnTokens = burnTokens; - submissions[0].variation = 1; - submissions[0].data = ""; - - assertEq(creatorCore721.balanceOf(address(other)), 4); - - // Burn 1 - example.burnRedeem(submissions); - - assertEq(creatorCore721.balanceOf(address(other)), 3); - - burnTokens[0].id = 2; - submissions[0].currentClaimCount = 1; - example.burnRedeem(submissions); - - vm.stopPrank(); - vm.startPrank(owner); - // 2 redemptions now... update claim to be 1 total supply... - claimPs.totalSupply = 1; - example.updatePhysicalClaim(instanceId, claimPs); - - // Make sure that the total supply is 2 (in sync) - IPhysicalClaimCore.PhysicalClaimView memory claim = example.getPhysicalClaim(instanceId); - assertEq(claim.totalSupply, 2); - - vm.stopPrank(); + function testInvalidPaymentAmountPrice() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 1; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + vm.expectRevert(IPhysicalClaim.InvalidPaymentAmount.selector); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + } + + function testInvalidSignature() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem because we have an invalid signature + // Change configured signer + vm.startPrank(owner); + example.updateSigner(other2); + vm.stopPrank(); + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + vm.expectRevert(IPhysicalClaim.InvalidSignature.selector); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + } + + function testInvalidMessage() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem because we change the data + submission.message = bytes32(0); + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + vm.expectRevert(IPhysicalClaim.InvalidSignature.selector); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + } + + function testInvalidDueToDataChange() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem because we change the data + submission.variationLimit = 10; + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + vm.expectRevert(IPhysicalClaim.InvalidSignature.selector); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + } + + function testInvalidDueToExpired() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp - 1); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem because it is expired + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + vm.expectRevert(IPhysicalClaim.ExpiredSignature.selector); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + } + + function testInvalidBurnSpec721() public { + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.INVALID, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + vm.expectRevert(IPhysicalClaim.InvalidBurnSpec.selector); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + + } + + function testInvalidBurnSpec1155() public { + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.INVALID, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + vm.expectRevert(IPhysicalClaim.InvalidBurnSpec.selector); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + + } + + + function testInvalidTokeSpec() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.INVALID, + burnSpec: IPhysicalClaim.BurnSpec.NONE, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + vm.expectRevert(IPhysicalClaim.InvalidTokenSpec.selector); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + + } + + function testNoBurnFromNonOwnerInvalid() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721_NO_BURN, + burnSpec: IPhysicalClaim.BurnSpec.NONE, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem from non-owner should fail + vm.startPrank(other2); + vm.expectRevert(IPhysicalClaim.TransferFailure.selector); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + + } + + function testNoBurnMultiUseInvalid() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721_NO_BURN, + burnSpec: IPhysicalClaim.BurnSpec.NONE, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + + // Check token not burned + assertEq(creatorCore721.ownerOf(1), other1); + + // Try redemption again, not allowed because nonce was used marked as consumed + vm.startPrank(other1); + vm.expectRevert(IPhysicalClaim.InvalidNonce.selector); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + + // Try redemption again (different nonce), not allowed because it was marked as consumed + nonce = bytes32(bytes4(0xdeafbeef)); + submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + vm.startPrank(other1); + vm.expectRevert(abi.encodeWithSelector(IPhysicalClaim.InvalidToken.selector, address(creatorCore721), 1)); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + } + + function testInvalidBurnAmount721() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 2 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + vm.expectRevert(abi.encodeWithSelector(IPhysicalClaim.InvalidToken.selector, address(creatorCore721), 1)); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + } + + function testPhysicalClaim721() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + + // Check token burned + vm.expectRevert("ERC721: invalid token ID"); + creatorCore721.ownerOf(1); + } + + function testPhysicalClaim721BurnSpecNone() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.NONE, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + + // Check token burned + assertEq(creatorCore721.ownerOf(1), address(0xdead)); + } + + function testPhysicalClaim1155() public { + vm.startPrank(owner); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 2 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + creatorCore1155.setApprovalForAll(address(example), true); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + + // Check token burned + assertEq(creatorCore1155.balanceOf(other1, 1), 8); + } + + function testPhysicalClaim1155BurnSpecNone() public { + vm.startPrank(owner); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.NONE, + amount: 2 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + creatorCore1155.setApprovalForAll(address(example), true); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + + // Check token burned + assertEq(creatorCore1155.balanceOf(other1, 1), 8); + assertEq(creatorCore1155.balanceOf(address(0xdead), 1), 2); + } + + function testPhysicalClaimWithPrice() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 1 ether; + address payable fundsRecipient = payable(seller); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + example.burnRedeem{value: burnFee+price}(submission); + vm.stopPrank(); + + // Check token burned + vm.expectRevert("ERC721: invalid token ID"); + creatorCore721.ownerOf(1); + + // Check seller got price + assertEq(address(seller).balance, 1 ether); + } + + function testPhysicalClaimWithPriceManifoldMember() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 1 ether; + address payable fundsRecipient = payable(seller); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + example.burnRedeem{value: price}(submission); + vm.stopPrank(); + + // Check token burned + vm.expectRevert("ERC721: invalid token ID"); + creatorCore721.ownerOf(1); + + // Check seller got price + assertEq(address(seller).balance, 1 ether); + } + + function testPhysicalClaimWithERC20PriceInvalid() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + mockERC20.fakeMint(other2, 200); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(mockERC20); + uint256 price = 15; + address payable fundsRecipient = payable(seller); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Approve spend from other2 + vm.startPrank(other2); + mockERC20.approve(address(example), 15); + vm.stopPrank(); + + // Test redeem (should fail, cannot steal someone else's balance) + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + vm.expectRevert("ERC20: insufficient allowance"); + example.burnRedeem{value: burnFee}(submission); + mockERC20.approve(address(example), 15); + vm.expectRevert("ERC20: transfer amount exceeds balance"); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + } + + function testPhysicalClaimWithERC20Price() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + mockERC20.fakeMint(other1, 200); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(mockERC20); + uint256 price = 15; + address payable fundsRecipient = payable(seller); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + vm.expectRevert("ERC20: insufficient allowance"); + example.burnRedeem{value: burnFee}(submission); + mockERC20.approve(address(example), 15); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + + // Check token burned + vm.expectRevert("ERC721: invalid token ID"); + creatorCore721.ownerOf(1); + + // Check seller got price + assertEq(mockERC20.balanceOf(address(seller)), 15); + assertEq(mockERC20.balanceOf(address(other1)), 185); + } + + function testPhysicalClaimMultiBurn() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](2); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + burnTokens[1] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 2 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).MULTI_BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + creatorCore1155.setApprovalForAll(address(example), true); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + + // Check token burned + vm.expectRevert("ERC721: invalid token ID"); + creatorCore721.ownerOf(1); + assertEq(creatorCore1155.balanceOf(other1, 1), 8); + } + + function testPhysicalMultiSubmission() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens1 = new IPhysicalClaim.BurnToken[](1); + burnTokens1[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + IPhysicalClaim.BurnToken[] memory burnTokens2 = new IPhysicalClaim.BurnToken[](1); + burnTokens2[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 2, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 1 ether; + address payable fundsRecipient = payable(seller); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE() * 2; + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission[] memory submissions = new IPhysicalClaim.BurnSubmission[](2); + submissions[0] = constructSubmission(instanceId, burnTokens1, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + nonce = bytes32(bytes4(0xdeadbee2)); + submissions[1] = constructSubmission(instanceId, burnTokens2, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + + // Test redeem + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + creatorCore721.approve(address(example), 2); + example.burnRedeem{value: burnFee+2 ether}(submissions); + vm.stopPrank(); + + // Check token burned + vm.expectRevert("ERC721: invalid token ID"); + creatorCore721.ownerOf(1); + vm.expectRevert("ERC721: invalid token ID"); + creatorCore721.ownerOf(2); + assertEq(address(example).balance, burnFee); + assertEq(address(seller).balance, 2 ether); + } + + /** + * Sold out due to variation limit + */ + function testPhysicalMultiSubmissionSoldOut1() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens1 = new IPhysicalClaim.BurnToken[](1); + burnTokens1[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + IPhysicalClaim.BurnToken[] memory burnTokens2 = new IPhysicalClaim.BurnToken[](1); + burnTokens2[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 2, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 1; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 1 ether; + address payable fundsRecipient = payable(seller); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE() * 2; + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission[] memory submissions = new IPhysicalClaim.BurnSubmission[](2); + submissions[0] = constructSubmission(instanceId, burnTokens1, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + nonce = bytes32(bytes4(0xdeadbee2)); + submissions[1] = constructSubmission(instanceId, burnTokens2, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + + // Test redeem + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + creatorCore721.approve(address(example), 2); + example.burnRedeem{value: burnFee + 2 ether}(submissions); + vm.stopPrank(); + + // Check token burned + vm.expectRevert("ERC721: invalid token ID"); + creatorCore721.ownerOf(1); + // Token 2 not burned because we were sold out and it didn't process + assertEq(creatorCore721.ownerOf(2), other1); + assertEq(address(example).balance, burnFee/2); + assertEq(address(seller).balance, 1 ether); + } + + /** + * Sold out due to total limit + */ + function testPhysicalMultiSubmissionSoldOut2() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens1 = new IPhysicalClaim.BurnToken[](1); + burnTokens1[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + IPhysicalClaim.BurnToken[] memory burnTokens2 = new IPhysicalClaim.BurnToken[](1); + burnTokens2[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 2, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 1; + address erc20 = address(0); + uint256 price = 1 ether; + address payable fundsRecipient = payable(seller); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE() * 2; + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission[] memory submissions = new IPhysicalClaim.BurnSubmission[](2); + submissions[0] = constructSubmission(instanceId, burnTokens1, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + nonce = bytes32(bytes4(0xdeadbee2)); + submissions[1] = constructSubmission(instanceId, burnTokens2, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + + // Test redeem + vm.startPrank(other1); + creatorCore721.approve(address(example), 1); + creatorCore721.approve(address(example), 2); + example.burnRedeem{value: burnFee + 2 ether}(submissions); + vm.stopPrank(); + + // Check token burned + vm.expectRevert("ERC721: invalid token ID"); + creatorCore721.ownerOf(1); + // Token 2 not burned because we were sold out and it didn't process + assertEq(creatorCore721.ownerOf(2), other1); + assertEq(address(example).balance, burnFee/2); + assertEq(address(seller).balance, 1 ether); + } + + /** + * Sold out due to variation limit + */ + function testPhysicalClaimSoldOut1() public { + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](0); + uint256 instanceId = 100; + uint8 variation = 1; + uint64 variationLimit = 2; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem (third time should fail because sold out) + vm.startPrank(other1); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + nonce = bytes32(bytes4(0xdeadbee1)); + submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + vm.startPrank(other1); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + nonce = bytes32(bytes4(0xdeadbee2)); + submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + vm.startPrank(other1); + vm.expectRevert(IPhysicalClaim.SoldOut.selector); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + } + + /** + * Sold out due to total limit + */ + function testPhysicalClaimSoldOut2() public { + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](0); + uint256 instanceId = 100; + uint8 variation = 1; + uint64 variationLimit = 0; + uint64 totalLimit = 2; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + uint256 burnFee = PhysicalClaim(example).BURN_FEE(); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem (third time should fail because sold out) + vm.startPrank(other1); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + nonce = bytes32(bytes4(0xdeadbee1)); + submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + vm.startPrank(other1); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + nonce = bytes32(bytes4(0xdeadbee2)); + variation = 2; + submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + vm.startPrank(other1); + vm.expectRevert(IPhysicalClaim.SoldOut.selector); + example.burnRedeem{value: burnFee}(submission); + vm.stopPrank(); + } } -} diff --git a/packages/manifold/test/physicalclaim/PhysicalClaim1155BatchTransfer.t.sol b/packages/manifold/test/physicalclaim/PhysicalClaim1155BatchTransfer.t.sol new file mode 100644 index 00000000..71c0481e --- /dev/null +++ b/packages/manifold/test/physicalclaim/PhysicalClaim1155BatchTransfer.t.sol @@ -0,0 +1,806 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "./PhysicalClaimBase.t.sol"; +import "../../contracts/physicalclaim/PhysicalClaim.sol"; +import "@manifoldxyz/libraries-solidity/contracts/access/AdminControl.sol"; +import "@manifoldxyz/creator-core-solidity/contracts/ERC721Creator.sol"; +import "@manifoldxyz/creator-core-solidity/contracts/ERC1155Creator.sol"; +import "../mocks/Mock.sol"; + +contract PhysicalClaim1155BatchTransferTest is PhysicalClaimBase { + PhysicalClaim public example; + ERC721Creator public creatorCore721; + ERC1155Creator public creatorCore1155; + MockERC20 public mockERC20; + MockManifoldMembership public manifoldMembership; + + address public owner = 0x6140F00e4Ff3936702E68744f2b5978885464cbB; + address public other1 = 0xc78Dc443c126af6E4f6Ed540c1e740C1b5be09cd; + address public other2 = 0x80AAC46bbd3C2FcE33681541a52CacBEd14bF425; + address public seller = 0x5174cD462b60c536eb51D4ceC1D561D3Ea31004F; + + address public zeroSigner = address(0); + + address public zeroAddress = address(0); + + address public signingAddress; + + function setUp() public { + vm.startPrank(owner); + creatorCore721 = new ERC721Creator("Token721", "NFT721"); + creatorCore1155 = new ERC1155Creator("Token1155", "NFT1155"); + mockERC20 = new MockERC20("Token20", "ERC20"); + manifoldMembership = new MockManifoldMembership(); + + signingAddress = vm.addr(privateKey); + example = new PhysicalClaim(owner, signingAddress); + + vm.deal(owner, 10 ether); + vm.deal(other1, 10 ether); + vm.deal(other2, 10 ether); + vm.stopPrank(); + } + + function testInvalidData() public { + vm.startPrank(owner); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + vm.startPrank(other1); + uint256[] memory ids = new uint256[](1); + ids[0] = 1; + uint256[] memory transferAmounts = new uint256[](1); + transferAmounts[0] = 1; + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, ""); + vm.stopPrank(); + } + + function testInvalidBurnTokenLength() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](0); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + uint256[] memory ids = new uint256[](1); + ids[0] = 1; + uint256[] memory transferAmounts = new uint256[](1); + transferAmounts[0] = 1; + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.InvalidInput.selector); + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, abi.encode(submission)); + vm.stopPrank(); + } + + function testCannotReceive1() public { + vm.startPrank(owner); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + uint256[] memory ids = new uint256[](1); + ids[0] = 1; + uint256[] memory transferAmounts = new uint256[](1); + transferAmounts[0] = 1; + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.InvalidInput.selector); + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, abi.encode(submission)); + vm.stopPrank(); + } + + function testCannotReceive2() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 1; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + uint256[] memory ids = new uint256[](1); + ids[0] = 1; + uint256[] memory transferAmounts = new uint256[](1); + transferAmounts[0] = 1; + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.InvalidInput.selector); + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidBurnToken1() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + uint256[] memory ids = new uint256[](1); + ids[0] = 1; + uint256[] memory transferAmounts = new uint256[](1); + transferAmounts[0] = 1; + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.InvalidInput.selector); + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidBurnToken2() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 2, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + uint256[] memory ids = new uint256[](1); + ids[0] = 1; + uint256[] memory transferAmounts = new uint256[](1); + transferAmounts[0] = 1; + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(abi.encodeWithSelector(IPhysicalClaim.InvalidToken.selector, address(creatorCore1155), 2)); + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidBurnToken3() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + uint256[] memory ids = new uint256[](1); + ids[0] = 1; + uint256[] memory transferAmounts = new uint256[](1); + transferAmounts[0] = 1; + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(abi.encodeWithSelector(IPhysicalClaim.InvalidToken.selector, address(creatorCore721), 1)); + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidSignature() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem because we have an invalid signature + // Change configured signer + vm.startPrank(owner); + example.updateSigner(other2); + vm.stopPrank(); + vm.startPrank(other1); + uint256[] memory ids = new uint256[](1); + ids[0] = 1; + uint256[] memory transferAmounts = new uint256[](1); + transferAmounts[0] = 1; + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.InvalidSignature.selector); + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidMessage() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem because we change the data + submission.message = bytes32(0); + vm.startPrank(other1); + uint256[] memory ids = new uint256[](1); + ids[0] = 1; + uint256[] memory transferAmounts = new uint256[](1); + transferAmounts[0] = 1; + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.InvalidSignature.selector); + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidDueToDataChange() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem because we change the data + submission.variationLimit = 10; + vm.startPrank(other1); + uint256[] memory ids = new uint256[](1); + ids[0] = 1; + uint256[] memory transferAmounts = new uint256[](1); + transferAmounts[0] = 1; + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.InvalidSignature.selector); + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidDueToExpired() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp - 1); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem because it is expired + vm.startPrank(other1); + uint256[] memory ids = new uint256[](1); + ids[0] = 1; + uint256[] memory transferAmounts = new uint256[](1); + transferAmounts[0] = 1; + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.ExpiredSignature.selector); + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, abi.encode(submission)); + vm.stopPrank(); + } + + function testNotEnoughTokens1() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 2 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 100); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + uint256[] memory ids = new uint256[](1); + ids[0] = 1; + uint256[] memory transferAmounts = new uint256[](1); + transferAmounts[0] = 1; + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.InvalidBurnAmount.selector); + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, abi.encode(submission)); + vm.stopPrank(); + } + + function testNotEnoughTokens2() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + amounts[0] = 20; + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](2); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 2 + }); + burnTokens[1] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 2, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 2 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 100); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + uint256[] memory ids = new uint256[](1); + ids[0] = 1; + uint256[] memory transferAmounts = new uint256[](1); + transferAmounts[0] = 2; + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.InvalidInput.selector); + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, abi.encode(submission)); + vm.stopPrank(); + } + + function testPhyiscalClaim() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + amounts[0] = 20; + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](2); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 2 + }); + burnTokens[1] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 2, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 3 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 100); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + uint256[] memory ids = new uint256[](2); + ids[0] = 1; + ids[1] = 2; + uint256[] memory transferAmounts = new uint256[](2); + transferAmounts[0] = 2; + transferAmounts[1] = 3; + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, abi.encode(submission)); + vm.stopPrank(); + + // Check token burned + assertEq(creatorCore1155.balanceOf(other1, 1), 8); + assertEq(creatorCore1155.balanceOf(other1, 2), 17); + } + + function testPhyiscalClaimSoldOut() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 2 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 1; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 100); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + uint256[] memory ids = new uint256[](1); + ids[0] = 1; + uint256[] memory transferAmounts = new uint256[](1); + transferAmounts[0] = 2; + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, abi.encode(submission)); + vm.stopPrank(); + + // Check correct amount of tokens burned + assertEq(creatorCore1155.balanceOf(other1, 1), 8); + + // Test redeem again (should fail) + nonce = bytes32(bytes4(0xdeafbeef)); + submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + vm.startPrank(other1); + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.SoldOut.selector); + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, abi.encode(submission)); + vm.stopPrank(); + + // Check correct amount of tokens burned + assertEq(creatorCore1155.balanceOf(other1, 1), 8); + } + + function testPhyiscalClaimWithERC20() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + mockERC20.fakeMint(other2, 200); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 2 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(mockERC20); + uint256 price = 15; + address payable fundsRecipient = payable(seller); + uint160 expiration = uint160(block.timestamp + 100); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Approve spend from other2 + vm.startPrank(other2); + mockERC20.approve(address(example), 15); + vm.stopPrank(); + + // Test redeem (should fail, cannot steal someone else's balance) + vm.startPrank(other1); + uint256[] memory ids = new uint256[](1); + ids[0] = 1; + uint256[] memory transferAmounts = new uint256[](1); + transferAmounts[0] = 2; + vm.expectRevert("ERC20: insufficient allowance"); + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, abi.encode(submission)); + mockERC20.approve(address(example), 15); + vm.expectRevert("ERC20: transfer amount exceeds balance"); + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, abi.encode(submission)); + vm.stopPrank(); + + // Mint to other1 + vm.startPrank(owner); + mockERC20.fakeMint(other1, 200); + vm.stopPrank(); + + // Approve spend from other1 + vm.startPrank(other1); + creatorCore1155.safeBatchTransferFrom(other1, address(example), ids, transferAmounts, abi.encode(submission)); + vm.stopPrank(); + + // Check token burned + vm.expectRevert("ERC721: invalid token ID"); + creatorCore721.ownerOf(1); + + // Check seller got price + assertEq(mockERC20.balanceOf(address(seller)), 15); + assertEq(mockERC20.balanceOf(address(other1)), 185); + } + + } diff --git a/packages/manifold/test/physicalclaim/PhysicalClaim1155Transfer.t.sol b/packages/manifold/test/physicalclaim/PhysicalClaim1155Transfer.t.sol new file mode 100644 index 00000000..3c63462b --- /dev/null +++ b/packages/manifold/test/physicalclaim/PhysicalClaim1155Transfer.t.sol @@ -0,0 +1,679 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "./PhysicalClaimBase.t.sol"; +import "../../contracts/physicalclaim/PhysicalClaim.sol"; +import "@manifoldxyz/libraries-solidity/contracts/access/AdminControl.sol"; +import "@manifoldxyz/creator-core-solidity/contracts/ERC721Creator.sol"; +import "@manifoldxyz/creator-core-solidity/contracts/ERC1155Creator.sol"; +import "../mocks/Mock.sol"; + +contract PhysicalClaim1155TransferTest is PhysicalClaimBase { + PhysicalClaim public example; + ERC721Creator public creatorCore721; + ERC1155Creator public creatorCore1155; + MockERC20 public mockERC20; + MockManifoldMembership public manifoldMembership; + + address public owner = 0x6140F00e4Ff3936702E68744f2b5978885464cbB; + address public other1 = 0xc78Dc443c126af6E4f6Ed540c1e740C1b5be09cd; + address public other2 = 0x80AAC46bbd3C2FcE33681541a52CacBEd14bF425; + address public seller = 0x5174cD462b60c536eb51D4ceC1D561D3Ea31004F; + + address public zeroSigner = address(0); + + address public zeroAddress = address(0); + + address public signingAddress; + + function setUp() public { + vm.startPrank(owner); + creatorCore721 = new ERC721Creator("Token721", "NFT721"); + creatorCore1155 = new ERC1155Creator("Token1155", "NFT1155"); + mockERC20 = new MockERC20("Token20", "ERC20"); + manifoldMembership = new MockManifoldMembership(); + + signingAddress = vm.addr(privateKey); + example = new PhysicalClaim(owner, signingAddress); + + vm.deal(owner, 10 ether); + vm.deal(other1, 10 ether); + vm.deal(other2, 10 ether); + vm.stopPrank(); + } + + function testInvalidData() public { + vm.startPrank(owner); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + vm.startPrank(other1); + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + creatorCore1155.safeTransferFrom(other1, address(example), 1, 1, ""); + vm.stopPrank(); + } + + function testInvalidBurnTokenLength() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](0); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.InvalidInput.selector); + creatorCore1155.safeTransferFrom(other1, address(example), 1, 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testCannotReceive1() public { + vm.startPrank(owner); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.InvalidInput.selector); + creatorCore1155.safeTransferFrom(other1, address(example), 1, 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testCannotReceive2() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 1; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.InvalidInput.selector); + creatorCore1155.safeTransferFrom(other1, address(example), 1, 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidBurnToken1() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.InvalidInput.selector); + creatorCore1155.safeTransferFrom(other1, address(example), 1, 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidBurnToken2() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 2, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(abi.encodeWithSelector(IPhysicalClaim.InvalidToken.selector, address(creatorCore1155), 2)); + creatorCore1155.safeTransferFrom(other1, address(example), 1, 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidBurnToken3() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(abi.encodeWithSelector(IPhysicalClaim.InvalidToken.selector, address(creatorCore721), 1)); + creatorCore1155.safeTransferFrom(other1, address(example), 1, 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidSignature() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem because we have an invalid signature + // Change configured signer + vm.startPrank(owner); + example.updateSigner(other2); + vm.stopPrank(); + vm.startPrank(other1); + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.InvalidSignature.selector); + creatorCore1155.safeTransferFrom(other1, address(example), 1, 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidMessage() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem because we change the data + submission.message = bytes32(0); + vm.startPrank(other1); + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.InvalidSignature.selector); + creatorCore1155.safeTransferFrom(other1, address(example), 1, 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidDueToDataChange() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem because we change the data + submission.variationLimit = 10; + vm.startPrank(other1); + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.InvalidSignature.selector); + creatorCore1155.safeTransferFrom(other1, address(example), 1, 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidDueToExpired() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp - 1); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem because it is expired + vm.startPrank(other1); + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.ExpiredSignature.selector); + creatorCore1155.safeTransferFrom(other1, address(example), 1, 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testNotEnoughTokens() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 2 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 100); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.InvalidBurnAmount.selector); + creatorCore1155.safeTransferFrom(other1, address(example), 1, 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testPhyiscalClaim() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 2 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 100); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + creatorCore1155.safeTransferFrom(other1, address(example), 1, 2, abi.encode(submission)); + vm.stopPrank(); + + // Check token burned + assertEq(creatorCore1155.balanceOf(other1, 1), 8); + } + + function testPhyiscalClaimSoldOut() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 2 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 1; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 100); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + creatorCore1155.safeTransferFrom(other1, address(example), 1, 2, abi.encode(submission)); + vm.stopPrank(); + + // Check correct amount of tokens burned + assertEq(creatorCore1155.balanceOf(other1, 1), 8); + + // Test redeem again (should fail) + nonce = bytes32(bytes4(0xdeafbeef)); + submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + vm.startPrank(other1); + // Note: Current ERC1155 implementation does not pass through revert messages + vm.expectRevert("ERC1155: transfer to non-ERC1155Receiver implementer"); + //vm.expectRevert(IPhysicalClaim.SoldOut.selector); + creatorCore1155.safeTransferFrom(other1, address(example), 1, 2, abi.encode(submission)); + vm.stopPrank(); + + // Check correct amount of tokens burned + assertEq(creatorCore1155.balanceOf(other1, 1), 8); + } + + function testPhyiscalClaimWithERC20() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + address[] memory recipients = new address[](1); + recipients[0] = other1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 10; + string[] memory uris = new string[](1); + creatorCore1155.mintBaseNew(recipients, amounts, uris); + mockERC20.fakeMint(other2, 200); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC1155, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 2 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(mockERC20); + uint256 price = 15; + address payable fundsRecipient = payable(seller); + uint160 expiration = uint160(block.timestamp + 100); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Approve spend from other2 + vm.startPrank(other2); + mockERC20.approve(address(example), 15); + vm.stopPrank(); + + // Test redeem (should fail, cannot steal someone else's balance) + vm.startPrank(other1); + vm.expectRevert("ERC20: insufficient allowance"); + creatorCore1155.safeTransferFrom(other1, address(example), 1, 2, abi.encode(submission)); + mockERC20.approve(address(example), 15); + vm.expectRevert("ERC20: transfer amount exceeds balance"); + creatorCore1155.safeTransferFrom(other1, address(example), 1, 2, abi.encode(submission)); + vm.stopPrank(); + + // Mint to other1 + vm.startPrank(owner); + mockERC20.fakeMint(other1, 200); + vm.stopPrank(); + + // Approve spend from other1 + vm.startPrank(other1); + creatorCore1155.safeTransferFrom(other1, address(example), 1, 2, abi.encode(submission)); + vm.stopPrank(); + + // Check token burned + vm.expectRevert("ERC721: invalid token ID"); + creatorCore721.ownerOf(1); + + // Check seller got price + assertEq(mockERC20.balanceOf(address(seller)), 15); + assertEq(mockERC20.balanceOf(address(other1)), 185); + } + + } diff --git a/packages/manifold/test/physicalclaim/PhysicalClaim721Transfer.t.sol b/packages/manifold/test/physicalclaim/PhysicalClaim721Transfer.t.sol new file mode 100644 index 00000000..1ca14a85 --- /dev/null +++ b/packages/manifold/test/physicalclaim/PhysicalClaim721Transfer.t.sol @@ -0,0 +1,577 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "./PhysicalClaimBase.t.sol"; +import "../../contracts/physicalclaim/PhysicalClaim.sol"; +import "@manifoldxyz/libraries-solidity/contracts/access/AdminControl.sol"; +import "@manifoldxyz/creator-core-solidity/contracts/ERC721Creator.sol"; +import "@manifoldxyz/creator-core-solidity/contracts/ERC1155Creator.sol"; +import "../mocks/Mock.sol"; + +contract PhysicalClaim721TransferTest is PhysicalClaimBase { + PhysicalClaim public example; + ERC721Creator public creatorCore721; + ERC1155Creator public creatorCore1155; + MockERC20 public mockERC20; + MockManifoldMembership public manifoldMembership; + + address public owner = 0x6140F00e4Ff3936702E68744f2b5978885464cbB; + address public other1 = 0xc78Dc443c126af6E4f6Ed540c1e740C1b5be09cd; + address public other2 = 0x80AAC46bbd3C2FcE33681541a52CacBEd14bF425; + address public seller = 0x5174cD462b60c536eb51D4ceC1D561D3Ea31004F; + + address public zeroSigner = address(0); + + address public zeroAddress = address(0); + + address public signingAddress; + + function setUp() public { + vm.startPrank(owner); + creatorCore721 = new ERC721Creator("Token721", "NFT721"); + creatorCore1155 = new ERC1155Creator("Token1155", "NFT1155"); + mockERC20 = new MockERC20("Token20", "ERC20"); + manifoldMembership = new MockManifoldMembership(); + + signingAddress = vm.addr(privateKey); + example = new PhysicalClaim(owner, signingAddress); + + vm.deal(owner, 10 ether); + vm.deal(other1, 10 ether); + vm.deal(other2, 10 ether); + vm.stopPrank(); + } + + function testInvalidData() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + + vm.startPrank(other1); + vm.expectRevert("ERC721: transfer to non ERC721Receiver implementer"); + creatorCore721.safeTransferFrom(other1, address(example), 1, ""); + vm.stopPrank(); + } + + function testInvalidBurnTokenLength() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](0); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + vm.expectRevert(IPhysicalClaim.InvalidInput.selector); + creatorCore721.safeTransferFrom(other1, address(example), 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testCannotReceive1() public { + vm.startPrank(owner); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + vm.expectRevert(IPhysicalClaim.InvalidInput.selector); + creatorCore721.safeTransferFrom(other1, address(example), 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testCannotReceive2() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 1; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + vm.expectRevert(IPhysicalClaim.InvalidInput.selector); + creatorCore721.safeTransferFrom(other1, address(example), 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidBurnToken1() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721_NO_BURN, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + vm.expectRevert(IPhysicalClaim.InvalidInput.selector); + creatorCore721.safeTransferFrom(other1, address(example), 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidBurnToken2() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 2, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + vm.expectRevert(abi.encodeWithSelector(IPhysicalClaim.InvalidToken.selector, address(creatorCore721), 2)); + creatorCore721.safeTransferFrom(other1, address(example), 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidBurnToken3() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore1155), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + vm.expectRevert(abi.encodeWithSelector(IPhysicalClaim.InvalidToken.selector, address(creatorCore1155), 1)); + creatorCore721.safeTransferFrom(other1, address(example), 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidBurnToken4() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 2 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + vm.startPrank(other1); + vm.expectRevert(abi.encodeWithSelector(IPhysicalClaim.InvalidToken.selector, address(creatorCore721), 1)); + creatorCore721.safeTransferFrom(other1, address(example), 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidSignature() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem because we have an invalid signature + // Change configured signer + vm.startPrank(owner); + example.updateSigner(other2); + vm.stopPrank(); + vm.startPrank(other1); + vm.expectRevert(IPhysicalClaim.InvalidSignature.selector); + creatorCore721.safeTransferFrom(other1, address(example), 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidMessage() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem because we change the data + submission.message = bytes32(0); + vm.startPrank(other1); + vm.expectRevert(IPhysicalClaim.InvalidSignature.selector); + creatorCore721.safeTransferFrom(other1, address(example), 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidDueToDataChange() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 1000); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem because we change the data + submission.variationLimit = 10; + vm.startPrank(other1); + vm.expectRevert(IPhysicalClaim.InvalidSignature.selector); + creatorCore721.safeTransferFrom(other1, address(example), 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testInvalidDueToExpired() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp - 1); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem because it is expired + vm.startPrank(other1); + vm.expectRevert(IPhysicalClaim.ExpiredSignature.selector); + creatorCore721.safeTransferFrom(other1, address(example), 1, abi.encode(submission)); + vm.stopPrank(); + } + + function testPhyiscalClaim() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 100); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + creatorCore721.safeTransferFrom(other1, address(example), 1, abi.encode(submission)); + vm.stopPrank(); + + // Check token burned + vm.expectRevert("ERC721: invalid token ID"); + creatorCore721.ownerOf(1); + } + + function testPhyiscalClaimSoldOut() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + creatorCore721.mintBase(other1, ""); + creatorCore721.mintBase(other1, ""); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 1; + address erc20 = address(0); + uint256 price = 0; + address payable fundsRecipient = payable(address(0)); + uint160 expiration = uint160(block.timestamp + 100); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Test redeem + vm.startPrank(other1); + creatorCore721.safeTransferFrom(other1, address(example), 1, abi.encode(submission)); + vm.stopPrank(); + + // Test redeem again (should fail) + burnTokens[0].tokenId = 2; + nonce = bytes32(bytes4(0xdeafbeef)); + submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + vm.startPrank(other1); + vm.expectRevert(IPhysicalClaim.SoldOut.selector); + creatorCore721.safeTransferFrom(other1, address(example), 2, abi.encode(submission)); + vm.stopPrank(); + } + + function testPhyiscalClaimWithERC20() public { + vm.startPrank(owner); + example.setMembershipAddress(address(manifoldMembership)); + manifoldMembership.setMember(other1, true); + creatorCore721.mintBase(other1, ""); + mockERC20.fakeMint(other2, 200); + vm.stopPrank(); + + IPhysicalClaim.BurnToken[] memory burnTokens = new IPhysicalClaim.BurnToken[](1); + burnTokens[0] = IPhysicalClaim.BurnToken({ + contractAddress: address(creatorCore721), + tokenId: 1, + tokenSpec: IPhysicalClaim.TokenSpec.ERC721, + burnSpec: IPhysicalClaim.BurnSpec.MANIFOLD, + amount: 1 + }); + + uint256 instanceId = 100; + uint8 variation = 2; + uint64 variationLimit = 0; + uint64 totalLimit = 0; + address erc20 = address(mockERC20); + uint256 price = 15; + address payable fundsRecipient = payable(seller); + uint160 expiration = uint160(block.timestamp + 100); + bytes32 nonce = bytes32(bytes4(0xdeadbeef)); + + IPhysicalClaim.BurnSubmission memory submission = constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + + // Approve spend from other2 + vm.startPrank(other2); + mockERC20.approve(address(example), 15); + vm.stopPrank(); + + // Test redeem (should fail, cannot steal someone else's balance) + vm.startPrank(other1); + vm.expectRevert("ERC20: insufficient allowance"); + creatorCore721.safeTransferFrom(other1, address(example), 1, abi.encode(submission)); + mockERC20.approve(address(example), 15); + vm.expectRevert("ERC20: transfer amount exceeds balance"); + creatorCore721.safeTransferFrom(other1, address(example), 1, abi.encode(submission)); + vm.stopPrank(); + + // Mint to other1 + vm.startPrank(owner); + mockERC20.fakeMint(other1, 200); + vm.stopPrank(); + + // Approve spend from other1 + vm.startPrank(other1); + creatorCore721.safeTransferFrom(other1, address(example), 1, abi.encode(submission)); + vm.stopPrank(); + + // Check token burned + vm.expectRevert("ERC721: invalid token ID"); + creatorCore721.ownerOf(1); + + // Check seller got price + assertEq(mockERC20.balanceOf(address(seller)), 15); + assertEq(mockERC20.balanceOf(address(other1)), 185); + } + + } diff --git a/packages/manifold/test/physicalclaim/PhysicalClaimBase.t.sol b/packages/manifold/test/physicalclaim/PhysicalClaimBase.t.sol new file mode 100644 index 00000000..e376bd84 --- /dev/null +++ b/packages/manifold/test/physicalclaim/PhysicalClaimBase.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "../../contracts/physicalclaim/IPhysicalClaim.sol"; + +abstract contract PhysicalClaimBase is Test { + + uint256 internal privateKey = 0x1010101010101010101010101010101010101010101010101010101010101010; + + function constructSubmission(uint256 instanceId, IPhysicalClaim.BurnToken[] memory burnTokens, uint8 variation, uint64 variationLimit, uint64 totalLimit, address erc20, uint256 price, address payable fundsRecipient, uint160 expiration, bytes32 nonce) internal view returns (IPhysicalClaim.BurnSubmission memory submission) { + // Hack because we were getting stack too deep, so need to pass into subfunction + submission = _constructSubmission(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce); + submission.instanceId = instanceId; + } + + function _constructSubmission(uint256 instanceId, IPhysicalClaim.BurnToken[] memory burnTokens, uint8 variation, uint64 variationLimit, uint64 totalLimit, address erc20, uint256 price, address payable fundsRecipient, uint160 expiration, bytes32 nonce) internal view returns (IPhysicalClaim.BurnSubmission memory submission) { + bytes32 message = keccak256(abi.encode(instanceId, burnTokens, variation, variationLimit, totalLimit, erc20, price, fundsRecipient, expiration, nonce)); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, message); + bytes memory signature = abi.encodePacked(r, s, v); + + submission.signature = signature; + submission.message = message; + submission.burnTokens = burnTokens; + submission.variation = variation; + submission.variationLimit = variationLimit; + submission.totalLimit = totalLimit; + submission.erc20 = erc20; + submission.price = price; + submission.fundsRecipient = fundsRecipient; + submission.expiration = expiration; + submission.nonce = nonce; + } + + }