From 0b86825c7d604357618f79c5f998d9f3e19654d8 Mon Sep 17 00:00:00 2001 From: jaxonL <11150920+jaxonL@users.noreply.github.com> Date: Tue, 2 Jul 2024 18:29:36 -0400 Subject: [PATCH] fix(CON-1104),refactor: allow editing of supply to unlimited/unlimited and minted claims; change name to serendipity (#96) * docs: update docs with past serendipity contracts * refactor: rename to serendipity tests * test: fix test name * test: move deprecation tests out; add test cases for toggling supply * refactor: rename to serendipity * refactor: rename emitted events * test: fix wrong assertion in test * fix: ensure we allow totalMax to be 0 if updating claim parameters, even if minted * chore: bump action version to use node 20 * refactor: name as simply serendipity * refactor: renamed the event for reserving a mint * chore: make artifact names unique despite different folders see https://github.com/actions/download-artifact/blob/main/docs/MIGRATION.md --- .github/workflows/ci.yml | 4 +- packages/manifold/README.md | 9 + ...haLazyClaim.sol => ERC1155Serendipity.sol} | 96 +- ...aLazyClaim.sol => IERC1155Serendipity.sol} | 6 +- .../{IGachaLazyClaim.sol => ISerendipity.sol} | 10 +- .../{GachaLazyClaim.sol => Serendipity.sol} | 14 +- ....sol => ERC1155SerendipityLazyClaim.s.sol} | 12 +- .../test/gacha/ERC1155GachaLazyClaim.t.sol | 699 ---------- .../test/gacha/ERC1155Serendipity.t.sol | 1143 +++++++++++++++++ 9 files changed, 1223 insertions(+), 770 deletions(-) rename packages/manifold/contracts/gachaclaims/{ERC1155GachaLazyClaim.sol => ERC1155Serendipity.sol} (72%) rename packages/manifold/contracts/gachaclaims/{IERC1155GachaLazyClaim.sol => IERC1155Serendipity.sol} (96%) rename packages/manifold/contracts/gachaclaims/{IGachaLazyClaim.sol => ISerendipity.sol} (90%) rename packages/manifold/contracts/gachaclaims/{GachaLazyClaim.sol => Serendipity.sol} (88%) rename packages/manifold/script/{ERC1155GachaLazyClaim.s.sol => ERC1155SerendipityLazyClaim.s.sol} (69%) delete mode 100644 packages/manifold/test/gacha/ERC1155GachaLazyClaim.t.sol create mode 100644 packages/manifold/test/gacha/ERC1155Serendipity.t.sol diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a4aa0a..b73bba9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,10 +104,10 @@ jobs: - name: Setup LCOV uses: hrishikesh-kadam/setup-lcov@v1 - name: Report code coverage - uses: zgosalvez/github-actions-report-lcov@v3 + uses: zgosalvez/github-actions-report-lcov@v4 with: coverage-files: packages/${{ matrix.folder }}/lcov.info - artifact-name: code-coverage-report + artifact-name: code-coverage-report-${{ matrix.folder }} github-token: ${{ secrets.GITHUB_TOKEN }} working-directory: packages/${{ matrix.folder }} update-comment: true \ No newline at end of file diff --git a/packages/manifold/README.md b/packages/manifold/README.md index 4da565e..d4f641e 100644 --- a/packages/manifold/README.md +++ b/packages/manifold/README.md @@ -40,6 +40,15 @@ These are the latest Burn Redeem extensions. | 1 (Mainnet) | ERC1155 | 0xde659726CfD166aCa4867994d396EFeF386EAD68 | | 2 (Goerli) | ERC1155 | 0x193bFD86F329508351ae899A92a963d5bfC77190 | +#### Manifold Serendipity Collection +These are the latest Serendipity (gacha) extensions. + +| Network | Output Token Spec | Address | Deprecation notice | +| ----------- | ------------------------ | ------------------------------------------ | ------------------------------ | +| v0 (Sepolia) | ERC1155 | 0xa160a0a48b6ddb9ffd01a7b85e0c2b331c912e29 | Cannot deploy unlimited supply | +| v1 (Sepolia, Base) | ERC1155 | 0x53C37ccC1C48f63BD35FFc93952d0880d7529b9e | Cannot edit unlimited supply | +| v2 (Sepolia, Base) | ERC1155 | \ | n/a (active) | + #### OperatorFilterer Shared extension to support OpenSea's Operator Filter Registry diff --git a/packages/manifold/contracts/gachaclaims/ERC1155GachaLazyClaim.sol b/packages/manifold/contracts/gachaclaims/ERC1155Serendipity.sol similarity index 72% rename from packages/manifold/contracts/gachaclaims/ERC1155GachaLazyClaim.sol rename to packages/manifold/contracts/gachaclaims/ERC1155Serendipity.sol index 1a69252..98ec434 100644 --- a/packages/manifold/contracts/gachaclaims/ERC1155GachaLazyClaim.sol +++ b/packages/manifold/contracts/gachaclaims/ERC1155Serendipity.sol @@ -10,15 +10,15 @@ import "@openzeppelin/contracts/utils/Strings.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; -import "./GachaLazyClaim.sol"; -import "./IERC1155GachaLazyClaim.sol"; +import "./Serendipity.sol"; +import "./IERC1155Serendipity.sol"; /** - * @title Gacha Lazy 1155 Payable Claim + * @title Serendipity Lazy Payable Claim - ERC-1155 * @author manifold.xyz * @notice */ -contract ERC1155GachaLazyClaim is IERC165, IERC1155GachaLazyClaim, ICreatorExtensionTokenURI, GachaLazyClaim { +contract ERC1155Serendipity is IERC165, IERC1155Serendipity, ICreatorExtensionTokenURI, Serendipity { using Strings for uint256; // stores mapping from contractAddress/instanceId to the claim it represents @@ -30,17 +30,17 @@ contract ERC1155GachaLazyClaim is IERC165, IERC1155GachaLazyClaim, ICreatorExten function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, AdminControl) returns (bool) { return - interfaceId == type(IERC1155GachaLazyClaim).interfaceId || - interfaceId == type(IGachaLazyClaim).interfaceId || + interfaceId == type(IERC1155Serendipity).interfaceId || + interfaceId == type(ISerendipity).interfaceId || interfaceId == type(ICreatorExtensionTokenURI).interfaceId || interfaceId == type(IAdminControl).interfaceId || interfaceId == type(IERC165).interfaceId; } - constructor(address initialOwner) GachaLazyClaim(initialOwner) {} + constructor(address initialOwner) Serendipity(initialOwner) {} /** - * See {IERC1155GachaLazyClaim-initializeClaim}. + * See {IERC1155Serendipity-initializeClaim}. */ function initializeClaim( address creatorContractAddress, @@ -50,16 +50,16 @@ contract ERC1155GachaLazyClaim is IERC165, IERC1155GachaLazyClaim, ICreatorExten if (deprecated) { revert ContractDeprecated(); } - if (instanceId == 0 || instanceId > MAX_UINT_56) revert IGachaLazyClaim.InvalidInstance(); + if (instanceId == 0 || instanceId > MAX_UINT_56) revert ISerendipity.InvalidInstance(); if (_claims[creatorContractAddress][instanceId].storageProtocol != StorageProtocol.INVALID) - revert IGachaLazyClaim.ClaimAlreadyInitialized(); + revert ISerendipity.ClaimAlreadyInitialized(); // Checks - if (claimParameters.storageProtocol == StorageProtocol.INVALID) revert IGachaLazyClaim.InvalidStorageProtocol(); + if (claimParameters.storageProtocol == StorageProtocol.INVALID) revert ISerendipity.InvalidStorageProtocol(); if (claimParameters.endDate != 0 && claimParameters.startDate >= claimParameters.endDate) - revert IGachaLazyClaim.InvalidDate(); - if (claimParameters.totalMax > MAX_UINT_32) revert IGachaLazyClaim.InvalidInput(); - if (claimParameters.tokenVariations > MAX_UINT_8) revert IGachaLazyClaim.InvalidInput(); - if (claimParameters.cost > MAX_UINT_96) revert IGachaLazyClaim.InvalidInput(); + revert ISerendipity.InvalidDate(); + if (claimParameters.totalMax > MAX_UINT_32) revert ISerendipity.InvalidInput(); + if (claimParameters.tokenVariations > MAX_UINT_8) revert ISerendipity.InvalidInput(); + if (claimParameters.cost > MAX_UINT_96) revert ISerendipity.InvalidInput(); address[] memory receivers = new address[](1); receivers[0] = msg.sender; @@ -67,7 +67,7 @@ contract ERC1155GachaLazyClaim is IERC165, IERC1155GachaLazyClaim, ICreatorExten string[] memory uris = new string[](claimParameters.tokenVariations); uint256[] memory newTokenIds = IERC1155CreatorCore(creatorContractAddress).mintExtensionNew(receivers, amounts, uris); - if (newTokenIds[0] > MAX_UINT_80) revert IGachaLazyClaim.InvalidStartingTokenId(); + if (newTokenIds[0] > MAX_UINT_80) revert ISerendipity.InvalidStartingTokenId(); // Create the claim _claims[creatorContractAddress][instanceId] = Claim({ @@ -90,11 +90,11 @@ contract ERC1155GachaLazyClaim is IERC165, IERC1155GachaLazyClaim, ICreatorExten } } - emit GachaClaimInitialized(creatorContractAddress, instanceId, msg.sender); + emit SerendipityClaimInitialized(creatorContractAddress, instanceId, msg.sender); } /** - * See {IERC1155GachaLazyClaim-updateClaim}. + * See {IERC1155Serendipity-updateClaim}. */ function updateClaim( address creatorContractAddress, @@ -105,13 +105,13 @@ contract ERC1155GachaLazyClaim is IERC165, IERC1155GachaLazyClaim, ICreatorExten revert ContractDeprecated(); } Claim memory claim = _getClaim(creatorContractAddress, instanceId); - if (instanceId == 0 || instanceId > MAX_UINT_56) revert IGachaLazyClaim.InvalidInstance(); + if (instanceId == 0 || instanceId > MAX_UINT_56) revert ISerendipity.InvalidInstance(); if (updateClaimParameters.endDate != 0 && updateClaimParameters.startDate >= updateClaimParameters.endDate) - revert IGachaLazyClaim.InvalidDate(); - if (updateClaimParameters.totalMax < claim.total) revert IGachaLazyClaim.CannotLowerTotalMaxBeyondTotal(); - if (updateClaimParameters.totalMax > MAX_UINT_32) revert IGachaLazyClaim.InvalidInput(); - if (updateClaimParameters.storageProtocol == StorageProtocol.INVALID) revert IGachaLazyClaim.InvalidStorageProtocol(); - if (updateClaimParameters.cost > MAX_UINT_96) revert IGachaLazyClaim.InvalidInput(); + revert ISerendipity.InvalidDate(); + if (updateClaimParameters.totalMax != 0 && updateClaimParameters.totalMax < claim.total) revert ISerendipity.CannotLowerTotalMaxBeyondTotal(); + if (updateClaimParameters.totalMax > MAX_UINT_32) revert ISerendipity.InvalidInput(); + if (updateClaimParameters.storageProtocol == StorageProtocol.INVALID) revert ISerendipity.InvalidStorageProtocol(); + if (updateClaimParameters.cost > MAX_UINT_96) revert ISerendipity.InvalidInput(); // Overwrite the existing values _claims[creatorContractAddress][instanceId] = Claim({ @@ -127,18 +127,18 @@ contract ERC1155GachaLazyClaim is IERC165, IERC1155GachaLazyClaim, ICreatorExten cost: updateClaimParameters.cost, erc20: claim.erc20 }); - emit GachaClaimUpdated(creatorContractAddress, instanceId); + emit SerendipityClaimUpdated(creatorContractAddress, instanceId); } /** - * See {IERC1155GachaLazyClaim-getClaim}. + * See {IERC1155Serendipity-getClaim}. */ function getClaim(address creatorContractAddress, uint256 instanceId) public view override returns (Claim memory) { return _getClaim(creatorContractAddress, instanceId); } /** - * See {IERC1155GachaLazyClaim-getClaimForToken}. + * See {IERC1155Serendipity-getClaimForToken}. */ function getClaimForToken( address creatorContractAddress, @@ -150,22 +150,22 @@ contract ERC1155GachaLazyClaim is IERC165, IERC1155GachaLazyClaim, ICreatorExten function _getClaim(address creatorContractAddress, uint256 instanceId) private view returns (Claim storage claim) { claim = _claims[creatorContractAddress][instanceId]; - if (claim.storageProtocol == StorageProtocol.INVALID) revert IGachaLazyClaim.ClaimNotInitialized(); + if (claim.storageProtocol == StorageProtocol.INVALID) revert ISerendipity.ClaimNotInitialized(); } /** - * See {IGachaLazyClaim-mintReserve}. + * See {ISerendipity-mintReserve}. */ function mintReserve(address creatorContractAddress, uint256 instanceId, uint32 mintCount) external payable override { - if (Address.isContract(msg.sender)) revert IGachaLazyClaim.CannotMintFromContract(); + if (Address.isContract(msg.sender)) revert ISerendipity.CannotMintFromContract(); Claim storage claim = _getClaim(creatorContractAddress, instanceId); // Checks for reserving - if (mintCount == 0 || mintCount >= MAX_UINT_32) revert IGachaLazyClaim.InvalidMintCount(); + if (mintCount == 0 || mintCount >= MAX_UINT_32) revert ISerendipity.InvalidMintCount(); if (claim.startDate > block.timestamp || (claim.endDate > 0 && claim.endDate < block.timestamp)) - revert IGachaLazyClaim.ClaimInactive(); - if (claim.totalMax != 0 && claim.total == claim.totalMax) revert IGachaLazyClaim.ClaimSoldOut(); - if (claim.total == MAX_UINT_32) revert IGachaLazyClaim.TooManyRequested(); - if (msg.value != (claim.cost + MINT_FEE) * mintCount) revert IGachaLazyClaim.InvalidPayment(); + revert ISerendipity.ClaimInactive(); + if (claim.totalMax != 0 && claim.total == claim.totalMax) revert ISerendipity.ClaimSoldOut(); + if (claim.total == MAX_UINT_32) revert ISerendipity.TooManyRequested(); + if (msg.value != (claim.cost + MINT_FEE) * mintCount) revert ISerendipity.InvalidPayment(); // calculate the amount to reserve and update totals uint32 amountToReserve = mintCount; if (claim.totalMax != 0) { @@ -181,13 +181,13 @@ contract ERC1155GachaLazyClaim is IERC165, IERC1155GachaLazyClaim, ICreatorExten uint256 refundAmount = msg.value - (claim.cost + MINT_FEE) * amountToReserve; _sendFunds(payable(msg.sender), refundAmount); } - emit GachaClaimMintReserved(creatorContractAddress, instanceId, msg.sender, amountToReserve); + emit SerendipityMintReserved(creatorContractAddress, instanceId, msg.sender, amountToReserve); } /** - * See {IGachaLazyClaim-deliverMints}. + * See {ISerendipity-deliverMints}. */ - function deliverMints(IGachaLazyClaim.ClaimMint[] calldata mints) external override { + function deliverMints(ISerendipity.ClaimMint[] calldata mints) external override { _validateSigner(); for (uint256 i; i < mints.length; ) { ClaimMint calldata mintData = mints[i]; @@ -198,19 +198,19 @@ contract ERC1155GachaLazyClaim is IERC165, IERC1155GachaLazyClaim, ICreatorExten for (uint256 j; j < mintData.variationMints.length; ) { VariationMint calldata variationMint = mintData.variationMints[j]; - if (variationMint.variationIndex > MAX_UINT_8) revert IGachaLazyClaim.InvalidVariationIndex(); + if (variationMint.variationIndex > MAX_UINT_8) revert ISerendipity.InvalidVariationIndex(); uint8 variationIndex = variationMint.variationIndex; - if (variationIndex > claim.tokenVariations || variationIndex < 1) revert IGachaLazyClaim.InvalidVariationIndex(); + if (variationIndex > claim.tokenVariations || variationIndex < 1) revert ISerendipity.InvalidVariationIndex(); address recipient = variationMint.recipient; - if (variationMint.amount > MAX_UINT_32) revert IGachaLazyClaim.TooManyRequested(); + if (variationMint.amount > MAX_UINT_32) revert ISerendipity.TooManyRequested(); uint32 amount = variationMint.amount; UserMintDetails storage userMintDetails = _mintDetailsPerWallet[mintData.creatorContractAddress][ mintData.instanceId ][recipient]; if (userMintDetails.deliveredCount + amount > userMintDetails.reservedCount) - revert IGachaLazyClaim.CannotMintMoreThanReserved(); - if (claim.startingTokenId > MAX_UINT_80) revert IGachaLazyClaim.InvalidStartingTokenId(); + revert ISerendipity.CannotMintMoreThanReserved(); + if (claim.startingTokenId > MAX_UINT_80) revert ISerendipity.InvalidStartingTokenId(); tokenIds[j] = claim.startingTokenId + variationIndex - 1; amounts[j] = amount; receivers[j] = recipient; @@ -228,7 +228,7 @@ contract ERC1155GachaLazyClaim is IERC165, IERC1155GachaLazyClaim, ICreatorExten } /** - * See {IGachaLazyClaim-getUserMints}. + * See {ISerendipity-getUserMints}. */ function getUserMints( address minter, @@ -243,7 +243,7 @@ contract ERC1155GachaLazyClaim is IERC165, IERC1155GachaLazyClaim, ICreatorExten */ function tokenURI(address creatorContractAddress, uint256 tokenId) external view override returns (string memory uri) { uint256 instanceId = _tokenInstances[creatorContractAddress][tokenId]; - if (instanceId == 0) revert IGachaLazyClaim.TokenDNE(); + if (instanceId == 0) revert ISerendipity.TokenDNE(); Claim memory claim = _getClaim(creatorContractAddress, instanceId); string memory prefix = ""; @@ -256,7 +256,7 @@ contract ERC1155GachaLazyClaim is IERC165, IERC1155GachaLazyClaim, ICreatorExten } /** - * See {IERC1155GachaLazyClaim-updateTokenURIParams}. + * See {IERC1155Serendipity-updateTokenURIParams}. */ function updateTokenURIParams( address creatorContractAddress, @@ -265,9 +265,9 @@ contract ERC1155GachaLazyClaim is IERC165, IERC1155GachaLazyClaim, ICreatorExten string calldata location ) external override creatorAdminRequired(creatorContractAddress) { Claim storage claim = _getClaim(creatorContractAddress, instanceId); - if (storageProtocol == StorageProtocol.INVALID) revert IGachaLazyClaim.InvalidStorageProtocol(); + if (storageProtocol == StorageProtocol.INVALID) revert ISerendipity.InvalidStorageProtocol(); claim.storageProtocol = storageProtocol; claim.location = location; - emit GachaClaimUpdated(creatorContractAddress, instanceId); + emit SerendipityClaimUpdated(creatorContractAddress, instanceId); } } diff --git a/packages/manifold/contracts/gachaclaims/IERC1155GachaLazyClaim.sol b/packages/manifold/contracts/gachaclaims/IERC1155Serendipity.sol similarity index 96% rename from packages/manifold/contracts/gachaclaims/IERC1155GachaLazyClaim.sol rename to packages/manifold/contracts/gachaclaims/IERC1155Serendipity.sol index 98e4140..963b379 100644 --- a/packages/manifold/contracts/gachaclaims/IERC1155GachaLazyClaim.sol +++ b/packages/manifold/contracts/gachaclaims/IERC1155Serendipity.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.0; /// @author: manifold.xyz -import "./IGachaLazyClaim.sol"; +import "./ISerendipity.sol"; /** - * Gacha 1155 Lazy Claim interface + * Serendipity Lazy Claim interface for ERC-1155 */ -interface IERC1155GachaLazyClaim is IGachaLazyClaim { +interface IERC1155Serendipity is ISerendipity { struct Claim { StorageProtocol storageProtocol; uint32 total; diff --git a/packages/manifold/contracts/gachaclaims/IGachaLazyClaim.sol b/packages/manifold/contracts/gachaclaims/ISerendipity.sol similarity index 90% rename from packages/manifold/contracts/gachaclaims/IGachaLazyClaim.sol rename to packages/manifold/contracts/gachaclaims/ISerendipity.sol index 23ed560..7f5f61a 100644 --- a/packages/manifold/contracts/gachaclaims/IGachaLazyClaim.sol +++ b/packages/manifold/contracts/gachaclaims/ISerendipity.sol @@ -5,9 +5,9 @@ pragma solidity ^0.8.0; /// @author: manifold.xyz /** - * Gacha Lazy Claim interface + * Serendipity Lazy Claim interface */ -interface IGachaLazyClaim { +interface ISerendipity { enum StorageProtocol { INVALID, NONE, @@ -39,9 +39,9 @@ interface IGachaLazyClaim { error CannotMintMoreThanReserved(); error CannotMintFromContract(); - event GachaClaimInitialized(address indexed creatorContract, uint256 indexed instanceId, address initializer); - event GachaClaimUpdated(address indexed creatorContract, uint256 indexed instanceId); - event GachaClaimMintReserved( + event SerendipityClaimInitialized(address indexed creatorContract, uint256 indexed instanceId, address initializer); + event SerendipityClaimUpdated(address indexed creatorContract, uint256 indexed instanceId); + event SerendipityMintReserved( address indexed creatorContract, uint256 indexed instanceId, address indexed collector, diff --git a/packages/manifold/contracts/gachaclaims/GachaLazyClaim.sol b/packages/manifold/contracts/gachaclaims/Serendipity.sol similarity index 88% rename from packages/manifold/contracts/gachaclaims/GachaLazyClaim.sol rename to packages/manifold/contracts/gachaclaims/Serendipity.sol index fbc33a9..1df2d3a 100644 --- a/packages/manifold/contracts/gachaclaims/GachaLazyClaim.sol +++ b/packages/manifold/contracts/gachaclaims/Serendipity.sol @@ -4,13 +4,13 @@ pragma solidity ^0.8.0; import "@manifoldxyz/libraries-solidity/contracts/access/AdminControl.sol"; -import "./IGachaLazyClaim.sol"; +import "./ISerendipity.sol"; /** - * @title Gacha Lazy Claim + * @title Serendipity Lazy Claim * @author manifold.xyz */ -abstract contract GachaLazyClaim is IGachaLazyClaim, AdminControl { +abstract contract Serendipity is ISerendipity, AdminControl { using EnumerableSet for EnumerableSet.AddressSet; string internal constant ARWEAVE_PREFIX = "https://arweave.net/"; @@ -55,22 +55,22 @@ abstract contract GachaLazyClaim is IGachaLazyClaim, AdminControl { } /** - * See {IGachaLazyClaim-withdraw}. + * See {ISerendipity-withdraw}. */ function withdraw(address payable receiver, uint256 amount) external override adminRequired { (bool sent, ) = receiver.call{ value: amount }(""); - if (!sent) revert IGachaLazyClaim.FailedToTransfer(); + if (!sent) revert ISerendipity.FailedToTransfer(); } /** - * See {IGachaLazyClaim-setSigner}. + * See {ISerendipity-setSigner}. */ function setSigner(address signer) external override adminRequired { _signer = signer; } function _validateSigner() internal view { - if (msg.sender != _signer) revert IGachaLazyClaim.InvalidSignature(); + if (msg.sender != _signer) revert ISerendipity.InvalidSignature(); } function _getUserMints( diff --git a/packages/manifold/script/ERC1155GachaLazyClaim.s.sol b/packages/manifold/script/ERC1155SerendipityLazyClaim.s.sol similarity index 69% rename from packages/manifold/script/ERC1155GachaLazyClaim.s.sol rename to packages/manifold/script/ERC1155SerendipityLazyClaim.s.sol index 882df01..acb6c5b 100644 --- a/packages/manifold/script/ERC1155GachaLazyClaim.s.sol +++ b/packages/manifold/script/ERC1155SerendipityLazyClaim.s.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.13; import "forge-std/Script.sol"; -import "../contracts/gachaclaims/ERC1155GachaLazyClaim.sol"; +import "../contracts/gachaclaims/ERC1155Serendipity.sol"; /** Pro tip for testing! The private key can be whatever. This is what I did to test. 1. Uncomment the lines below and follow the instructions there to change the code. - 2. Run the script, like `forge script script/ERC1155GachaLazyClaim.s.sol:DeployERC1155GachaLazyClaim --rpc-url https://eth-sepolia.g.alchemy.com/v2/xxx --broadcast` + 2. Run the script, like `forge script script/ERC1155Serendipity.s.sol:DeployERC1155Serendipity --rpc-url https://eth-sepolia.g.alchemy.com/v2/xxx --broadcast` 3. It will print out the address, but give you an out of eth error. 4. Now you have the address, use your real wallet and send it some sepolia eth. 5. Now, run the script again. It will deploy and transfer the contract to your wallet. @@ -16,7 +16,7 @@ import "../contracts/gachaclaims/ERC1155GachaLazyClaim.sol"; In the end, you just basically used a random pk in the moment to deploy. You never had to expose your personal pk to your mac's environment variable or anything. */ -contract DeployERC1155GachaLazyClaim is Script { +contract DeployERC1155Serendipity is Script { function run() external { // address initialOwner = ; // uncomment this and put in your desired owner address address initialOwner = vm.envAddress("INITIAL_OWNER"); // comment this out on sepolia @@ -31,9 +31,9 @@ contract DeployERC1155GachaLazyClaim is Script { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); // comment this out when testing on goerli vm.startBroadcast(deployerPrivateKey); - // forge script script/ERC1155GachaLazyClaim.s.sol:DeployERC1155GachaLazyClaim --optimizer-runs 1000 --rpc-url --broadcast - // forge verify-contract --compiler-version 0.8.17 --optimizer-runs 1000 --chain sepolia contracts/gachaclaims/ERC1155GachaLazyClaim.sol:ERC1155GachaLazyClaim --constructor-args $(cast abi-encode "constructor(address)" "${INITIAL_OWNER}") --watch - new ERC1155GachaLazyClaim{salt: 0x16091cc3cd908d7d973f650f59bd476ac79090f0358f87c50a7f5caee0835a84}(initialOwner); + // forge script script/ERC1155Serendipity.s.sol:DeployERC1155Serendipity --optimizer-runs 1000 --rpc-url --broadcast + // forge verify-contract --compiler-version 0.8.17 --optimizer-runs 1000 --chain sepolia contracts/gachaclaims/ERC1155Serendipity.sol:ERC1155Serendipity --constructor-args $(cast abi-encode "constructor(address)" "${INITIAL_OWNER}") --watch + new ERC1155Serendipity{salt: 0x16091cc3cd908d7d973f650f59bd476ac79090f0358f87c50a7f5caee0835a84}(initialOwner); vm.stopBroadcast(); } } \ No newline at end of file diff --git a/packages/manifold/test/gacha/ERC1155GachaLazyClaim.t.sol b/packages/manifold/test/gacha/ERC1155GachaLazyClaim.t.sol deleted file mode 100644 index b49a6af..0000000 --- a/packages/manifold/test/gacha/ERC1155GachaLazyClaim.t.sol +++ /dev/null @@ -1,699 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; - -import "forge-std/Test.sol"; -import "../../contracts/gachaclaims/IERC1155GachaLazyClaim.sol"; -import "../../contracts/gachaclaims/ERC1155GachaLazyClaim.sol"; -import "../../contracts/gachaclaims/IGachaLazyClaim.sol"; -import "../../contracts/gachaclaims/GachaLazyClaim.sol"; - -import "@manifoldxyz/creator-core-solidity/contracts/ERC1155Creator.sol"; -import "@openzeppelin/contracts/utils/math/SafeMath.sol"; -import "../mocks/Mock.sol"; - -contract ERC1155GachaLazyClaimTest is Test { - using SafeMath for uint256; - - ERC1155GachaLazyClaim public example; - ERC1155 public erc1155; - ERC1155Creator public creatorCore1; - ERC1155Creator public creatorCore2; - - address public creator = 0xc78Dc443c126af6E4f6Ed540c1e740C1b5be09cd; - address public owner = 0x6140F00e4Ff3936702E68744f2b5978885464cbB; - address public signingAddress = 0xc78dC443c126Af6E4f6eD540C1E740c1B5be09CE; - address public other = 0x5174cD462b60c536eb51D4ceC1D561D3Ea31004F; - address public other2 = 0x80AAC46bbd3C2FcE33681541a52CacBEd14bF425; - - address public zeroAddress = address(0); - - uint256 privateKey = 0x1010101010101010101010101010101010101010101010101010101010101010; - - uint32 MAX_UINT_32 = 0xffffffff; - uint256 public constant MINT_FEE = 500000000000000; - - // Test setup - function setUp() public { - vm.startPrank(creator); - creatorCore1 = new ERC1155Creator("Token1", "NFT1"); - creatorCore2 = new ERC1155Creator("Token2", "NFT2"); - vm.stopPrank(); - - vm.startPrank(owner); - example = new ERC1155GachaLazyClaim(owner); - example.setSigner(address(signingAddress)); - vm.stopPrank(); - - vm.startPrank(creator); - creatorCore1.registerExtension(address(example), "override"); - creatorCore2.registerExtension(address(example), "override"); - vm.stopPrank(); - - vm.deal(creator, 2147483647500004294967295); - vm.deal(other, 10 ether); - vm.deal(other2, 10 ether); - } - - function testAccess() public { - vm.startPrank(other); - // Must be admin - vm.expectRevert(); - example.withdraw(payable(other), 20); - vm.expectRevert("AdminControl: Must be owner or admin"); - example.setSigner(other); - // Must be admin - vm.expectRevert(); - - uint48 nowC = uint48(block.timestamp); - uint48 later = nowC + 1000; - - IERC1155GachaLazyClaim.ClaimParameters memory claimP = IERC1155GachaLazyClaim.ClaimParameters({ - storageProtocol: IGachaLazyClaim.StorageProtocol.IPFS, - totalMax: 100, - startDate: nowC, - endDate: later, - tokenVariations: 5, - location: "arweaveHash1", - paymentReceiver: payable(creator), - cost: 0.01 ether, - erc20: zeroAddress - }); - // Must be admin - vm.expectRevert(); - example.initializeClaim(address(creatorCore1), 1, claimP); - // Succeeds because is admin - vm.stopPrank(); - vm.startPrank(creator); - example.initializeClaim(address(creatorCore1), 1, claimP); - // Try as a different non admin - vm.stopPrank(); - vm.startPrank(other2); - vm.expectRevert(); - example.initializeClaim(address(creatorCore1), 2, claimP); - vm.expectRevert(); - - vm.stopPrank(); - } - - function testinitializeClaimSanitization() public { - vm.startPrank(creator); - - uint48 nowC = uint48(block.timestamp); - uint48 later = nowC + 1000; - - IERC1155GachaLazyClaim.ClaimParameters memory claimP = IERC1155GachaLazyClaim.ClaimParameters({ - storageProtocol: IGachaLazyClaim.StorageProtocol.INVALID, - location: "arweaveHash1", - totalMax: 100, - startDate: nowC, - endDate: later, - tokenVariations: 5, - paymentReceiver: payable(other), - cost: 1, - erc20: zeroAddress - }); - - vm.expectRevert(IGachaLazyClaim.InvalidStorageProtocol.selector); - example.initializeClaim(address(creatorCore1), 1, claimP); - - claimP.storageProtocol = IGachaLazyClaim.StorageProtocol.ARWEAVE; - claimP.startDate = nowC + 2000; - vm.expectRevert(IGachaLazyClaim.InvalidDate.selector); - example.initializeClaim(address(creatorCore1), 1, claimP); - - // successful initialization with no end date - claimP.endDate = 0; - example.initializeClaim(address(creatorCore1), 1, claimP); - vm.stopPrank(); - - vm.startPrank(owner); - example.deprecate(true); - vm.stopPrank(); - - vm.startPrank(creator); - // can't initialize if deprecated - vm.expectRevert(IGachaLazyClaim.ContractDeprecated.selector); - example.initializeClaim(address(creatorCore1), 2, claimP); - vm.stopPrank(); - - vm.startPrank(owner); - example.deprecate(false); - vm.stopPrank(); - - vm.startPrank(creator); - example.initializeClaim(address(creatorCore1), 2, claimP); - vm.stopPrank(); - - // Cannot deprecate if not an admin - vm.startPrank(other); - vm.expectRevert(bytes("AdminControl: Must be owner or admin")); - example.deprecate(true); - vm.stopPrank(); - } - - function testUpdateClaimSanitization() public { - vm.startPrank(creator); - - uint48 nowC = uint48(block.timestamp); - uint48 later = nowC + 1000; - IERC1155GachaLazyClaim.ClaimParameters memory claimP = IERC1155GachaLazyClaim.ClaimParameters({ - storageProtocol: IGachaLazyClaim.StorageProtocol.IPFS, - location: "arweaveHash1", - totalMax: 100, - startDate: nowC, - endDate: later, - tokenVariations: 5, - paymentReceiver: payable(other), - cost: 1, - erc20: zeroAddress - }); - example.initializeClaim(address(creatorCore1), 1, claimP); - - IERC1155GachaLazyClaim.UpdateClaimParameters memory claimU = IERC1155GachaLazyClaim.UpdateClaimParameters({ - storageProtocol: IGachaLazyClaim.StorageProtocol.ARWEAVE, - totalMax: 100, - startDate: nowC, - endDate: later, - location: "arweaveHash1", - paymentReceiver: payable(creator), - cost: 1 - }); - - example.mintReserve{ value: 1 + MINT_FEE }(address(creatorCore1), 1, 1); - - claimU.storageProtocol = IGachaLazyClaim.StorageProtocol.INVALID; - vm.expectRevert(IGachaLazyClaim.InvalidStorageProtocol.selector); - example.updateClaim(address(creatorCore1), 1, claimU); - - claimU.storageProtocol = IGachaLazyClaim.StorageProtocol.ARWEAVE; - claimU.totalMax = 0; - vm.expectRevert(IGachaLazyClaim.CannotLowerTotalMaxBeyondTotal.selector); - example.updateClaim(address(creatorCore1), 1, claimU); - - claimU.totalMax = 100; - claimU.startDate = nowC + 2000; - vm.expectRevert(IGachaLazyClaim.InvalidDate.selector); - example.updateClaim(address(creatorCore1), 1, claimU); - - claimU.endDate = nowC; - vm.expectRevert(IGachaLazyClaim.InvalidDate.selector); - example.updateClaim(address(creatorCore1), 1, claimU); - claimU.endDate = later; - - //successful data and cost update - claimU.cost = 2; - claimU.storageProtocol = IGachaLazyClaim.StorageProtocol.IPFS; - claimU.startDate = nowC + 1000; - claimU.endDate = later + 3000; - claimU.totalMax = 1; - example.updateClaim(address(creatorCore1), 1, claimU); - IERC1155GachaLazyClaim.Claim memory claim = example.getClaim(address(creatorCore1), 1); - assertEq(claim.cost, 2); - // storage protocol for IPFS is 3 - assertEq(uint(claim.storageProtocol), 3); - assertEq(claim.startDate, nowC + 1000); - assertEq(claim.endDate, later + 3000); - assertEq(claim.totalMax, 1); - - // able to update to no end or start date - claimU.startDate = 0; - claimU.endDate = 0; - example.updateClaim(address(creatorCore1), 1, claimU); - claim = example.getClaim(address(creatorCore1), 1); - assertEq(claim.startDate, 0); - assertEq(claim.endDate, 0); - - claimU.startDate = nowC; - claimU.endDate = 0; - example.updateClaim(address(creatorCore1), 1, claimU); - claim = example.getClaim(address(creatorCore1), 1); - assertEq(claim.startDate, nowC); - assertEq(claim.endDate, 0); - vm.stopPrank(); - - vm.startPrank(owner); - example.deprecate(true); - vm.stopPrank(); - - // can't update if deprecated - vm.startPrank(creator); - claimU.startDate = nowC + 2000; - vm.expectRevert(IGachaLazyClaim.ContractDeprecated.selector); - example.updateClaim(address(creatorCore1), 1, claimU); - vm.stopPrank(); - - vm.startPrank(owner); - example.deprecate(false); - vm.stopPrank(); - - vm.startPrank(creator); - example.updateClaim(address(creatorCore1), 1, claimU); - vm.stopPrank(); - } - - function testMintReserveLowPayment() public { - vm.startPrank(creator); - - uint48 nowC = 0; - uint48 later = uint48(block.timestamp) + 2000; - - IERC1155GachaLazyClaim.ClaimParameters memory claimP = IERC1155GachaLazyClaim.ClaimParameters({ - storageProtocol: IGachaLazyClaim.StorageProtocol.ARWEAVE, - totalMax: 100, - startDate: nowC, - endDate: later, - tokenVariations: 5, - location: "arweaveHash1", - paymentReceiver: payable(creator), - cost: 1, - erc20: zeroAddress - }); - - example.initializeClaim(address(creatorCore1), 1, claimP); - - // Insufficient payment - vm.expectRevert(IGachaLazyClaim.InvalidPayment.selector); - example.mintReserve{ value: 1 }(address(creatorCore1), 1, 2); - - vm.stopPrank(); - } - - function testMintReserveEarly() public { - // claim hasn't started yet - vm.startPrank(creator); - - uint48 start = uint48(block.timestamp) + 2000; - uint48 end = 0; - - IERC1155GachaLazyClaim.ClaimParameters memory claimP = IERC1155GachaLazyClaim.ClaimParameters({ - storageProtocol: IGachaLazyClaim.StorageProtocol.ARWEAVE, - totalMax: 100, - startDate: start, - endDate: end, - tokenVariations: 5, - location: "arweaveHash1", - paymentReceiver: payable(creator), - cost: 1, - erc20: zeroAddress - }); - example.initializeClaim(address(creatorCore1), 1, claimP); - vm.expectRevert(IGachaLazyClaim.ClaimInactive.selector); - example.mintReserve{ value: 3 }(address(creatorCore1), 1, 1); - vm.stopPrank(); - } - - function testMintReserveLate() public { - vm.startPrank(creator); - - uint48 start = 0; - uint48 end = uint48(block.timestamp.sub(1)); - - IERC1155GachaLazyClaim.ClaimParameters memory claimP = IERC1155GachaLazyClaim.ClaimParameters({ - storageProtocol: IGachaLazyClaim.StorageProtocol.ARWEAVE, - totalMax: 100, - startDate: start, - endDate: end, - tokenVariations: 5, - location: "arweaveHash1", - paymentReceiver: payable(creator), - cost: 1, - erc20: zeroAddress - }); - example.initializeClaim(address(creatorCore1), 1, claimP); - vm.stopPrank(); - - vm.startPrank(other); - example.mintReserve{ value: 1 + MINT_FEE }(address(creatorCore1), 1, 1); - vm.stopPrank(); - } - - function testMintReserveSoldout() public { - vm.startPrank(creator); - - uint48 start = 0; - uint48 end = uint48(block.timestamp) + 2000; - - IERC1155GachaLazyClaim.ClaimParameters memory claimP = IERC1155GachaLazyClaim.ClaimParameters({ - storageProtocol: IGachaLazyClaim.StorageProtocol.ARWEAVE, - totalMax: 1, - startDate: start, - endDate: end, - tokenVariations: 5, - location: "arweaveHash1", - paymentReceiver: payable(creator), - cost: 1, - erc20: zeroAddress - }); - example.initializeClaim(address(creatorCore1), 1, claimP); - example.mintReserve{ value: 1 + MINT_FEE }(address(creatorCore1), 1, 1); - vm.stopPrank(); - - vm.startPrank(other); - vm.expectRevert(IGachaLazyClaim.ClaimSoldOut.selector); - example.mintReserve{ value: 1 + MINT_FEE }(address(creatorCore1), 1, 1); - vm.stopPrank(); - } - - function testMintReserveNone() public { - vm.startPrank(creator); - - uint48 start = 0; - uint48 end = uint48(block.timestamp) + 2000; - - IERC1155GachaLazyClaim.ClaimParameters memory claimP = IERC1155GachaLazyClaim.ClaimParameters({ - storageProtocol: IGachaLazyClaim.StorageProtocol.ARWEAVE, - totalMax: 1, - startDate: start, - endDate: end, - tokenVariations: 5, - location: "arweaveHash1", - paymentReceiver: payable(creator), - cost: 1, - erc20: zeroAddress - }); - example.initializeClaim(address(creatorCore1), 1, claimP); - vm.expectRevert(IGachaLazyClaim.InvalidMintCount.selector); - example.mintReserve{ value: 1 + MINT_FEE }(address(creatorCore1), 1, 0); - vm.stopPrank(); - } - - function testMintReserveTooMany() public { - vm.startPrank(creator); - - uint48 start = 0; - uint48 end = uint48(block.timestamp) + 2000; - - IERC1155GachaLazyClaim.ClaimParameters memory claimP = IERC1155GachaLazyClaim.ClaimParameters({ - storageProtocol: IGachaLazyClaim.StorageProtocol.ARWEAVE, - totalMax: 0, - startDate: start, - endDate: end, - tokenVariations: 2, - location: "arweaveHash1", - paymentReceiver: payable(creator), - cost: 1, - erc20: zeroAddress - }); - example.initializeClaim(address(creatorCore1), 1, claimP); - vm.stopPrank(); - vm.startPrank(other); - example.mintReserve{ value: 1 + MINT_FEE }(address(creatorCore1), 1, 1); - vm.stopPrank(); - - vm.startPrank(creator); - vm.expectRevert(IGachaLazyClaim.InvalidMintCount.selector); - example.mintReserve{ value: (1 + MINT_FEE) * MAX_UINT_32 }(address(creatorCore1), 1, MAX_UINT_32); - - // max out mints - example.mintReserve{ value: (1 + MINT_FEE) * (MAX_UINT_32 - 1) }(address(creatorCore1), 1, MAX_UINT_32 - 1); - - // try to mint one more - vm.expectRevert(IGachaLazyClaim.TooManyRequested.selector); - example.mintReserve{ value: 1 + MINT_FEE }(address(creatorCore1), 1, 1); - vm.stopPrank(); - } - - function testMintReserveDeliverTotalMax0() public { - vm.startPrank(creator); - - uint48 start = 0; - uint48 end = 0; - - IERC1155GachaLazyClaim.ClaimParameters memory claimP = IERC1155GachaLazyClaim.ClaimParameters({ - storageProtocol: IGachaLazyClaim.StorageProtocol.ARWEAVE, - totalMax: 0, - startDate: start, - endDate: end, - tokenVariations: 5, - location: "arweaveHash1", - paymentReceiver: payable(creator), - cost: 1, - erc20: zeroAddress - }); - example.initializeClaim(address(creatorCore1), 1, claimP); - vm.stopPrank(); - - // should be able to reserve mint even if totalMax is 0 - vm.startPrank(other); - example.mintReserve{ value: 1 + MINT_FEE }(address(creatorCore1), 1, 1); - vm.stopPrank(); - - vm.startPrank(signingAddress); - IGachaLazyClaim.ClaimMint[] memory mints = new IGachaLazyClaim.ClaimMint[](1); - IGachaLazyClaim.VariationMint[] memory variationMints = new IGachaLazyClaim.VariationMint[](1); - variationMints[0] = IGachaLazyClaim.VariationMint({ variationIndex: 1, amount: 1, recipient: other }); - mints[0] = IGachaLazyClaim.ClaimMint({ - creatorContractAddress: address(creatorCore1), - instanceId: 1, - variationMints: variationMints - }); - example.deliverMints(mints); - vm.stopPrank(); - } - - function testMintReserveMoreThanAvailable() public { - vm.startPrank(creator); - - uint48 start = 0; - uint48 end = uint48(block.timestamp) + 2000; - - uint256 collectorBalanceBefore = address(other).balance; - uint96 mintPrice = 1 ether; - - IERC1155GachaLazyClaim.ClaimParameters memory claimP = IERC1155GachaLazyClaim.ClaimParameters({ - storageProtocol: IGachaLazyClaim.StorageProtocol.ARWEAVE, - totalMax: 4, - startDate: start, - endDate: end, - tokenVariations: 5, - location: "arweaveHash1", - paymentReceiver: payable(creator), - cost: mintPrice, - erc20: zeroAddress - }); - example.initializeClaim(address(creatorCore1), 1, claimP); - example.mintReserve{ value: mintPrice + MINT_FEE }(address(creatorCore1), 1, 1); - uint256 creatorBalanceBefore = address(creator).balance; - // the manifoled fee is saved to the extension contract - uint256 extensionBalanceBefore = address(example).balance; - vm.stopPrank(); - - vm.startPrank(other); - example.mintReserve{ value: (mintPrice + MINT_FEE) * 5 }(address(creatorCore1), 1, 5); - vm.stopPrank(); - - //confirm user - GachaLazyClaim.UserMintDetails memory userMintDetails = example.getUserMints(other, address(creatorCore1), 1); - assertEq(userMintDetails.reservedCount, 3); - - //check payment balances: for creator and collector, difference should be for only three mints instead of 5 - uint creatorBalanceAfter = address(creator).balance; - assertEq(creatorBalanceAfter, creatorBalanceBefore + mintPrice * 3); - assertEq(address(other).balance, collectorBalanceBefore - ((mintPrice + MINT_FEE) * 3)); - assertEq(address(example).balance, extensionBalanceBefore + MINT_FEE * 3); - } - - function testInvalidSigner() public { - vm.startPrank(creator); - uint48 nowC = uint48(block.timestamp); - uint48 later = nowC + 1000; - - IERC1155GachaLazyClaim.ClaimParameters memory claimP = IERC1155GachaLazyClaim.ClaimParameters({ - storageProtocol: IGachaLazyClaim.StorageProtocol.ARWEAVE, - totalMax: 100, - startDate: nowC, - endDate: later, - tokenVariations: 5, - location: "arweaveHash1", - paymentReceiver: payable(creator), - cost: 1, - erc20: zeroAddress - }); - example.initializeClaim(address(creatorCore1), 1, claimP); - vm.stopPrank(); - - vm.startPrank(other); - IGachaLazyClaim.ClaimMint[] memory mints = new IGachaLazyClaim.ClaimMint[](1); - IGachaLazyClaim.VariationMint[] memory variationMints = new IGachaLazyClaim.VariationMint[](1); - variationMints[0] = IGachaLazyClaim.VariationMint({ variationIndex: 1, amount: 1, recipient: other }); - mints[0] = IGachaLazyClaim.ClaimMint({ - creatorContractAddress: address(creatorCore1), - instanceId: 1, - variationMints: variationMints - }); - - vm.expectRevert(); - example.deliverMints(mints); - vm.stopPrank(); - } - - function testDeliverMints() public { - vm.startPrank(creator); - - uint48 nowC = uint48(block.timestamp); - uint48 later = nowC + 1000; - - IERC1155GachaLazyClaim.ClaimParameters memory claimP = IERC1155GachaLazyClaim.ClaimParameters({ - storageProtocol: IGachaLazyClaim.StorageProtocol.ARWEAVE, - totalMax: 100, - startDate: nowC, - endDate: later, - tokenVariations: 5, - location: "arweaveHash1", - paymentReceiver: payable(creator), - cost: 1, - erc20: zeroAddress - }); - example.initializeClaim(address(creatorCore1), 1, claimP); - example.mintReserve{ value: (1 + MINT_FEE) * 2 }(address(creatorCore1), 1, 2); - vm.stopPrank(); - - vm.startPrank(other2); - example.mintReserve{ value: (1 + MINT_FEE) * 4 }(address(creatorCore1), 1, 4); - vm.stopPrank(); - - vm.startPrank(signingAddress); - IGachaLazyClaim.ClaimMint[] memory mints = new IGachaLazyClaim.ClaimMint[](2); - IGachaLazyClaim.VariationMint[] memory variationMints = new IGachaLazyClaim.VariationMint[](2); - variationMints[0] = IGachaLazyClaim.VariationMint({ variationIndex: 1, amount: 2, recipient: other2 }); - variationMints[1] = IGachaLazyClaim.VariationMint({ variationIndex: 2, amount: 1, recipient: other }); - mints[0] = IGachaLazyClaim.ClaimMint({ - creatorContractAddress: address(creatorCore1), - instanceId: 1, - variationMints: variationMints - }); - mints[1] = IGachaLazyClaim.ClaimMint({ - creatorContractAddress: address(creatorCore1), - instanceId: 1, - variationMints: variationMints - }); - // revert for receiver with no reserved mints - vm.expectRevert(IGachaLazyClaim.CannotMintMoreThanReserved.selector); - example.deliverMints(mints); - GachaLazyClaim.UserMintDetails memory otherMint = example.getUserMints(other, address(creatorCore1), 1); - assertEq(otherMint.reservedCount, 0); - assertEq(otherMint.deliveredCount, 0); - GachaLazyClaim.UserMintDetails memory other2Mint = example.getUserMints(other2, address(creatorCore1), 1); - assertEq(other2Mint.reservedCount, 4); - assertEq(other2Mint.deliveredCount, 0); - vm.stopPrank(); - - // deliver for valid receivers and mintCount - vm.startPrank(signingAddress); - variationMints[0] = IGachaLazyClaim.VariationMint({ variationIndex: 1, amount: 1, recipient: creator }); - variationMints[1] = IGachaLazyClaim.VariationMint({ variationIndex: 2, amount: 2, recipient: other2 }); - mints[0] = IGachaLazyClaim.ClaimMint({ - creatorContractAddress: address(creatorCore1), - instanceId: 1, - variationMints: variationMints - }); - mints[1] = IGachaLazyClaim.ClaimMint({ - creatorContractAddress: address(creatorCore1), - instanceId: 1, - variationMints: variationMints - }); - example.deliverMints(mints); - GachaLazyClaim.UserMintDetails memory creatorMints = example.getUserMints(creator, address(creatorCore1), 1); - assertEq(creatorMints.deliveredCount, 2); - assertEq(creatorMints.reservedCount, 2); - GachaLazyClaim.UserMintDetails memory other2Mints = example.getUserMints(other2, address(creatorCore1), 1); - assertEq(other2Mints.reservedCount, 4); - assertEq(other2Mints.deliveredCount, 4); - vm.stopPrank(); - } - - function testTokenURI() public { - vm.startPrank(creator); - uint48 nowC = uint48(block.timestamp); - uint48 later = nowC + 1000; - uint totalMintPrice = 1 + MINT_FEE; - - IERC1155GachaLazyClaim.ClaimParameters memory claimP = IERC1155GachaLazyClaim.ClaimParameters({ - storageProtocol: IGachaLazyClaim.StorageProtocol.ARWEAVE, - totalMax: 100, - startDate: nowC, - endDate: later, - tokenVariations: 5, - location: "arweaveHash1", - paymentReceiver: payable(creator), - cost: 1, - erc20: zeroAddress - }); - - example.initializeClaim(address(creatorCore1), 1, claimP); - example.mintReserve{ value: totalMintPrice }(address(creatorCore1), 1, 1); - - // mint in between on another extension - address[] memory receivers = new address[](1); - receivers[0] = signingAddress; - uint[] memory amounts = new uint[](1); - amounts[0] = 1; - string[] memory uris = new string[](1); - uris[0] = "0x0"; - creatorCore1.mintBaseNew(receivers, amounts, uris); - vm.stopPrank(); - - // mintreserving should have no effect - vm.startPrank(other); - example.mintReserve{ value: totalMintPrice * 2 }(address(creatorCore1), 1, 2); - vm.stopPrank(); - vm.startPrank(other2); - example.mintReserve{ value: totalMintPrice }(address(creatorCore1), 1, 1); - vm.stopPrank(); - - vm.startPrank(creator); - claimP.tokenVariations = 3; - claimP.location = "arweaveHash2"; - // create another gacha claim - example.initializeClaim(address(creatorCore1), 2, claimP); - vm.stopPrank(); - - assertEq("https://arweave.net/arweaveHash1/1", creatorCore1.uri(1)); - assertEq("https://arweave.net/arweaveHash1/2", creatorCore1.uri(2)); - assertEq("https://arweave.net/arweaveHash1/3", creatorCore1.uri(3)); - assertEq("https://arweave.net/arweaveHash1/4", creatorCore1.uri(4)); - assertEq("https://arweave.net/arweaveHash1/5", creatorCore1.uri(5)); - assertTrue( - keccak256(bytes("https://arweave.net/arweaveHash1/6")) != keccak256(bytes(creatorCore1.uri(6))), - "URI should not match the specified value." - ); - assertEq("https://arweave.net/arweaveHash2/1", creatorCore1.uri(7)); - assertEq("https://arweave.net/arweaveHash2/2", creatorCore1.uri(8)); - assertEq("https://arweave.net/arweaveHash2/3", creatorCore1.uri(9)); - } - - function testUpdateTokenURI() public { - vm.startPrank(creator); - uint48 nowC = uint48(block.timestamp); - uint48 later = nowC + 1000; - - IERC1155GachaLazyClaim.ClaimParameters memory claimP = IERC1155GachaLazyClaim.ClaimParameters({ - storageProtocol: IGachaLazyClaim.StorageProtocol.ARWEAVE, - totalMax: 100, - startDate: nowC, - endDate: later, - tokenVariations: 5, - location: "arweaveHash1", - paymentReceiver: payable(creator), - cost: 1, - erc20: zeroAddress - }); - example.initializeClaim(address(creatorCore1), 1, claimP); - - // tokens with original location - assertEq("https://arweave.net/arweaveHash1/1", creatorCore1.uri(1)); - assertEq("https://arweave.net/arweaveHash1/2", creatorCore1.uri(2)); - assertEq("https://arweave.net/arweaveHash1/3", creatorCore1.uri(3)); - assertEq("https://arweave.net/arweaveHash1/4", creatorCore1.uri(4)); - assertEq("https://arweave.net/arweaveHash1/5", creatorCore1.uri(5)); - - // update tokenURI - example.updateTokenURIParams(address(creatorCore1), 1, IGachaLazyClaim.StorageProtocol.ARWEAVE, "arweaveHashNEW"); - assertEq("https://arweave.net/arweaveHashNEW/1", creatorCore1.uri(1)); - assertEq("https://arweave.net/arweaveHashNEW/2", creatorCore1.uri(2)); - assertEq("https://arweave.net/arweaveHashNEW/3", creatorCore1.uri(3)); - assertEq("https://arweave.net/arweaveHashNEW/4", creatorCore1.uri(4)); - assertEq("https://arweave.net/arweaveHashNEW/5", creatorCore1.uri(5)); - vm.stopPrank(); - } -} diff --git a/packages/manifold/test/gacha/ERC1155Serendipity.t.sol b/packages/manifold/test/gacha/ERC1155Serendipity.t.sol new file mode 100644 index 0000000..085f0d5 --- /dev/null +++ b/packages/manifold/test/gacha/ERC1155Serendipity.t.sol @@ -0,0 +1,1143 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import "../../contracts/gachaclaims/IERC1155Serendipity.sol"; +import "../../contracts/gachaclaims/ERC1155Serendipity.sol"; +import "../../contracts/gachaclaims/ISerendipity.sol"; +import "../../contracts/gachaclaims/Serendipity.sol"; + +import "@manifoldxyz/creator-core-solidity/contracts/ERC1155Creator.sol"; +import "@openzeppelin/contracts/utils/math/SafeMath.sol"; +import "../mocks/Mock.sol"; + +contract ERC1155SerendipityTest is Test { + using SafeMath for uint256; + + ERC1155Serendipity public example; + ERC1155 public erc1155; + ERC1155Creator public creatorCore1; + ERC1155Creator public creatorCore2; + + address public creator = 0xc78Dc443c126af6E4f6Ed540c1e740C1b5be09cd; + address public owner = 0x6140F00e4Ff3936702E68744f2b5978885464cbB; + address public signingAddress = 0xc78dC443c126Af6E4f6eD540C1E740c1B5be09CE; + address public other = 0x5174cD462b60c536eb51D4ceC1D561D3Ea31004F; + address public other2 = 0x80AAC46bbd3C2FcE33681541a52CacBEd14bF425; + + address public zeroAddress = address(0); + + uint256 privateKey = 0x1010101010101010101010101010101010101010101010101010101010101010; + + uint32 MAX_UINT_32 = 0xffffffff; + uint256 public constant MINT_FEE = 500000000000000; + + // Test setup + function setUp() public { + vm.startPrank(creator); + creatorCore1 = new ERC1155Creator("Token1", "NFT1"); + creatorCore2 = new ERC1155Creator("Token2", "NFT2"); + vm.stopPrank(); + + vm.startPrank(owner); + example = new ERC1155Serendipity(owner); + example.setSigner(address(signingAddress)); + vm.stopPrank(); + + vm.startPrank(creator); + creatorCore1.registerExtension(address(example), "override"); + creatorCore2.registerExtension(address(example), "override"); + vm.stopPrank(); + + vm.deal(creator, 2147483647500004294967295); + vm.deal(other, 10 ether); + vm.deal(other2, 10 ether); + } + + function testAccess() public { + vm.startPrank(other); + // Must be admin + vm.expectRevert(); + example.withdraw(payable(other), 20); + vm.expectRevert("AdminControl: Must be owner or admin"); + example.setSigner(other); + // Must be admin + vm.expectRevert(); + + uint48 nowC = uint48(block.timestamp); + uint48 later = nowC + 1000; + + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.IPFS, + totalMax: 100, + startDate: nowC, + endDate: later, + tokenVariations: 5, + location: "arweaveHash1", + paymentReceiver: payable(creator), + cost: 0.01 ether, + erc20: zeroAddress + }); + // Must be admin + vm.expectRevert(); + example.initializeClaim(address(creatorCore1), 1, claimP); + // Succeeds because is admin + vm.stopPrank(); + vm.startPrank(creator); + example.initializeClaim(address(creatorCore1), 1, claimP); + // Try as a different non admin + vm.stopPrank(); + vm.startPrank(other2); + vm.expectRevert(); + example.initializeClaim(address(creatorCore1), 2, claimP); + vm.expectRevert(); + + vm.stopPrank(); + } + + function testInitializeClaimSanitization() public { + vm.startPrank(creator); + + uint48 nowC = uint48(block.timestamp); + uint48 later = nowC + 1000; + + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.INVALID, + location: "arweaveHash1", + totalMax: 100, + startDate: nowC, + endDate: later, + tokenVariations: 5, + paymentReceiver: payable(other), + cost: 1, + erc20: zeroAddress + }); + + vm.expectRevert(ISerendipity.InvalidStorageProtocol.selector); + example.initializeClaim(address(creatorCore1), 1, claimP); + + claimP.storageProtocol = ISerendipity.StorageProtocol.ARWEAVE; + claimP.startDate = nowC + 2000; + vm.expectRevert(ISerendipity.InvalidDate.selector); + example.initializeClaim(address(creatorCore1), 1, claimP); + + // successful initialization with no end date + claimP.endDate = 0; + example.initializeClaim(address(creatorCore1), 1, claimP); + + // successful initialization with no start date + claimP.startDate = 0; + claimP.endDate = later; + example.initializeClaim(address(creatorCore1), 2, claimP); + + // successful initialization with no start or end date + claimP.endDate = 0; + example.initializeClaim(address(creatorCore1), 3, claimP); + vm.stopPrank(); + } + + function test_InitializeClaimWithUnlimitedSupply() public { + vm.startPrank(creator); + // init some arguments for mint base new + address[] memory receivers = new address[](1); + receivers[0] = other2; + uint[] memory amounts = new uint[](1); + amounts[0] = 5; + string[] memory uris = new string[](1); + uris[0] = "some hash"; + // premint some tokens + creatorCore1.mintBaseNew(receivers, amounts, uris); + + uint48 nowC = uint48(block.timestamp); + uint48 later = nowC + 1000; + + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + location: "arweaveHash1", + totalMax: 0, + startDate: nowC, + endDate: later, + tokenVariations: 5, + paymentReceiver: payable(other), + cost: 1, + erc20: zeroAddress + }); + + example.initializeClaim(address(creatorCore1), 1, claimP); + vm.stopPrank(); + + vm.startPrank(other); + IERC1155Serendipity.Claim memory claim = example.getClaim(address(creatorCore1), 1); + // check equality of claim parameters + assertEq(uint(claim.storageProtocol), 2); + assertEq(claim.location, "arweaveHash1"); + assertEq(claim.total, 0); + assertEq(claim.totalMax, 0); + assertEq(claim.startDate, nowC); + assertEq(claim.endDate, later); + assertEq(claim.tokenVariations, 5); + assertEq(claim.paymentReceiver, payable(other)); + assertEq(claim.cost, 1); + assertEq(claim.erc20, zeroAddress); + assertEq(claim.startingTokenId, 2); + + vm.stopPrank(); + } + + function test_UpdateClaim_LimitedSupplySanitization() public { + vm.startPrank(creator); + + uint48 nowC = uint48(block.timestamp); + uint48 later = nowC + 1000; + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.IPFS, + location: "arweaveHash1", + totalMax: 100, + startDate: nowC, + endDate: later, + tokenVariations: 5, + paymentReceiver: payable(other), + cost: 1, + erc20: zeroAddress + }); + example.initializeClaim(address(creatorCore1), 1, claimP); + + IERC1155Serendipity.UpdateClaimParameters memory claimU = IERC1155Serendipity.UpdateClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + totalMax: 100, + startDate: nowC, + endDate: later, + location: "arweaveHash1", + paymentReceiver: payable(creator), + cost: 1 + }); + + example.mintReserve{ value: 1 + MINT_FEE }(address(creatorCore1), 1, 1); + + claimU.storageProtocol = ISerendipity.StorageProtocol.INVALID; + vm.expectRevert(ISerendipity.InvalidStorageProtocol.selector); + example.updateClaim(address(creatorCore1), 1, claimU); + + // reset to valid storage protocol + claimU.storageProtocol = ISerendipity.StorageProtocol.ARWEAVE; + claimU.startDate = nowC + 2000; + vm.expectRevert(ISerendipity.InvalidDate.selector); + example.updateClaim(address(creatorCore1), 1, claimU); + + claimU.endDate = nowC; + vm.expectRevert(ISerendipity.InvalidDate.selector); + example.updateClaim(address(creatorCore1), 1, claimU); + claimU.endDate = later; + + //successful data and cost update + claimU.cost = 2; + claimU.storageProtocol = ISerendipity.StorageProtocol.IPFS; + claimU.startDate = nowC + 1000; + claimU.endDate = later + 3000; + example.updateClaim(address(creatorCore1), 1, claimU); + IERC1155Serendipity.Claim memory claim = example.getClaim(address(creatorCore1), 1); + assertEq(claim.cost, 2); + // storage protocol for IPFS is 3 + assertEq(uint(claim.storageProtocol), 3); + assertEq(claim.startDate, nowC + 1000); + assertEq(claim.endDate, later + 3000); + + // able to update to no end or start date + claimU.startDate = 0; + claimU.endDate = 0; + example.updateClaim(address(creatorCore1), 1, claimU); + claim = example.getClaim(address(creatorCore1), 1); + assertEq(claim.startDate, 0); + assertEq(claim.endDate, 0); + + claimU.startDate = nowC; + claimU.endDate = 0; + example.updateClaim(address(creatorCore1), 1, claimU); + claim = example.getClaim(address(creatorCore1), 1); + assertEq(claim.startDate, nowC); + assertEq(claim.endDate, 0); + + // update location + claimU.location = "newArweaveHash"; + example.updateClaim(address(creatorCore1), 1, claimU); + claim = example.getClaim(address(creatorCore1), 1); + assertEq(claim.location, "newArweaveHash"); + vm.stopPrank(); + } + + function test_UpdateClaim_UnlimitedSupplySanitization() public { + vm.startPrank(creator); + + uint48 nowC = uint48(block.timestamp); + uint48 later = nowC + 1000; + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.IPFS, + location: "arweaveHash1", + totalMax: 0, + startDate: nowC, + endDate: later, + tokenVariations: 5, + paymentReceiver: payable(other), + cost: 1, + erc20: zeroAddress + }); + example.initializeClaim(address(creatorCore1), 1, claimP); + + IERC1155Serendipity.UpdateClaimParameters memory claimU = IERC1155Serendipity.UpdateClaimParameters({ + totalMax: 0, + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + startDate: nowC, + endDate: later, + location: "arweaveHash1", + paymentReceiver: payable(creator), + cost: 1 + }); + + example.mintReserve{ value: 1 + MINT_FEE }(address(creatorCore1), 1, 1); + + claimU.storageProtocol = ISerendipity.StorageProtocol.INVALID; + vm.expectRevert(ISerendipity.InvalidStorageProtocol.selector); + example.updateClaim(address(creatorCore1), 1, claimU); + + // reset to valid storage protocol + claimU.storageProtocol = ISerendipity.StorageProtocol.ARWEAVE; + claimU.startDate = nowC + 2000; + vm.expectRevert(ISerendipity.InvalidDate.selector); + example.updateClaim(address(creatorCore1), 1, claimU); + + claimU.endDate = nowC; + vm.expectRevert(ISerendipity.InvalidDate.selector); + example.updateClaim(address(creatorCore1), 1, claimU); + claimU.endDate = later; + + //successful data and cost update + claimU.cost = 2; + claimU.storageProtocol = ISerendipity.StorageProtocol.IPFS; + claimU.startDate = nowC + 1000; + claimU.endDate = later + 3000; + example.updateClaim(address(creatorCore1), 1, claimU); + IERC1155Serendipity.Claim memory claim = example.getClaim(address(creatorCore1), 1); + assertEq(claim.cost, 2); + // storage protocol for IPFS is 3 + assertEq(uint(claim.storageProtocol), 3); + assertEq(claim.startDate, nowC + 1000); + assertEq(claim.endDate, later + 3000); + + // able to update to no end or start date + claimU.startDate = 0; + claimU.endDate = 0; + example.updateClaim(address(creatorCore1), 1, claimU); + claim = example.getClaim(address(creatorCore1), 1); + assertEq(claim.startDate, 0); + assertEq(claim.endDate, 0); + + claimU.startDate = nowC; + claimU.endDate = 0; + example.updateClaim(address(creatorCore1), 1, claimU); + claim = example.getClaim(address(creatorCore1), 1); + assertEq(claim.startDate, nowC); + assertEq(claim.endDate, 0); + + // update location + claimU.location = "newArweaveHash"; + example.updateClaim(address(creatorCore1), 1, claimU); + claim = example.getClaim(address(creatorCore1), 1); + assertEq(claim.location, "newArweaveHash"); + vm.stopPrank(); + } + + function test_UpdateClaim_SwitchTokenSupplyWhenNoMints() public { + vm.startPrank(creator); + + uint48 nowC = uint48(block.timestamp); + uint48 later = nowC + 1000; + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + location: "arweaveHash1", + totalMax: 100, + startDate: nowC, + endDate: later, + tokenVariations: 5, + paymentReceiver: payable(other), + cost: 1, + erc20: zeroAddress + }); + example.initializeClaim(address(creatorCore1), 1, claimP); + + IERC1155Serendipity.UpdateClaimParameters memory claimU = IERC1155Serendipity.UpdateClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + totalMax: 0, + startDate: nowC, + endDate: later, + location: "arweaveHash1", + paymentReceiver: payable(other), + cost: 1 + }); + + // when mint count is 0, change from limited (100) -> unlimited (0) supply + example.updateClaim(address(creatorCore1), 1, claimU); + IERC1155Serendipity.Claim memory claim = example.getClaim(address(creatorCore1), 1); + assertEq(claim.total, 0, "total should be 0"); + assertEq(claim.totalMax, 0, "totalMax should be updated to 0"); + + // update from unlimited (0) -> limited (5) supply + claimU.totalMax = 5; + example.updateClaim(address(creatorCore1), 1, claimU); + claim = example.getClaim(address(creatorCore1), 1); + assertEq(claim.total, 0, "total should be 0"); + assertEq(claim.totalMax, 5, "totalMax should be updated to 5"); + + // update from limited (5) -> limited (1000) supply + claimU.totalMax = 1000; + example.updateClaim(address(creatorCore1), 1, claimU); + claim = example.getClaim(address(creatorCore1), 1); + assertEq(claim.total, 0, "total should be 0"); + assertEq(claim.totalMax, 1000, "totalMax should be updated to 5"); + + vm.stopPrank(); + } + + function test_UpdateClaim_ChangeSupplyForLimitedInitialSupply() public { + vm.startPrank(creator); + + uint48 nowC = uint48(block.timestamp); + uint48 later = nowC + 1000; + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + location: "arweaveHash1", + totalMax: 100, + startDate: nowC, + endDate: later, + tokenVariations: 5, + paymentReceiver: payable(other), + cost: 1, + erc20: zeroAddress + }); + example.initializeClaim(address(creatorCore1), 1, claimP); + example.mintReserve{ value: 5 + 5*MINT_FEE }(address(creatorCore1), 1, 5); + + IERC1155Serendipity.Claim memory claim = example.getClaim(address(creatorCore1), 1); + // sanity setup + assertEq(claim.totalMax, 100, "totalMax should be 100"); + assertEq(claim.total, 5, "total should be 5"); + + IERC1155Serendipity.UpdateClaimParameters memory claimU = IERC1155Serendipity.UpdateClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + totalMax: 5, + startDate: nowC, + endDate: later, + location: "arweaveHash1", + paymentReceiver: payable(other), + cost: 1 + }); + + + // update when minted count is 5 + claimU.totalMax = 4; + vm.expectRevert(ISerendipity.CannotLowerTotalMaxBeyondTotal.selector); + example.updateClaim(address(creatorCore1), 1, claimU); + + // confirm can set total max to minted count + claimU.totalMax = 5; + example.updateClaim(address(creatorCore1), 1, claimU); + claim = example.getClaim(address(creatorCore1), 1); + assertEq(claim.totalMax, 5, "totalMax should be updated to 5"); + assertEq(claim.total, 5, "total should be 5"); + // confirm nothing else changed + assertEq(uint(claim.storageProtocol), uint(ISerendipity.StorageProtocol.ARWEAVE)); + assertEq(claim.location, "arweaveHash1"); + assertEq(claim.startDate, nowC); + assertEq(claim.endDate, later); + assertEq(claim.tokenVariations, 5); + assertEq(claim.paymentReceiver, payable(other)); + assertEq(claim.cost, 1); + assertEq(claim.erc20, zeroAddress); + + // confirm can set total max to > minted count + claimU.totalMax = 10; + example.updateClaim(address(creatorCore1), 1, claimU); + claim = example.getClaim(address(creatorCore1), 1); + assertEq(claim.totalMax, 10, "totalMax should be updated to 10"); + assertEq(claim.total, 5, "total should be 5"); + + // confirm can set supply to unlimited (0) + claimU.totalMax = 0; + example.updateClaim(address(creatorCore1), 1, claimU); + claim = example.getClaim(address(creatorCore1), 1); + assertEq(claim.totalMax, 0, "totalMax should be updated to 0"); + assertEq(claim.total, 5, "total should be 5"); + + vm.stopPrank(); + } + + function test_UpdateClaim_ChangeSupplyForUnlimitedInitialSupply() public { + vm.startPrank(creator); + + uint48 nowC = uint48(block.timestamp); + uint48 later = nowC + 1000; + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + location: "arweaveHash1", + totalMax: 0, + startDate: nowC, + endDate: later, + tokenVariations: 5, + paymentReceiver: payable(other), + cost: 1, + erc20: zeroAddress + }); + example.initializeClaim(address(creatorCore1), 1, claimP); + example.mintReserve{ value: 5 + 5*MINT_FEE }(address(creatorCore1), 1, 5); + + IERC1155Serendipity.Claim memory claim = example.getClaim(address(creatorCore1), 1); + // sanity setup + assertEq(claim.totalMax, 0, "totalMax should be 0 (unlimited)"); + assertEq(claim.total, 5, "total should be 5"); + + IERC1155Serendipity.UpdateClaimParameters memory claimU = IERC1155Serendipity.UpdateClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + totalMax: 5, + startDate: nowC, + endDate: later, + location: "arweaveHash1", + paymentReceiver: payable(other), + cost: 1 + }); + + + // update when minted count is 5 + claimU.totalMax = 4; + vm.expectRevert(ISerendipity.CannotLowerTotalMaxBeyondTotal.selector); + example.updateClaim(address(creatorCore1), 1, claimU); + + // confirm can set total max to minted count + claimU.totalMax = 5; + example.updateClaim(address(creatorCore1), 1, claimU); + claim = example.getClaim(address(creatorCore1), 1); + assertEq(claim.totalMax, 5, "totalMax should be updated to 5"); + assertEq(claim.total, 5, "total should be 5"); + // confirm nothing else changed + assertEq(uint(claim.storageProtocol), uint(ISerendipity.StorageProtocol.ARWEAVE)); + assertEq(claim.location, "arweaveHash1"); + assertEq(claim.startDate, nowC); + assertEq(claim.endDate, later); + assertEq(claim.tokenVariations, 5); + assertEq(claim.paymentReceiver, payable(other)); + assertEq(claim.cost, 1); + assertEq(claim.erc20, zeroAddress); + + // confirm can set total max to > minted count + claimU.totalMax = 10; + example.updateClaim(address(creatorCore1), 1, claimU); + claim = example.getClaim(address(creatorCore1), 1); + assertEq(claim.totalMax, 10, "totalMax should be updated to 10"); + assertEq(claim.total, 5, "total should be 5"); + + // other cases covered by change supply for limited initial supply + vm.stopPrank(); + } + + function testFail_UpdateClaim_NotClaimOwnerUpdate() public { + vm.startPrank(creator); + + uint48 nowC = uint48(block.timestamp); + uint48 later = nowC + 1000; + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.IPFS, + location: "arweaveHash1", + totalMax: 5, + startDate: nowC, + endDate: later, + tokenVariations: 5, + paymentReceiver: payable(creator), + cost: 1, + erc20: zeroAddress + }); + example.initializeClaim(address(creatorCore1), 1, claimP); + vm.stopPrank(); + + vm.startPrank(other); + IERC1155Serendipity.UpdateClaimParameters memory claimU = IERC1155Serendipity.UpdateClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + totalMax: 10, + startDate: nowC, + endDate: later, + location: "arweaveHash1", + paymentReceiver: payable(other), + cost: 1 + }); + example.updateClaim(address(creatorCore1), 1, claimU); + vm.stopPrank(); + } + + function testMintReserveLowPayment() public { + vm.startPrank(creator); + + uint48 nowC = 0; + uint48 later = uint48(block.timestamp) + 2000; + + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + totalMax: 100, + startDate: nowC, + endDate: later, + tokenVariations: 5, + location: "arweaveHash1", + paymentReceiver: payable(creator), + cost: 1, + erc20: zeroAddress + }); + + example.initializeClaim(address(creatorCore1), 1, claimP); + + // Insufficient payment + vm.expectRevert(ISerendipity.InvalidPayment.selector); + example.mintReserve{ value: 1 }(address(creatorCore1), 1, 2); + + vm.stopPrank(); + } + + function testMintReserveEarly() public { + // claim hasn't started yet + vm.startPrank(creator); + + uint48 start = uint48(block.timestamp) + 2000; + uint48 end = 0; + + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + totalMax: 100, + startDate: start, + endDate: end, + tokenVariations: 5, + location: "arweaveHash1", + paymentReceiver: payable(creator), + cost: 1, + erc20: zeroAddress + }); + example.initializeClaim(address(creatorCore1), 1, claimP); + vm.expectRevert(ISerendipity.ClaimInactive.selector); + example.mintReserve{ value: 3 }(address(creatorCore1), 1, 1); + vm.stopPrank(); + } + + function testMintReserveLate() public { + vm.startPrank(creator); + + uint48 start = 0; + uint48 end = uint48(block.timestamp.sub(1)); + + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + totalMax: 100, + startDate: start, + endDate: end, + tokenVariations: 5, + location: "arweaveHash1", + paymentReceiver: payable(creator), + cost: 1, + erc20: zeroAddress + }); + example.initializeClaim(address(creatorCore1), 1, claimP); + vm.stopPrank(); + + vm.startPrank(other); + example.mintReserve{ value: 1 + MINT_FEE }(address(creatorCore1), 1, 1); + vm.stopPrank(); + } + + function testMintReserveSoldout() public { + vm.startPrank(creator); + + uint48 start = 0; + uint48 end = uint48(block.timestamp) + 2000; + + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + totalMax: 1, + startDate: start, + endDate: end, + tokenVariations: 5, + location: "arweaveHash1", + paymentReceiver: payable(creator), + cost: 1, + erc20: zeroAddress + }); + example.initializeClaim(address(creatorCore1), 1, claimP); + example.mintReserve{ value: 1 + MINT_FEE }(address(creatorCore1), 1, 1); + vm.stopPrank(); + + vm.startPrank(other); + vm.expectRevert(ISerendipity.ClaimSoldOut.selector); + example.mintReserve{ value: 1 + MINT_FEE }(address(creatorCore1), 1, 1); + vm.stopPrank(); + } + + function testMintReserveNone() public { + vm.startPrank(creator); + + uint48 start = 0; + uint48 end = uint48(block.timestamp) + 2000; + + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + totalMax: 1, + startDate: start, + endDate: end, + tokenVariations: 5, + location: "arweaveHash1", + paymentReceiver: payable(creator), + cost: 1, + erc20: zeroAddress + }); + example.initializeClaim(address(creatorCore1), 1, claimP); + vm.expectRevert(ISerendipity.InvalidMintCount.selector); + example.mintReserve{ value: 1 + MINT_FEE }(address(creatorCore1), 1, 0); + vm.stopPrank(); + } + + function testMintReserveTooMany() public { + vm.startPrank(creator); + + uint48 start = 0; + uint48 end = uint48(block.timestamp) + 2000; + + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + totalMax: 0, + startDate: start, + endDate: end, + tokenVariations: 2, + location: "arweaveHash1", + paymentReceiver: payable(creator), + cost: 1, + erc20: zeroAddress + }); + example.initializeClaim(address(creatorCore1), 1, claimP); + vm.stopPrank(); + vm.startPrank(other); + example.mintReserve{ value: 1 + MINT_FEE }(address(creatorCore1), 1, 1); + vm.stopPrank(); + + vm.startPrank(creator); + vm.expectRevert(ISerendipity.InvalidMintCount.selector); + example.mintReserve{ value: (1 + MINT_FEE) * MAX_UINT_32 }(address(creatorCore1), 1, MAX_UINT_32); + + // max out mints + example.mintReserve{ value: (1 + MINT_FEE) * (MAX_UINT_32 - 1) }(address(creatorCore1), 1, MAX_UINT_32 - 1); + + // try to mint one more + vm.expectRevert(ISerendipity.TooManyRequested.selector); + example.mintReserve{ value: 1 + MINT_FEE }(address(creatorCore1), 1, 1); + vm.stopPrank(); + } + + function testMintReserveDeliverTotalMax0() public { + vm.startPrank(creator); + + uint48 start = 0; + uint48 end = 0; + + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + totalMax: 0, + startDate: start, + endDate: end, + tokenVariations: 5, + location: "arweaveHash1", + paymentReceiver: payable(creator), + cost: 1, + erc20: zeroAddress + }); + example.initializeClaim(address(creatorCore1), 1, claimP); + vm.stopPrank(); + + // should be able to reserve mint even if totalMax is 0 + vm.startPrank(other); + example.mintReserve{ value: 1 + MINT_FEE }(address(creatorCore1), 1, 1); + vm.stopPrank(); + + vm.startPrank(signingAddress); + ISerendipity.ClaimMint[] memory mints = new ISerendipity.ClaimMint[](1); + ISerendipity.VariationMint[] memory variationMints = new ISerendipity.VariationMint[](1); + variationMints[0] = ISerendipity.VariationMint({ variationIndex: 1, amount: 1, recipient: other }); + mints[0] = ISerendipity.ClaimMint({ + creatorContractAddress: address(creatorCore1), + instanceId: 1, + variationMints: variationMints + }); + example.deliverMints(mints); + vm.stopPrank(); + } + + function testMintReserveMoreThanAvailable() public { + vm.startPrank(creator); + + uint48 start = 0; + uint48 end = uint48(block.timestamp) + 2000; + + uint256 collectorBalanceBefore = address(other).balance; + uint96 mintPrice = 1 ether; + + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + totalMax: 4, + startDate: start, + endDate: end, + tokenVariations: 5, + location: "arweaveHash1", + paymentReceiver: payable(creator), + cost: mintPrice, + erc20: zeroAddress + }); + example.initializeClaim(address(creatorCore1), 1, claimP); + example.mintReserve{ value: mintPrice + MINT_FEE }(address(creatorCore1), 1, 1); + uint256 creatorBalanceBefore = address(creator).balance; + // the manifoled fee is saved to the extension contract + uint256 extensionBalanceBefore = address(example).balance; + vm.stopPrank(); + + vm.startPrank(other); + example.mintReserve{ value: (mintPrice + MINT_FEE) * 5 }(address(creatorCore1), 1, 5); + vm.stopPrank(); + + //confirm user + Serendipity.UserMintDetails memory userMintDetails = example.getUserMints(other, address(creatorCore1), 1); + assertEq(userMintDetails.reservedCount, 3); + + //check payment balances: for creator and collector, difference should be for only three mints instead of 5 + uint creatorBalanceAfter = address(creator).balance; + assertEq(creatorBalanceAfter, creatorBalanceBefore + mintPrice * 3); + assertEq(address(other).balance, collectorBalanceBefore - ((mintPrice + MINT_FEE) * 3)); + assertEq(address(example).balance, extensionBalanceBefore + MINT_FEE * 3); + } + + function testInvalidSigner() public { + vm.startPrank(creator); + uint48 nowC = uint48(block.timestamp); + uint48 later = nowC + 1000; + + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + totalMax: 100, + startDate: nowC, + endDate: later, + tokenVariations: 5, + location: "arweaveHash1", + paymentReceiver: payable(creator), + cost: 1, + erc20: zeroAddress + }); + example.initializeClaim(address(creatorCore1), 1, claimP); + vm.stopPrank(); + + vm.startPrank(other); + ISerendipity.ClaimMint[] memory mints = new ISerendipity.ClaimMint[](1); + ISerendipity.VariationMint[] memory variationMints = new ISerendipity.VariationMint[](1); + variationMints[0] = ISerendipity.VariationMint({ variationIndex: 1, amount: 1, recipient: other }); + mints[0] = ISerendipity.ClaimMint({ + creatorContractAddress: address(creatorCore1), + instanceId: 1, + variationMints: variationMints + }); + + vm.expectRevert(); + example.deliverMints(mints); + vm.stopPrank(); + } + + function testDeliverMints() public { + vm.startPrank(creator); + + uint48 nowC = uint48(block.timestamp); + uint48 later = nowC + 1000; + + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + totalMax: 100, + startDate: nowC, + endDate: later, + tokenVariations: 5, + location: "arweaveHash1", + paymentReceiver: payable(creator), + cost: 1, + erc20: zeroAddress + }); + example.initializeClaim(address(creatorCore1), 1, claimP); + example.mintReserve{ value: (1 + MINT_FEE) * 2 }(address(creatorCore1), 1, 2); + vm.stopPrank(); + + vm.startPrank(other2); + example.mintReserve{ value: (1 + MINT_FEE) * 4 }(address(creatorCore1), 1, 4); + vm.stopPrank(); + + vm.startPrank(signingAddress); + ISerendipity.ClaimMint[] memory mints = new ISerendipity.ClaimMint[](2); + ISerendipity.VariationMint[] memory variationMints = new ISerendipity.VariationMint[](2); + variationMints[0] = ISerendipity.VariationMint({ variationIndex: 1, amount: 2, recipient: other2 }); + variationMints[1] = ISerendipity.VariationMint({ variationIndex: 2, amount: 1, recipient: other }); + mints[0] = ISerendipity.ClaimMint({ + creatorContractAddress: address(creatorCore1), + instanceId: 1, + variationMints: variationMints + }); + mints[1] = ISerendipity.ClaimMint({ + creatorContractAddress: address(creatorCore1), + instanceId: 1, + variationMints: variationMints + }); + // revert for receiver with no reserved mints + vm.expectRevert(ISerendipity.CannotMintMoreThanReserved.selector); + example.deliverMints(mints); + Serendipity.UserMintDetails memory otherMint = example.getUserMints(other, address(creatorCore1), 1); + assertEq(otherMint.reservedCount, 0); + assertEq(otherMint.deliveredCount, 0); + Serendipity.UserMintDetails memory other2Mint = example.getUserMints(other2, address(creatorCore1), 1); + assertEq(other2Mint.reservedCount, 4); + assertEq(other2Mint.deliveredCount, 0); + vm.stopPrank(); + + // deliver for valid receivers and mintCount + vm.startPrank(signingAddress); + variationMints[0] = ISerendipity.VariationMint({ variationIndex: 1, amount: 1, recipient: creator }); + variationMints[1] = ISerendipity.VariationMint({ variationIndex: 2, amount: 2, recipient: other2 }); + mints[0] = ISerendipity.ClaimMint({ + creatorContractAddress: address(creatorCore1), + instanceId: 1, + variationMints: variationMints + }); + mints[1] = ISerendipity.ClaimMint({ + creatorContractAddress: address(creatorCore1), + instanceId: 1, + variationMints: variationMints + }); + example.deliverMints(mints); + Serendipity.UserMintDetails memory creatorMints = example.getUserMints(creator, address(creatorCore1), 1); + assertEq(creatorMints.deliveredCount, 2); + assertEq(creatorMints.reservedCount, 2); + Serendipity.UserMintDetails memory other2Mints = example.getUserMints(other2, address(creatorCore1), 1); + assertEq(other2Mints.reservedCount, 4); + assertEq(other2Mints.deliveredCount, 4); + vm.stopPrank(); + } + + function testTokenURI() public { + vm.startPrank(creator); + uint48 nowC = uint48(block.timestamp); + uint48 later = nowC + 1000; + uint totalMintPrice = 1 + MINT_FEE; + + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + totalMax: 100, + startDate: nowC, + endDate: later, + tokenVariations: 5, + location: "arweaveHash1", + paymentReceiver: payable(creator), + cost: 1, + erc20: zeroAddress + }); + + example.initializeClaim(address(creatorCore1), 1, claimP); + example.mintReserve{ value: totalMintPrice }(address(creatorCore1), 1, 1); + + // mint in between on another extension + address[] memory receivers = new address[](1); + receivers[0] = signingAddress; + uint[] memory amounts = new uint[](1); + amounts[0] = 1; + string[] memory uris = new string[](1); + uris[0] = "0x0"; + creatorCore1.mintBaseNew(receivers, amounts, uris); + vm.stopPrank(); + + // mintreserving should have no effect + vm.startPrank(other); + example.mintReserve{ value: totalMintPrice * 2 }(address(creatorCore1), 1, 2); + vm.stopPrank(); + vm.startPrank(other2); + example.mintReserve{ value: totalMintPrice }(address(creatorCore1), 1, 1); + vm.stopPrank(); + + vm.startPrank(creator); + claimP.tokenVariations = 3; + claimP.location = "arweaveHash2"; + // create another claim + example.initializeClaim(address(creatorCore1), 2, claimP); + vm.stopPrank(); + + assertEq("https://arweave.net/arweaveHash1/1", creatorCore1.uri(1)); + assertEq("https://arweave.net/arweaveHash1/2", creatorCore1.uri(2)); + assertEq("https://arweave.net/arweaveHash1/3", creatorCore1.uri(3)); + assertEq("https://arweave.net/arweaveHash1/4", creatorCore1.uri(4)); + assertEq("https://arweave.net/arweaveHash1/5", creatorCore1.uri(5)); + assertTrue( + keccak256(bytes("https://arweave.net/arweaveHash1/6")) != keccak256(bytes(creatorCore1.uri(6))), + "URI should not match the specified value." + ); + assertEq("https://arweave.net/arweaveHash2/1", creatorCore1.uri(7)); + assertEq("https://arweave.net/arweaveHash2/2", creatorCore1.uri(8)); + assertEq("https://arweave.net/arweaveHash2/3", creatorCore1.uri(9)); + } + + function testUpdateTokenURI() public { + vm.startPrank(creator); + uint48 nowC = uint48(block.timestamp); + uint48 later = nowC + 1000; + + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + totalMax: 100, + startDate: nowC, + endDate: later, + tokenVariations: 5, + location: "arweaveHash1", + paymentReceiver: payable(creator), + cost: 1, + erc20: zeroAddress + }); + example.initializeClaim(address(creatorCore1), 1, claimP); + + // tokens with original location + assertEq("https://arweave.net/arweaveHash1/1", creatorCore1.uri(1)); + assertEq("https://arweave.net/arweaveHash1/2", creatorCore1.uri(2)); + assertEq("https://arweave.net/arweaveHash1/3", creatorCore1.uri(3)); + assertEq("https://arweave.net/arweaveHash1/4", creatorCore1.uri(4)); + assertEq("https://arweave.net/arweaveHash1/5", creatorCore1.uri(5)); + + // update tokenURI + example.updateTokenURIParams(address(creatorCore1), 1, ISerendipity.StorageProtocol.ARWEAVE, "arweaveHashNEW"); + assertEq("https://arweave.net/arweaveHashNEW/1", creatorCore1.uri(1)); + assertEq("https://arweave.net/arweaveHashNEW/2", creatorCore1.uri(2)); + assertEq("https://arweave.net/arweaveHashNEW/3", creatorCore1.uri(3)); + assertEq("https://arweave.net/arweaveHashNEW/4", creatorCore1.uri(4)); + assertEq("https://arweave.net/arweaveHashNEW/5", creatorCore1.uri(5)); + vm.stopPrank(); + } + + function test_RevertWhen_InitializeClaimOnDeprecated() public { + vm.startPrank(owner); + example.deprecate(true); + vm.stopPrank(); + + vm.startPrank(creator); + uint48 nowC = uint48(block.timestamp); + uint48 later = nowC + 1000; + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.IPFS, + location: "arweaveHash1", + totalMax: 0, + startDate: nowC, + endDate: later, + tokenVariations: 5, + paymentReceiver: payable(other), + cost: 1, + erc20: zeroAddress + }); + vm.expectRevert(ISerendipity.ContractDeprecated.selector); + example.initializeClaim(address(creatorCore1), 1, claimP); + vm.stopPrank(); + + vm.startPrank(owner); + example.deprecate(false); + vm.stopPrank(); + + vm.startPrank(creator); + // can initialize claim after deprecation removal + example.initializeClaim(address(creatorCore1), 1, claimP); + vm.stopPrank(); + } + + function test_RevertWhen_UpdateClaimOnDeprecated() public { + vm.startPrank(creator); + uint48 nowC = uint48(block.timestamp); + uint48 later = nowC + 1000; + IERC1155Serendipity.ClaimParameters memory claimP = IERC1155Serendipity.ClaimParameters({ + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + location: "arweaveHash1", + totalMax: 0, + startDate: nowC, + endDate: later, + tokenVariations: 5, + paymentReceiver: payable(other), + cost: 1, + erc20: zeroAddress + }); + example.initializeClaim(address(creatorCore1), 1, claimP); + + claimP.location = "arweaveHash2"; + claimP.totalMax = 10; + claimP.startDate = 0; + claimP.tokenVariations = 10; + example.initializeClaim(address(creatorCore1), 2, claimP); + vm.stopPrank(); + + vm.startPrank(owner); + example.deprecate(true); + vm.stopPrank(); + + vm.startPrank(creator); + IERC1155Serendipity.UpdateClaimParameters memory claimU = IERC1155Serendipity.UpdateClaimParameters({ + totalMax: 0, + storageProtocol: ISerendipity.StorageProtocol.ARWEAVE, + startDate: 0, + endDate: 0, + location: "arweaveHash1", + paymentReceiver: payable(creator), + cost: 1 + }); + vm.expectRevert(ISerendipity.ContractDeprecated.selector); + example.updateClaim(address(creatorCore1), 1, claimU); + vm.expectRevert(ISerendipity.ContractDeprecated.selector); + example.updateClaim(address(creatorCore1), 2, claimU); + vm.stopPrank(); + + vm.startPrank(owner); + example.deprecate(false); + vm.stopPrank(); + + vm.startPrank(creator); + // run without reverts + example.updateClaim(address(creatorCore1), 1, claimU); + example.updateClaim(address(creatorCore1), 2, claimU); + vm.stopPrank(); + } + + function test_Deprecate() public { + assertEq(example.deprecated(), false); + + vm.startPrank(owner); + example.deprecate(true); + assertEq(example.deprecated(), true); + example.deprecate(false); + assertEq(example.deprecated(), false); + vm.stopPrank(); + + + // Cannot call deprecate if not an admin + vm.startPrank(other); + vm.expectRevert(bytes("AdminControl: Must be owner or admin")); + example.deprecate(true); + vm.stopPrank(); + + vm.startPrank(owner); + example.approveAdmin(other); + vm.stopPrank(); + + vm.startPrank(other); + example.deprecate(true); + assertEq(example.deprecated(), true); + vm.stopPrank(); + + vm.startPrank(owner); + example.revokeAdmin(other); + vm.stopPrank(); + + vm.startPrank(other); + vm.expectRevert(bytes("AdminControl: Must be owner or admin")); + example.deprecate(false); + assertEq(example.deprecated(), true); + vm.stopPrank(); + } +}